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:
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:
- In the top level of the body of a function component
- 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.