I am starting a new next js project,we are migrating from a normal react app to a next app.
we intend to use redux toolkit for our global state management and we are using server side rendering.so we came across next-redux-wrapper npm package which looked like it solves most of our problems with redux and ssr but form some reason when ever we use server side rendering on one of the pages the HYDRATE action from next-redux-wrapper is getting called atleast twice sometimes even 4 times.What exactly is going wrong because the article i referred to seems to work fine,i have attached my Redux store details and the Redux slice details,and my getServerSideProps function details.
import { createStore, applyMiddleware, combineReducers } from "redux";
import count from "../ReduxSlices/CounterSlice";
import { createWrapper, HYDRATE } from "next-redux-wrapper";
import { configureStore } from "#reduxjs/toolkit";
const combinedReducer = combineReducers({
count,
});
const reducer = (state, action) => {
if (action.type === HYDRATE) {
const nextState = {
...state, // use previous state
...action.payload, // apply delta from hydration
count: {
count: state.count.count + action.payload.count.count,
},
};
return nextState;
} else {
return combinedReducer(state, action);
}
};
export const makeStore = () =>
configureStore({
reducer: reducer,
});
export const wrapper = createWrapper(makeStore, { debug: true });
and my Redux slice is
import { createSlice } from "#reduxjs/toolkit";
import { HYDRATE } from "next-redux-wrapper";
const initialState = {
count: 0,
};
const counterSlice = createSlice({
name: "counter",
initialState: initialState,
reducers: {
increment: (state) => {
state.count = state.count + 1;
},
},
});
export const { increment } = counterSlice.actions;
export default counterSlice.reducer;
and finally this is how i dispatch an action inside getServerSideProps
export const getServerSideProps = wrapper.getServerSideProps(
(store) => async () => {
store.dispatch(increment());
console.log("server", new Date());
}
);
The console log is logging only once but the HYDRATE action is getting dispatched atleast two times...any insight will be helpful,thank you.
I had the same issue. Disabling react strict mode worked for me.
const nextConfig = {
// reactStrictMode: true,
...
}
module.exports = nextConfig
Related
I have come across Redux Toolkit (RTK) and wanting to implement further functionality it provides. My application dispatches to reducers slices created via the createSlice({}) (see createSlice api docs)
This so far works brilliantly. I can easily use the built in dispatch(action) and useSelector(selector) to dispatch the actions and receive/react to the state changes well in my components.
I would like to use an async call from axios to fetch data from the API and update the store as the request is A) started B) completed.
I have seen redux-thunk and it seems as though it is designed entirely for this purpose, but the new RTK does not seem to support it within a createSlice() following general googling.
Is the above the current state of implementing thunk with slices?
I have seen in the docs that you can add extraReducers to the slice but unsure if this means I could create more traditional reducers that use thunk and have the slice implement them?
Overall, it is misleading as the RTK docs show you can use thunk, but doesn't seem to mention it not being accessible via the new slices api.
Example from Redux Tool Kit Middleware
const store = configureStore({
reducer: rootReducer,
middleware: [thunk, logger]
})
My code for a slice showing where an async call would fail and some other example reducers that do work.
import { getAxiosInstance } from '../../conf/index';
export const slice = createSlice({
name: 'bundles',
initialState: {
bundles: [],
selectedBundle: null,
page: {
page: 0,
totalElements: 0,
size: 20,
totalPages: 0
},
myAsyncResponse: null
},
reducers: {
//Update the state with the new bundles and the Spring Page object.
recievedBundlesFromAPI: (state, bundles) => {
console.log('Getting bundles...');
const springPage = bundles.payload.pageable;
state.bundles = bundles.payload.content;
state.page = {
page: springPage.pageNumber,
size: springPage.pageSize,
totalElements: bundles.payload.totalElements,
totalPages: bundles.payload.totalPages
};
},
//The Bundle selected by the user.
setSelectedBundle: (state, bundle) => {
console.log(`Selected ${bundle} `);
state.selectedBundle = bundle;
},
//I WANT TO USE / DO AN ASYNC FUNCTION HERE...THIS FAILS.
myAsyncInSlice: (state) => {
getAxiosInstance()
.get('/')
.then((ok) => {
state.myAsyncResponse = ok.data;
})
.catch((err) => {
state.myAsyncResponse = 'ERROR';
});
}
}
});
export const selectBundles = (state) => state.bundles.bundles;
export const selectedBundle = (state) => state.bundles.selectBundle;
export const selectPage = (state) => state.bundles.page;
export const { recievedBundlesFromAPI, setSelectedBundle, myAsyncInSlice } = slice.actions;
export default slice.reducer;
My store setup (store config).
import { configureStore } from '#reduxjs/toolkit';
import thunk from 'redux-thunk';
import bundlesReducer from '../slices/bundles-slice';
import servicesReducer from '../slices/services-slice';
import menuReducer from '../slices/menu-slice';
import mySliceReducer from '../slices/my-slice';
const store = configureStore({
reducer: {
bundles: bundlesReducer,
services: servicesReducer,
menu: menuReducer,
redirect: mySliceReducer
}
});
export default store;
I'm a Redux maintainer and creator of Redux Toolkit.
FWIW, nothing about making async calls with Redux changes with Redux Toolkit.
You'd still use an async middleware (typically redux-thunk), fetch data, and dispatch actions with the results.
As of Redux Toolkit 1.3, we do have a helper method called createAsyncThunk that generates the action creators and does request lifecycle action dispatching for you, but it's still the same standard process.
This sample code from the docs sums up the usage;
import { createAsyncThunk, createSlice } from '#reduxjs/toolkit'
import { userAPI } from './userAPI'
// First, create the thunk
const fetchUserById = createAsyncThunk(
'users/fetchByIdStatus',
async (userId, thunkAPI) => {
const response = await userAPI.fetchById(userId)
return response.data
}
)
// Then, handle actions in your reducers:
const usersSlice = createSlice({
name: 'users',
initialState: { entities: [], loading: 'idle' },
reducers: {
// standard reducer logic, with auto-generated action types per reducer
},
extraReducers: (builder) => {
// Add reducers for additional action types here, and handle loading state as needed
builder.addCase(fetchUserById.fulfilled, (state, action) => {
// Add user to the state array
state.entities.push(action.payload)
})
},
})
// Later, dispatch the thunk as needed in the app
dispatch(fetchUserById(123))
See the Redux Toolkit "Usage Guide: Async Logic and Data Fetching" docs page for some additional info on this topic.
Hopefully that points you in the right direction!
You can use createAsyncThunk to create thunk action, which can be trigger using dispatch
teamSlice.ts
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
const axios = require("axios");
export const fetchPlayerList = createAsyncThunk(
"team/playerListLoading",
(teamId: string) =>
axios
.get(`https://api.opendota.com/api/teams/${teamId}/players`)
.then((response) => response.data)
.catch((error) => error)
);
const teamInitialState = {
playerList: {
status: "idle",
data: {},
error: {},
},
};
const teamSlice = createSlice({
name: "user",
initialState: teamInitialState,
reducers: {},
extraReducers: {
[fetchPlayerList.pending.type]: (state, action) => {
state.playerList = {
status: "loading",
data: {},
error: {},
};
},
[fetchPlayerList.fulfilled.type]: (state, action) => {
state.playerList = {
status: "idle",
data: action.payload,
error: {},
};
},
[fetchPlayerList.rejected.type]: (state, action) => {
state.playerList = {
status: "idle",
data: {},
error: action.payload,
};
},
},
});
export default teamSlice;
Team.tsx component
import React from "react";
import { useSelector, useDispatch } from "react-redux";
import { fetchPlayerList } from "./teamSlice";
const Team = (props) => {
const dispatch = useDispatch();
const playerList = useSelector((state: any) => state.team.playerList);
return (
<div>
<button
onClick={() => {
dispatch(fetchPlayerList("1838315"));
}}
>
Fetch Team players
</button>
<p>API status {playerList.status}</p>
<div>
{playerList.status !== "loading" &&
playerList.data.length &&
playerList.data.map((player) => (
<div style={{ display: "flex" }}>
<p>Name: {player.name}</p>
<p>Games Played: {player.games_played}</p>
</div>
))}
</div>
</div>
);
};
export default Team;
Use redux-toolkit v1.3.0-alpha.8
Try this
import { createAsyncThunk, createSlice } from '#reduxjs/toolkit';
export const myAsyncInSlice = createAsyncThunk('bundles/myAsyncInSlice', () =>
getAxiosInstance()
.get('/')
.then(ok => ok.data)
.catch(err => err),
);
const usersSlice = createSlice({
name: 'bundles',
initialState: {
bundles: [],
selectedBundle: null,
page: {
page: 0,
totalElements: 0,
size: 20,
totalPages: 0,
},
myAsyncResponse: null,
myAsyncResponseError: null,
},
reducers: {
// add your non-async reducers here
},
extraReducers: {
// you can mutate state directly, since it is using immer behind the scenes
[myAsyncInSlice.fulfilled]: (state, action) => {
state.myAsyncResponse = action.payload;
},
[myAsyncInSlice.rejected]: (state, action) => {
state.myAsyncResponseError = action.payload;
},
},
});
I'm sorry if my code is extremely amateur. I just recently started working with react-redux and javascript. I feel like I am a bit spaced out when looking at tutorials when it comes to visualizing states and what the store does. So I'm trying to code something to visually see it in code. My code is similar to the default preloaded code from Visual Studio when a new project for ASP.NET Web app w/ react-redux is created. I want to just call an API using redux and pass that information to the page.
For what I've got right now, I noticed that my API is not called, as putting a red light on my actionCreator is not reached.
///////////File 1 CreaterAction and Reducer/////////////////
const requestEvents = 'REQUEST_EVENTS';
const recieveEvents = 'RECEIVE_EVENTS';
const initialState = { events: [], isLoading: false };
async function getData(url) {
const response = await fetch(url);
return response.json();
}
export const actionCreators = {
requestEvents: () => async (dispatch) => {
dispatch({ type: requestEvents});
const url = 'api\stuff';
const events = await getData(url);
dispatch({ type: recieveEvents, events });
}
};
export const reducer = (state, action) => {
state = state || initialState;
if (action.type === requestEvents) {
return {
...state,
events: action.events,
isLoading: true
};
}
return state;
};
//////////////File 2 My Actual component//////////////////
import React, { Component } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { actionCreators } from '../store/file1';
class Events extends Component {
componenetDidMount() {
this.ensureDataFetched();
}
ensureDataFetched() {
const stuff = this.props.//!!!! Does not show .requestEvents
}
render() {
return (
<div>
<h1>Events</h1>
<p>This component demonstrates fetching data from the server and working with URL parameters.</p>
{renderData(this.props)}
</div>
);
}
}
function renderData(props) {
return (
<p>{props.events}</p>
);
}
export default connect(
state => state.events,
dispatch => bindActionCreators(actionCreators, dispatch)
)(Events);
///////////////FILE 3 Config Store//////////////////
import { applyMiddleware, combineReducers, compose, createStore } from 'redux';
import thunk from 'redux-thunk';
import { routerReducer, routerMiddleware } from 'react-router-redux';
import * as Counter from './Counter';
import * as file1 from './file1';
export default function configureStore (history, initialState) {
const reducers = {
events: file1.reducer
};
const middleware = [
thunk,
routerMiddleware(history)
];
// In development, use the browser's Redux dev tools extension if installed
const enhancers = [];
const isDevelopment = process.env.NODE_ENV === 'development';
if (isDevelopment && typeof window !== 'undefined' && window.devToolsExtension) {
enhancers.push(window.devToolsExtension());
}
const rootReducer = combineReducers({
...reducers,
routing: routerReducer
});
return createStore(
rootReducer,
initialState,
compose(applyMiddleware(...middleware), ...enhancers)
);
}
It runs just shows
Events
This component demonstrates fetching data from the server and working with URL parameters.
My guess is that it's because I haven't used/called this.props.requestEvents
Solution(updated):
I thought any action would cause react-redux-connect to call the mapState functions but when an action doesn't change anything then this is not the case.
I have a localStorage module that dispatches actions but don't change state, instead thy will write to localStorage. The module has selectors that are used in the containers but they won't get called until the state actually changes so the UI would only show correctly after another action was dispatched that would change the state.
Problem
When I put the store on window (window.store=store), add a console.log in the mapStateToProps, then in the console I dispatch an action: store.dispatch({type:'some action'}) then the console.log of the mapStateToProps does not show.
I do memoize the result but the mapStateToProps should be called see here
Full code is here and running example here (you can open a console clicking on 'console' link in the right bottom of the screen).
package.json
store.js:
import { createStore } from 'redux';
export default (initialState, reducer) => {
const store = createStore(
reducer,
initialState,
window.__REDUX_DEVTOOLS_EXTENSION__ &&
window.__REDUX_DEVTOOLS_EXTENSION__()
);
window.store = store;
return store;
};
app.js
import React from 'react';
import { connect } from 'react-redux';
import './App.css';
import createStore from './store';
import { Provider } from 'react-redux';
import initCounter from './components/Counter';
import {
createWrapper,
memoize,
} from './components/#common';
const COUNTER = 'COUNTER';
const selectCounterState = state => state.myCounter;
const counter = initCounter({
actionWrapper: createWrapper(COUNTER, 'counter1'),
selectors: { myState: selectCounterState },
connect,
memoize,
});
const initialState = {
myCounter: counter.initialState,
};
const reducer = (state = initialState, action) => {
if (action.emittedBy === COUNTER) {
return {
...state,
myCounter: counter.reducer(
selectCounterState(state),
action.payload
),
};
}
return state;
};
const store = createStore(initialState, reducer);
const Counter = counter.container;
const App = () => (
<Provider store={store}>
<Counter id="counter1" parentId={[]} />
</Provider>
);
export default App;
component/Counter/index:
import component from './component';
const INCREASE = 'INCREASE';
const reducer = (state, action) => {
if (action.type === INCREASE) {
return { ...state, count: state.count + 1 };
}
return state;
};
const makeState = memoize =>
memoize((id, parentId, { count }) => ({
id: parentId.concat(id),
parentId,
count,
}));
const mapStateToProps = ({ myState }, memoize) => () => {
const newState = makeState(memoize);
return (state, ownProps) =>
console.log('in map state to props', new Date()) ||
newState(
ownProps.id,
ownProps.parentId,
myState(state)
);
};
export default ({
actionWrapper,
selectors,
connect,
memoize,
}) => {
const actions = {
increase: ({ id }) =>
actionWrapper({
type: INCREASE,
id,
}),
};
const container = connect(
mapStateToProps(selectors, memoize),
actions
)(component);
return {
container,
reducer,
initialState: { count: 0 },
};
};
components/counter/component.js:
import React from 'react';
export default props => (
<div>
<button onClick={() => props.increase(props)}>
add
</button>
{props.count}
</div>
);
This problem was caused because I had a localStorage module that did dispatch actions but did not change the state, instead it would write to localStorage.
The module had selectors that would get the right data and the containers would use them to build the correct state but since the dispatched action did not change the state in the redux store react-redux would skip calling my mapState functions (probably memoizing state in Provider).
The solution is to let the root reducer return a new state reference {...state} so any action would cause the mapState functions to be called.
Your example codepen works just fine, you just have to trigger an action that gets past your top level guard and is of the expected structure, as to not cause any followup errors:
Post this into the console of your codepen:
store.dispatch({emittedBy: "COUNTER", type: "COUNTER -> INCREASE", id: "counter1", payload: {type: "INCREASE", id: ["counter1"]}})
As part of my ongoing project to learn React (I'm natively an ASP.NET guy) I've hit this issue. I have a suite of React apps in which I want to use some common UI elements, so I've attempted to break these out into a separate npm package. For the shared components themselves this has worked fine.
However, some of these components depend on redux actions to operate, so I've tried to bundle these actions and a reducer function into the external package. Here's a simplified version of my actions\index.js:
export const SNACKBAR_MESSAGE = "SNACKBAR_MESSAGE";
export const SNACKBAR_HIDE = "SNACKBAR_HIDE";
export function showSnackBarMessage(message) {
console.log('hit 1');
return (dispatch, getState) => {
console.log('hit 2');
dispatch(hideSnackBar());
dispatch({
type: SNACKBAR_MESSAGE,
message: message
});
}
}
export const hideSnackBar = () => {
type: SNACKBAR_HIDE
};
And this is reducer\index.js:
import {
SNACKBAR_MESSAGE,
SNACKBAR_HIDE
} from "../actions";
const initialState = {
snackBarMessage: null,
snackBarVisible: false
};
export default function UiReducer(state = initialState, action) {
switch(action.type) {
case SNACKBAR_MESSAGE:
return Object.assign({}, state, {
snackBarMessage: action.message,
snackBarVisible: true
});
case SNACKBAR_HIDE:
return Object.assign({}, state, {
snackBarMessages: '',
snackBarVisible: false
});
default:
return state;
}
}
This is the same code that worked fine when part of the original project. These are exported by my package's entry point file like this:
// Reducer
export { default as uiReducer } from './reducer';
// Actions
export { showSnackBarMessage as uiShowPrompt } from './actions';
export { hideSnackBar as uiHidePrompt } from './actions';
Then in my consuming project, my default reducer looks like this:
import { routerReducer } from 'react-router-redux';
import { combineReducers } from 'redux';
import { uiReducer } from 'my-custom-ui-package';
// Import local reducers
const reducer = combineReducers(
{
// Some local reducers
ui: uiReducer
}
);
export default reducer;
The problem is when I try to dispatch one of these actions imported from my external package. I include the action, e.g. import { uiShowPrompt } from "my-custom-ui-package"; and dispatch it like dispatch(uiShowPrompt("Show me snackbar")); then I see the two console messages (hit 1 and hit 2) displayed, but then the following error:
Uncaught TypeError: Cannot read property 'type' of undefined
at store.js:12
at dispatch (applyMiddleware.js:35)
at my-custom-ui-package.js:1
at index.js:8
at middleware.js:22
at store.js:15
at dispatch (applyMiddleware.js:35)
at auth.js:28
at index.js:8
at middleware.js:22
The store itself looks like this:
import { createStore, combineReducers, applyMiddleware, compose } from "redux";
import thunk from 'redux-thunk';
import { browserHistory } from "react-router";
import {
syncHistoryWithStore,
routerReducer,
routerMiddleware
} from "react-router-redux";
import reducer from "./reducer";
const loggerMiddleware = store => next => action => {
console.log("Action type:", action.type);
console.log("Action payload:", action.payload);
console.log("State before:", store.getState());
next(action);
console.log("State after:", store.getState());
};
const initialState = {};
const createStoreWithMiddleware = compose(
applyMiddleware(
loggerMiddleware,
routerMiddleware(browserHistory),
thunk)
)(createStore);
const store = createStoreWithMiddleware(reducer, initialState);
export default store;
I'm afraid I don't understand this error. I don't see what I'm doing differently other than essentially moving identical code from my local project to an npm package. Since neither the actions nor reducer actually depend on redux, my npm package doesn't itself have a dependency on react-redux. Is that a problem? If there's anything else I could share to help you help me just let me know. Like I say, I'm still fairly new to all this so clearly there's something I'm not getting right!
The problem might be in declaration of hideSnackBar function
export const hideSnackBar = () => {
type: SNACKBAR_HIDE
};
Here the function is trying to return an Object Literal from Arrow Function. This will always return undefined. As the parser doesn't interpret the two braces as an object literal, but as a block statement. Thus the error, Cannot read property 'type' of undefined as store is expecting an action with property type.
Replace code like this and see if it works.
export const hideSnackBar = () => ({
type: SNACKBAR_HIDE
});
The parentheses forces it to parse as Object Literal. Hope this helps
I had exported it like
export default userReducer();
and not like this:
export default userReducer;
Just get rid of that ()
Found out that it was case of wrong order in receiving the arguments when using redux-thunk.
// wrong argument order
const anAction = () => (getState, dispatch) => {...}
// correct one
const anAction = () => (dispatch, getState) => {...}
I want to persist some parts of my state tree to the localStorage. What is the appropriate place to do so? Reducer or action?
Reducer is never an appropriate place to do this because reducers should be pure and have no side effects.
I would recommend just doing it in a subscriber:
store.subscribe(() => {
// persist your state
})
Before creating the store, read those persisted parts:
const persistedState = // ...
const store = createStore(reducer, persistedState)
If you use combineReducers() you’ll notice that reducers that haven’t received the state will “boot up” as normal using their default state argument value. This can be pretty handy.
It is advisable that you debounce your subscriber so you don’t write to localStorage too fast, or you’ll have performance problems.
Finally, you can create a middleware that encapsulates that as an alternative, but I’d start with a subscriber because it’s a simpler solution and does the job well.
To fill in the blanks of Dan Abramov's answer you could use store.subscribe() like this:
store.subscribe(()=>{
localStorage.setItem('reduxState', JSON.stringify(store.getState()))
})
Before creating the store, check localStorage and parse any JSON under your key like this:
const persistedState = localStorage.getItem('reduxState')
? JSON.parse(localStorage.getItem('reduxState'))
: {}
You then pass this persistedState constant to your createStore method like this:
const store = createStore(
reducer,
persistedState,
/* any middleware... */
)
In a word: middleware.
Check out redux-persist. Or write your own.
[UPDATE 18 Dec 2016] Edited to remove mention of two similar projects now inactive or deprecated.
If anybody is having any problem with the above solutions, you can write your own to. Let me show you what I did. Ignore saga middleware things just focus on two things localStorageMiddleware and reHydrateStore method. the localStorageMiddleware pull all the redux state and puts it in local storage and rehydrateStore pull all the applicationState in local storage if present and puts it in redux store
import {createStore, applyMiddleware} from 'redux'
import createSagaMiddleware from 'redux-saga';
import decoristReducers from '../reducers/decorist_reducer'
import sagas from '../sagas/sagas';
const sagaMiddleware = createSagaMiddleware();
/**
* Add all the state in local storage
* #param getState
* #returns {function(*): function(*=)}
*/
const localStorageMiddleware = ({getState}) => { // <--- FOCUS HERE
return (next) => (action) => {
const result = next(action);
localStorage.setItem('applicationState', JSON.stringify(
getState()
));
return result;
};
};
const reHydrateStore = () => { // <-- FOCUS HERE
if (localStorage.getItem('applicationState') !== null) {
return JSON.parse(localStorage.getItem('applicationState')) // re-hydrate the store
}
}
const store = createStore(
decoristReducers,
reHydrateStore(),// <-- FOCUS HERE
applyMiddleware(
sagaMiddleware,
localStorageMiddleware,// <-- FOCUS HERE
)
)
sagaMiddleware.run(sagas);
export default store;
Building on the excellent suggestions and short code excerpts provided in other answers (and Jam Creencia's Medium article), here's a complete solution!
We need a file containing 2 functions that save/load the state to/from local storage:
// FILE: src/common/localStorage/localStorage.js
// Pass in Redux store's state to save it to the user's browser local storage
export const saveState = (state) =>
{
try
{
const serializedState = JSON.stringify(state);
localStorage.setItem('state', serializedState);
}
catch
{
// We'll just ignore write errors
}
};
// Loads the state and returns an object that can be provided as the
// preloadedState parameter of store.js's call to configureStore
export const loadState = () =>
{
try
{
const serializedState = localStorage.getItem('state');
if (serializedState === null)
{
return undefined;
}
return JSON.parse(serializedState);
}
catch (error)
{
return undefined;
}
};
Those functions are imported by store.js where we configure our store:
NOTE: You'll need to add one dependency: npm install lodash.throttle
// FILE: src/app/redux/store.js
import { configureStore, applyMiddleware } from '#reduxjs/toolkit'
import throttle from 'lodash.throttle';
import rootReducer from "./rootReducer";
import middleware from './middleware';
import { saveState, loadState } from 'common/localStorage/localStorage';
// By providing a preloaded state (loaded from local storage), we can persist
// the state across the user's visits to the web app.
//
// READ: https://redux.js.org/recipes/configuring-your-store
const store = configureStore({
reducer: rootReducer,
middleware: middleware,
enhancer: applyMiddleware(...middleware),
preloadedState: loadState()
})
// We'll subscribe to state changes, saving the store's state to the browser's
// local storage. We'll throttle this to prevent excessive work.
store.subscribe(
throttle( () => saveState(store.getState()), 1000)
);
export default store;
The store is imported into index.js so it can be passed into the Provider that wraps App.js:
// FILE: src/index.js
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import App from './app/core/App'
import store from './app/redux/store';
// Provider makes the Redux store available to any nested components
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
Note that absolute imports require this change to YourProjectFolder/jsconfig.json - this tells it where to look for files if it can't find them at first. Otherwise, you'll see complaints about attempting to import something from outside of src.
{
"compilerOptions": {
"baseUrl": "src"
},
"include": ["src"]
}
I cannot answer #Gardezi but an option based on his code could be:
const rootReducer = combineReducers({
users: authReducer,
});
const localStorageMiddleware = ({ getState }) => {
return next => action => {
const result = next(action);
if ([ ACTIONS.LOGIN ].includes(result.type)) {
localStorage.setItem(appConstants.APP_STATE, JSON.stringify(getState()))
}
return result;
};
};
const reHydrateStore = () => {
const data = localStorage.getItem(appConstants.APP_STATE);
if (data) {
return JSON.parse(data);
}
return undefined;
};
return createStore(
rootReducer,
reHydrateStore(),
applyMiddleware(
thunk,
localStorageMiddleware
)
);
the difference is that we are just saving some actions, you could event use a debounce function to save only the last interaction of your state
If you don't need to copy all redux store to localStorage you can use the specific store arguments:
store.subscribe(()=>{
window.localStorage.setItem('currency', store.getState().lang)
})
And set initial state argument value like:
const initialState = {
currency: window.localStorage.getItem('lang') ?? 'en',
}
In this case, you don't need to pass const persistedState to const store = createStore()
I'm a bit late but I implemented a persistent state according to the examples stated here. If you want to update the state only every X seconds, this approach may help you:
Define a wrapper function
let oldTimeStamp = (Date.now()).valueOf()
const millisecondsBetween = 5000 // Each X milliseconds
function updateLocalStorage(newState)
{
if(((Date.now()).valueOf() - oldTimeStamp) > millisecondsBetween)
{
saveStateToLocalStorage(newState)
oldTimeStamp = (Date.now()).valueOf()
console.log("Updated!")
}
}
Call a wrapper function in your subscriber
store.subscribe((state) =>
{
updateLocalStorage(store.getState())
});
In this example, the state is updated at most each 5 seconds, regardless how often an update is triggered.
I was looking badly for an entire example on how to persist state into a local storage using redux-toolkit-persist with no success until I came across #canProm response above to solve my issue.
This is what is working for me
//package.json
"reduxjs-toolkit-persist": "^7.0.1",
"lodash": "^4.17.21"
//localstorage.ts
import localStorage from 'reduxjs-toolkit-persist/es/storage';
export const saveState = (state: any) => {
try {
console.log(state);
const serializableState = JSON.stringify(state);
localStorage.setItem('globalState', serializableState);
} catch (err) {
console.log('Redux was not able to persist the state into the localstorage');
}
};
export const loadState = () => {
try {
const serializableState: string | any =
localStorage.getItem('globalState');
return serializableState !== null || serializableState === undefined ? JSON.parse(serializableState) : undefined;
} catch (error) {
return undefined;
}
};
//slices - actions
//reduxjs-toolkit-slices.ts
import { combineReducers, createSlice, PayloadAction } from '#reduxjs/toolkit';
import { UserModel } from '../model/usermodel';
import { GlobalState } from './type';
const deaultState: GlobalState = {
counter: 0,
isLoggedIn: false
};
const stateSlice = createSlice({
name: "state",
initialState: deaultState,
reducers: {
isLoggedIn: (state, action: PayloadAction<boolean>) => {
console.log('isLogged');
console.log(state.isLoggedIn);
console.log(action);
state.isLoggedIn = action.payload;
console.log(state.isLoggedIn);
},
setUserDetails: (state, action: PayloadAction<UserModel>) => {
console.log('setUserDetails');
console.log(state);
console.log(action);
//state.userContext.user = action.payload;
}
}
});
//export actions under slices
export const {
isLoggedIn: isUserLoggedAction,
setUserDetails: setUserDetailActions
} = stateSlice.actions;
//TODO: use the optimal way for combining reducer using const
//combine reducer from all slice
export const combineReducer = combineReducers({
stateReducer: stateSlice.reducer
});
//storeConfig
//reduxjs-toolkit-store.ts
import { configureStore } from '#reduxjs/toolkit';
import { throttle } from 'lodash';
import { persistReducer } from 'reduxjs-toolkit-persist';
import autoMergeLevel2 from 'reduxjs-toolkit-persist/lib/stateReconciler/autoMergeLevel2';
import storage from 'reduxjs-toolkit-persist/lib/storage';
import { loadState, saveState } from './localStorage';
import { combineReducer } from './reduxjs-toolkit-slices';
// persist config
const persistConfig = {
key: 'root',
storage: storage,
stateReconciler: autoMergeLevel2,
};
const persistedReducer = persistReducer(persistConfig, combineReducer);
// export reducers under slices
const store = configureStore({
reducer: persistedReducer,
devTools: process.env.NODE_ENV !== 'production',
preloadedState: loadState(), //call loadstate method to initiate store from localstorage
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
thunk: true,
serializableCheck: false,
}),
});
// handle state update event. Whenever the state will change, this subscriber will call the saveState methode to update and persist the state into the store
store.subscribe(throttle(() => {
saveState(store.getState());
}, 1000));
export default store;
//App.ts
import { persistStore } from 'reduxjs-toolkit-persist';
import { PersistGate } from 'reduxjs-toolkit-persist/integration/react';
import './i18n';
let persistor = persistStore(store);
ReactDOM.render(
<Provider store={store}>
<PersistGate loading={<div>Loading .....</div>} persistor={persistor}>
<HalocarburesRouter />
</PersistGate>
</Provider>,
document.getElementById('ingenierieMdn'));