简体   繁体   中英

ReactJS: How to synchronize sessionStorage state between components

In my app, I have a React component that renders a list of numbers and it also stores the sum of these numbers via sessionStorage .

My app also has a component 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 component 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.

在此处输入图片说明

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 component of your app via a context provider and then wrap child components 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 component

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 components 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 component

Now, render the context's Provider component 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 component's render() method, wrap children with context provider */
<IsUseContext.Provider value={ this.state }>
  <LimitsIndicator />
  <InputComponent />
</IsUseContext.Provider>

4. Update shared state in child component

Finally, we access the shared limitInUse from nested child components 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 component

/* 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 components 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 .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM