Home > OS >  Can't use my React custom hooks inside funcation give me ERROR
Can't use my React custom hooks inside funcation give me ERROR

Time:12-10

I built a simple project to improve my skill in react framework, my project, it take some data from API called Bored API which is given some activity in different categories like cooking or diy etc. and use translator API to translate the activity to Arabic . My problem is I made two hooks the First one is useBored, it take the activity and send it to the second hook called useTranslate that sand the text to translator API for better understanding here is the code:

App.tsx :

import React, { useEffect, useState } from "react";
import CardComp from "./components/CardComp";
import images no need to copy it here
const App: React.FC = () => {
  const [final, setfinalD] = useState<string>(""); //this state used after I get the final text from translator 
  useEffect(() => {
    console.log(final); 
  }, [final]);

  return (
    <div className=" bg-gradient-to-bl from-gray-700 via-gray-900 to-black flex justify-center items-center h-[100vh] w-[100vw]">
      <div className="grid gap-2 grid-cols-2 grid-rows-3 ">
        <CardComp
          placeholder="تــعـلـوم"
          image={education}
          setD={setfinalD}
          type={"education"}
        />
        <CardComp
          placeholder="نـشـاطـات"
          image={kite}
          setD={setfinalD}
          type={"recreational"}
        />
        <CardComp
          placeholder="أجتـماعـيات"
          image={social}
          setD={setfinalD}
          type={"social"}
        />
        <CardComp
          placeholder="أفعلها بـنـفسك"
          image={diy}
          setD={setfinalD}
          type={"diy"}
        />
        <CardComp
          placeholder="عمل خــيـر"
          image={social}
          setD={setfinalD}
          type={"charity"}
        />
        <CardComp
          placeholder="طـبخ"
          image={cooking}
          setD={setfinalD}
          type={"cooking"}
        />
        <CardComp
          placeholder="أسـترخاء"
          image={education}
          setD={setfinalD}
          type={"relaxation"}
        />
        <CardComp
          placeholder="عــمــل"
          image={work}
          setD={setfinalD}
          type={"busywork"}
        />
      </div>
      {final ? <p>{final}</p> : null} // here print the translated text
    </div>
  );
};

export default App;

Now the CardComp.tsx :

import React from "react";
import { useBored } from "../hooks/useBored";
import { useTranslate } from "../hooks/useTranslate";

interface ICard {
  placeholder: string;
  image: string;
  type: string;
  setD: React.Dispatch<React.SetStateAction<string>>; 
}

const CardComp: React.FC<ICard> = ({ placeholder, image, type, setD }) => {
  const getTheData = () => { // *ERROR* came from here
    const { data: dd } = useBored(type);
    const { data, isLoading, error } = useTranslate(dd[0]);
    if (isLoading === false) {
      setD(data);
    }
  };
  return (
    <div>
      <div
        onClick={getTheData}
        className="flex flex-col justify-center items-center p-7 cursor-pointer  font-mono bg-[radial-gradient(ellipse_at_top_right,_var(--tw-gradient-stops))] from-blue-700 via-blue-800 to-gray-900"
      >
        <div className=" ">
          <img src={image} className="w-[2.3em]" />
        </div>
        <h3>{placeholder}</h3>
      </div>
    </div>
  );
};

export default CardComp;

Now the useBored.ts :

import { useState, useEffect } from "react";

export const useBored = (type: string) => {
  const [data, setData] = useState<string[]>([]);
  const [isLoading, setIsLoading] = useState<boolean>(true);
  const [error, setError] = useState<string>("");

  useEffect(() => {
    fetch(`http://www.boredapi.com/api/activity?type=${type}`)
      .then((r) => r.json())
      .then((r) => (setIsLoading(false), setData([r.activity, r.link])))
      .catch((e) => setError(e));
  }, [type]);
  console.log(data);
  return { data };
};

And the last hook is useTranslate.ts :

import { useState, useEffect } from "react";

export const useTranslate = (text: string) => {
  const [data, setData] = useState<null | string>(null);
  const [isLoading, setIsLoading] = useState<boolean>(true);
  const [error, setError] = useState<string>("");

  useEffect(() => {
    fetch(`https://api.mymemory.translated.net/get?q=${text}&langpair=en|ar`)
      .then((r) => r.json())
      .then((r) => (setIsLoading(false), setData(r.matches[0].translation)))
      .catch((e) => setError(e));
  }, [text]);
  return { data, isLoading, error };
};

as you can see this is my project the error came form function called getTheData in CardComp if use the hooks outside the function it works but not as I want to, the ERROR is:

enter image description here

Please tell me what is wrong, and thank you.

I use Tailwind for styling.

CodePudding user response:

You cannot be calling any hooks from a function that does not identify itself as a react component. Just to give you some knowledge, since you are new, a function is identified as a react functional component as follows:

  • The function should start with an uppercase alphabet (eg: Component and not component)
  • Can or cannot return some JSX but should return null if no JSX is being returned

Subsequently, you should also return a component from a react class component's render function or from another functional component instead of calling it like a regular function (eg: Component() will throw errors. is the correct way to use a react component).

Now, that we are clear what a react component looks like, you should pretty much understand from here what your problem is.

You are instantiating your hook useBored inside

const getTheData = () => {
    const { data: dd } = useBored(type);
    const { data, isLoading, error } = useTranslate(dd[0]);
    if (isLoading === false) {
      setD(data);
    }
};

You cannot be instantiating your hook in the getTheData function since it does not identify itself as a react component (refer the points above).

So to fix this, you will have to instantiate your hook outside the getTheData function.

I think you can modify your codebase to follow this. Hope it helped.

CodePudding user response:

In the CardComp component getTheData function where the error is coming from, the docs say you're not allowed to do that (call a hook inside a function that's an onclick handler). So you can't do it. The link stated in your console error says that hooks can only be called:

  1. In the top level of the body of a function component
  2. In the top level in the body of a custom hook

The docs also say you can install the eslint-plugin-react-hooks plugin to catch these mistakes

But this makes sense - those custom hooks also call useState and useEffect. These are meant to work with the lifecycle of a component. To have those called in an onClick handler doesn't make sense because that is outside of a component's lifecycle and initiated by user interaction. useEffect runs on each update and optionally can return a cleanup callback when the component unmounts. In your case, the useEffect isn't called inside your custom hook until the button is clicked, which breaks the intended paradigm.

It seems to me from looking at your two hooks that you're trying fetch data in an onClick handler. useEffect is often used to load initial data for a component, but not in a handler.

We use Redux with React and so the way we would do this would be to dispatch an action to make the fetch call and have that action dispatch a "response" action with the payload of the fetch response. Then the reducer could update the state and the CardComp would re-render appropriately.

  • Related