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 .
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 >
Create redux\store.ts
, this will complain it needs a reducer.
1 2 3 import { createStore } from "redux" ;export const store = createStore ();
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 } }
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);
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 >
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 >
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 ();
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 }) }
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 })
Update the dispatch
1 2 3 4 5 import { addNote } from './redux/notesActions' ;const onAddNote = (note:string ) => { dispatch (addNote (note)); }
install redux-devtools-extension
1 npm install --save-dev redux-devtools-extension
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 );
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; }
Then configureStore
can be used instead of import { store } from './redux/store'
1 const store = configureStore (['whee' ]);