0

I am Trying to separate some part of my business logic from view logic for this I am employing the use of custom react hooks which works like a controller.

My Component Structure

  1. Parent that contains toggle to switch between child 1 and child 2
  2. useCustomHook custom react hook that makes an api call and uses a state to add a loader
  3. child2 contains content to be shown which was retrieved from api call and state variable to show loader calls useCustomHook
  4. parent also calls useCustomHook which makes the api call on mount.

Why can't I see loading on the page inside child 2 no matter how long it takes

I believe useState is setting flag to its default false when custom hook is called again on child 2

What way do i have to make use of useState hook in a custom react hook which is called from more than one place and not have the state reverted back to default value

flag is never true if you open child2

Here is the codesandbox link code

Here is the code

App.js acts as parent

import "./styles.css";
import Child1 from "./Child1";
import Child2 from "./Child2";
import { useEffect, useState } from "react";
import useCustomHook from "./customHook";
import { makeStyles } from "@material-ui/core";

const useStyles = makeStyles((theme) => {
  return {
    parent: {
      padding: "10px"
    },
    toggle: {
      margin: "10px",
      border: "1px solid black",
      display: "flex",
      justifyContent: "space-around"
    },
    child: {
      border: "1px solid black",
      width: "50%"
    }
  };
});

export default function App() {
  const [isChild1, setIsChild1] = useState(true);
  const classes = useStyles();
  const { flag, func } = useCustomHook();

  useEffect(() => {
    func();
  }, []);

  return (
    <div className="App">
      <div className={classes.parent}>Parent</div>
      <div className={classes.toggle}>
        <div
          onClick={() => {
            setIsChild1(true);
          }}
          className={classes.child}
        >
          ch1
        </div>
        <div
          onClick={() => {
            setIsChild1(false);
          }}
          className={classes.child}
        >
          ch2
        </div>
      </div>
      {isChild1 ? <Child1 /> : <Child2 />}
    </div>
  );
}

Child1.js

const Child1 = () => {
  return <div>Child1</div>;
};

export default Child1;

Child2.js

import useCustomHook from "./customHook";

const Child2 = () => {
  const { flag } = useCustomHook();

  console.log('flag ',flag);
  return (
    <div>
      <div>Child2</div>
      <div>{flag ? "loading..." : "content"}</div>
    </div>
  );
};

export default Child2;

CustomHook.js

import { useState } from "react";

const useCustomHook = () => {
  const [flag, setFlag] = useState(false);

  const sleep = async (ms) => {
    console.log("waiting");
    await new Promise((res, rej) => {
      setTimeout(() => {
        console.log("wait over");
      }, ms);
    });
  };
  const func = async () => {
    setFlag(true);

    //do some work like api call
    await sleep(10000);

    setFlag(false);
  };

  return { flag, func };
};

export default useCustomHook;
ash1102
  • 409
  • 4
  • 20
  • 2
    Each implementation of a hook is separate. Using the hook in `App` and `Child2` means you have two separate `flag` states. Setting it to `true` in `App` won't make it `true` in `Child2`. – sallf Feb 25 '22 at 17:04
  • I thought a hook is a function and believed state is being defaulted but two different instances seems more like a class. Is there a way to have the same state shared when calling hook from two different places. I know one way is redux so may be useReducer will also work. Are there any more ? – ash1102 Feb 25 '22 at 17:22
  • 1
    Why not simply pass `flag` down as a `prop` to `Child2`? – sallf Feb 25 '22 at 17:28
  • 1
    If that's ultimately what your question is, then [this is a pretty good answer](https://stackoverflow.com/a/53455474/3550318). – sallf Feb 25 '22 at 17:30
  • 1
    React hooks don't share state as they are each a separate "instance". If you want to share state then the state needs to be centrally located. Using a React context and the `useContext` hook in your custom hook allows your hook to access the common single state stored in the context. – Drew Reese Feb 25 '22 at 17:44
  • Understood.. All answers point to useContext and Redux takes use of context api so these two ways will work – ash1102 Feb 25 '22 at 17:47
  • 1
    Does this answer your question? [Is it possible to share states between components using the useState() hook in React?](https://stackoverflow.com/questions/53451584/is-it-possible-to-share-states-between-components-using-the-usestate-hook-in-r) – sallf Feb 26 '22 at 04:12

1 Answers1

1

If you call useCustomHook from App and Child2, you get two different objects!

  • Either call useCustomHook and func from Child2,
  • Or pass down flag from App to Child2 (using props or context)

Btw. in the sleep function you forgot to resolve the promise.

  const sleep = async (ms) => {
    console.log("waiting");
    await new Promise((res, rej) => {
      setTimeout(() => {
        console.log("wait over");
        res(); // important!
      }, ms);
    });
  };
Mihályi Zoltán
  • 822
  • 6
  • 11
  • Those are both good ways to handle this situation but I want to know if I can use the hook in two different places even if it contains useState and can all places share the same state and not have it defaulted even if it is called from a different place – ash1102 Feb 25 '22 at 17:17
  • 1
    useState returns different values if called from different component instances. But you can share values among components by wrapping everything with a React context, and using useContext() to access the value – Mihályi Zoltán Feb 25 '22 at 17:26