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!
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.
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 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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With