From c2a2c2e43e4c3ba5d93f72f47703ca377f3f797f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Roig?= Date: Wed, 16 Mar 2022 19:41:11 +0100 Subject: [PATCH] feat(persistInStorage): add redux-persist and retrieve from localStorage --- package-lock.json | 5 +++ package.json | 1 + src/app/FullScreenLoading.tsx | 21 ++++++++++ src/components/trackerList/TrackerList.tsx | 11 +---- src/index.tsx | 12 ++++-- src/store/createTestStore.ts | 12 ++---- src/store/rootReducer.ts | 10 +++++ src/store/store.ts | 34 ++++++++++++--- src/store/trackers/trackers.actions.ts | 49 +--------------------- src/store/trackers/trackersSlice.spec.ts | 19 +-------- src/store/trackers/trackersSlice.ts | 28 ++++--------- 11 files changed, 87 insertions(+), 115 deletions(-) create mode 100644 src/app/FullScreenLoading.tsx create mode 100644 src/store/rootReducer.ts diff --git a/package-lock.json b/package-lock.json index 84a1acdc..64c4ea26 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11675,6 +11675,11 @@ "@babel/runtime": "^7.9.2" } }, + "redux-persist": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/redux-persist/-/redux-persist-6.0.0.tgz", + "integrity": "sha512-71LLMbUq2r02ng2We9S215LtPu3fY0KgaGE0k8WRgl6RkqxtGfl7HUozz1Dftwsb0D/5mZ8dwAaPbtnzfvbEwQ==" + }, "redux-thunk": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.1.tgz", diff --git a/package.json b/package.json index 0d597dfa..9129f179 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "react-redux": "^7.2.6", "react-router-dom": "^6.2.2", "react-scripts": "5.0.0", + "redux-persist": "^6.0.0", "typescript": "~4.1.5", "uuid": "^8.3.2" }, diff --git a/src/app/FullScreenLoading.tsx b/src/app/FullScreenLoading.tsx new file mode 100644 index 00000000..2c27d05f --- /dev/null +++ b/src/app/FullScreenLoading.tsx @@ -0,0 +1,21 @@ +import { Box, CircularProgress, Typography } from '@mui/material'; + +function FullScreenLoading() { + return ( + + + Chargement des données... + + + + ); +} + +export default FullScreenLoading; diff --git a/src/components/trackerList/TrackerList.tsx b/src/components/trackerList/TrackerList.tsx index e9a7806c..bb497ccf 100644 --- a/src/components/trackerList/TrackerList.tsx +++ b/src/components/trackerList/TrackerList.tsx @@ -1,22 +1,13 @@ import { Box, CircularProgress, Typography } from '@mui/material'; -import { useEffect } from 'react'; -import { useAppSelector, useAppDispatch } from '../../app/hooks'; +import { useAppSelector } from '../../app/hooks'; import SliceStatus from '../../models/SliceStatus'; -import { fetchAllTrackers } from '../../store/trackers/trackersSlice'; import selectTrackers from '../../store/trackers/trackers.selectors'; import TrackerCard from '../TrackerCard/TrackerCard'; function TrackerList() { const { status, trackers } = useAppSelector(selectTrackers); - const dispatch = useAppDispatch(); - - useEffect(() => { - if (trackers === undefined) { - dispatch(fetchAllTrackers()); - } - }, [trackers]); return ( diff --git a/src/index.tsx b/src/index.tsx index 12d913fe..3916325a 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -3,16 +3,20 @@ import ReactDOM from 'react-dom'; import './index.css'; import App from './app/App'; import { BrowserRouter } from 'react-router-dom'; -import { store } from './store/store'; +import { store, persistor } from './store/store'; import { Provider } from 'react-redux'; import * as serviceWorker from './serviceWorker'; +import { PersistGate } from 'redux-persist/integration/react'; +import FullScreenLoading from './app/FullScreenLoading'; ReactDOM.render( - - - + } persistor={persistor}> + + + + , document.getElementById('root') diff --git a/src/store/createTestStore.ts b/src/store/createTestStore.ts index 222f1c91..fc191ea2 100644 --- a/src/store/createTestStore.ts +++ b/src/store/createTestStore.ts @@ -1,13 +1,9 @@ -import { configureStore } from '@reduxjs/toolkit'; -import counterReducer from './counter/counterSlice'; -import trackersReducer from './trackers/trackersSlice'; +import { configureStore, EnhancedStore } from '@reduxjs/toolkit'; +import rootReducer from './rootReducer'; -export function createTestStore() { +export function createTestStore(): EnhancedStore { const store = configureStore({ - reducer: { - counter: counterReducer, - trackers: trackersReducer - } + reducer: rootReducer }); return store; } diff --git a/src/store/rootReducer.ts b/src/store/rootReducer.ts new file mode 100644 index 00000000..c371b7fe --- /dev/null +++ b/src/store/rootReducer.ts @@ -0,0 +1,10 @@ +import { combineReducers } from '@reduxjs/toolkit'; +import counterReducer from './counter/counterSlice'; +import trackersReducer from './trackers/trackersSlice'; + +const rootReducer = combineReducers({ + counter: counterReducer, + trackers: trackersReducer +}); + +export default rootReducer; diff --git a/src/store/store.ts b/src/store/store.ts index 580b90ed..27acea1f 100644 --- a/src/store/store.ts +++ b/src/store/store.ts @@ -1,14 +1,36 @@ import { configureStore, ThunkAction, Action } from '@reduxjs/toolkit'; -import counterReducer from './counter/counterSlice'; -import trackersReducer from './trackers/trackersSlice'; +import { + persistStore, + persistReducer, + REHYDRATE, + FLUSH, + PAUSE, + PERSIST, + PURGE, + REGISTER +} from 'redux-persist'; +import storage from 'redux-persist/lib/storage'; // defaults to localStorage for web +import rootReducer from './rootReducer'; + +const persistConfig = { + key: 'root', + storage +}; + +const persistedReducer = persistReducer(persistConfig, rootReducer); export const store = configureStore({ - reducer: { - counter: counterReducer, - trackers: trackersReducer - } + reducer: persistedReducer, + middleware: (getDefaultMiddleware) => + getDefaultMiddleware({ + serializableCheck: { + ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER] // redux-persist actions are not serializable, ignore them + } + }) }); +export const persistor = persistStore(store); + export type AppDispatch = typeof store.dispatch; export type RootState = ReturnType; export type AppThunk = ThunkAction< diff --git a/src/store/trackers/trackers.actions.ts b/src/store/trackers/trackers.actions.ts index 116c029c..bc20cbde 100644 --- a/src/store/trackers/trackers.actions.ts +++ b/src/store/trackers/trackers.actions.ts @@ -1,49 +1,2 @@ -import { subDays } from 'date-fns'; -import Tracker from '../../models/Tracker'; -import TrackerStatus from '../../models/TrackerStatus'; -import { v4 as uuidv4 } from 'uuid'; - -class TrackersActions { - // A mock function to mimic making an async request for data - fetchAll() { - return new Promise<{ data: Tracker[] }>((resolve) => - setTimeout( - () => - resolve({ - data: [ - { - id: uuidv4(), - beginDate: new Date().toString(), - name: "Boire plus d'eau", - defaultQuantity: 1, - status: TrackerStatus.active, - unit: "litre d'eau", - entries: [] - }, - { - id: uuidv4(), - beginDate: new Date().toString(), - defaultQuantity: 1, - name: 'Faire du yoga', - status: TrackerStatus.active, - unit: 'séance', - entries: [] - }, - { - id: uuidv4(), - beginDate: subDays(new Date(), 7).toString(), - defaultQuantity: 15, - duration: 45, - name: 'Faire des pompes', - status: TrackerStatus.active, - unit: 'pompes', - entries: [] - } - ] - }), - 900 - ) - ); - } -} +class TrackersActions {} export default new TrackersActions(); diff --git a/src/store/trackers/trackersSlice.spec.ts b/src/store/trackers/trackersSlice.spec.ts index 86ba2023..3d0c1fca 100644 --- a/src/store/trackers/trackersSlice.spec.ts +++ b/src/store/trackers/trackersSlice.spec.ts @@ -1,12 +1,7 @@ import SliceStatus from '../../models/SliceStatus'; -import trackersReducer, { TrackersState, fetchAllTrackers } from './trackersSlice'; +import trackersReducer from './trackersSlice'; describe('counter reducer', () => { - const initialState: TrackersState = { - error: {}, - status: SliceStatus.idle, - trackers: undefined - }; it('should handle initial state', () => { expect(trackersReducer(undefined, { type: 'unknown' })).toEqual({ error: {}, @@ -14,16 +9,4 @@ describe('counter reducer', () => { trackers: undefined }); }); - - it('should handle fetch all trackers pending', () => { - const actual = trackersReducer(initialState, fetchAllTrackers.pending); - expect(actual.status).toEqual(SliceStatus.loading); - expect(actual.error).toEqual({}); - }); - - it('should handle fetch all trackers error', () => { - const actual = trackersReducer(initialState, fetchAllTrackers.rejected); - expect(actual.status).toEqual(SliceStatus.failed); - expect(actual.error).not.toEqual({}); - }); }); diff --git a/src/store/trackers/trackersSlice.ts b/src/store/trackers/trackersSlice.ts index 380600ac..bea239c6 100644 --- a/src/store/trackers/trackersSlice.ts +++ b/src/store/trackers/trackersSlice.ts @@ -1,6 +1,5 @@ -import { createAsyncThunk, createSlice, SerializedError } from '@reduxjs/toolkit'; +import { createSlice, SerializedError } from '@reduxjs/toolkit'; -import TrackersActions from './trackers.actions'; import Tracker from '../../models/Tracker'; import SliceStatus from '../../models/SliceStatus'; @@ -20,33 +19,20 @@ const initialState: TrackersState = { // ===== Thunk -export const fetchAllTrackers = createAsyncThunk('trackers/fetchAllTrackers', async () => { - const response = await TrackersActions.fetchAll(); - return response.data; -}); +// export const fetchAllTrackers = createAsyncThunk('trackers/fetchAllTrackers', async () => { +// const response = await TrackersActions.fetchAll(); +// return response.data; +// }); // ===== Reducers export const trackersSlice = createSlice({ name: 'trackers', initialState, - reducers: {}, + reducers: {} // The `extraReducers` field lets the slice handle actions defined elsewhere, // including actions generated by createAsyncThunk or in other slices. - extraReducers: (builder) => { - builder - .addCase(fetchAllTrackers.pending, (state) => { - state.status = SliceStatus.loading; - }) - .addCase(fetchAllTrackers.fulfilled, (state, action) => { - state.status = SliceStatus.idle; - state.trackers = action.payload; - }) - .addCase(fetchAllTrackers.rejected, (state, action) => { - state.status = SliceStatus.failed; - state.error = action.error; - }); - } + // extraReducers: () => {} }); export default trackersSlice.reducer;