Alex Gogl
Alex Gogl

Reputation: 719

React Hooks, how to share state between two functions

I'm coming from a Vue background and I'm having a really hard time understanding how to show something conditionally when the HTML is split into multiple parts.

Suppose I got the following structure:

import React, { useState } from "react";
const [mobileNavOpen, setMobileNavOpen] = useState(false);

function Home() {
  return (
    <div>
      <button
        onClick={(): void => setMobileNavOpen(true)}
        type="button"
        className="btn"
      >
        X
      </button>
      {mobileNavOpen && <MobileNav />}
    </div>
  );
}

function MobileNav() {
  return (
    <div>
      <button
        onClick={(): void => setMobileNavOpen(false)}
        type="button"
        className="btn"
      >
        X
      </button>
    </div>
  );
}

export default Home;

How would I be able to access [mobileNavOpen, setMobileNavOpen] in both Home() and MobileNav(). Basically what I want to achieve is a Home Component where users can press a button upon which a MobileMenu Component opens with yet another button they can use to close the menu again.

Right now, the 2nd line from the top const [mobileNavOpen, setMobileNavOpen] = useState(false); results in Error: Invalid hook call. Hooks can only be called inside of the body of a function component. but where would I put it?

Should this line be in the Home() Component and all child components emit an event to show or close the Menu? Or do I need a state management library for something as simple as this? What is the "React" way to do this?

Upvotes: 1

Views: 6843

Answers (4)

EML
EML

Reputation: 66

if your Home() Component renders MobileNav() Component you should put const [mobileNavOpen, setMobileNavOpen] = useState(false) in Home() like:

const Home = () => {
   const [mobileNavOpen, setMobileNavOpen] = useState(false)

   return ( 
   <>
    ...
    <MobileNav 
      handleMobileNav={mobileNavOpen}
    />
    ...
   </>)
}

const MobileNav = ({ handleMobileNav }) => {

   return <></>
}

Upvotes: 2

Oliver
Oliver

Reputation: 9002

A simple way would be to pass the state modifier down to the child component. For this the hook needs to be moved inside the parent component.

import React, { useState } from "react";


function Home() {
  const [mobileNavOpen, setMobileNavOpen] = useState(false);
  return (
    <div>
      <button
        onClick={(): void => setMobileNavOpen(true)}
        type="button"
        className="btn"
      >
        X
      </button>
      {mobileNavOpen && <MobileNav setMobileNavOpen={setMobileNavOpen} />}
    </div>
  );
}

function MobileNav(setMobileNavOpen) {
  return (
    <div>
      <button
        onClick={(): void => setMobileNavOpen(false)}
        type="button"
        className="btn"
      >
        X
      </button>
    </div>
  );
}

export default Home;

Alternatively, you could abstract this a little so that your child component defines only what's required:

import React, { useState } from "react";

function Home() {
  const [mobileNavOpen, setMobileNavOpen] = useState(false);
  return (
    <div>
      <button
        onClick={(): void => setMobileNavOpen(true)}
        type="button"
        className="btn"
      >
        X
      </button>
      {mobileNavOpen && <MobileNav onClose={() => setMobileNavOpen(false)} />}
    </div>
  );
}

function MobileNav(onClose) {
  return (
    <div>
      <button
        onClick={(): void => onClose()}
        type="button"
        className="btn"
      >
        X
      </button>
    </div>
  );
}

export default Home;

The docs provide some good best practices for managing shared state.

Upvotes: 1

abdulrahmanAbdullah
abdulrahmanAbdullah

Reputation: 333

Should this line be in the Home() Component and all child components emit an event to show or close the Menu?

Yes. Then pass state to the MobileNavComponent

Upvotes: 0

Laszlo
Laszlo

Reputation: 2328

import React, { useState } from "react";

function Home() {
  const [mobileNavOpen, setMobileNavOpen] = useState(false);
  return (
    <div>
      <button
        onClick={() => setMobileNavOpen(true)}
        type="button"
        className="btn"
      >
        X
      </button>
      {mobileNavOpen && <MobileNav setMobileNavOpen={setMobileNavOpen} />}
    </div>
  );
}

function MobileNav({setMobileNavOpen}) {
  return (
    <div>
      <button
        onClick={() => setMobileNavOpen(false)}
        type="button"
        className="btn"
      >
        X
      </button>
    </div>
  );
}

export default Home;
  1. you have to move the useState hook to the component body
  2. pass down the state setter function to your child component

Upvotes: 4

Related Questions