localStorage Getting Undefined State - reactjs

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().

Related

Can't persist MobX state tree in React Native

I am using MobX-state-tree for state management and mst-persist to persist my data. The problem is when I reload the app the initial data of the store renders first and then the persist data gets loaded.
So whenever I want to check a persisted data in my store the initial data renders first, My function runs based on that and when everything stopped rendering my persisted data renders.
This code is a simplified version of my problem. When app renders first I get "false" in my console then I get "True". Even after I comment out the setTemp().
Is there any way to fix this or is there another package that I can use for persisting MST?
Rootstore.ts
import {
types,
Instance,
applySnapshot,
getSnapshot,
} from 'mobx-state-tree';
import {createContext, useContext} from 'react';
import AsyncStorage from '#react-native-async-storage/async-storage';
import {persist} from 'mst-persist';
const RootStore = types
.model('RootStore', {
temp: false,
})
.actions(store => ({
setTemp() {
applySnapshot(store, {...getSnapshot(store), temp: true});
},
}));
let _store: any = null;
export function initializeStore() {
_store = RootStore.create({});
persist('#initstore', _store, {
storage: AsyncStorage,
jsonify: true,
whitelist: ['temp'],
});
return _store;
}
export type RootInstance = Instance<typeof RootStore>;
const RootStoreContext = createContext<null | RootInstance>(null);
export const Provider = RootStoreContext.Provider;
export function useStore(): Instance<typeof RootStore> {
const store = useContext(RootStoreContext);
if (store === null) {
throw new Error('Store cannot be null, please add a context provider');
}
return store;
}
App.ts
import {initializeStore,Provider} from './src/store/RootStore';
const store = initializeStore();
<Provider value={store}>
<RootStack /> //the App
</Provider>
InitializeScreen.ts
import {observer} from 'mobx-react-lite';
import {useStore} from '../../store/RootStore';
const InitializeScreen = observer((): JSX.Element => {
const {setTemp,temp} = useStore();
useEffect(() => {
setTemp()
}, []);
console.log('init page',temp); // <-- Every time app reloads reneders false then true
return (
<Text>InitializeScreen</Text>
);
});
export default InitializeScreen;

Redux useSelector returns nothing in child component

I am new to React Native and Redux, and was hoping someone could help out in my issue? I have a parent component that fetches some user data (their location) and dispatches to a redux store:
Parent
import { useDispatch } from 'react-redux'
import { setLocation } from './store/locationSlice'
const App = () => {
const dispatch = useDispatch()
const getLocation = () => {
const location = await fetchLoc()
dispatch(setLocation(location))
}
useEffect(() => {
getLocation()
},[])
}
My child component is intended to retrieve this data using the useSelector hook
Child
import { useSelector } from 'react-redux'
const HomeScreen = () => {
const location = useSelector(state => state.location)
useEffect(() => {
if (location) {
getEntitiesBasedOnLocation(location)
}
},[location])
}
However, in my case, useSelector never retrieves the up-to-date information that i have dispatched in the parent, with location returning undefined. I'm fairly certain there's a simple oversight here, but i'm at a loss as to what this could be. I was under the impression that useSelector subscribes to state changes, so why is it that that my dispatched action that causes a change of state is ignored? Using my debugger, I can see that my state is definitely updated with the correct data, but the child component doesn't pick this up..
Here's my location slice:
import { createSlice } from '#reduxjs/toolkit'
const initialState = {
location: {
id: null,
name: null,
latitude: null,
longitude: null
}
}
const locationSlice = createSlice({
name: 'location',
initialState,
reducers: {
setLocation: (state, action) => {
const { id, name, latitude, longitude } = action.payload
state.location = { id, name, latitude, longitude }
}
}
})
export const { setLocation } = locationSlice.actions
export default locationSlice.reducer
UPDATE
The store is configured by wrapping the App.js component in a Provider component, with the store passed as its props as follows:
Root.js
import { configureStore } from '#reduxjs/toolkit'
import { Provider } from 'react-redux'
import locationReducer from './src/store/locationSlice'
import App from './src/App'
const Root = () => {
const store = configureStore({ reducer: locationReducer })
return (
<Provider store={store)>
<App />
</Provider>
)
}
The issue is in your selector. You've created the slice called 'location' and within that slice you've got your state { location: {...}}. So from the perspective of the selector (which accesses your global state, not just the location slice) the path to your data would be state.location.location. But your selector is trying to read out of state.location which only has a location prop. Anything else you tried to read out would be undefined.
It is common to export a custom selection function from the slice configuration. Remember that the selector must take exactly the data that you want to share in your component tree (locationSlice.state.location in this case). This is not mandatory, it is just to facilitate development.
// locationSlice
import { createSlice } from '#reduxjs/toolkit'
//...
export const { setLocation } = locationSlice.actions
export const selectLocation = (state) => state.location.location
export default locationSlice.reducer
// Child
import { useSelector } from 'react-redux'
import {selectLocation} from './src/store/locationSlice'
const HomeScreen = () => {
const location = useSelector(selectLocation)
//...
}
My workaround was to move my getLocation() function in the parent to the child component. useSelector now gets the state as expected. I feel that this work-around defeats the object of having global state access though, and i could probably just use local state rather than Redux.

