I'm trying to persist data using redux-persist. Here is my code:
import { createStore as _createStore, applyMiddleware, compose } from 'redux';
import createMiddleware from './middleware/clientMiddleware';
import { routerMiddleware } from 'react-router-redux';
import {persistStore, autoRehydrate} from 'redux-persist';
export default function createStore(history, client, data) {
// Sync dispatched route actions to the history
const reduxRouterMiddleware = routerMiddleware(history);
const middleware = [createMiddleware(client), reduxRouterMiddleware];
let finalCreateStore;
if (__DEVELOPMENT__ && __CLIENT__ && __DEVTOOLS__) {
const { persistState } = require('redux-devtools');
const DevTools = require('../containers/DevTools/DevTools');
finalCreateStore = compose(
applyMiddleware(...middleware),
window.devToolsExtension ? window.devToolsExtension() : DevTools.instrument(),
persistState(window.location.href.match(/[?&]debug_session=([^&]+)\b/))
)(_createStore);
} else {
finalCreateStore = applyMiddleware(...middleware)(_createStore);
}
const reducer = require('./modules/reducer');
// const store = finalCreateStore(reducer, data);
const store = finalCreateStore(reducer, data, autoRehydrate());
if (typeof window !== 'undefined') persistStore(store);
if (__DEVELOPMENT__ && module.hot) {
module.hot.accept('./modules/reducer', () => {
store.replaceReducer(require('./modules/reducer'));
});
}
return store;
}
It works fine with a single flow. But if I refresh page on any page except homePage, everything disturbs on page. I got following warnings as well:
[1] redux-persist asyncLocalStorage requires a global localStorage object. Either use a different storage backend or if this is a universal redux application you probably should conditionally persist like so: https://gist.github.com/rt2zz/ac9eb396793f95ff3c3b
[1] Warning: React can't find the root component node for data-reactid value `.15mzo5h179c.3.0.0`. If you're seeing this message, it probably means that you've loaded two copies of React on the page. At this time, only a single copy of React can be loaded at a time.
P.s. I'm using react-redux-universal-hot-example boilerplate
You want to conditionally create your store using the localStorage store enhancer only when you're running on the client.
...
const reducer = require('./modules/reducer');
let store;
if (__CLIENT__) {
store = finalCreateStore(reducer, data, autoRehydrate());
persistStore(store);
} else {
store = finalCreateStore(reducer, data);
}
...
Related
Today I started to build a Next.js application.
I am using Redux (next-redux-wrapper) to manage my global state.
(I am also persisting some reducer with redux-persist).
I just implemented redux-thunk and immediately got a very strange error. I really do not know why that error is occurring. I basically just did the setup, without creating any reducers (The error was still there, after I created the reducer).
It is not the first time, I did the same setup without any problems, but that time I can not get rid of that error.
Error code
Warning: Using UNSAFE_componentWillReceiveProps in strict mode is not
recommended and may indicate bugs in your code. See
https://reactjs.org/link/unsafe-component-lifecycles for details.***
Move data fetching code or side effects to componentDidUpdate.
If you're updating state whenever props change, refactor your code to use memoization ***techniques or move it to static
getDerivedStateFromProps. Learn more at:
https://reactjs.org/link/derived-state Please update the following
components: withRedux(MyApp)
If you need more code, please ask. I just do not really have much more. Maybe package.json.
_app.js
import App from 'next/app'
import { wrapper } from '../reduxStore/store'
import { useStore } from 'react-redux'
import PropTypes from 'prop-types'
function MyApp({ Component, pageProps }) {
const store = useStore((state) => state)
return <Component {...pageProps} />
}
MyApp.propTypes = {
Component: PropTypes.func,
pageProps: PropTypes.object,
}
export default wrapper.withRedux(MyApp)
store.js
import { createStore, applyMiddleware, combineReducers } from 'redux'
import { HYDRATE, createWrapper } from 'next-redux-wrapper'
import thunkMiddleware from 'redux-thunk'
import userReducer from './reducers/userReducer'
const bindMiddleware = (middleware) => {
if (process.env.NODE_ENV !== 'production') {
const { composeWithDevTools } = require('redux-devtools-extension')
return composeWithDevTools(applyMiddleware(...middleware))
}
return applyMiddleware(...middleware)
}
const combinedReducer = combineReducers({
user: userReducer,
})
const reducer = (state, action) => {
if (action.type === HYDRATE) {
const nextState = {
...state, // use previous state
...action.payload, // apply delta from hydration
}
// if (state.count.count) nextState.count.count = state.count.count // preserve count value on client side navigation
return nextState
} else {
return combinedReducer(state, action)
}
}
export const makeStore = ({ isServer }) => {
if (isServer) {
//If it's on server side, create a store
return createStore(reducer, bindMiddleware([thunkMiddleware]))
} else {
//If it's on client side, create a store which will persist
const { persistStore, persistReducer } = require('redux-persist')
const storage = require('redux-persist/lib/storage').default
const persistConfig = {
key: 'nextjs',
whitelist: ['user'], // only counter will be persisted, add other reducers if needed
storage, // if needed, use a safer storage
}
const persistedReducer = persistReducer(persistConfig, reducer) // Create a new reducer with our existing reducer
const store = createStore(
persistedReducer,
bindMiddleware([thunkMiddleware]),
) // Creating the store again
store.__persistor = persistStore(store) // This creates a persistor object & push that persisted object to .__persistor, so that we can avail the persistability feature
return store
}
}
export const wrapper = createWrapper(makeStore)
This is because the third party library is using componentWillReceiveProps - componentWillReceiveProps gets automatically renamed to UNSAFE_componentWillReceiveProps. This happens because those methods are now considered legacy code and React is deprecating that lifecycle method as well as others.
Unfortunately, their isn't an immediate solution.
You can fork the code and update it yourself
Start an issue on the code's GIT page and hope the update their maintainer fixes the issue.
Find another library to do the same job.
Write custom logic to do the same thing as the library
Use the library and hope they fix it before it's deprecated by React.
the error caused by the third-party library (next-redux-wrapper) about Legacy Lifecycle Methods (UNSAFE), the previous name is componentWillReceiveProps, That name will continue to work until version 17.
to fix it
you can use the react-codemod to automatically update your components. and use rename-unsafe-lifecycles property
npx react-codemod rename-unsafe-lifecycles <path>
my path to a third party library is
path=./node_module/next-redux-wrapper/lib/index.js
thank you
TL;DR: State updates correctly (viewed from Redux DevTools) but does not persist in Local Storage as it says that the state is "undefined" (screenshots attached).
Explanation:
I am new to Redux. I am trying to save my state to Local Storage. The state is updating correctly when I view from Redux DevTools and console.log() statements. However, when I check the application's local storage, it shows that the state is undefined.
What I am trying to do:
I am adding a service to my cart whenever I press the "Add" button in one of my components (which I then want to save to the Local Storage of the browser).
Here are the screenshots from Redux DevTools and browser's local storage:
Please help me find and fix the issue.
Here is the code of my root component App.js which contains my Redux store and local storage funtions:
import Servicesdata from "./ServicesData";
import { createStore } from "redux";
import reducer from "./Reducer";
import { Provider } from "react-redux";
// Local Storage Functions:
function saveToLocalStorage(state) {
try {
const serializedState = JSON.stringify(state);
localStorage.setItem("state", serializedState);
} catch (e) {
console.log(e);
}
}
function loadFromLocalStorage() {
try {
const serializedState = localStorage.getItem("state");
if (serializedState === null) return undefined;
return JSON.parse(serializedState);
} catch (e) {
console.log(e);
return undefined;
}
}
//initial store
const initialStore = {
services: Servicesdata,
cart: [],
bill: 0,
// quantity: 0,
total_items: 0, //saves total items in the cart
};
const persistedState = loadFromLocalStorage();
//store
const store = createStore(
reducer,
persistedState,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
store.subscribe(() => saveToLocalStorage(store.getState));
const App = () => {
return (
<Provider store={store}>
//more code
</Provider>
);
};
export default App;
In my Reducer.js, I am just dispatching an INCREASE action to add a new item to the cart.
Change store.getState to store.getState().
I am beginning with Redux and I always used it in components with connect() and mapStateToProps(), but now I want to call my API with setInterval() every x time to check if the server has new data not stored in the Redux store, and substitute it.
My approach was to create a function that reads the store and update it like that:
import { store } from './dir/dir/store.js'
const refresher = async () => {
const state = store.getState();
// Call API, compare 'state.calendar' with calendar from server
// Call store.dispatch() if they are different, then update Redux store
}
export default refresher
My questions are:
Is this a good practise to use Redux?
Is there a better approach to this problem?
Thanks
It's perfectly fine to export the store and use within a vanilla js/ts file.
Example Redux Store
Make sure to export the store that you create
import { configureStore } from "#reduxjs/toolkit";
import { slice } from "../features/counterSlice";
export const store = configureStore({
reducer: {
counter: slice.reducer
}
});
Usage in Non-Component Code:
You can then import the created store in any other code
import { store } from "../App/store";
import { slice as counterSlice } from "../features/counterSlice";
export function getCount(): number {
const state = store.getState();
return state.counter.value;
}
export function incrementCount() {
store.dispatch(counterSlice.actions.increment());
}
Traditional Usage in Functional Component
import { useDispatch, useSelector } from "react-redux";
import { RootState } from "../App/store";
import { slice as counterSlice } from "../features/counterSlice";
export function Clicker() {
const dispatch = useDispatch();
const count = useSelector((state: RootState) => state.counter.value);
const dispatchIncrement = () => dispatch(counterSlice.actions.increment())
// ...
Example Slice
import { createSlice } from "#reduxjs/toolkit";
export const slice = createSlice({
name: "counter",
initialState: { value: 0 },
reducers: {
increment: (state) => {
state.value += 1;
}
}
});
Demo in Codesandbox
Note: You cannot use this option with Server Side Rendering. If you need to support SSR, you can use middleware to listen to dispatched actions and handle elsewhere.
Further Reading
What is the best way to access redux store outside a react component? | Stack Overflow
Access the Redux Store Outside a React Component | Blog
How can I access the store in non react components? | Github Issues
Here you can access the store and action out side any component like index.js file in react-native.
import {updateLocationAlertItem} from './src/store/actions/locationAlertAction';
import {store} from './src/store/index';
store.subscribe(listener);
function listener() {
state = store.getState().locationAlertReducer;
}
store.dispatch(
updateLocationAlertItem({
index: index,
is_in_radius: true,
is_notification: true,
arrival_time: moment().format('DD/MM/YYYY hh:mm'),
exit_time: item.exit_time,
}),
);
I have been getting the following error since sometime in my console, i have no idea what it means and why it is originating. Please spread some light on this matter.
it says:
persistReducer.js:50 Uncaught TypeError: action.rehydrate is not a function
at _rehydrate (persistReducer.js:50)
at persistReducer.js:54
redux-persist version on package.json: "^5.6.11"
locked version: "5.9.1"
Store configuration code:
import thunk from 'redux-thunk';
import { persistStore } from 'redux-persist';
import { History, createBrowserHistory } from 'history';
import { createUserManager, loadUser } from "redux-oidc";
import { routerReducer, routerMiddleware } from 'react-router-redux';
import { createStore, applyMiddleware, compose, combineReducers, GenericStoreEnhancer, Store, StoreEnhancerStoreCreator, ReducersMapObject } from 'redux';
import * as StoreModule from './reducers';
import { ApplicationState, reducers } from './reducers';
import userManager from "./utils/userManager";
// Create browser history to use in the Redux store
const baseUrl = document.getElementsByTagName('base')[0].getAttribute('href')!;
const history = createBrowserHistory({ basename: baseUrl });
export default function configureStore(history: History, initialState?: ApplicationState) {
// Build middleware. These are functions that can process the actions before they reach the store.
const windowIfDefined = typeof window === 'undefined' ? null : window as any;
// If devTools is installed, connect to it
const devToolsExtension = windowIfDefined && windowIfDefined.__REDUX_DEVTOOLS_EXTENSION__ as () => GenericStoreEnhancer;
const createStoreWithMiddleware = compose(
applyMiddleware(thunk, routerMiddleware(history)),
devToolsExtension ? devToolsExtension() : <S>(next: StoreEnhancerStoreCreator<S>) => next
)(createStore);
// Combine all reducers and instantiate the app-wide store instance
const allReducers = buildRootReducer(reducers);
const store = createStoreWithMiddleware(allReducers, initialState) as Store<ApplicationState>;
loadUser(store, userManager);
// Enable Webpack hot module replacement for reducers
if (module.hot) {
module.hot.accept('./reducers', () => {
const nextRootReducer = require<typeof StoreModule>('./reducers');
store.replaceReducer(buildRootReducer(nextRootReducer.reducers));
});
}
const persistor = persistStore(store);
return { store, persistor };
}
function buildRootReducer(allReducers: ReducersMapObject) {
return combineReducers<ApplicationState>(Object.assign({}, allReducers, { routing: routerReducer }));
}
// Get the application-wide store instance, prepopulating with state from the server where available.
const initialState = (window as any).initialReduxState as ApplicationState;
export const { store, persistor } = configureStore(history, initialState);
if you are working on localhost and using redux-devtools try to check Access File Url checkbox on extension options.
you can manage your chrome extensions by typing chrome://extensions/ in the address bar or by going to the setting and chose extensions from left menu.
I get this error when using redux devtools, remove this and it should go away.
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
Your code is using API of an older redux-persist version.
Refer to Basic Usage for the updated API:
import { persistStore, persistReducer } from 'redux-persist'
const persistConfig = {
key: 'root',
storage,
}
const allReducers = persistReducer(persistConfig, buildRootReducer(reducers));
I'm using React Boilerplate which uses Redux. I have downloaded the Redux Chrome Dev Tools and I keep getting this error
combineReducers.js:29 The previous state received by the reducer is of unexpected type. Expected argument to be an instance of Immutable.Iterable with the following properties: "route", "language", "global".
I have been debugging this thing through and through to no avail. What I've seen is that combineReducers returns a function and the first line of it is
var inputState = arguments.length <= 0 || arguments[0] === undefined ? _immutable2.default.Map() : arguments[0];
I noticed that a breakpoint here hits twice. The first time, the arguments are exactly what they should be
But the second time my arguments look like this
For some reason, arguments[0] changes from a Map type to a literal object. So two questions:
Why does Redux run combineReducers multiple times on INIT?
Why would my arguments change when initializing?
Here's the code from store.js in React Boilerplate. I only added the import and call for persistStore.
import { createStore, applyMiddleware, compose } from 'redux';
import { fromJS } from 'immutable';
import { routerMiddleware } from 'react-router-redux';
import createSagaMiddleware from 'redux-saga';
import createReducer from './reducers';
import { persistStore, autoRehydrate } from 'redux-persist';
const sagaMiddleware = createSagaMiddleware();
export default function configureStore(initialState = {}, history) {
// Create the store with two middlewares
// 1. sagaMiddleware: Makes redux-sagas work
// 2. routerMiddleware: Syncs the location/URL path to the state
const middlewares = [
sagaMiddleware,
routerMiddleware(history),
];
const enhancers = [
applyMiddleware(...middlewares),
];
// If Redux DevTools Extension is installed use it, otherwise use Redux compose
/* eslint-disable no-underscore-dangle */
const composeEnhancers =
process.env.NODE_ENV !== 'production' &&
typeof window === 'object' &&
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ : compose;
/* eslint-enable */
//get the state from localStorage
// const persistedState = loadState();
const store = createStore(
createReducer(),
fromJS(initialState),
composeEnhancers(...enhancers)
);
// Extensions
store.runSaga = sagaMiddleware.run;
store.asyncReducers = {}; // Async reducer registry
// Make reducers hot reloadable, see http://mxs.is/googmo
/* istanbul ignore next */
if (module.hot) {
module.hot.accept('./reducers', () => {
import('./reducers').then((reducerModule) => {
const createReducers = reducerModule.default;
const nextReducers = createReducers(store.asyncReducers);
store.replaceReducer(nextReducers);
});
});
}
persistStore(store);
return store;
}
EDIT
Here is the main reducer file. It brings in other reducer files.
/**
* Combine all reducers in this file and export the combined reducers.
* If we were to do this in store.js, reducers wouldn't be hot reloadable.
*/
import { combineReducers } from 'redux-immutable';
import { fromJS } from 'immutable';
import { LOCATION_CHANGE } from 'react-router-redux';
import languageProviderReducer from 'containers/LanguageProvider/reducer';
import globalReducer from 'containers/App/reducer';
/*
* routeReducer
*
* The reducer merges route location changes into our immutable state.
* The change is necessitated by moving to react-router-redux#4
*
*/
// Initial routing state
const routeInitialState = fromJS({
locationBeforeTransitions: null,
});
/**
* Merge route into the global application state
*/
function routeReducer(state = routeInitialState, action) {
switch (action.type) {
/* istanbul ignore next */
case LOCATION_CHANGE:
return state.merge({
locationBeforeTransitions: action.payload,
});
default:
return state;
}
}
/**
* Creates the main reducer with the asynchronously loaded ones
*/
export default function createReducer(asyncReducers) {
return combineReducers({
route: routeReducer,
language: languageProviderReducer,
global: globalReducer,
...asyncReducers,
});
}