最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - ReactJS: How to synchronize sessionStorage state between components - Stack Overflow

programmeradmin1浏览0评论

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.

Share Improve this question edited Apr 24, 2019 at 21:40 Dacre Denny 30.4k5 gold badges51 silver badges66 bronze badges asked Apr 22, 2019 at 19:40 RamonRamon 551 silver badge7 bronze badges 11
  • 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 that sessionStorage 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 in sessionStorage 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
 |  Show 6 more ments

2 Answers 2

Reset to default 3

One 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.

发布评论

评论列表(0)

  1. 暂无评论