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;
Related
Here I have a weather app which uses an API in it's own component to fetch weather data. I'm at the point now where I'm trying to append the retrieved data values to the DOM/webpage but am having trouble. I have declared state in my Header component (working with only the Temp right now until it works) but can't figure how to manipulate it.
Things I've tried and the outcomes: I have tried putting the state into the top level as well as the API function (so it was all in App.js) but this didn't work because I couldn't call the nested function and if I make it global, it doesn't recognize the state variable. I also tried creating a function that explicitly changes state and using /importing that but I run into the same issue.
I have also theorized about maybe instead of trying to send the state to other components, to send the data to the component that holds the state (header) but I'm not sure on this since it's to my understanding that data should only live in one place? I appreciate any help.
APP.js
`
import logo from './logo.svg';
import './App.css';
import Header from './components/header'
import Body from './components/body'
import Footer from './components/footer'
import { useState } from 'react';
function App() {
return (
<div className="container">
<Header />
<Body />
<Footer />
</div>
);
}
export default App;
api.js
import {Header} from '../components/header'
export async function FetchAPI(location) {
try {
let result = await fetch ('http://api.openweathermap.org/data/2.5/weather?q=' + location +'&APPID=bd3dd8d1151b1e784fcf021aa29927c5',
{mode: 'cors'});
let final = await result.json()
return console.log(final.cod)
}
catch(err) {
alert(err)
}
}
export async function processData(data) {
console.log(data)
}
Header.js
import {processData} from "../api/api"
import {useState} from 'react'
export function Header({tempValue, locationValue}) {
const handleTemp = (newValue) => { setTemp(newValue) }
const [tempDOM, setTemp] = useState(2)
return (
<header className="header">
<div>
<h1 value={setTemp}> </h1>
<h2></h2>
</div>
<div>
<h1>Time</h1>
<h2>Windspeed</h2>
</div>
</header>
);
}
export default Header;
`
I have tried using state and changing that using a function as well as nesting the state and function that renders the data together in a top level component.
While not necessary for this example, I would suggest you check out react stores. My personal favorite is MobX - they can make managing your data a lot easier and reduce the amount of states and things you need to pass through to each components.
Now for your code here are a few notes:
You don't seem to call FetchAPI from anywhere. You should either call it on page load (in App.js) or call it inside your Header component when the location changes
Your FetchAPI also seems to return a console.log() not an actual value. I would suggest you return final.cod or whatever it is that you need returned. Perhaps even the entire json (return final)
Inside your h1 tag, you are setting the value to a set method of the state - this won't work. in useState, if you save it to an array, the first value (tempDOM) is the value of the state, and the second value is a method that is used to change the state value. So instead, as a comment has already suggested, use <h1>{tempDOM}</h1>. This will display the initial value you set (2) and nothing else since you are not changing the state value via setTemp() method
This is what I would expect the code to look like (haven't tested):
import './App.css';
import Header from './components/header'
import Body from './components/body'
import Footer from './components/footer'
function App() {
return (
<div className="container">
<Header location='London'/>
<Body />
<Footer />
</div>
);
}
export default App;
import {FetchAPI} from "../api/api"
import {useState, useEffect} from 'react'
export function Header({location}) {
const [apiData, setApiData] = useState({});
useEffect(()=>{
async function getApiData() {
const data = await FetchAPI(location);
setApiData(data);
}
getApiData();
// including location in useEffect dependency array,
// which means this effect will be called every time location property changes
}, [location]);
return (
<header className="header">
<div>
{
// I don't know what your API model looks like.
// adjust the property that you are accessing based on your knowledge
}
<h1>{apiData.temp}</h1>
<h2></h2>
</div>
<div>
<h1>Time</h1>
<h2>Windspeed</h2>
</div>
</header>
);
}
export default Header;
export async function FetchAPI(location) {
try {
const result = await fetch ('http://api.openweathermap.org/data/2.5/weather?q=' + location +'&APPID=bd3dd8d1151b1e784fcf021aa29927c5',
{mode: 'cors'});
const final = await result.json()
return final
}
catch(err) {
alert(err)
}
}
As mentioned, I haven't tested this, but this is more along the lines of what I would expect a working application to look like.
More on useEffect
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.
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
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.
I'm having some troubles in a implementation with React, Redux and Hooks.
I do not know how to avoid this:
import React, { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import * as R from "ramda";
function Main() {
const mainBanners = useSelector(state => state.mainBanners);
const features = useSelector(state => state.features);
const banners = useSelector(state => state.banners);
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchMainBanners());
dispatch(fetchFeatures());
dispatch(fetchBanner());
}, [dispatch]);
console.log(features);
return (
<div className="Main">
{R.isEmpty(mainBanners || features) ? (
<p>Loading...</p>
) : (
<MainBanner mainBanners={mainBanners} features={features} />
)}
<Banners banners={banners} />
</div>
);
}
export default Main;
The result console.log of this example is as follows:
You have three different useSelector calls, and you are executing fetches for three different sets of data. So yes, I would expect that to result in a total of four separate renders:
Initial render
Render after mainBanners is fetched
Render after features is fetched
Render after banners is fetched
This is both expected based on the code you have written, and fine in general, given that your component might want to render something different when any of those changes.