State is being resetted due to incorrect load function - javascript

I'm new to react-redux and have some trouble persisting the state, mainly problem with loading the state from localstorage. I can save data to localstorage with no problem, however the data inside redux dev tools resets on refresh. The data in localstorage is as it should be.
I speculate that the loadstate function is not working properly, thus not correctly fetching data from localstorage.
The reason for using (key, data) rather than (state) is that i don't get the error "objects are not valid as a react child", but it should work as good as using (state).
My localstorage.js
export const saveState = (key, data) => {
try {
const serialized = JSON.stringify(data);
localStorage.setItem(key, serialized);
} catch (err) {
// Ignore errors.
}
}
export const loadState = () => {
try {
const serializedState = localStorage.getItem('state');
if (serializedState === null) {
return undefined;
}
return JSON.parse(serializedState);
} catch (err) {
return undefined;
}
};
my subscribe method:
store.subscribe(() => {
const state = store.getState();
Object.keys(state).forEach(
key => {saveState(key, state[key])}
)
})
My store class:
import allReducers from './reducers'
import {createStore} from 'redux';
import { loadState } from './localStorage';
export const store = createStore(allReducers, loadState(), window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());

It’s because you are trying to get from storage entire state but you are saving it by keys that’s why loadState is not returning state. Try something like this
store.subscribe(() => {
const state = store.getState();
saveState('state', state);
});

Related

useMemo for efficient global data availability using reactJS and recoilJS

I am trying to figure out how to solve the following problem in the best way possible:
I have multiple components all requiring a global state (I am using recoil for this, since I have many different "atom" states).
Only if a component gets loaded that requires that state, it will perform an expensive computation that fetches the data. This should happen only once upon initialisation. Other components that require the same piece of data should not re-trigger the data fetching, unless they explicitly call an updateState function.
Ideally, my implementation would look something like this:
const initialState = {
uri: '',
balance: '0',
};
const fetchExpensiveState = () => {
uri: await fetchURI(),
balance: await fetchBalance(),
});
const MyExpensiveData = atom({
key: 'expensiveData',
default: initialState,
updateState: fetchExpensiveState,
});
function Component1() {
const data = useRecoilMemo(MyExpensiveData); // triggers `fetchExpensiveState` upon first call
return ...
}
function Component2() {
const data = useRecoilMemo(MyExpensiveData); // doesn't trigger `fetchExpensiveState` a second time
return ...
}
I could solve this by using useRecoilState and additional variables in the context that tell me whether this has been initialised already, like so:
export function useExpensiveState() {
const [context, setContext] = useRecoilState(MyExpensiveData);
const updateState = useCallback(async () => {
setContext({...fetchExpensiveState(), loaded: true});
}, []);
useEffect(() => {
if (!context.loaded) {
setContext({...context, loaded: true});
updateState();
}
}, []);
return { ...context, updateState };
}
It would be possible to make this solution more elegant (not mixing loaded with the state for example). Although, because this should be imo essential and basic, it seems as though I'm missing some solution that I haven't come across yet.
I solved it first by using a loaded and loading state using more useRecoilStates. However, when mounting components, that had other components as children, that all used the same state, I realized that using recoil's state would not work, since the update is only performed on the next tick. Thus, I chose to use globally scoped dictionaries instead (which might not look pretty, but works perfectly fine for this use case).
Full code, in case anyone stumbles upon a problem like this.
useContractState.js
import { useWeb3React } from '#web3-react/core';
import { useEffect, useState } from 'react';
import { atomFamily, useRecoilState } from 'recoil';
const contractState = atomFamily({
key: 'ContractState',
default: {},
});
var contractStateInitialized = {};
var contractStateLoading = {};
export function useContractState(key, fetchState, initialState, initializer) {
const [state, setState] = useRecoilState(contractState(key));
const [componentDidMount, setComponentMounting] = useState(false);
const { library } = useWeb3React();
const provider = library?.provider;
const updateState = () => {
fetchState()
.then(setState)
.then(() => {
contractStateInitialized[key] = true;
contractStateLoading[key] = false;
});
};
useEffect(() => {
// ensures that this will only be called once per init or per provider update
// doesn't re-trigger when a component re-mounts
if (provider != undefined && !contractStateLoading[key] && (componentDidMount || !contractStateInitialized[key])) {
console.log('running expensive fetch:', key);
contractStateLoading[key] = true;
if (initializer != undefined) initializer();
updateState();
setComponentMounting(true);
}
}, [provider]);
if (!contractStateInitialized[key] && initialState != undefined) return [initialState, updateState];
return [state, updateState];
}
useSerumContext.js
import { useSerumContract } from '../lib/ContractConnector';
import { useContractState } from './useContractState';
export function useSerumContext() {
const { contract } = useSerumContract();
const fetchState = async () => ({
owner: await contract.owner(),
claimActive: await contract.claimActive(),
});
return useContractState('serumContext', fetchState);
}
The reason why I have so many extra checks is that I don't want to re-fetch the state when the component re-mounts, but the state has already been initialised. The state should however subscribe to updates on provider changes and re-fetch if it has changed.

