I have a standard React/Redux app, that when loads, initially pulls localStorage values (via useEffect hook that triggers the reducers, etc.), and then stores/saves them when changed.
However, I have legacy code, that stores and saves one of these localStorage values, and saves it directly.
It's all on the same domain so the localStorage value is saved via the React app or legacy code. But I need a way for the React/Redux end to reactively update if localStorage is updated from the legacy code.
You can handle Window: storage event like so:
useEffect(() => {
const localStorageListener = (event) => {
const value = JSON.parse(window.localStorage.getItem('sampleValue'))
dispatch(updateValue(value));
};
window.addEventListener('storage', localStorageListener);
return () => {
window.removeEventListener('storage', localStorageListener);
};
}, []);
alternatively you could window.onstorage property... but IMO listener seems cleaner in React.
Related
Please I'm new to react and I'm trying to set a value on session storage the get it on another page. The problem is, I'm forced to refresh the page to get the updated value stored in the session storage.
I tried setting this way :
sessionStorage.setItem("annee", JSON.stringify(annees));
sessionStorage.setItem("commune", JSON.stringify(communes));
And getting this other way :
{JSON.parse(sessionStorage.getItem("commune")).ctd_name_commune}
{JSON.parse(sessionStorage.getItem("annee")).ctd_annee_libelle}
You can use event listener. When any change is made to storage this function will be triggered. You won't have to refresh the page.
const handleChange = ({ key, value }) => {
// Do Something with the key and value key
};
useEffect(() => {
window.addEventListener(
'storage',
handleChange,
false,
);
return () => window.removeEventListener('storage', handleChange);
}, []);
For better result you can simply create a hook to handle storage similar to state. I have created one for LocalStorage. You can create a similar one for sessionStorage.
Hook Link
Edit
If it's on the same tab then we have to dispatch it separately on the same page. It might end up triggering the same twice for other pages but that can be handled conditionally.
To dispatch,
window.dispatchEvent( new Event('storage') )
More reading on MDN
Once you set something in sessionStorage you should be able to get it immediately without refreshing, but only if it's on the same tab. If you are trying to get the data in sessionStorage from another tab, then you would always have to refresh, unless you open the tab after setting the item. You could do that like this:
Promise.resolve( sessionStorage.setItem('test', true) )
.then(() => {
window.open(window.location.href, '_blank')
})
Then when you are on this newly opened tab you will be able to access the same data in sessionStorage.
Same goes for localStorage and cookies. If you're unable to do it like that then I'd suggest storing the data on the server and retrieving it that way.
Read all about sessionStorage and localStorage here: https://blog.logrocket.com/localstorage-javascript-complete-guide/
I'm currently creating a history list component for a form in a react app and am having some trouble with the local storage.
Essentially, I want the app to render a list of past inputs from the user's local storage data. My current idea is based in duplicating the local storage data is a state variable.
const [history, setHistory] = useState([]);
On form submit I call this function with the form input as the parameter (the input is a single string)
const setLocalStorage = (input) => {
const hist = JSON.parse(localStorage.getItem('history')) || [];
console.log(hist)
hist.push(input)
localStorage.setItem('history', JSON.stringify(hist))
setHistory(hist);
}
This is meant to put the history from local story into hist, push the input that was just submitted into the existing array hist, and update the local storage with the new value. The state variable should then be updated with the most updated array of strings with the setHistory(hist) call.
Also, I want local storage to be pulled on first render so I can use that data to render the history list on initial load. I have a useEffect hook for this as shown:
useEffect(() => {
setHistory(JSON.parse(localStorage.getItem('history')))
console.log(history)
}, []);
The problem I'm facing is that the state never seems to get updated? I can instead do a console log for JSON.parse(localStorage.getItem('history')) and get the local storage array returned but this of course isn't helpful for data usage. I know that the local storage is properly being pulled from this but I'm unable to update the state for some reason. I need the state updated so I can conditionally render and use the array for mapping each item on the history list. When I console log "history" I get an empty array.
TL;DR
Concisely, what is the cleanest method to have local storage and state values maintain equivalency? Hope my post was clear enough to understand!
I'm remaking and updating a regular JS app on React for practice so I'm able to provide a live link of how I want this simple component to work.
https://giovannimalcolm.github.io/weather-dashboard/
The second returned parameter of useState is similar to the this.setState which is asynchronous. You may see that state is not changed even setHistory is called. Passing function instead of the value will avoid this issue as it will be executed after the state is updated. This might be useful for better understanding Passing function to setState()
useEffect(() => {
const hist = JSON.parse(localStorage.getItem('history'))
setHistory(prevHistory => [...prevHistory, ...hist])
}, []);
I am trying to use react hooks to make a Table component that displays rows of data from an API based on a set of filters that the user can choose. I want to make a new call to fetch data whenever the user clicks an 'Apply Filters' button, not when the user makes changes to the filters.
I am using context to manage the 'filters' state and a 'lastFetched' state which tracks when the user last clicked the 'Apply Filters' button (as well as other states on the page). Updates to the context are made via the useReducer hook and its dispatch method (see here).
The data fetching occurs in a useEffect hook that reruns whenever the 'lastFetched' state changes. This appears to be working correctly; however, the effect references other values from the context (i.e. the filters) that are not included in the dependencies. I am aware of the exhaustive-deps eslint rule, and I am concerned that I am not handling the hook's dependencies correctly.
const Table = () => {
const [context, dispatch] = useTableContext(); // implemented with createContext and useReducer
const { filters, lastFetched } = context;
useEffect(() => {
if (!filters.run) {
return;
}
dispatch({ type: 'FETCH_DATA_BEGIN' });
const params = convertContextToParams(context); // this is lazy, but essentially just uses the the filters and some other state from the context
API.fetchData(params)
.then((data) => {
dispatch({ type: 'FETCH_DATA_SUCCESS', payload: data.results });
})
.catch((e) => {
dispatch({ type: 'FETCH_DATA_FAILURE', payload: e.response.data.message });
});
return () => { ... some cleanup... };
}, [lastFetched]); // <== This is the part in question
return <...some jsx.../>
};
Again, this appears to be working, but according to the react docs, it seems I should be including all the values from the context used in the hook in the hook's dependencies in order to prevent stale references. This would cause the logic to break, since I don't want to fetch data whenever the filters change.
My question is: when the user clicks 'Apply Filters', updates context.lastFetched, and triggers the useEffect hook, will the hook be referencing stale filter state from the context? If so, why? Since the effect is rerun whenever the button is clicked, and all the state updates are done via a reducer, does the usual danger of referencing stale variables in a closure still apply?
Any guidance appreciated!
Note: I have thought about using useRef to prevent this issue, or perhaps devising some custom async middleware to fetch data on certain dispatches, but this is the solution I currently have.
I am not an expert but I would like to provide my takes. According to my understanding of how Context works, you will not get stale filter data with the current implementation. useReducer updates the state with a new object which will trigger Table to be re-render.
Also, Table component doesn't really care about filter data unless lastFetched is changed by a click event. If lastFetched is changed, all the Consumer of TableContext will be re-render again. You should not get stale filter data either.
Is the ngrx store persistent? In other words can we close the browser reopen it, and retrieve state that was added to the ngrx store?
Currently ngrx/store doesn't support such functionality. But you can maintain state by using a library like ngrx-store-persist to save data to the browsers indexedDB, localStorage or WebStorage. This library automatically saves and restores ngrx/store's data. You just need to add redux keys that you want to store in the config (see "Usage" section).
The nrxg/store is in memory only. To manage state via the browser with something like pouchdb ngrx/effects can be used. More here.
You can maintain state by using a library like idb.js to save data to the browsers indexDB, then by using the ngrx effect you can have an init effect to reload the state back when webapp loads. here is a example code let say I want to reload selected language. the effect would be:
#Effect()
init$: Observable<Action> = defer(() => {
return from(storageDb.readData('app', Lang.ActionType.LANG_KEY)).pipe(
map((data: any) => {
const tmpData = data ? data.state : {dir: 'rtl', selected: 'ar'};
this.translate.setDefaultLang(tmpData.selected);
return new Lang.LanguageInit(tmpData);
})
);
});
the storageDb.readData is a wrapper for idb to load data by key, the effect will kick in when effects are getting loaded it will get the data from the indexdb and if it does not find any sets default value then dispatchs an Action. ngrx best practices is to use promises in effects not in reducers since loading the data is async operation, then in your state reducer you can catch the Action like so:
case lang.ActionType.LANGUAGE_INIT: {
return {...state, ...action.payload};
}
Using it in this way you can slice your states save them and loaded them even when lazyloading.
I am new to react js.
I have two routes like A & B. Now i am passing some values from A to B as props. If B page is refreshed, then all props values from A is gone and B page is not rendering. I am using react with redux.
mapDispatchToProps & mapStateToProps functions are used to pass values between A & B routes as props.
For example: Route A has done some calculations and store the values in redux state and Route B is exported as connect(mapStateToProps, mapDispatchToProps)(B), by using mapStateToProps in which A's state values are passed to B as props.
Please suggest me the best way to handle browser refresh on above mentioned use case and also if any other best way to pass the values between routes. Thanks in advance.
Your question talks about two different concerns. First is passing props from one page to another in a React/Redux application, and second is maintaining the application state when the page is refreshed.
You've described the correct method of passing data between two routes in a redux based application.
Which brings us to the second concern.
How to maintain the state of a React/Redux application when the page is refreshed?
When a React/Redux application is refreshed, it gets initialised again and the redux store gets it's default values.
If you wish to maintain the app state across page refreshes or across different sessions, you need to store the state somewhere, and load it when the app initialises.
We can divide this problem into three parts:
Where to store the data
How to store redux state
How to reload the data when the application is initialised
Let's look at each sub-problem individually.
Where to store the data?
You can use the Web Storage API to store data within the user's browser. This API provides 2 mechanisms to store data:
sessionStorage: Stored data is preserved as long as the browser is open, including page reloads and restores.
localStorage: Data is preserved until it is cleared by the user or the application. It persists even if the browser is closed and reopened.
Both sessionStorage and localStorage allow you to store key-value pairs in the browser, and both provide the same set of functions to manage data.
For sessionStorage (example taken from MDN):
// Save data to sessionStorage
window.sessionStorage.setItem('key', 'value');
// Get saved data from sessionStorage
var data = window.sessionStorage.getItem('key');
// Remove saved data from sessionStorage
window.sessionStorage.removeItem('key');
// Remove all saved data from sessionStorage
window.sessionStorage.clear();
For localStorage:
// Save data to localStorage
window.localStorage.setItem('key', 'value');
// Get saved data from localStorage
var data = window.localStorage.getItem('key');
// Remove saved data from localStorage
window.localStorage.removeItem('key');
How to store redux state?
As you are already aware, Redux provides a createStore function which takes our root reducer and returns the application store.
The store object holds the entire application store, and provides a few methods including one to register a listener.
store.subscribe(listener) can be used to add a change listener to the store, which will get called every time the store gets updated.
We will add a listener to the store, which will save the application state to localStorage.
Try adding this in the file where you create your store using createStore:
/**
* This function accepts the app state, and saves it to localStorage
* #param state
*/
const saveState = (state) => {
try {
// Convert the state to a JSON string
const serialisedState = JSON.stringify(state);
// Save the serialised state to localStorage against the key 'app_state'
window.localStorage.setItem('app_state', serialisedState);
} catch (err) {
// Log errors here, or ignore
}
};
/**
* This is where you create the app store
*/
const store = createStore(rootReducer);
/**
* Add a change listener to the store, and invoke our saveState function defined above.
*/
store.subscribe(() => {
saveState(store.getState());
});
How to reload the stored data, and restore the application state when the app is initialised again?
When we create our app store using createStore, we have the option to pass an initial state to the store using the second parameter to the function.
When the application starts up, we will check the localStorage for any saved data. If we find it, we will send it as the second parameter to createStore.
This way, when the app finishes initialising, it will have the same state as it did before the page was refreshed or the browser was closed.
Try adding this in the file where you create your store using createStore:
/**
* This function checks if the app state is saved in localStorage
*/
const loadState = () => {
try {
// Load the data saved in localStorage, against the key 'app_state'
const serialisedState = window.localStorage.getItem('app_state');
// Passing undefined to createStore will result in our app getting the default state
// If no data is saved, return undefined
if (!serialisedState) return undefined;
// De-serialise the saved state, and return it.
return JSON.parse(serialisedState);
} catch (err) {
// Return undefined if localStorage is not available,
// or data could not be de-serialised,
// or there was some other error
return undefined;
}
};
/**
* This is where you create the app store
*/
const oldState = loadState();
const store = createStore(rootReducer, oldState);
That's it! Now, combine the last two blocks of code, and your application has the ability to maintain state across page refreshes, or even across browser restarts.
Hope this helps.
Cheers!
:)
you can try redux-persist or redux-storage ,
when you initialize the store
createStore(reducer, [preloadedState], [enhancer]),
you can get the data and assign it to preloadedState
Try using react-router and this will render components based on route.
When the app is initialized, app should fetch data and update the store with the required information.
Eg: In the below example, When IntilizeApp component is mounting compute the information required and update the store by dispatching the actions. Use react's life cycle method like componentWillMount to compute.
import {Router, Route, hashHistory} from 'react-router'
// import initializeApp
// import ComponentA
// import ComponentB
<Router history={hashHistory}>
<Route path="/" component={InitializeApp}>
<Route name="A" path="A" component={ComponentA} />
<Route name="B" path="B" component={ComponentB} />
</Route>
</Router>