How can I use useEffect correctly in a function? - reactjs

I don't think I am using useEffect correctly in the function I have written. If I do the same with classes there is no problem and I am able to do ComponentDidMount(). But I don't know Hooks very well. So I think I am going wrong somewhere.
//import { onValue } from 'firebase/database';
import React, { useEffect } from "react";
import {db} from '../../firebase';
import {ref,onValue} from 'firebase/database'
import Grid from '#mui/material/Grid';
import Box from '#mui/material/Box';
export default function NiftyChart() {
React.useEffect(()=>{
const dbRef=ref(db,"1rh1Ta-8dqZKmh1xy5ans2lOqReoiVAT81WyDKqRaxl0/Nifty");
onValue(dbRef,(snapshot)=>{
let records=[];
snapshot.forEach(childSnapshot=>{
let keyName=childSnapshot.key;
let data=childSnapshot.val();
console.log(snapshot.val());
records.push({"key":keyName,"data":data})
console.log(records[records.length-1])
});
this.setState();
})
},[]);
return(
<div>
{this.state.map((row, index)=>{
return(
<Box component="span" sx={{}}>
<Grid > {row.Close}</Grid>
</Box>
)
})}
</div>
)
}
Also, no value is printed for row.Close. In the console I seem to be getting Cannot read properties of undefined (reading 'state') this error.
Any help is appreciated.

