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([]);
Related
I'm trying to create an array state variable called usersInfos that I can append to whenever I do an api call, but useEffect is updating with the initial value of the userData state variable.
// api.js
import axios from 'axios';
export function fetchUserData () {
return axios.get('https://randomuser.me/api')
.then(res => {
return res;
})
.catch(err => {
console.error(err);
})
}
import { fetchUserData } from '../../src/api';
import { useState, useEffect } from 'react';
import ProfileCard from './profilecard';
export default function UserProfile() {
const [userData, setUserData] = useState();
const [usersInfos, setUsersInfos] = useState([]);
const getUserData = async () => {
let ud = await fetchUserData()
setUserData(ud);
}
useEffect(() => {
getUserData();
}, [])
useEffect(() => {
const newInfos = [
...usersInfos,
userData,
]
setUsersInfos(newInfos);
console.log(usersInfos);
}, [userData])
return (
<div className='userprofile'>
<button onClick={() => { getUserData() }}> Fetch Random User </button>
<button onClick={() => { console.log(usersInfos) }}> log usersInfos </button>
{usersInfos.map((user, idx) => {
<ProfileCard userData={user} key={idx} />
})}
</div>
)
}
I'm assuming it's something to do with the state batch updating, but I'm not sure. What would be the best practice way of doing this?
I am console logging with the button at the bottom, after the page has loaded.
When userData is initialized to (), I get console log: (2) [undefined, {…}]
When userData is initialized to (0), I get console log: (2) [0, {…}].
When userData is initialized to ([]), i get console log:(2) [Array(0), {…}]
Thanks
When the 2nd effect runs for the first time, userData is undefined because the network request isn't resolved yet. You could simply run the effect only when userData is not undefined.
useEffect(() => {
if (userData) {
const newInfos = [
...usersInfos,
userData,
]
setUsersInfos(newInfos);
}
}, [userData])
However, if the code presented in the question is almost complete, I would use the code below instead. In this way I can update both states with a single effect.
The code below is a snippet from the CodeSandbox fully functional example.
export default function UserProfile() {
const [userData, setUserData] = useState();
const [usersInfos, setUsersInfos] = useState([]);
const getUserData = useCallback(async () => {
let ud = await fetchUserData();
setUserData(ud);
setUsersInfos((prevInfos) => [...prevInfos, ud]);
}, []);
useEffect(() => {
getUserData();
}, [getUserData]);
return (
<div className="userprofile">
<button onClick={getUserData}>Fetch Random User</button>
<button
onClick={() => {
console.log(usersInfos);
}}
>
log usersInfos
</button>
{userData && (
<p>
Current user: {userData.name.first} {userData.name.last}
</p>
)}
<ul>
{usersInfos.map((user, idx) => (
<ProfileCard userData={user} key={idx} />
))}
</ul>
</div>
);
}
If you try to log userInfos just after the setter, it will log the previous state because the function inside the effect is a closure and it captures the value of the state when the effect runs. Since React state is immutable the state update does not works like an assignment but it will be updated at the next render.
Hi guys I am trying to fetch data using get, and I want the data to be displayed after I click on the button, as a normal crud
I am new in programming if there is someone that can help me. I APPRECIATE THANKS
everything in my backend is ok, I try in postman and console.log is everything good. My problem is only in this part thanks
import { useState, useEffect } from "react";
import axios from "axios";
function Usuarios() {
const [users, setUsers] = useState([]);
useEffect(()=> {
const todosUsers = async () => {
const res= await axios.get("/users");
console.log(res)
setUsers(res.data);
}
todosUsers()
},[])
return (
<>
<button onClick=
{users.map((users) => (
<h1>{users.username}</h1>
))}></button>
</>
)
}
export default Usuarios;
one solution would be to keep a seperate variable to see if button is clicked as S.Singh mentioned
const [users, setUsers] = useState([]);
const [clicked, setClicked] = useState(false);
and in your component you can set the clicked variable to true on click
return (
<>
<button onClick={() => setClicked(true)}></button> //setting clicked true onclick
{clicked && users.map((users) => ( //only renders when click is true
<h1>{users.username}</h1>
))}
</>
)
if you want to hide and show alternatively on click just change the line to onClick={() => setClicked(!clicked)}
codesandbox demo
Move
{users.map((users) => (
<h1>{users.username}</h1>
))
}
from onClick definition and place it in an a markup where you want it to render. Define onClick hendler, which will be setting data from an API to a state
import { useState, useEffect } from "react";
import axios from "axios";
function Usuarios() {
const [users, setUsers] = useState([]);
const fetchOnClick = async () => {
await axios.get("/users");
console.log(res)
setUsers(res.data);
}
return (
<>
<button onClick={fetchOnClick}>
Fetch
</button>
<div>
{users.map((users) => (
<h1>{users.username}</h1>
))}
</div>
</>
)
}
OR
If you want to fetch the data inside useEffect hook, like you did in your example
import React, { useState, useEffect } from "react";
function Usuarios() {
const [users, setUsers] = useState([]);
const [isContainerShowed, setIsContainerShowed] = useState(false)
useEffect(() => {
const res= await axios.get("/users");
console.log(res)
setUsers(res.data);
}, [])
const displayContainer = () => setIsContainerShowed(true);
return (
<>
<button onClick={displayContainer}>
Fetch
</button>
<div style={{display: isContainerShowed ? "block" : "none"}}>
{users.map(users => <h1>{users.username}</h1>)}
</div>
</>
)
}
use Effect hook runs every time the component is rendered but as I understand, you want to display users when the button is clicked, so you can fetch users once the button is clicked and display them using the code below
import { useState, useEffect } from "react";
import axios from "axios";
function Usuarios() {
const [users, setUsers] = useState([]);
const fetchUsers= async () => {
const res= await axios.get("/users");
setUsers(res.data);
}
return (
<>
<button onClick={() => fetchUsers()}></button>
{
users.length && users.map(user => <h2>{user.username}</h2>)
}
</>
)
}
I want to make a component, which should have an input and button. The button should do some kind of work, I want it to show the information that I need to receive with fetch. In this case my code is not working, it shows a lot of [objects], however I want it to show the information from the base. if you can please help... : )
import React, { useState } from 'react';
const App = () => {
const [User, setUser] = useState({num: ""})
const [Open, setOpen] = useState(false)
const users = () => {
fetch("https://jsonplaceholder.typicode.com/users")
.then((response) => response.json())
.then((json) => {
document.write(json);
});
};
return( <>
<input name="Num" />
<button onClick={users}> Click Me for users </button>
</>)
}
export default App ```
You should almost never use document.write
Following is one of they ways you can solve your problem
import React, { useState } from "react";
const App = () => {
const [User, setUser] = useState({ num: "" });
const [Open, setOpen] = useState(false);
const [users, setUsers] = useState([]);
const fetchAndSetUsers = () => {
fetch("https://jsonplaceholder.typicode.com/users")
.then((response) => response.json())
.then((users) => {
setUsers(users);
});
};
return (
<>
<input name="Num" />
<button onClick={fetchAndSetUsers}> Click Me for users </button>
{users.map((user) => {
return <div>{user.name}</div>;
})}
</>
);
};
export default App;
Change the line of
document.write
to
setUsers(json)
You shouldn't manipulate UI directly in React using DOM methods since React uses Virtual DOM concept to update UI
document.write receive html element as parameter not a json object, if you just want to check if the fetch work you can do JSON.stringigy(json)
PS: sorry for my poor english
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>
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