Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use React Hooks Context with multiple values for Providers

What is the best way to share some global values and functions in react?

Now i have one ContextProvider with all of them inside:

<AllContext.Provider
  value={{
    setProfile, // second function that changes profile object using useState to false or updated value
    profileReload, // function that triggers fetch profile object from server
    deviceTheme, // object
    setDeviceTheme, // second function that changes theme object using useState to false or updated value
    clickEvent, // click event
    usePopup, // second function of useState that trigers some popup
    popup, // Just pass this to usePopup component
    windowSize, // manyUpdates on resize (like 30 a sec, but maybe can debounce)
   windowScroll // manyUpdates on resize (like 30 a sec, but maybe can debounce)
  }}
>

But like sad in docs: Because context uses reference identity to determine when to re-render, there are some gotchas that could trigger unintentional renders in consumers when a provider’s parent re-renders. For example, the code below will re-render all consumers every time the Provider re-renders because a new object is always created for value:

This is bad:

<Provider value={{something: 'something'}}>

This is ok:

this.state = {
      value: {something: 'something'},
    };
<Provider value={this.state.value}>

I imagine that in future i will have maybe up to 30 context providers and it's not very friendly :/

So how can i pass this global values and functions to components? I can just

  1. Create separate contextProvider for everything.
  2. Group something that used together like profile and it's functions, theme and it's functions (what about reference identity than?)
  3. Maybe group only functions because thay dont change itself? what about reference identity than?)
  4. Other simpliest way?

Examples of what i use in Provider:

// Resize
  const [windowSize, windowSizeSet] = useState({
    innerWidth: window.innerWidth,
    innerHeight: window.innerHeight
  })
// profileReload
const profileReload = async () => {
    let profileData = await fetch('/profile')
    profileData = await profileData.json()

    if (profileData.error)
      return usePopup({ type: 'error', message: profileData.error })

    if (localStorage.getItem('deviceTheme')) {
      setDeviceTheme(JSON.parse(localStorage.getItem('deviceTheme'))) 
    } else if (profileData.theme) {
      setDeviceTheme(JSON.parse(JSON.stringify(profileData.theme)))
    } else {
      setDeviceTheme(settings.defaultTheme) 
    }
    setProfile(profileData)
  }

// Click event for menu close if clicked outside somewhere and other
const [clickEvent, setClickEvent] = useState(false)
const handleClick = event => {
  setClickEvent(event)
}
// Or in some component user can change theme just like that
setDeviceTheme({color: red})
like image 211
ZiiMakc Avatar asked Jan 09 '19 22:01

ZiiMakc


2 Answers

The main consideration (from a performance standpoint) for what to group together is less about which ones are used together and more about which ones change together. For things that are mostly set into context once (or at least very infrequently), you can probably keep them all together without any issue. But if there are some things mixed in that change much more frequently, it may be worth separating them out.

For instance, I would expect deviceTheme to be fairly static for a given user and probably used by a large number of components. I would guess that popup might be managing something about whether you currently have a popup window open, so it probably changes with every action related to opening/closing popups. If popup and deviceTheme are bundled in the same context, then every time popup changes it will cause all the components dependent on deviceTheme to also re-render. So I would probably have a separate PopupContext. windowSize and windowScroll would likely have similar issues. What exact approach to use gets deeper into opinion-land, but you could have an AppContext for the infrequently changing pieces and then more specific contexts for things that change more often.

The following CodeSandbox provides a demonstration of the interaction between useState and useContext with context divided a few different ways and some buttons to update the state that is held in context.

Edit zk58011yol

You can go to this URL to view the result in a full browser window. I encourage you to first get a handle for how the result works and then look at the code and experiment with it if there are other scenarios you want to understand.

like image 56
Ryan Cogswell Avatar answered Sep 28 '22 03:09

Ryan Cogswell


The answer by Ryan is fantastic and you should consider that while designing how to structure the context provider hierarchy.

I've come up with a solution which you can use to update multiple values in provider with having many useStates

Example :

const TestingContext = createContext()


const TestingComponent = () => {
    const {data, setData} = useContext(TestingContext)
    const {value1} = data
    return (
        <div>
            {value1} is here
            <button onClick={() => setData('value1', 'newline value')}>
                Change value 1
            </button>
        </div>
    )
}

const App = () => {
    const values = {
        value1: 'testing1',
        value2: 'testing1',
        value3: 'testing1',
        value4: 'testing1',
        value5: 'testing1',
    }

    const [data, setData] = useState(values)

    const changeValues = (property, value) => {
        setData({
            ...data,
            [property]: value
        })
    }

    return (
        <TestingContext.Provider value={{data, setData: changeValues}}>
            <TestingComponent/>
            {/* more components here which want to have access to these values and want to change them*/}
        </TestingContext.Provider>
    )
    }
like image 27
Koushik Das Avatar answered Sep 28 '22 02:09

Koushik Das