I can't render the API I fetched with createAsyncThunk when I refresh the page

When I first open the app it gets the data from api but when I refresh the page it says Cannot read properties of undefined (reading 'memes'). When I console.log the store item It shows an empty object but I can see the api with Redux Devtools extension. I got stuck with this problem and can't figure it out for two days.
slice.js
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
export const fetchJson = createAsyncThunk(
"json/fetchJson",
async () => {
const data = await fetch("https://api.imgflip.com/get_memes");
const json = await data.json();
return json;
}
);
export const loadJsonSlice = createSlice({
name: "loadJson",
initialState: {
isLoading: false,
hasError: false,
memes:{}
},
extraReducers: (builder) => {
builder
.addCase(fetchJson.pending, (state) => {
state.isLoading = true;
state.hasError = false;
})
.addCase(fetchJson.fulfilled, (state, action) => {
state.isLoading = false;
state.memes = action.payload;
})
.addCase(fetchJson.rejected, (state, action) => {
state.isLoading = false;
state.hasError = true;
state.memes = {};
});
}
});
export default loadJsonSlice.reducer;
export const selectAllJson = (state) => state.loadJsonReducer.memes;
export const isLoading = (state) => state.loadJsonReducer.isLoading;
display.js
import { useEffect, useState } from "react";
import { useSelector, useDispatch } from "react-redux";
import { selectAllJson, isLoading, fetchJson } from "./jsonSlice";
const DisplayJson = () => {
const allMemes = useSelector(selectAllJson);
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchJson());
console.log(allMemes.data.memes[0].id); //Here is where the code gives error.
}, [dispatch]);
if (useSelector(isLoading)) {
return <h1 style={{ fontSize: "48px" }}>WAIT PUST</h1>;
}
return <div>{allMemes.data.memes[0].id}</div>; //Here is where the code gives error.
};
export default DisplayJson;
store.js
import { configureStore } from "#reduxjs/toolkit";
import loadJsonSlice from "./jsonSlice";
const store = configureStore({
reducer: {
loadJsonReducer: loadJsonSlice
}
});
export default store;
I believe you have a small miss-understanding of how asynchronicity works in JavaScript.
Your code in the slide.js is correct and will work.
However, your display.js has to wait for the asynchronous action to complete before it can access the state.
useEffect(() => {
// This dispatch will return a Promise, only after the promise resolves you can access the data
dispatch(fetchJson());
console.log(allMemes.data.memes[0].id); //Here is where the code gives error.
}, [dispatch]);
Make sure you check whether allMemes is already populated when you access it:
const allMemes = useSelector(selectAllJson);
console.log('memes: ', allMemes); // initially 'undefined' but eventually populated via your thunk
// When using memes, make sure it is populated
return allMemes ? allMemes.data.memes[0].id : 'Loading...';
I think this solution works, but the reason for the error is not the async type of your fetch request. The reason for this error is the current closure of your effect-function. Quoted by Eric Elliott:
"A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives you access to an outer function’s scope from an inner function. In JavaScript, closures are created every time a function is created, at function creation time."
See also this link: https://medium.com/javascript-scene/master-the-javascript-interview-what-is-a-closure-b2f0d2152b36
Depending on this, allMemes in the effect-function will be an empty object (like your initialization) also by dispatching an action-object and not a thunk.

React/Redux how to access the state in the networkservice

