React JS Redux Store

This seems to be the classic way I see most people handeling state. Also see Redux Toolkit

Create the app and install the following

1
2
3
4
5
6
7
npx create-react-app --template typescript react-redux-typescript
cd react-redux-typescript

npm i redux react-redux
npm add @types/react-redux

code .
  1. In index.tsx wrap the App in a Provider, this will complain it needs a store.
1
2
3
4
5
import { Provider } from 'react-redux';

<Provider>
</App>
</Provider>
  1. Create redux\store.ts, this will complain it needs a reducer.
1
2
3
import { createStore } from "redux";

export const store = createStore();
  1. Create redux\notesReducer.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
export interface INotesState {
notes: string[]
}

const initialState = {
notes: []
}

type Action = {type: "ADD_NOTE", payload: string}

export const notesReducer = (state:INotesState = initialState, action:Action) => {
switch(action.type) {
case "ADD_NOTE": {
return { ...state, notes: [ ...state.notes, action.payload ] }
}
default:
return state
}
}
  1. Update store.ts to take the new reducer
1
2
3
4
import { createStore } from "redux";
import { notesReducer } from "./notesReducer";

export const store = createStore(notesReducer);
  1. Update index.tsx to take the store
1
2
3
4
5
6
import { Provider } from 'react-redux';
import { store } from './redux/store'

<Provider store={store}>
</App>
</Provider>
  1. Now we can read the store in any component with a selector.
1
2
3
4
5
6
7
8
9
10
import { useSelector } from 'react-redux';
import { INotesState } from './redux/notesReducer';

const notes = useSelector<INotesState, INotesState["notes"]>((state) => state.notes);

<ul>
{notes.map((note) => {
return <li key={note}>{note}</li>
})}
</ul>
  1. In redux state is updated by dispatching actions, we can use the useDispatch hook
1
2
3
import { useDispatch, useSelector } from 'react-redux';

const dispatch = useDispatch();
  1. Create an action, here the action is in the onAddNote callback which is passed to NewNoteInput as a prop. An action is an object creating the action type and the payload.
1
2
3
const onAddNote = (note:string) => {
dispatch({ type: "ADD_NOTE", payload:note })
}
  1. Create redux\notesActions.ts and refactor to have an actions creator. Move the action from the reducer into this file and export it. You will need to then import it in the reducer.
1
2
3
4
5
6
export type Action = {type: "ADD_NOTE", payload: string}

export const addNote = (note:string):Action => ({
type: "ADD_NOTE",
payload: note
})
  1. Update the dispatch
1
2
3
4
5
import { addNote } from './redux/notesActions';

const onAddNote = (note:string) => {
dispatch(addNote(note));
}

Redux Devtools

  1. install redux-devtools-extension
1
npm install --save-dev redux-devtools-extension
  1. create the store with the reducer(s), pre-defined state and composed enhancers
1
2
3
4
5
6
7
8
9
10
11
12
import { createStore } from 'redux';
import { notesReducer } from './notesReducer';
import { composeWithDevTools } from 'redux-devtools-extension';

const composedEnhancers = composeWithDevTools();

export const store = createStore(
notesReducer,
{ notes: ['Initial note'] },
composedEnhancers
);

  1. store.ts can be written as
1
2
3
4
5
6
7
8
9
10
11
import { createStore } from 'redux';
import { notesReducer } from './notesReducer';
import { composeWithDevTools } from 'redux-devtools-extension';

const composedEnhancers = composeWithDevTools();

export function configureStore(notes: string[]) {
const store = createStore(notesReducer, { notes: notes }, composedEnhancers);

return store;
}
  1. Then configureStore can be used instead of import { store } from './redux/store'
1
const store = configureStore(['whee']);