How to maintain the state of React/Redux application across page refreshes? - javascript

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>

Related

React: Mapping a component with local storage data

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])
}, []);

How to persist UI component state data in Sapper?

In a Sapper app, I want to be able to persist the state of some UI components so I can navigate the app without losing state when the user returns to the pages using those components.
In a Svelte-only app, this is usually done with a custom store that uses the sessionStorage or localStorage API. A good example of that can be found in R. Mark Volkmann's book Svelte and Sapper in Action, ยง6.24:
store-util.js
import {writable} from 'svelte/store';
function persist(key, value) {
sessionStorage.setItem(key, JSON.stringify(value));
}
export function writableSession(key, initialValue) {
const sessionValue = JSON.parse(sessionStorage.getItem(key));
if (!sessionValue) persist(key, initialValue);
const store = writable(sessionValue || initialValue);
store.subscribe(value => persist(key, value));
return store;
}
Unfortunately, using stores that way breaks immediately in Sapper because the scripts run on the server first, where sessionStorage is not defined. There are ways to prevent some parts of code from running on the server (using the onMount lifecycle function in a component, or checking process.browser === true), but that doesn't seem possible here.
Persisting some state locally looks like a very common use case so I'm wondering what's the right way to do it in a Sapper app (considering that I haven't even found the wrong way).
Provide a dummy store for SSR.
It is always possible to do feature detection with something like typeof localStorage !== 'undefined'.
Your component code will re-run in the browser, even if the page was SSR'd. This means that if it is fed a different store, the browser-only values will take over and update existing state (inherited from the server).
See this answer for an example.

How do i access state of one reducer in other in react redux. State flow between reducers and store

I'm an absolute beginner in react-redux and I watched many videos and articles,docs and couldn't understand how the state flows between reducers and between store to reducer.
I am having state for each reducer like this
const initState = {todos:[]}
cont reducer = (state=initState,action) ....
Reducer 2 similarly with different state
initState2 = {todo:" "}
reducer2 = (state=initState2,action) ...
And then I import and combine the reducers. Here I am using two different reducers which have different states and is it the right way of doing things ? If so, How can redux be called single state if each reducer have its own individual state.
Don't we have a single state in the store which is accessed by all the reducers and dispatch actions to change state of store directly instead of changing the reducer's store. Any help is appreciated and it may seems like a silly question but filling the gaps is really important and many beginners have same doubt as mine and please do help. Thank you
You only need 1 reducer to store your todos.
How can redux be called single state if each reducer have its own
individual state.
The application effectively has only 1 global store where all the application state is stored. What the reducer returns is what effectively gets stored in the store.
The configuration of what is stored is a map (key-value) where the key is defined in the root reducer, and the value is what is returned from the reducer function.
The way you have to look at it is that the view is "dumb", in that the only thing it does is tell the app what it wants by dispatching an action. This action is just an event that is marked with some string you give it to identify clearly what it is the view wants. The reducer intercepts this action and updates the state in the store accordingly. This state in turn is accessible to all the components in your app. So it clearly is global.
In your example, the view would just tell the application for example: "Add a todo". The reducer will intercept this message and return an array with the added todo. This returned array is what will be saved in the store.
If you want a seperate "todo", this will probably refer to the currently "active" to do. Marking it as such will make the purpose more expressive.
This is single state because your root reducer will end up with something like:
{
"activeTodo": activeTodoReducer
"todos": todosReducer
}
And you can access these key / values in your components throughout the entire application.
How can redux be called single state if each reducer have its own individual state.
Because the state is not saved in the reducers. The state is only saved in the store and there is only one store. That is why is called single state.
To create the store:
const store = createStore(myBeautifulReducers)
In your case, myBeautifulReducers would be:
const myBeautifulReducers = combineReducers({reducer, reducer2});
myBeautifulReducers will be an object that contains both reducers (reducer and reducer2) and the logic you wrote in each of them (switch statements and so on).

Fetch graphql query result on render and re-renders BUT LAZILY

I have a React Apollo app and what I am trying to do is that I have a component that renders some data using charts. For this data, I have some filters that I save in the local state of the component (Using hooks)
const [filters, setFilters] = useState(defaultFilters);
Now what I want is that whenever the component mounts, fetch the data using the default filters. But I also want to re-fetch data when the user updates the filters AND CLICKS ON SUBMIT and I'd fetch the results using new filters.
Since I also want to fetch the results on filter update, I am using useLazyQuery hook provided by apollo
const [getData, {data}] = useLazyQuery(GET_DATA_QUERY, { variables: {filters} });
useEffect(getData, []); // this useEffect runs only when the component mounts and never again
But, what happens is whenever my state, filters, updates the getData function is automatically run! ALWAYS! (BEHIND THE SCENE)
How do I handle such cases, where I want to fetch results on mounting and re-rendering.
I have tried using useQuery and refetch provided by it but I get the same problem there, whenever I update the state, the component rerenders and the useQuery hooks is run and makes the call. (That's how I believe it runs)
How do I fix my current code. Calling the getData function inside the useEffect function makes it run on every re-render.
I think I the problem defined in this stackoverflow-question is somewhat similar to mine.
Part of the problem is that you really have two different states that you're trying to utilize a single hook for. You have state that represents your inputs' values in the UI, and then you have state that represents the filters you want to actually apply to your charts. These are two separate bits of state.
The simplest solution is to just do something like this:
const [inputFilters, setInputFilters] = useState(defaultFilters)
const [appliedFilters, setAppliedFilters] = useState(inputFilters)
const { data } = useQuery(GET_DATA_QUERY, { variables: { filters: appliedFilters } })
const handleSubmit = () => setAppliedFilters(inputFilters)
const handleSomeInputChange = event => setInputFilters(...)
This way, you use inputFilters/setInputFilters only to manage your inputs' state. When the user clicks your submit button, the appliedFilters are set to whatever the inputFilters are at the time, and your query will update to reflect the new variables.

Detect redux store changes

I want to save data of every reducer in localStorage in the form of key value pairs
UserReducer: {isLoding: true, ....}
OrderReducer : {isRecieved: false, ....}
So if there is any changes happen in any of the reducer I want to set the updated data in local storage.
What I am thinking is I can do this store.subscribe but how I could know that which reducer has been changed so I can set the whole reducer data to localStorage.
Need help. Thanks :)
One way to do it is to create a lastAction reducer which will store the value of the last dispatched action. Then in store.subscribe you could do:
let nextState = store.getState();
let dispatchedAction = nextState.lastAction.type;
And when you know the last dispatched action, you can update the local storage depending on that action type.
You can create a redux middleware, it will get fired everytime an action is dispatched. Within the middleware you can put a conditional check to see if prev and next store are same. If not, you can update your local storage.
I found this helpful for creating a middleware. And it's fairly easy.
I think you could not know the changes from the store.subscribe.
The last place when you know the changes are the actions.
You can put the persisting logic to the reducers. But maybe you should not want to introduce side effect in your reducer.
So basically you have two options if you rule out the first one.
Persist the whole store as a single object to local storage. It is the easier option. I would try this solution first and I would measure the overhead with a worst case scenario.
Make a redux middleware and capture the individual actions. You can copy the reducer pattern to persist only the object that changed.
You can use subscribe for option 2. You can use middleware for option 2 and option 3.
I came up with one more solution.
You can do the persisting in your action creator with Redux Thunk or with Redux Saga.
You can use React Component for that. Connect it to the Store, and use it to write to localStorage instead of rendering something.
Imho, it would be easier to write than middleware.
You can use redux-watch to listen to the changes in redux store
for e.g.
// ... other imports/requires
import watch from 'redux-watch'
// assuming you have an admin reducer / state slice
console.log(store.getState().admin.name) // 'JP'
// store is THE redux store
let w = watch(store.getState, 'admin.name')
store.subscribe(w((newVal, oldVal, objectPath) => {
console.log('%s changed from %s to %s', objectPath, oldVal, newVal)
// admin.name changed from JP to JOE
}))
// somewhere else, admin reducer handles ADMIN_UPDATE
store.dispatch({ type: 'ADMIN_UPDATE', payload: { name: 'JOE' }})
this may help you.
I want to extend the approach 3 that #PeterAmbruzs shared. Actually the problem which I faced was whenever there was an action dispatched, store wasn't updated immediately but I still wanted to persist it so I imported reducer and passed state and action into it to get the required next state
import reducer from './reducer'
const someMiddleware = store => next => action => {
const state = store.getState(),
const nextState = reducer(state, action)
// Save it to Local Storage
// Or Make an API Call to save it to server
next(action)
}

Categories