Redux
Redux is a centralized state store that can be accessed from any component in your application. It acts as basically a centralized reducer, where it has a state and an dispatch method to update that state. Redux Toolkit is a library that simplifies creating actions to interact with the store, and is the recommended approach going forward.
ts
// store/slices/song.ts
import { createSlice } from "@reduxjs/toolkit";
import { reset } from "../actions";
const songsSlice = createSlice({
name: "song",
initialState: [],
reducers: {
addSong(state, action) {
state.push(action.payload);
},
removeSong(state, action) {
// action.payload === string, the song we want to remove
const index = state.indexOf(action.payload);
state.splice(index, 1);
},
},
// listen to manual actions not created by this generator
extraReducers(builder) {
builder.addCase(reset, (state, action) => {
return [];
});
},
});
export const { addSong, removeSong } = songsSlice.actions;
// can also export this as the default
export const songsReducer = songsSlice.reducer;
ts
// store/actions.ts
// Declare common actions shared by multiple reducers
import { createAction } from "@reduxjs/toolkit";
export const reset = createAction("app/reset");
ts
// store/index.ts
import { configureStore } from "@reduxjs/toolkit";
import { songsReducer, addSong, removeSong } from "./slices/songsSlice";
import { reset } from "./actions";
const store = configureStore({
reducer: {
songs: songsReducer,
},
});
// Export store and slice actions
export { store, reset, addSong, removeSong };
ts
// index.ts
import { createRoot } from "react-dom/client";
import { Provider } from "react-redux";
import App from "./App";
import { store } from "./store";
const rootElement = document.getElementById("root");
const root = createRoot(rootElement);
root.render(
<Provider store={store}>
<App />
</Provider>
);
tsx
// components/songs.tsx
import { useDispatch, useSelector } from "react-redux";
import { createRandomSong } from "../data";
import { addSong, removeSong } from "../store";
function SongPlaylist() {
// object to call actions
const dispatch = useDispatch();
// fetch state
// can also filter/map state within here if you want
const songPlaylist = useSelector((state) => {
return state.songs;
});
// dispatch actions with some payload
const handleSongAdd = (song) => {
dispatch(addSong(song));
};
const handleSongRemove = (song) => {
dispatch(removeSong(song));
};
const renderedSongs = songPlaylist.map((song) => {
return (
<li key={song}>
{song}
<button
onClick={() => handleSongRemove(song)}
className="button is-danger"
>
X
</button>
</li>
);
});
return (
<div className="content">
<div className="table-header">
<h3 className="subtitle is-3">Song Playlist</h3>
<div className="buttons">
<button
onClick={() => handleSongAdd(createRandomSong())}
className="button is-link"
>
+ Add Song to Playlist
</button>
</div>
</div>
<ul>{renderedSongs}</ul>
</div>
);
}
export default SongPlaylist;
Optimize with Typescript
ts
// store/index.ts
export type AppStore = typeof store;
export type RootState = ReturnType<AppStore["getState"]>;
export type AppDispatch = AppStore["dispatch"];
ts
// custom hook
import type { TypedUseSelectorHook } from "react-redux";
import { useDispatch, useSelector, useStore } from "react-redux";
import type { AppDispatch, AppStore, RootState } from "./store";
// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch = useDispatch.withTypes<AppDispatch>();
export const useAppSelector = useSelector.withTypes<RootState>();
export const useAppStore = useStore.withTypes<AppStore>();
ts
// typing your slices
import type { RootState } from "../";
interface CounterState {
value: number;
}
// Define the initial state using that type
const initialState: CounterState = {
value: 0,
};
export const counterSlice = createSlice({
name: "counter",
// `createSlice` will infer the state type from the `initialState` argument
initialState,
reducers: {
// Use the PayloadAction type to declare the contents of `action.payload`
incrementByAmount: (state, action: PayloadAction<number>) => {
state.value += action.payload;
},
},
});
// Other code such as selectors can use the imported `RootState` type
export const selectCount = (state: RootState) => state.counter.value;