Search code examples
javascriptreactjsreact-nativereact-hooksreact-context

Handle Nested Context Providers


I have a few nested Context Providers in my app that look like this

export const LangContext = React.createContext("javascript");
export const FontContext = React.createContext("mono-space");
export const FontSizeContext = React.createContext("16px");

const Store = ({ children }) => {
  const [lang, setLang] = useState("javascript");
  const [font, setFont] = useState("mono-space");
  const [fontSize, setFontSize] = useState("16px");
  return (
      <LangContext.Provider value={[lang, setLang]}>
        <FontContext.Provider value={[font, setFont]}>
          <FontSizeContext.Provider value={[fontSize, setFontSize]}>
              {children}
          </FontSizeContext.Provider>
        </FontContext.Provider>
      </LangContext.Provider>
  );
};

I'm sure this is a bad practice but I'm not sure how to handle this. I want to be able to create a single context provider for all the contexts.


Solution

  • You can simply use a single provider and pass on the required values as an object:

    export const StoreContext = React.createContext({});
    const Store = ({ children }) => {
      const [lang, setLang] = useState("javascript");
      const [font, setFont] = useState("mono-space");
      const [fontSize, setFontSize] = useState("16px");
      return (
          <StoreContext.Provider value={{lang, setLang, font, setFont, fontSize, setFontSize}}>
                  {children}
          </StoreContext.Provider>
      );
    };
    

    Also instead of using useState you could modify the above to use useReducer and make the API even simpler:

    const initialState= {
       lang: 'javascript',
       font: 'mono-space',
       fontSize: '16px',
    }
    
    const reducer = (state, action) => {
        switch (action.type) {
            case 'SET_LANG': return {...state, lang: action.payload}
            case 'SET_FONT': return {...state, font: action.payload}
            case 'SET_FONTSIZE': return {...state, fontSize: action.payload}
            default: return state;
        }
    }
    export const StoreContext = React.createContext({});
    const Store = ({ children }) => {
      const [state, dispatch] = useReducer(reducer, initialState);
      return (
          <StoreContext.Provider value={[state, dispatch]}>
                  {children}
          </StoreContext.Provider>
      );
    };
    

    and in the child you can use it like:

    const Child = () => {
        const [state, dispatch] = useContext(StoreContext);
        const handleChange = (size) => {
             dispatch({type: 'SET_FONTSIZE', payload: size})
        }
        ....
    }