In my app, I have a React ponent that renders a list of numbers and it also stores the sum of these numbers via sessionStorage
.
My app also has a ponent with an <input />
so that new numbers can be added. This also causes the value stored by sessionStorage
to be updated. For each number, a button
exists to allow numbers to be removed, and this immediately updates the value stored in sessionStorage
.
The problem is that I have another ponent that uses the value stored in sessionStorage
as a state using react hooks, but when I update the value in sessionStorage
the value of state doesn't change.
I'm trying to make it update using useEffect()
, but it doesn't work:
import React from 'react';
import { useState, useEffect } from "react";
const LimitsIndicator = props => {
const {
limits: { total, used },
currency,
t,
type,
} = props;
const [limitInUse, setInUse] = useState(sessionStorage.getItem('inUse'));
useEffect(() => {
setInUse(sessionStorage.getItem('inUse'))
})
return (
<div>{limitInUse}</div>
)
}
In this image it shows the sum: 250, and the two values: 100 and 150, but the value 150 was canceled, and as you can see in the console, the sessionStorage
is update, but the value of the sum doesn't change.
In my app, I have a React ponent that renders a list of numbers and it also stores the sum of these numbers via sessionStorage
.
My app also has a ponent with an <input />
so that new numbers can be added. This also causes the value stored by sessionStorage
to be updated. For each number, a button
exists to allow numbers to be removed, and this immediately updates the value stored in sessionStorage
.
The problem is that I have another ponent that uses the value stored in sessionStorage
as a state using react hooks, but when I update the value in sessionStorage
the value of state doesn't change.
I'm trying to make it update using useEffect()
, but it doesn't work:
import React from 'react';
import { useState, useEffect } from "react";
const LimitsIndicator = props => {
const {
limits: { total, used },
currency,
t,
type,
} = props;
const [limitInUse, setInUse] = useState(sessionStorage.getItem('inUse'));
useEffect(() => {
setInUse(sessionStorage.getItem('inUse'))
})
return (
<div>{limitInUse}</div>
)
}
In this image it shows the sum: 250, and the two values: 100 and 150, but the value 150 was canceled, and as you can see in the console, the sessionStorage
is update, but the value of the sum doesn't change.
-
How are you updating
sessionStorage
? – Arup Rakshit Commented Apr 22, 2019 at 19:47 -
useEffect
will render, in your configuration, at every render. What I guess is happening, is that your code correctly does the work, but since it's not aware thatsessionStorage
has changed,LimitsIndicator
doesn't have the chance of re-render ( and doesn't update its local state ) in the first place. Since react is not aware that any change insessionStorage
may require a re-render of your ponent, you need to help the tools be aware of that, and force a render. – Federkun Commented Apr 22, 2019 at 19:47 - Actually, the sessionStorage is updating, I checked in the console when the values change, but as you said, the ponent is not aware of this update – Ramon Commented Apr 22, 2019 at 19:53
- Do you know how can I make the ponent be aware of this update in sessionStorage? – Ramon Commented Apr 22, 2019 at 19:54
- I added an image to explain better what I'm saying – Ramon Commented Apr 22, 2019 at 20:02
2 Answers
Reset to default 3One way to achieve state synchronization between different parts of your app would be via React's Context API.
The general idea would be to centralize shared state (ie limitInUse
) at (or near) the root ponent of your app via a context provider and then wrap child ponents that need access to the shared state via the corresponding context consumer:
1. Create a context for shared state
Create a context which gives us a state "provider" and a state "consumer". The context consumer will be used to access and interact with shared state throughout the app:
const IsUseContext = React.createContext();
2. Define state access for shared state in root ponent
Next, define get/set logic for the shared limitInUse
state. This should be defined in state at (or near) the root level of your app. Here, I define this in the root ponents state
object:
this.state = {
/* Define initial value for shared limitInUse state */
limitInUse : sessionStorage.getItem('inUse'),
/* Define setter method by which limitInUse state is updated */
setLimitInUse: (inUse) => {
/* Persist data to session storage, and update state to trigger re-render */
sessionStorage.setItem('inUse', `${ inUse }`)
this.setState({ limitInUse })
},
}
3. Render context provider from root ponent
Now, render the context's Provider
ponent at the root level of your app, and pass the state
object via the provider's value
prop. The state
object will now be accessible from any context consumer used in the app (see below):
/* In your app ponent's render() method, wrap children with context provider */
<IsUseContext.Provider value={ this.state }>
<LimitsIndicator />
<InputComponent />
</IsUseContext.Provider>
4. Update shared state in child ponent
Finally, we access the shared limitInUse
from nested child ponents in our app via our context's consumer. Notice that the state and setter method defined in the object passed to our provider's value
prop are available and accessible:
/* Update shared limitInUse state via context consumer */
return (<IsUseContext.Consumer>
{ ({ setLimitInUse }) => <input onChange={
(event) => setLimitInUse(event.currentTarget.value) } />
}
</IsUseContext.Consumer>)
Display shared state in child ponent
/* Access shared limitInUse via context consumer */
return (<IsUseContext.Consumer>
{ ({ limitInUse }) => (<div> {limitInUse} </div>) }
</IsUseContext.Consumer>)
For a full working demo, see this jsFiddle. Hope this helps!
A few weeks ago, I had a similar issue and created an NPM package to do just that : sharing states between ponents through the Context API, et automatically persisting it in the localStorage
. Have a look and give it a try : it's called repersist.
Example.
1. You start by defining your shared persisted state in a config file :
import repersist from 'repersist'
const { Provider, useStore } = repersist({
storageKey: 'mystorekey',
init: {
counter: 0,
search: ''
},
actions: {
increment: () => ({ counter}) => ({
counter: counter + 1
}),
typeSearch: search => ({ search })
}
})
export { Provider, useStore }
2. Inject the state via Context :
import { Provider } from './storeConfig'
ReactDOM.render(
<Provider>
<App/>
</Provider>,
document.getElementById('root')
)
3. Use it everywhere you want. Every change triggers a React and a localStorage
update, and that's precisely what you want. Your localStorage is basically in sync no matter what.
import { useStore } from './storeConfig'
const SearchField = () => {
const [{ search }, { typeSearch }] = useStore()
return (
<input value={search} onChange={e => typeSearch(e.target.value)}/>
)
}
Everytime your page refreshes, the state gets rehydrated by the localStorage
.