Search code examples
reactjsjsxuse-stateuse-context

React context with useState not updating


I am trying to use the react context api. I am experiencing an issue though where the context/state value is not updating. I have no idea why this is happening and have looked at numerous threads but have found that nothing works for me.

Here is the code:

curtain-context.js

For creating the contexts and exporting them:

const CurtainContext = createContext({
    curtainVisible: false,
    setCurtainVisible: (value) => {}
});

export function CurtainContextProvider(props) {
    const [curtainVisible, setCurtainVisible] = useState();

    function setCurtainVisibleHandler(value) {
        setCurtainVisible(value);
        console.log(value);
    }

    const context = {
        curtainVisible: curtainVisible,
        setCurtainVisible: setCurtainVisibleHandler
    };

    return (
        <CurtainContext.Provider value={context}>
            {props.children}
        </CurtainContext.Provider>
    );
}

export default CurtainContext;

App.js

The main application code which is surrounded by the context provider:

<Layout>
    <CurtainContextProvider>
        <Routes>
            <Route element={<HomePage/>} path='/' exact/>
            <Route element={<HomePage/>} path='/home' exact/>
            <Route element={<ServicesPage/>} path='/services' exact/>
            <Route element={<ProductsPage/>} path='/products' exact/>
            <Route element={<ContactPage/>} path='/contact' exact/>
            <Route element={<LoginPage/>} path='/login' exact/>
        </Routes>
    </CurtainContextProvider>
</Layout>

MainNavigation.js

The place where I want to use the context value to render something if curtainVisible is true:

import { NavLink } from 'react-router-dom';

import classes from './MainNavigation.module.css';
import React, {useContext, useState} from "react";

import { useLocation } from "react-router";
import MobileCurtain from "../ui/MobileCurtain";

import CurtainContext from "../../store/curtain-context";

function MainNavigation() {
    var curtainContext = useContext(CurtainContext);

    const { pathname } = useLocation();
    const activeClass = ({isActive}) => (isActive ? classes.active : classes.inactive);
    const activeUserClass = ({paths = ['/login', '/settings']}) => (paths.includes(pathname) ? classes.active : classes.inactive);

    function handleBarsClicked() {
        curtainContext.setCurtainVisible(true);
    }

    return (
        <div className={classes.menu}>
            <ul>
                <li className={classes.textLinkBars}><button className={classes.iconButton} onClick={handleBarsClicked}><FontAwesomeIcon icon={faBars} className={classes.bars}/></button></li>
                { curtainContext.curtainVisible ? <MobileCurtain/> : null}

                <li className={classes.textLink}><NavLink to="/" className={activeClass}>Home</NavLink></li>
                <li className={classes.textLink}><NavLink to="/services" className={activeClass}>Services</NavLink></li>
                <li className={classes.textLink}><NavLink to="/products" className={activeClass}>Products</NavLink></li>
                <li className={classes.textLink}><NavLink to="/contact" className={activeClass}>Contact</NavLink></li>
                </div>
            </ul>
        </div>
    );
}

export default MainNavigation;

Solution

  • Only components that are descendants of the Provider can use context value.

    In your example, MainNavigation isn't a descendant of CurtainContextProvider hence the issue.

    You set your initial value to

    {
        curtainVisible: false,
        setCurtainVisible: (value) => {}
    }
    

    which didn't helped, because this (value) => {} was run instead of setCurtainVisibleHandler.

    I would suggest using undefined as an initial value of context

    Also, hooks like this can help prevent the issue like yours:

    const useCurtainContext = () => {
      const context = useContext(CurtainContext);
    
      if (!context) {
        throw new Error('`useCurtainContext` have to be used inside `CurtainContextProvider`')
      }
    
      return context
    }