37

All the documentation and blog posts I've managed to find so far only deals with a single [value, setValue] pair scenario. In my case I would like/need to pass multiple pairs of [value, setValue] variables to a Provider using the useContext hook.

Here is the typical example I setup in CodePen: https://codepen.io/nardove/pen/XWrZRoE?editors=0011

const App = () => {
    return(
        <div>
            <MyProvider>
                <ComponentA />
            </MyProvider>
        </div>
    );
}

const MyContext = React.createContext();
const MyProvider = (props) => {
    const [value, setValue] = React.useState("foo");
    return(
        <MyContext.Provider value={[value, setValue]}>
            {props.children}
        </MyContext.Provider>
    );
}

const ComponentA = () => {
const [value, setValue] = React.useContext(MyContext);
    return(
        <div>
            <h1>
                The value is: {value}
            </h1>
        </div>
    );
}

ReactDOM.render(<App /> , document.getElementById('app'));

If you can share any ideas on how pass multiple [value, setValue] pairs to the Provider or an alternative to my problem will be much appreciated

Ricardo Sanchez
  • 4,935
  • 11
  • 56
  • 86

4 Answers4

68

Context.Provider accepts any value, so you can try passing an object:

<MyContext.Provider
  value={{ value: [value, setValue], value2: [value2, setValue2] }}
>
  {props.children}
</MyContext.Provider>;
const App = () => {
  return (
    <MyProvider>
      <ComponentA />
    </MyProvider>
  );
};

const MyContext = React.createContext();

const MyProvider = props => {
  const [value, setValue] = React.useState("foo");
  const [value2, setValue2] = React.useState("goo");

  return (
    <MyContext.Provider
      value={{ value: [value, setValue], value2: [value2, setValue2] }}
    >
      {props.children}
    </MyContext.Provider>
  );
};

const ComponentA = () => {
  const { value, value2 } = React.useContext(MyContext);
  const [stateValue, setStateValue] = value;
  const [stateValue2, setStateValue2] = value2;

  return (
    <div>
      <h1>The value is: {stateValue}</h1>
      <h1>The value2 is: {stateValue2}</h1>
    </div>
  );
};

ReactDOM.render(<App />, document.getElementById("app"));

Notice that there is a caveat when trying to optimize useless renders (be sure you not just optimizing prematurely): there is no render bailout for Context Consumers.

As for v17, may be change in near future.

Dennis Vash
  • 50,196
  • 9
  • 100
  • 118
12

To pass in multiple state values to a provider, you just need to create another state object and pass it in.

But inlining these comes with a caveat mentioned in the docs. Since the object (and arrays) in render are created every render, they lose the referential equality and hance any components connected to this context will need to refresh.

To get around this in a functional component, you can use useMemo to memoise the value and refresh only when one of these values change.

const MyContext = React.createContext();
const MyProvider = (props) => {
    const [valueA, setValueA] = React.useState("foo");
    const [valueB, setValueB] = React.useState("bar");
    const providerValue = React.useMemo(() => ({
        valueA, setValueA,
        valueB, setValueB,
    }), [valueA, valueB]);
    return(
        <MyContext.Provider value={providerValue}>
            {props.children}
        </MyContext.Provider>
    );
}
Agney
  • 18,522
  • 7
  • 57
  • 75
  • I get the error that value `is not assignable to type 'null'` – gaitat Feb 17 '21 at 01:01
  • `useMemo` is used so that any parent rerendering does not generate a new instance of context. [See More](https://blog.agney.dev/useMemo-inside-context/) – Agney Apr 01 '21 at 06:08
  • 1
    for anyone who came here looking to make memo of setter function that is used to set the context object value, I used `useCallback` by wrapping the function as below. `const updateContextState = useCallback((value: updateContextType) => { setContextState((prevState) => ({ ...prevState, ...value })) }, [])` – Naing May 20 '22 at 16:20
  • 1
    and that function is safe to export as below `const context = useMemo(() => ({ userID: contextState.userID, updateContextState: updateContextState }), [contextState.userID, updateContextState]) return {props.children} ` – Naing May 20 '22 at 16:20
2

Create Provider component contains:

import React, {useState} from "react";
export const Context = React.createContext();

const Provider = props => {
    const [value1, setValue1] = useState("");
    const [value2, setValue2] = useState("");

    return (
        <Context.Provider
            value={{ value1, setValue1, value2, setValue2 }}
        >
            {props.children}
        </Context.Provider>
    );
};

export default Provider;

And in your app:

<Provider>
   <ComponentA/>
</Provider>

Then in your ComponentA:

const { value1, setValue1, value2, setValue2} = useContext(Context);

Now you can use each value or set new value for it.

Pedram
  • 15,766
  • 10
  • 44
  • 73
1

You can send values as shown below. I have sent demo1, setdemo1, demo2, and setdemo2. Inside the {{}} inside exportValues.provider It worked for me

import {View, Text} from 'react-native';
import React, {createContext, useState} from 'react';

export const exportValues = createContext();

const ContextTab = ({children}) => {
  const [demo1, setdemo1] = useState([]);
  const [demo2, setdemo2] = useState([]);
  return (
    <exportValues.Provider
      value={{demo1, setdemo1,demo2, setdemo2}}>
      {children}
    </exportValues.Provider>
  );
};

export default ContextTab;