You need to use useState:
const [records, setRecords] = useState([]);
useEffect(() => ..get records and set to state, []);
So code would look like this:
export default function MyComponent() {
const [records, setRecords] = useState([]);
useEffect(() => {
// ... other code is omitted for the brevity
setRecords(records)
}, [])
return(
<div>
{records &&
records.map((row, index)=>{ //...other code is omitted
// for the brevity }
</div>
)
}
What's hook? The hook is:
A Hook is a special function that lets you “hook into” React features.
For example, useState is a Hook that lets you add React state to
function components
[] in useEffect means:
If you want to run an effect and clean it up only once (on mount and
unmount), you can pass an empty array ([]) as a second argument. This
tells React that your effect doesn’t depend on any values from props
or state, so it never needs to re-run. This isn’t handled as a special
case — it follows directly from how the dependencies array always
works.
Read more about API call in this great article

Related

Cannot update a component (`TodoForm`) while rendering a different component (`TodoTask`). [SOLUTION] [React Redux To-Do App]

WHILE WRITING THIS POST I REALIZED WHAT THE SOLUTION WAS
Every time I dispatch a task to my store the following error occurs:
I have some idea of why it happens. It happens precisely when I try to get the to-do list using useSelector and then mapping through the list. However, the mapping is not the issue but rather returning a react component on the map function. It works just fine if I do not return a functional component and instead use HTML. So the issue, from my POV, is returning a react functional component while passing props to it on a map function.
Here's the code for my home component:
import Input from '../components/Input';
import TodoForm from '../components/TodoForm';
function Home() {
document.title = "MyTodo | Home"
return (
<div className="App">
<h1>MyTodo</h1>
<Input />
<TodoForm />
</div>
);
}
export default Home;
The input component where the action is being dispatched on key down:
import {useState} from 'react'
import { useDispatch } from 'react-redux';
import { todoActions } from '../store/todo';
const Input = () => {
const [inputText, setInputText] = useState("");
const dispatch = useDispatch();
const handleChange = (e) => setInputText(e.target.value)
const handleKeyPress = (event) => {
if (event.code === "Enter") {
// if the expression is false, that means the string has a length of 0 after stripping white spaces
const onlyWhiteSpaces = !inputText.replace(/\s/g, "").length;
!onlyWhiteSpaces &&
dispatch(
todoActions.addTask({ label: inputText, done: false })
);
setInputText("");
}
};
return (
<input
type="text"
onKeyDown={(e) => handleKeyPress(e)}
onChange={(e) => handleChange(e)}
value={inputText}
/>
);
}
export default Input
The TodoForm where I am using useSelector to get the todo list from the redux store and mapping thru it:
import { useSelector } from "react-redux";
import { v4 as uuidv4 } from "uuid";
import TodoTask from "./TodoTask";
const TodoForm = () => {
const tasks = useSelector((state) => state.todo.taskList);
const renderedListItems = tasks.map((task, index) => {
return (
<TodoTask
key={uuidv4()}
task={task}
targetIndex={index}
/>
);
});
return <div className="container">{renderedListItems}</div>;
};
export default TodoForm;
Finally the TodoTask component which is the child component being returned on the map function above:
import { useDispatch } from "react-redux";
import { todoActions } from "../store/todo";
const TodoTask = ({ task, targetIndex }) => {
const {text, done} = task;
console.log("Task: ", task);
const dispatch = useDispatch()
const removeTask = dispatch(todoActions.deleteTask(targetIndex))
return (
<div
className="alert alert-primary d-flex justify-content-between"
role="alert"
>
{text}
<button type="button" className="btn-close" onClick={()=>removeTask}></button>
</div>
);
};
export default TodoTask;
This is my first time facing this issue, and I know it has something to do with redux and how the useSelector hook forces a component to re-render. So the useSelector is re-rendering the TodoForm component, and since we are mapping and returning another component, that component is also being rendered simultaneously. At least, that is how I understand it. Let me know if I am wrong.
Things I have tried:
Wrapping the TodoTask in React.memo. Saw it somewhere as a possible solution to this kind of issue, but that did not work.
Passing shallowEqual as a second parameter on the TodoForm useSelector. This does prevent the page from going into an infinity loop, but the tasks show up empty but are being added to the redux store. However, with this method, the first warning stills shows up, and the console log in the TodoTask component does not execute.
Passing shallowEqual as a second parameter on the TodoForm useSelector. This does prevent the page from going into an infinity loop but the tasks show up empty but are being added to the redux store. However, with this method, the first warning stills shows up and the console log in the TodoTask component does not execute.
I realized what I was doing wrong while writing this part. The console log in the TodoTask component was working, but I had the browser console filtering for errors only. When I check the messages section, I saw everything working fine. Then when I checked the Task component, I noticed I was trying to read a property that did not exist and hence why the tasks had no text.
In other words, the solution was adding shallowEqual as second parameter of the useSelector hook in my TodoForm component that was the one mapping thru the todo tasks array. As I said, useSelector forces a component to re-render. shallowEquals checks if the existing state isn't the same as we already had and avoids unnecessary re-renders, which can lead my application to exceed the maximum update length.
Code fix [Solution]:
import { memo } from "react";
import { shallowEqual, useSelector } from "react-redux";
import { v4 as uuidv4 } from "uuid";
import TodoTask from "./TodoTask";
const TodoForm = () => {
// shallowEqual prevents unnecessary re-renders which can lead to an infinite loop
// it compares the current state with the previous one, if they are the same, it does not re-render the component
const tasks = useSelector((state) => state.todo.taskList, shallowEqual);
const renderedListItems = tasks.map((task, index) => {
return (
<TodoTask
key={uuidv4()}
task={task}
targetIndex={index}
/>
);
});
return <div className="container">{renderedListItems}</div>;
};
export default memo(TodoForm);
Honestly, I have been stuck on this since yesterday and I cannot believe I realize the solution just when I was about to ask for help. Hope this helps anyone else who faces a similar issue in the future.

How to pass API data into state in React

I have been learning react for the past week and i am finding it really interesting. However, i am stuck. I am making a call to an API and using a custom fetch hook to get the data. This is represented where i have declaring a constant and extracting the data along with other stuff which are general functions to show error and show a loader before the data is displayed. Then i pass this data as a prop into another component called animelist which is responsible for mapping through the data. What i want to do now is to pass this data into a state so i can modify it later as required. I then want to pass the state as a prop to the animelist component rather than the data directly. I have declared a piece of state called topAnime. Then i am using the useEffect hook to set the topAnime state to the data. However i get an error in the console saying that data is basically null which i don't understand how. I feel like it might be something to do with the dependency array in the useEffect hook but i am not sure. Any guidance would be much appreciated. Below is my code. Thanks.
import { useFetch } from '../hooks/useFetch'
import Error from "./Error"
import Spinner from "../components/spinner/Spinner"
import AnimeList from "../components/animelist/AnimeList"
import Header from '../Header'
import './TopAnime.css'
import { useState } from 'react'
import { useEffect } from 'react'
function TopAnime() {
const [topAnime, setTopAnime] = useState([])
const {data, isPending, error} = useFetch('https://api.jikan.moe/v4/top/anime')
useEffect(() => {
(async () => {
setTopAnime(data);
})();
}, [data]);
return (
<div className="top-anime">
<Header/>
{error && <Error/>}
{isPending && <Spinner/>}
{data && <h1>Top Anime</h1>}
{data && <AnimeList animelist={topAnime}/>}
</div>
)
}
export default TopAnime
Here is the error i get
enter image description here
Update: I managed to fix it myself somehow lol. All i had to do was add && topAnime like i have done for data. I am not sure why that fixed it but an explanation would be really helpful. Below is the working code now.
import { useFetch } from '../hooks/useFetch'
import Error from "./Error"
import Spinner from "../components/spinner/Spinner"
import AnimeList from "../components/animelist/AnimeList"
import Header from '../Header'
import './TopAnime.css'
import { useState } from 'react'
import { useEffect } from 'react'
function TopAnime() {
const [topAnime, setTopAnime] = useState([])
const {data, isPending, error} = useFetch('https://api.jikan.moe/v4/top/anime')
useEffect(() => {
(async () => {
setTopAnime(data);
})();
});
return (
<div className="top-anime">
<Header/>
{error && <Error/>}
{isPending && <Spinner/>}
{data && <h1>Top Anime</h1>}
{data && topAnime && <AnimeList animelist={topAnime}/>}
</div>
)
}
export default TopAnime
Editted answer:
I see that you found your answer. The reason why it works, if I recall correctly, is because when you initially load the apiData, topAnime is still null as the calls are made asynchronously. Basically it tries to render your AnimeList with the (empty) topAnime prop before the Data is loaded, thus giving you the error that it's null. A better way to fix it (if you'd like more components to use the topAnime data) is to check for it at the start of the return, like this:
return ( topAnime ? (
<div className="top-anime">
<Header/>
{error && <Error/>}
{isPending && <Spinner/>}
{data && <h1>Top Anime</h1>}
{data && <AnimeList animelist={topAnime}/>}
</div> ) : (<h1>loading..</h1>)
)
This will first check if topAnime contains data and if not it will show loading until it does.
Old answer:
If you move the api call inside the useEffect you should be good. I used axios instead of useFetch for this example and I removed some clutter to make it more obvious to see what's happening. Here's the codesandbox link where you can see the data being loaded in the console: https://codesandbox.io/s/examplegetdata-bk56bi
import { useState, useEffect } from "react";
import axios from "axios";
function TopAnime() {
const [topAnime, setTopAnime] = useState([]);
useEffect(() => {
(async () => {
const data = await axios.get("https://api.jikan.moe/v4/top/anime");
setTopAnime(data);
})();
}, []);
return <div className="top-anime">{console.log(topAnime)}</div>;
}
export default TopAnime;

React JS coponent not rendering using map function

I hava a component called videoRow i try to render this component using dummy values now i get data from a useEffect Hook i have to use that data to render my component but when i try to do so it dont show anything. I even try console log to check weather i get my data or not it print my data on console means my useEffect is working But when i try this data on my videoRow component it not show anything
import React, { useState, useEffect } from "react";
import "../css/searchPage.css";
import TuneSharpIcon from "#mui/icons-material/TuneSharp";
import ChannelRow from "./ChannelRow";
import VideoRow from "./VideoRow";
import { selectInput } from "../features/inputSlice";
import { useSelector } from "react-redux";
import Axios from "axios";
function SearchPage() {
const getQuery = useSelector(selectInput);
const API_URL = `https://www.googleapis.com/youtube/v3/search?part=snippet&maxResults=4&key=APIKEY&type=video&q=${getQuery.input}`;
const [data, setData] = useState([]);
useEffect(() => {
async function fetchData() {
let request = await Axios.get(API_URL);
setData(request);
}
fetchData();
}, [API_URL]);
console.log(data);
return (
<div className="searchPage">
<div className="filter">
<TuneSharpIcon></TuneSharpIcon>
<h2>FILTERS</h2>
</div>
<hr></hr>
<ChannelRow
image="https://images.indianexpress.com/2022/01/Republic-Day_1200_AP2022.jpg"
channelName="Dummy"
verified
subs="670k"
noOfVideos={567}
desc="You can find awesome programming lessons here! Also, expect programming tips and tricks that will take your coding skills to the ..."
></ChannelRow>
<hr></hr>
{data?.data?.items?.forEach((item) => {
console.log(item.snippet.title);
console.log(item?.snippet.thumbnails.high.url)
console.log(item?.snippet.publishedAt)
console.log(item?.snippet.description)
console.log(item?.snippet.channelTitle)
return(<VideoRow
image={item?.snippet.thumbnails.high.url}
channelName={item?.channelTitle}
timestamp={item?.snippet.publishedAt}
title={item?.snippet.title}
desc={item?.snippet.description}
views="1.4M"
subs="1.4M"
></VideoRow>)
})}
</div>
);
}
export default SearchPage;
Change data?.data?.items?.forEach to data?.data?.items?.map. forEach returns nothing. So, even if you return the component from the callback, forEach will just ignore it. But, map will return all transformed results as an array.
You can read more about lists in react here.

Invalid hook call. Hooks can only be used inside of a react function component... useEffect, redux

I encountered a problem with my react application related to hooks. Technologies being used: React, Redux, Apollo, ChakraUI.
Here is the React component that is troubling me:
import React, { useEffect } from "react";
import { Flex, Container, Heading, Text } from "#chakra-ui/react";
import { connect, useSelector, useDispatch } from "react-redux";
import { State } from "../state/store";
import { fetchRecipes } from "../state/recipe/actions";
interface RecipesListProps {}
const RecipesList: React.FC<RecipesListProps> = ({}) => {
const recipes = useSelector<State>(
(state) => state.recipe.recipes
) as State["recipe"]["recipes"];
const loading = useSelector<State>(
(state) => state.recipe.loading
) as State["recipe"]["loading"];
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchRecipes());
}, []);
if (loading) {
return <h1>Loading....</h1>;
}
return (
<Flex
m="auto"
mt="5rem"
w="50%"
direction="column"
justifyContent="center"
alignItems="center"
>
<Heading>Your Recipes</Heading>
<Flex mt="2rem" direction="column" w="100%" padding="0" gridGap="2rem">
{recipes &&
recipes.map((recipe) => (
<Container
key={recipe.id}
bg="orange.100"
borderRadius="0.2rem"
padding="1rem"
maxW="100%"
>
<Text fontSize="xl" fontWeight="bold">
{recipe.title}
</Text>
<Text>{recipe.description}</Text>
</Container>
))}
</Flex>
</Flex>
);
};
export default RecipesList;
Notice the use of the useEffect() hook. This is the error I am getting:
Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
I am pretty sure I am disobeying rule number, 2 i.e. I am breaking the Rules of Hooks. As soon as I take the useEffect() call out of the component, it doesn't throw an error.
Could someone please give some guidance as to what I am doing wrong?
Thanks.
Edit:
The fetchRecipes function is a Redux thunk function that fetches recipes from a graphql server
Update:
I have been hacking away at a solution to this problem. I replaced the dispatch(fetchRecipes()) call with a console.log("hello world"), and it worked perfectly!
This is boggling my mind! Is this a problem with the fetchRecipes function?
Edit:
Here's the code for the fetchRecipes function:
export const fetchRecipes = () => {
return (dispatch: Dispatch) => {
dispatch(fetchRecipesPending());
const { data } = useRecipesQuery();
const errors = data?.recipes.errors;
const recipes = data?.recipes.recipes;
if (errors?.length) {
dispatch(fetchRecipesFailure(errors));
} else {
dispatch(fetchRecipesSuccess(recipes));
}
};
};
useRecipesQuery is a custom hook that was auto generated using the graphql-codegen library. It builds up on the useQuery hook from the #apollo/client library.
Your useEffect needs a little rewrite. You are dispatching the function fetchRecipes which in itself is a hook, but the thing dispatched should be a plain "action" (using Redux terminology here). So I guess we can fix that by breaking up your fetchRecipes fn.
A snippet of the component would now look like following:
const { data } = useRecipesQuery();
useEffect(() => {
if (!data) {
dispatch(fetchRecipesPending()) // I only assume you fetch on render
}
if (data?.recipes?.errors) {
dispatch(fetchRecipesFailure(data?.recipes.errors)
}
if (data?.recipes?.recipes) {
dispatch(fetchRecipesSuccess(data?.recipes?.recipes)))
}
}, [data]);
Now it should be fine AND more readable. Either way, as some has already suggested, I would think about using some more standardised way like using Redux w/ Thunks or Sagas, or, even better - I see you might be doing a GQL query - if so, just use a hook for it and handle the data with Apollo Client.