useSelector not updating with Redux Toolkit

I've been battling this all day long and I'd appreciate any help.
I have a redux store built with Redux Toolkit and createSlice that looks like so:
const initialState = {
analiticaNumber: "",
animal: {},
tests: [{ key: "G9116", value: "DERMATOFITOS PCR/ MUESTRA" }],
};
const PeticionSlice = createSlice({
name: "peticion",
initialState,
reducers: {
addTest: (state, action) => {
state.tests.push(action.payload);
},
},
});
export const { addTest: addTestActionCreator } = PeticionSlice.actions;
export const testsArray = (state) => state.Peticion.tests;
export default PeticionSlice.reducer;
I also have a root reducer that imports the rest of the slices and names them as such
import { combineReducers } from "redux";
import NavigationSlice from "./NavigationSlice";
const RootReducer = combineReducers({
Peticion: PeticionSlice,
});
export default RootReducer;
When I add tests to the tests array it works fine and shows in the redux devtools.
The promblem comes that react does not see the change in the store and won't update the child component:
import { testsArray } from "./Store/PeticionSlice";
That's how I import namely the testsArray to call with the useSelector.
The tradicional way of const { tests } = useSelector( (state) => state.Peticion) doesn't work either.
function App() {
const tests = useSelector(testsArray);
useEffect(() => {
console.log("tests");
}, [tests]);
return (
<StylesProvider injectFirst>
<div className="App">
<nav>
<Navbar />
</nav>
{tests.map((test) => (
<p>{test.key}</p>
))}
</div>
</StylesProvider>
);
}
I belive it has to do something with the mutability of the state, but I thought the toolkit took care of that, and for the life of me I don't know how to solve this.
Any help??? Thanks a lot.
** UPDATE **
I believe it has to do with the way I dispatch the actions. Because I needed to add several boundaries to what the app does, I decided to have an external function that filters and dispatches accordingly. It is not a react component.
import { configureStore } from "#reduxjs/toolkit";
import { addTestToList, addTestActionCreator } from "../Store/PeticionSlice";
import RootReducer from "../Store/RootReuder";
const PruebasToSubmitArray = [];
const store = configureStore({
reducer: RootReducer,
});
const handleTestList = (test, action) => {
const anatomia = "A";
const microbiologia = "M";
function oneBiopsia() {
while (test.key.toString().charAt(0) === anatomia) {
return PruebasToSubmitArray.some(
(pruebaInArray) => pruebaInArray.key.toString().charAt(0) === anatomia
);
}
return false;
}
if (!oneBiopsia() && action === "add") {
switch (test.key.toString().charAt(0)) {
case anatomia:
// console.log("Open pdf for anatomia");
store.dispatch(addTestActionCreator(test));
break;
case microbiologia:
// console.log("Open pdf for micro");
store.dispatch(addTestActionCreator(test));
break;
default:
// console.log("add test to the list, ", test);
store.dispatch(addTestActionCreator(test));
break;
}
} else if (action === "remove") {
// console.log("remove test from the list, ", test);
} else if (oneBiopsia()) {
console.log("Only one biopsia per peticion, ", newState);
}
return null;
};
export default handleTestList;
I added a button on App component and it worked as expected (i showed the updated state), as is right now redux updates the state but the component won't reflect it.
Code SandBox as complete as I can
Very odd behavior in my case.
I did
state = action.payload
and that didn't work.
Once I switched to
state.viewer = action.payload.viewer
everything worked!
Multiple Instances of Store
You create a store variable in your index.js file and pass that store to the react-redux Provider component. This is the store instance which all react-redux useSelector and useDispatch hooks will interact with.
In your HandleTestList.js file you create a different store variable. You then dispatch actions to that store, but those actions won't be reflected in your React app because this isn't the store that your React app uses.
handleTestList needs to either A) import the same global store variable. You will want to move this out of index.js and into store.js to avoid circular dependencies. or B) accept dispatch as an argument.

Empty Redux store when attempting to dispatch from outside React component