I have created a Network service component which deals with the API call. I want to retrieve state from other components which update the store.
Im having trouble getting the state so I started using Redux, but I havent used Redux before and still trying to find a way to pass the state to the NetworkService. Any help would be great, thanks!
Here is my NetworkService.js
import RequestService from './RequestService';
import store from '../store';
const BASE_URL = 'api.example.com/';
const REGION_ID = //Trying to find a way to get the state here
// My attempt to get the state, but this unsubscribes and
// doesnt return the value as it is async
let Updated = store.subscribe(() => {
let REGION_ID = store.getState().regionId;
})
class NetworkService {
getForecast48Regional(){
let url =`${BASE_URL}/${REGION_ID }`;
return RequestService.getRequest(url)
}
}
export default new NetworkService();
store.js
import {createStore} from 'redux';
const initialState = {
regionId: 0
};
const reducer = (state = initialState, action) => {
if(action.type === "REGIONAL_ID") {
return {
regionId: action.regionId
};
}
return state;
}
const store = createStore(reducer);
export default store;
My folder heirarchy looks like this:
-App
----Components
----NetworkService
----Store
Do not import store directly. Use thunks/sagas/whatever for these reasons.
NetworkService should not know about anything below.
Thunks know only about NetworkService and plain redux actions.
Components know only about thunks and store (not store itself, but Redux's selectors, mapStateToProps, mapDispatchToProps).
Store knows about plain redux actions only.
Knows - e.g. import's.
//////////// NetworkService.js
const networkCall = (...args) => fetch(...) // say, returns promise
//////////// thunks/core/whatever.js
import { networkCall } from 'NetworkService'
const thunk = (...args) => (dispatch, getState) => {
dispatch(startFetch(...args))
const componentData = args
// I'd suggest using selectors here to pick only required data from store's state
// instead of passing WHOLE state to network layer, since it's a leaking abstraction
const storeData = getState()
networkCall(componentData, storeData)
.then(resp => dispatch(fetchOk(resp)))
.catch(err => dispatch(fetchFail(err)))
}
//////////// Component.js
import { thunk } from 'thunks/core/whatever'
const mapDispatchToProps = {
doSomeFetch: thunk,
}
const Component = ({ doSomeFetch }) =>
<button onClick={doSomeFetch}>Do some fetch</button>
// store.subscribe via `connect` from `react-redux`
const ConnectedComponent = connect(..., mapDispatchToProps)(Component)

Persist multiple states using LocalStorage in Redux

I need to persist multiple states in Redux store using LocalStorage. I already have working one key in my case it is drivers. also need to do with buses and carriers states.
Store.js
import ReduxThunk from 'redux-thunk';
import { createStore, applyMiddleware, compose } from 'redux';
import Reducers from './reducers';
import { loadState, saveState } from '../../utils/localstorage';
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
// LOCAL STORAGE FOR DRIVERS, BUSES, CARRIERS
const persistedState = loadState()
const store = createStore(
Reducers,
persistedState,
composeEnhancers(
applyMiddleware(
ReduxThunk
)
)
);
store.subscribe(() => {
saveState({
drivers: store.getState().drivers
});
});
export default store;
localstorage.js
export const loadState = () => {
try {
const serializedDriversState = localStorage.getItem('drivers')
if (serializedDriversState === null) {
return undefined;
}
return JSON.parse(serializedDriversState);
} catch (err) {
return undefined;
}
};
export const saveState = (drivers) => {
try {
const serializedDriversState = JSON.stringify(drivers);
localStorage.setItem('drivers', serializedDriversState)
} catch (err) {
// Ignore errors.
}
}
I'm using Dan Abramov's example: Redux LocalStorage.
So how to store multiple states using LocalStorage in Redux store? Is it good approach or use some middleware like redux-persist?
I think, you should add forEach over state in store.subscribe function and you can save the whole store partly.
export const saveState = (key, data) => {
try {
const serialized = JSON.stringify(data);
localStorage.setItem(key, serialized);
} catch (err) {
// Ignore errors.
}
}
store.subscribe(() => {
const state = store.getState();
Object.keys(state).forEach( key => {
saveState(key, state[key])
})
});

Saving only a particular piece of state in localStorage w/ Redux

How to save in localStorage using Redux, only a particular piece of state?
For example, my state in list reducer is defined as follows:
state = {
companies: [],
currentDisplay: '',
recordNotFound: false,
}
This is my combineReducer file:
const rootReducer = combineReducers({
list: listReducer,
form: formReducer
})
localStorage.js:
export const loadState = () => {
try {
const serializedState = localStorage.getItem('state')
if (serializedState === null) {
return undefined;
}
return JSON.parse(serializedState)
} catch (err) {
return undefined
}
}
export const saveState = (state) => {
try {
const serializedState = JSON.stringify(state)
localStorage.setItem('state', serializedState)
} catch (err) {
// to define
}
}
And after browser reloads I want only companies: [{obj1}, {obj2}, ...] array to be preloaded and the rest of state reset to default values f.e. currentDisplay: '' to be equal ''.
Right now responsible for this operation code looks like this:
store.subscribe(() => {
saveState({
list: store.getState().list
})
})
And it stores the whole list obviously...
I guess I could easily reset these parameters in React using setState(), but would like to do this properly.
You can save just the companies parameter on localStorage if you don't need the other parameters to be loaded.
store.subscribe(() => {
saveState({
companies: store.getState().list.companies
})
})

Categories