I am not getting console logged my state change by one of the reducers

When I am dispatching an action of the loader, the reducer is changing the state when I see in redux logger but it is not console logged in the line next to when I have dispatched the action to change the state.
import React from 'react';
import { useEffect } from 'react';
import { useState } from 'react';
import {connect} from 'react-redux';
import {loader} from "../../actions/loaderAction";
import './Loader.css';
function LoaderPage(props) {
let [showloader,setshowloader] = useState(0);
const testfunc = ()=>{
props.loader(true);
console.log(props.Loader);
}
useEffect(()=>{
testfunc();
// eslint-disable-next-line
},[]);
return (
<>
{showloader
?
<div className="loader-comp">
<div className="animated yt-loader"></div>
<div className="mask"></div>
</div>
:
""
}
</>
)
}
const mapStateToProps= (state) => (
{
Loader:state.Loader
}
)
export default connect(mapStateToProps, {
loader
})(LoaderPage);
I know reducer returns new state asynchronously, so when I should use console.log for props.Loader
Any help or suggestion is valuable.
When you pass an empty array as second argument to useEffect means the code will only execute on mount. And your log doesn't reflect because state update is not sync.
You want log to be executed everytime on Loader changes. Remove your console.log from your function. Create another useEffect to log your Loader and pass Loader to array, which means your code will execute on Loader changes:
useEffect(()=>{
console.log(props.Loader);
},[props.Loader]);
You can use redux dev tools extension instead of console.log to view and manage your state. It is so simple to use and perfectly built
try using callback pattern
const callbackFunction = () => console.log("Some code");
const loader = (option = {}) => {
if(option.cb){
option.cb();
}
};
const option = {};
option.cb = callbackFunction;
loader(option);

Resources