Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use React Context with useState hook to share state from different components?

I am new to React and currently I'm working on a Gatsby site where I have a Layout.js(Parent) and Menu.js(Child), when the state changes on Menu, I'd like it passed to Layout.js.

What I am trying to do is when the menu is active, the text in the layout will change.

Menu.js

import React, {useState, createContext} from "react"
const MenuContext = createContext(1)

const Menu = (props) => {
  const [active, setActive] = useState(1)
  const clickHandler = () => {
    setActive(!active);
  }
  return(
    <div className={(active ? `open` : `close`)} onClick={clickHandler}></div>
  )
}

export { Menu, MenuContext }

Layout.js

import React, {useContext} from "react"
import { Menu, MenuContext } from "../components/menu"

const Layout = ({ children }) => {

  const menuActive = useContext(MenuContext)

  return (
    <>
      <h1 style={{color:`#fff`}}>{(menuActive) ? `Menu Opened` : `Menu Closed`}</h1>
      <main>{children}</main>
      <Menu />
    </>
  )
}

export default Layout

It seems like menuActive is always printing 1. I can make sure the state is working fine inside Menu.js, but I don't know how to pass the state to Layout.js.

Any advices please, thank you!

like image 298
whalesingswee Avatar asked Jul 22 '19 10:07

whalesingswee


People also ask

How do you share state across React components with context?

In a small app, React Context along with useState is the best way to share state or simply pass data around components without mutating it. Context: It provides a way to share values between components without having to explicitly pass a prop through every level of the tree.

How do you share data across different components in React?

First, you'll need to create two components, one parent and one child. Next, you'll import the child component in the parent component and return it. Then you'll create a function and a button to trigger that function. Also, you'll create a state using the useState Hook to manage the data.

Do two components use the same Hook Share state?

Do two components using the same Hook share state? No. Custom Hooks are a mechanism to reuse stateful logic (such as setting up a subscription and remembering the current value), but every time you use a custom Hook, all state and effects inside of it are fully isolated.


1 Answers

You need to have a Provider that wraps your App before you try to access the context values. In order to have a global and single provider, you need to export wrapRootElement instance from the gatsby-browser.js file. It would look like

MenuContext.js

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

export const MenuContext = createContext()

export const MenuProvider = ({ children }) => {
  const [active, setActive] = useState(true);
  return (
    <MenuContext.Provider value={{active,setActive}}>
      {children}
    </MenuContext.Provider>
  );
};

gatsby-browser.js

import React, { useState } from 'react';
import MenuContext from './src/context/MenuContext';
const wrapRootElement = ({ element }) => {
  return (
      <MenuProvider>
        {element}
      </MenuProvider>
  );
};
export { wrapRootElement }

Now you could use it within Layout like

import React, { useContext } from "react"
import { Menu } from "../components/menu"
import { MenuContext } from '../menuContext';
const Layout = ({ children }) => {

  const {active} = useContext(MenuContext)

  return (
    <>
      <h1 style={{color:`#fff`}}>{(active) ? `Menu Opened` : `Menu Closed`}</h1>
      <main>{children}</main>
      <Menu />
    </>
  )
}

export default Layout

and within Menu you would have

import React, { useContext } from "react"
import  { MenuContext } from '../context/MenuContext';

const Menu = (props) => {
  const {active, setActive} = useContext(MenuContext)
  const clickHandler = () => {
    setActive(!active);
  }
  return(
    <div className={(active ? `open` : `close`)} onClick={clickHandler}></div>
  )
}

export { Menu }

Note: You need to create and export the context from a separate file to avoid any circular dependency


However, what you want to achieve can be done without the use of context provided you just to communicate between layout and Menu by lifting the state up to the Layout component

Menu.js

import React from "react"

const Menu = ({clickHandler, active}) => {
  return(
    <div className={(active ? `open` : `close`)} onClick={clickHandler}></div>
  )
}

export { Menu }

Layout.js

import React, {useState} from "react"
import { Menu } from "../components/menu"

const Layout = ({ children }) => {

  const [active, setActive] = useState(1)
  const clickHandler = () => {
    setActive(!active);
  }

  return (
    <>
      <h1 style={{color:`#fff`}}>{(menuActive) ? `Menu Opened` : `Menu Closed`}</h1>
      <main>{children}</main>
      <Menu clickHandler={clickHandler} active={active}/>
    </>
  )
}

export default Layout
  
like image 189
Shubham Khatri Avatar answered Oct 21 '22 18:10

Shubham Khatri