I have what I think is a fairly standard React-Redux setup with some persistence mixed in.
app.jsx
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { PersistGate } from 'redux-persist/integration/react';
import AppRouter from './routers/AppRouter';
import configureStore from './store/configureStore';
import config from 'cgConfig';
const { store, persistor } = configureStore();
const jsx = (
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<AppRouter />
</PersistGate>
</Provider>
);
ReactDOM.render(jsx, document.getElementById(config.constants.element.rootId));
configureStore.js
import {
createStore,
combineReducers
} from 'redux';
import {
persistStore,
persistReducer
} from 'redux-persist';
import autoMergeLevel2 from 'redux-persist/lib/stateReconciler/autoMergeLevel2';
import storage from 'redux-persist/lib/storage';
import config from 'cgConfig';
//...import reducers...
export default () => {
const persistConfig = {
key: 'root',
storage,
stateReconciler: autoMergeLevel2
};
let rootReducer = combineReducers({
//...all of my reducers...
});
let store = undefined;
let pr = persistReducer(persistConfig, rootReducer);
if (config.env.includeReduxDevTools) {
store = createStore(
pr,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
} else {
store = createStore(pr);
}
let persistor = persistStore(store);
return {
store,
persistor
};
};
However, the one thing that is a bit unconventional is that I need to update the store outside the context of a react component. As far as I understand (for example), this should not be too difficult to accomplish. Simply call configureStore() to get the store and run a store.dispatch(action). Problem is that I keep getting the initial state of the store back (IE empty), which isn't the same as the one I have already set up through the process of logging in etc. Thus when I run a dispatch, the wrong state is being updated.
Not sure what I am doing wrong, would appreciate any help.
EDIT to answer Uma's question about what the router looks like:
First some more context. The website I am working on will basically have shapes generated in something akin to a graph. These shapes can be manipulated after they are selected from various sources like contextual menus and a toolbar. It is in the context of this change event that I am working on.
When a change is made, the info of the selected item and what needs to be changed will be sent to a generic function which will determine which reducer/action to use to update the Redux store. All my reducers follow the same pattern and look like this:
const reducerInitialState = {};
const setSomeProperty= (state, action) => {
let newState = { ...state };
newState.some_property = action.some_update;
return newState;
};
export default (state = reducerInitialState, action) => {
switch (action.type) {
case 'UPDATE_SOME_PROPERTY':
return setSomeProperty(state, action);
case 'LOG_OUT':
return { ...reducerInitialState };
default:
return state;
}
};
Action for completeness:
export const setSomeProperty = update_data => ({
type: 'UPDATE_SOME_PROPERTY',
some_update: update_data
});
The generic function I have would look something like this:
import configureStore from './store/configureStore';
import { setSomeProperty} from './actions/SomeAction';
import API from './utilities/API';
export default async ({ newValue, selectedShape }) => {
if (!newValue || !selectedShape)
throw "Cannot update the server with missing parameters.";
await API()
.post(
`/api/update-shape`,
{
newValue,
shape: selectedShape
}
)
.then(response => {
updateValue({ selectedShape, newValue });
})
.catch(error => {
// handle error
});
}
const getAction = ({ shape }) => {
switch (shape.type) {
case 0:
return setSomeProperty;
default:
throw `Invalid type of ${shape.type} attempting to update.`;
}
}
const updateValue = ({ selectedShape, newValue }) => {
const action = getAction({ shape: selectedShape })
const { store } = configureStore();
store.dispatch(action(newValue))
}
Note: API is a wrapper for Axios.
Since posting this yesterday I have read that creating a second store like what I am doing with const { store } = configureStore(); is where one of my problems lie in that React/Redux can't have 2 of them. I have also come to realize that the problem most likely have to do with the initial state in my reducers, that somehow using configureStore() does not send the actual state to the reducers and thus all my reducers are showing their initial states when I look at them using console.log(store.getState());. If this is true, at least I know the problem and that is half the battle, but I am unsure how to proceed as I have tried to ReHydrate the state I get from configureStore() but nothing seems to work the way I expect it to.
As far as I can tell you end up in a weird loop where you call an action setSomeProperty then it gets you to the reducer, and in reducer you call that setSomeProperty action again.
In reducer I would expect to see something like this:
export default (state = reducerInitialState, action) => {
switch (action.type) {
case 'UPDATE_SOME_PROPERTY':
return {
...state, // preserve already set state properties if needed
some_update: action.some_update
}
case 'LOG_OUT':
return { ...reducerInitialState };
default:
return state;
}
};

asyncLocalStorage requires a global localStorage object

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);
}
...

Resources