Search code examples
javascriptreactjsreact-hooksreact-state-management

How to implement the Context API with a dynamic number of states?


The number of components in the array listOfComponents should be dynamic. In my application components get added and removed over time while the application is running. When I add a new component, I want to add a state for it inside the listOfComponents array in my ExampleContext. When I remove a component I want to remove the state. The component should only rerender if the settings in the listOfComponents get updated where the ids match. How would I go about implementing this?

import React, { createContext, useState } from 'react'

export const ExampleContext = createContext()
export const { Consumer: ExampleConsumer } = ExampleContext

export function ExampleProvider({ children }) {
  const [state, setState] = useState({
    listOfComponents: [{
        id: 1,
        settings: {color: 'red'}
    },
    {
        id: 2,
        settings: {color: 'blue'}
    },
    {
        id: 3,
        settings: {color: 'green'}
    }]
 })
  return (
    <ExampleContext.Provider value={[state, setState]}>
      {children}
    </ExampleContext.Provider>
  )
}

export function Component({id}) {
  const [state, setState] = useContext(ExampleContext)
  return (
    <h1> Only rerender me if settings of matching id are updated! </h1>
  )
}

Solution

  • Here is how I made it work with useMemo. Maybe it is helpful for somebody else since it took me some time to figure it out. Also this blog post was pretty helpful. If there is any better way please let me know.

    -- App.js

    import React, {useContext, useMemo} from 'react';
    import {ExampleContext, ExampleProvider} from './Context'
    
    export function Component({id}){
      const [state, setState] = useContext(ExampleContext)
    
      function onClick(){
        setState(prev => {
          return {...prev, [id]: { color: 'blue'}}
        })
      }
    
      return useMemo(() => {
        console.log(`update ${id}`)
        return (
          <>
            <h1 style={{color: state[id].color}}>{id}</h1>
            <button onClick={onClick}>{`Update from ${id}`}</button>
          </>
        )
      }, [state[id]])
    }
    
    function App() {
      return (
        <ExampleProvider>
            <Component id={1}/>
            <Component id={2}/>
        </ExampleProvider>
      );
    }
    
    export default App;
    

    -- Context.js

    import React, { createContext, useState } from 'react'
    
    export const ExampleContext = createContext()
    export const { Consumer: ExampleConsumer } = ExampleContext
    
    export function ExampleProvider({ children }) {
      const [state, setState] = useState({
        '1': {color: 'red'},
        '2': {color: 'green'},
      })
      return (
        <ExampleContext.Provider value={[state, setState]}>
          {children}
        </ExampleContext.Provider>
      )
    }