I use react and when I get data from the api i store that data inside a hook and when i console.log that data is called for an infinity times and this lag my website. Here is the code if someone can help i will appreciate =>
//importing components
import Main from './components/Main/Main'
import Second from './components/Second/Second'
//import style
import './App.scss'
// api for testing => https://jsonplaceholder.typicode.com/todos/1
const App = () => {
const [data, setData] = useState() //here we get the data from the API
const [drop, setDrop] = useState(null)
const getValue = (e) => {
setDrop(e.target.value)
}
console.log(data + ' here is the data')
useEffect(() => {
let URL;
if (drop === null) {
URL = 'https://disease.sh/v3/covid-19/all'
} else {
URL = `https://disease.sh/v3/covid-19/countries/${drop}?strict=true`
}
//getting data from the api
fetch(URL).then(res => res.json()).then(data => setData(data))
})
return (
< div className="wrapper" >
<div className="first">
{data !== undefined && <Main info={data} getValue={getValue} />}
<button onClick={() => { console.log(drop) }}>testing</button>
<button onClick={() => { console.log(data) }}>testing API</button>
<button onClick={() => { console.log(data.deaths) }}>testing deaths</button>
</div>
<div className="bla">
<Second />
</div>
</div >
)
}
export default App```
You need to add an empty dependency array to your useEffect. This means it will run only when the component mounts. If you don't pass one in, as you've done, it will run every time the component re-renders. Since the effect sets the state and causes a re-render, this will lead to an infinite loop. Change to this:
useEffect(() => {
let URL;
if (drop === null) {
URL = 'https://disease.sh/v3/covid-19/all'
} else {
URL = `https://disease.sh/v3/covid-19/countries/${drop}?strict=true`
}
//getting data from the api
fetch(URL).then(res => res.json()).then(data => setData(data))
}, [drop]) //empty array added here
Related
I'm making a Blog Web Page and the API I'm using to build it, it's already paginated so it returns 10 objects in every request I make.
But the client wants the page to have a "load more" button, in each time the user click on it, it will keep the already loaded data and load more 10 objects.
So far, I've made the button call more 10 new objects, everytime I clicked on it but I also need to keep the already loaded data.
This is my file so far:
MainPage.js
import React, { useState, useEffect} from 'react';
const MainPage = () => {
const [blogs, setBlogs] = useState('');
const [count, setCount] = useState(1);
useEffect(() => {
fetch("https://blog.apiki.com/wp-json/wp/v2/posts?_embed&categories=518&page="+count)
.then((response) => response.json())
.then((json) => {
console.log(json)
setBlogs(json)
})
}, [count])
const clickHandler = () => {
console.log(count);
return setCount( count+1)
}
return (
<div>
<p>All the recent posts</p>
{ blogs && blogs.map((blog) => {
return (
<div key={blog.id}>
<img width="100px" src={blog._embedded["wp:featuredmedia"][0].source_url}/>
<p>{blog.title["rendered"]}</p>
</div>
)
})
}
<button onClick={clickHandler}>LoadMore</button>
</div>
)
}
export default MainPage;
The idea is pretty simple. Just concatenate arrays using the Spread syntax as follows.
var first =[1, 2, 3];
var second = [2, 3, 4, 5];
var third = [...first, ...second];
So, do this thing when you're clicking the load more button.
Here I've come up with handling the whole thing:
Firstly, I will call a function inside the useEffect hook to load some blog posts initially. Secondly I've declared an extra state to show Loading and Load More text on the button.
Here is the full code snippet:
import React, { useState, useEffect } from "react";
const MainPage = () => {
const [loading, setLoading] = useState(false);
const [blogs, setBlogs] = useState([]);
const [count, setCount] = useState(1);
useEffect(() => {
const getBlogList = () => {
setLoading(true);
fetch(
"https://blog.apiki.com/wp-json/wp/v2/posts?_embed&categories=518&page=" +
count
)
.then((response) => response.json())
.then((json) => {
setBlogs([...blogs, ...json]);
setLoading(false);
});
};
getBlogList();
}, [count]);
return (
<div>
<p>All the recent posts</p>
{blogs &&
blogs.map((blog) => {
return (
<div key={blog.id}>
<img
width="100px"
src={blog._embedded["wp:featuredmedia"][0].source_url}
/>
<p>{blog.title["rendered"]}</p>
</div>
);
})}
{
<button onClick={() => setCount(count + 1)}>
{loading ? "Loading..." : "Load More"}
</button>
}
</div>
);
};
export default MainPage;
According to React documentation:
If the new state is computed using the previous state, you can pass a function to setState.
So you could append newly loaded blog posts to the existing ones in useEffect like this:
setBlogs((prevBlogs) => [...prevBlogs, ...json])
I would also set the initial state to an empty array rather than an empty string for consistency:
const [blogs, setBlogs] = useState([]);
I have the following react code that pulls some data from an api and outputs it. In result['track_list'] I receive a list of tracks with a timestamp and in aggTrackList() I am aggregating the data into key value pair based on the day/month/year then displaying that aggregated data in a Card component I created.
import React, { useState, useEffect } from "react";
function App() {
const [error, setError] = useState(null);
const [isLoaded, setIsLoaded] = useState(false);
const [trackList, settracks] = useState([]);
const [sortby, setSortby] = useState("day");
const [sortedList, setSortedList] = useState([]);
useEffect(() => {
aggTrackList();
}, [sortby]);
const aggTrackList = () => {
setSortedList([]);
let sortedObj = {};
switch (sortby) {
case "day":
trackList.forEach((track) => {
let dayVal = new Date(track[3]).toDateString();
dayVal in sortedObj
? sortedObj[dayVal].push(track)
: (sortedObj[dayVal] = [track]);
});
setSortedList(sortedObj);
break;
case "month":
trackList.forEach((track) => {
let monthVal = new Date(track[3]).toDateString().split(" ");
let monthYear = monthVal[1] + monthVal[3];
monthYear in sortedObj
? sortedObj[monthYear].push(track)
: (sortedObj[monthYear] = [track]);
});
setSortedList(sortedObj);
break;
case "year":
trackList.forEach((track) => {
let yearVal = new Date(track[3]).toDateString().split(" ");
let year = yearVal[3];
year in sortedObj
? sortedObj[year].push(track)
: (sortedObj[year] = [track]);
});
setSortedList(sortedObj);
break;
}
};
const getUserTracks = (username) => {
fetch(`http://localhost/my/api/${username}`, {
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
})
.then((res) => res.json())
.then(
(result) => {
settracks(result["tracks_played"]);
aggTrackList();
setIsLoaded(true);
},
(error) => {
console.log(error);
setIsLoaded(true);
setError(error);
}
);
};
return (
<div className="App">
<SortMenu
setSort={(selected) => {
setSortby(selected);
}}
/>
<UserForm onSubmit={getUserTracks} />
<div className="trackList">
{isLoaded ? (
Object.entries(sortedList).map(([day, track]) => (
<Card
className="card"
displayMode={sortby}
key={day}
timestamp={day}
content={track}
/>
))
) : (
<div>...</div>
)}
</div>
</div>
);
}
export default App;
The issue I am having is when UserForm is submitted and receives the data. The Card elements do not render unless I update the sortby state by clicking on one of the sortmenu options after the data has loaded. How can I get the data to show automatically after it has been loaded?
I'm creating this project to learn React so if something can be done better or if I am doing things wrong, please let me know.
Thanks.
Edit:
My code on codesandbox - https://codesandbox.io/s/fervent-minsky-bjko8?file=/src/App.js with sample data from my API.
You can do so in two ways:
In a blocking way using useLayoutEffect hook. Refer this.
In a non-blocking way using useEffect hook. Refer this.
1. useLayoutEffect
The thing to note here is that the function passed in the hook is executed first and the component is rendered.
Make the API call inside useLayoutEffect and then set the data once you obtain the response from the API. Where the data can initially be
const [data, setData] = useState(null)
The JSX must appropriately handle all the cases of different responses from the server.
2. useEffect
The thing to note here is that this function runs after the component has been rendered.
Make the API call inside the useEffect hook. Once the data is obtained, set the state variable accordingly.
Here your jsx can be something like
{
data === null ? (
<Loader />
) : data.length > 0 ? (
<Table data={data} />
) : (
<NoDataPlaceholder />
)
}
Here I am assuming the data is a list of objects. but appropriate conditions can be used for any other format. Here while the data is being fetched using the API call made inside useEffect, the user will see a loading animation. Once the data is obtained, the user will be shown the data. In case the data is empty, the user will be show appropriate placeholder message.
i have setup a strapi API, and i am using react to consume that API (with Axios).
here's what the code look like inside App.js
import axios from "axios";
import React, {useEffect, useState} from "react";
import LineCard from "./components/Linecard"
function App() {
// don't mind the URL i will fix them later
const root = "http://localhost:1337"
const URL = 'http://localhost:1337/pick-up-lines'
// this is the "messed up" data from strapi
const [APIdata, setAPIdata] = useState([])
//this is the clean data
const [lines, setLines] = useState([])
// the array that i will be using later to "setLines" state
const linesFromApi = APIdata.map((line, index) => {
const profileImage = root + line.users_permissions_user.profilePicture.formats.thumbnail.url
const userName = line.users_permissions_user.username
const title = line.title
const lineBody = line.line
const rating = line.rating
const categories = line.categories.map((category, index) => category.categoryName)
return {
profileImage,
userName,
title,
lineBody,
rating,
categories
}
})
useEffect(() => {
// calling the API with get method to fetch the data and store it inside APIdata state
axios.get(URL).then((res) => {
setAPIdata(res.data)
})
setLines(linesFromApi)
}, [URL, linesFromApi])
return (
<div>
// mapping through the lines list and rendering a card for each element
{lines.map((line, index) => <LineCard line={line} />)}
</div >
);
}
export default App;
i know for sure that this is causing the problem
return (
<div>
{lines.map((line, index) => <LineCard line={line} />)}
</div >
);
my problem is that react keeps sending GET requests constantly, and i want it to stop after the first time it has the list.
how can i do that!
Try adding a check in your hook so that it restricts the api call if the value is already set.
Something like this
useEffect(() => {
if(lines.length === 0){
axios.get(URL).then((res) => {
setAPIdata(res.data)
})
setLines(linesFromApi)
}
}, [URL, linesFromApi])
You need to add the key property to the element in a map.
<div>
{lines.map((line, index) => <LineCard key={index} line={line} />)}
</div>
Why my child component not get new props when parent component change state. I use a list with 3 element in database to test and state list_user_selected in Add_Friend_Modal to store all child Add_Friend_Selected. I trying when user click remove button in Add_Friend_Modal then state list_user_selected will be updated. But when I click button delete of first child component, props.list_user_selected just got array []. Second child component got array [First Child Component]. Third child component got array [First Child component,Second Child Component]. I have tried everything but it did not work. Can anyone explain to me why, and how to fix it
Child Component
const Add_Friend_Selected = props => {
return (
<li className="list-group-item px-0 d-flex">
<figure className="avatar mr-3">
<img src={props.avatar} className="rounded-circle" alt="image" />
</figure>
<div>
<div>{props.name}</div>
<div className="small text-muted">{props.mobile}</div>
</div>
<a onClick={() => props.delete_user_selected(props.id)} className="text-danger ml-auto" data-toggle="tooltip" title="Remove">
<i className="mdi mdi-delete-outline"></i>
</a>
</li>
)
}
Parent Component
const Add_Friend_Modal = props => {
const [count_user_selected,set_count_user_selected] = useState(0);
const [list_user_selected,set_list_user_selected] = useState([]);
const user = useSelector((state) => state.user);
const input_mobile_friend = useRef("");
const delete_user_selected = (id) => {
const delete_user_index = list_user_selected.findIndex(user_selected => user_selected.props.id === id);
console.log(delete_user_index)
set_list_user_selected([...list_user_selected.splice(delete_user_index,1)])
}
const add_invite_friend = () => {
if(input_mobile_friend.current.value) {
call_api({
url : `/users/search?mobile=${input_mobile_friend.current.value}`
})
.then(response => {
const user_find = response.data.data;
if(user_find && !list_user_selected.some(user_selected => user_selected.props.id === user_find._id )) {
const new_user_selected = (
<Add_Friend_Selected name={user_find.name} avatar={user_find.avatar}
mobile={user_find.mobile} id={user_find._id}
list_user_selected={list_user_selected}
delete_user_selected={(id) => {
delete_user_selected(id)
set_count_user_selected(list_user_selected.length + 1)
}}
/>
)
set_list_user_selected([...list_user_selected,new_user_selected])
set_count_user_selected(list_user_selected.length + 1)
}
else {
console.log("Not found")
}
})
.catch(error => {
console.log(error)
})
}
}
Steps to fixing your code:
Dont call apis in a place other than 'componentDidMount' in the case of hooks in 'useEffect'
If you want to use a function for render your component you need to call it in the return method of your functional component
Look if this code can help you:
//state for update when your api call finished
const [user_find, set_user_find] = useState(null)
//useEffect for call your api
useEffect(() => {
//componentSkillMount its a variable for not update an unmounted component
let componentSkillMount = true;
call_api({
url : `/users/search?mobile=${input_mobile_friend.current.value}`
})
.then(response => {
//if your component skill mounted you can update that
componentSkillMount && set_user_find(response.data.data)
})
return () => componentSkillMount = false
}, [])
return (
//if the api call finished you can return a full component
user_find && (
<Add_Friend_Selected name={user_find.name} avatar={user_find.avatar}
mobile={user_find.mobile} id={user_find._id}
list_user_selected={list_user_selected}
delete_user_selected={(id) => {
delete_user_selected(id)
set_count_user_selected(list_user_selected.length + 1)
}}
/>
)
)
Try to adapt this code to yours :)
Using React and React-Dom CDN 16
I am new to React and trying to build a dashboard component that takes the value of one of three buttons in a Buttons component and sends the value to a List component. The List component fetches data from an API and renders the results.
The feature works fine up until the data fetching, which it only does once the app is rendered the first time. I've logged that the state that's set by the Buttons component is making its way to the List component and the fetch action is updating dynamically correctly, but the fetching functionality isn't getting triggered when that state updates.
Here's the code.
const { useState, useEffect } = React
const App = props => {
return (
<div className="app-content">
<Dashboard />
</div>
);
};
const Dashboard = props => {
const [timespan, setTimespan] = useState('week');
const changeTime = time => setTimespan(time);
return(
<div>
<p>{timespan}</p> // this state updates when the buttons are clicked
<Buttons onUpdate={changeTime} />
<List timespan={timespan}/>
</div>
);
};
const Buttons = props => {
return (
<div>
<button onClick={props.onUpdate.bind( this, 'week' )}>
week
</button>
<button onClick={props.onUpdate.bind( this, 'month' )}>
month
</button>
<button onClick={props.onUpdate.bind( this, 'all' )}>
all
</button>
</div>
);
};
const List = props => {
const timespan = props.timespan;
const homepage = `${location.protocol}//${window.location.hostname}`;
const action = `${homepage}?fetchDataset=1×pan=${timespan}`;
// if I console.log(action) here the URL is updated correctly
const [error, setError] = useState(null);
const [isLoaded, setIsLoaded] = useState(false);
const [obj, setObj] = useState([]);
useEffect(() => {
fetch(action)
.then(res => res.json())
.then(
(result) => { // if I console.log(result) here I only get a response at initialization
setIsLoaded(true);
setObj(result);
},
(error) => {
setIsLoaded(true);
setError(error);
}
)
}, []);
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <div>Loading...</div>;
} else {
return (
<div>
// my API returns "timespan is: $timespan", but this is only ever "week" because that's the initial state of the timespan
{obj}
</div>
);
};
};
ReactDOM.render(
<App />,
document.getElementById('app')
);
I think I must be overlooking something very obvious because this seems like one of the core purposes of React, but it's hard to find documentation that is relevant with version 16 updates like function classes and hooks.
I really appreciate any help. Thanks!
you need to add timeSpan (or action) to your useEffect dependency array:
useEffect(() => {
fetch(action)
.then(res => res.json())
.then(
result => {
setIsLoaded(true);
setObj(result);
},
error => {
setIsLoaded(true);
setError(error);
}
);
}, [timeSpan]); // [action] will also solve this
This way the effect will know it needs to run every time the timeSpan prop changes.
By passing an empty dependency array you are telling the effect to only run once - when the component it mounted.