import { useState, useEffect } from 'react';
import axios from 'axios'
import { Loading } from './loading';
function News({ pageSize }) {
const [isLoading, setIsLoading] = useState(false)
const [state, setState] = useState({
article: [],
page: 1
}
)
const getUsers = async () => {
setIsLoading(true)
let res = await axios.get(`https://newsapi.org/v2/everything?domains=wsj.com&apiKey=79b02b430c1946cd9c505d3f91d7aec6&page=1&pageSize=${pageSize}`);
setState({article: res.data.articles})
setIsLoading(false)
};
useEffect(() => {
getUsers()
}, [])
const handleNext = async () => {
setIsLoading(true)
let res = await axios.get(`https://newsapi.org/v2/everything?domains=wsj.com&apiKey=79b02b430c1946cd9c505d3f91d7aec6&page=${state.page + 1}&pageSize=${pageSize}`);
setState({article: res.data.articles, page: state.page + 1})
setIsLoading(false)
}
let data = Array.from(state.article)
return (
<div>
<h2>News</h2>
<button onClick={handleNext}>Next</button>
{isLoading && <Loading />}
{!isLoading && data.map((elements) => {
return (
<div key={elements.url} style={{ marginBottom: '2rem' }}>
<div> {elements.description} </div>
<div>{new Date(elements.publishedAt).toGMTString()}</div>
</div>
)
})}
</div>
);
}
export default News;
When I take states separately for data and page, I'm able to display next page's data. But now that I've created one state to manage multiple objects, it displays back first page's data instead of next page's data. I don't know what I'm doing wrong. Pls help me!
Ignore the redundancy.
Try this:
const getUsers = async () => {
setIsLoading(true)
let res = await axios.get(`https://newsapi.org/v2/everything?domains=wsj.com&apiKey=79b02b430c1946cd9c505d3f91d7aec6&page=1&pageSize=${pageSize}`);
setState({...state, article: res.data.articles})
setIsLoading(false)
};
Related
I have 2 APIs, one call the products and the other one call the image of the products.
Products API: https://inventory.dearsystems.com/ExternalApi/v2/Product
The second API is required call the ID of the products
Image API: https://inventory.dearsystems.com/ExternalApi/v2/product/attachments?ProductID=
How can I call the second one to show the image.
Here is my code:
import axios from "axios";
import { useEffect, useState } from "react";
import { SingleContent } from "../../components/SingleContent/SingleContent";
export const All = () => {
const [content, setContent] = useState([]);
const fetchAllProducts = async () => {
const { data } = await axios.get('https://inventory.dearsystems.com/ExternalApi/v2/Product',{
headers: {
"api-auth-accountid": process.env.REACT_APP_API_ID,
"api-auth-applicationkey": process.env.REACT_APP_API_KEY
}
});
console.log(data.Products);
setContent(data.Products)
}
useEffect(() =>{
fetchAllProducts();
}, [])
return (
<div>
<h1 className="pageTitle">All Products</h1>
<div className="all">
{content && content.map((c) =>
<SingleContent
key={c.id}
id={c.ID}
name={c.Name}
sku={c.SKU}
category={c.Category}/> )}
</div>
</div>
)
}
Inside fetchAllProducts() you could map the data.Products array you get from the first request, call the second api for each item and add the product image to the item.
Then you can update the contents with the resulting array.
Edited: example code below.
const fetchAllProducts = async () => {
const { data } = await axios.get('https://inventory.dearsystems.com/ExternalApi/v2/Product',{
headers: {
"api-auth-accountid": process.env.REACT_APP_API_ID,
"api-auth-applicationkey": process.env.REACT_APP_API_KEY
}
});
const productsWithImage = data.Products
.map(async product => {
const imageUrl = await axios.get("https://inventory.dearsystems.com/ExternalApi/v2/product/attachments?ProductID=" + product.id)
return {...product, imageUrl }
})
setContent(productsWithImage)
}
// Then you can use product.imageUrl when you map your products
Use for instead of map because it was returning a Promise. The problem is that if setContent(result) is called outside of the for, it only returns on register.
See the code:
export const AllHandle = () => {
const [content, setContent] = useState([]);
const fetchAllProducts = async () => {
const { data } = await axios.get("https://inventory.dearsystems.com/ExternalApi/v2/Product?limit=10",{
headers: {
"api-auth-accountid": process.env.REACT_APP_API_ID,
"api-auth-applicationkey": process.env.REACT_APP_API_KEY
}
});
let result = [];
const totaProducts = await data.Products;
for (let product of totaProducts) {
const imageUrl = await axios.get(`https://inventory.dearsystems.com/ExternalApi/v2/product/attachments?ProductID=${product.ID}`, {
headers: {
"api-auth-accountid": process.env.REACT_APP_API_ID,
"api-auth-applicationkey": process.env.REACT_APP_API_KEY
}
});
const imgUrl = imageUrl.data
result = [{ ...product, imgUrl}]
}
setContent(result)
}
useEffect(() =>{
fetchAllProducts();
}, [])
return (
<div>
<h1 className="pageTitle">All Products</h1>
<div className="all">
{content && content.map((c) =>
<SingleContent
key={c.ID}
id={c.ID}
name={c.Name}
sku={c.SKU}
category={c.Category}
attachment= { c.imgUrl[0].DownloadUrl}
/>
)}
</div>
</div>
)
}
I am trying to setup infinite scrolling using React Hooks, I am getting the data correctly from the node backend (3 dataset per request), and when I scroll new data is also added correctly in the array (in the loadedPlaces state), but the page is going back to top on re render, I need to prevent this behavior. How do I prevent this, and below is my code
import React, { useEffect, useState } from "react";
import "./App.css";
function App() {
const [page, setPage] = useState(1);
const [loadedPlaces, setLoadedPlaces] = useState([]);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
const getPlaces = async () => {
try {
setIsLoading(true);
const url = `http://localhost:5000/api/places/user/${id}?page=${page}&size=3`;
const responseData = await fetch(url);
const data = await responseData.json();
console.log(data);
setLoadedPlaces((prev) => [...prev, ...data.places]);
setIsLoading(false);
} catch (error) {}
};
getPlaces();
}, [page]);
window.onscroll = function (e) {
if (
window.innerHeight + document.documentElement.scrollTop ===
document.documentElement.offsetHeight
) {
setPage(page + 1);
}
};
return (
<div className='App'>
<h1>Infinite Scroll</h1>
{!isLoading &&
loadedPlaces.length > 0 &&
loadedPlaces.map((place, index) => (
<div key={index} className={"container"}>
<h1>{place.location.lat}</h1>
<h1>{place.location.lng}</h1>
<h1>{place.title}</h1>
<h1>{place.description}</h1>
<h1>{place.address}</h1>
<hr></hr>
</div>
))}
</div>
);
}
export default App;
Any help is highly appreciated
This is happening because whenever you scroll you are calling
window.onscroll = function (e) {
if (
window.innerHeight + document.documentElement.scrollTop ===
document.documentElement.offsetHeight
) {
setPage(page + 1);
}
};
And it's changing the page count and that changed page count leads to again run the
useEffect(() => {
const getPlaces = async () => {
try {
setIsLoading(true);
const url = `http://localhost:5000/api/places/user/${id}?page=${page}&size=3`;
const responseData = await fetch(url);
const data = await responseData.json();
console.log(data);
setLoadedPlaces((prev) => [...prev, ...data.places]);
setIsLoading(false);
} catch (error) {}
};
getPlaces();
}, [page]);
and in that function, you are doing setIsLoading(true) so that it is again rendering this because of
{!isLoading && <-----
loadedPlaces.length > 0 &&
loadedPlaces.map((place, index) => (
<div key={index} className={"container"}>
<h1>{place.location.lat}</h1>
<h1>{place.location.lng}</h1>
<h1>{place.title}</h1>
<h1>{place.description}</h1>
<h1>{place.address}</h1>
<hr></hr>
</div>
))}
And that leads you to the top of the page
You can try this approach:
import React, { useEffect, useState } from "react";
import "./App.css";
function App() {
const [page, setPage] = useState(1);
const [loadedPlaces, setLoadedPlaces] = useState([]);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
const getPlaces = async () => {
try {
setIsLoading(true);
const url = `http://localhost:5000/api/places/user/${id}?page=${page}&size=3`;
const responseData = await fetch(url);
const data = await responseData.json();
console.log(data);
setLoadedPlaces((prev) => [...prev, ...data.places]);
setIsLoading(false);
} catch (error) {}
};
getPlaces();
}, [page]);
window.onscroll = function (e) {
if (
window.innerHeight + document.documentElement.scrollTop ===
document.documentElement.offsetHeight
) {
setPage(page + 1);
}
};
return (
<div className='App'>
<h1>Infinite Scroll</h1>
{
loadedPlaces.length > 0 &&
loadedPlaces.map((place, index) => (
<div key={index} className={"container"}>
<h1>{place.location.lat}</h1>
<h1>{place.location.lng}</h1>
<h1>{place.title}</h1>
<h1>{place.description}</h1>
<h1>{place.address}</h1>
<hr></hr>
</div>
))}
</div>
);
}
export default App;
You can add this.
function ScrollToBottom(){
const elementRef = useRef();
useEffect(() => elementRef.current.scrollIntoView());
return <div ref={elementRef} />;
};
And then:
return (
<div className='App'>
<h1>Infinite Scroll</h1>
{!isLoading &&
loadedPlaces.length > 0 &&
loadedPlaces.map((place, index) => (
<div key={index} className={"container"}>
<h1>{place.location.lat}</h1>
<h1>{place.location.lng}</h1>
<h1>{place.title}</h1>
<h1>{place.description}</h1>
<h1>{place.address}</h1>
<hr></hr>
</div>
))}
<ScrollToBottom />
</div>
);
I have problem with load data to component after click on button.
I use getInitialProps to first load data on page.
How to load new data and past them to {data} after click?
export default function Users({ data }) {
const fetchData = async () => {
const req = await fetch("https://randomuser.me/api/?gender=male&results=100");
const data = await req.json();
return { data: data.results };
};
const handleClick = (event) => {
event.preventDefault();
fetchData();
};
return (
<Layout>
<button onClick={handleClick}>FETCH DATA</button>
{data.map((user) => {
return (
<div>
{user.email}
<img src={user.picture.medium} alt="" />
</div>
);
})}
</Layout>
);
}
Users.getInitialProps = async () => {
const req = await fetch(
"https://randomuser.me/api/?gender=female&results=10"
);
const data = await req.json();
return { data: data.results };
};
Thank a lot for help!
Use useState with the default value being the data you initially retrieved via getInitialProps:
import { useState } from 'React';
export default function Users({ initialData }) {
const [data, setData] = useState(initialData);
const fetchData = async () => {
const req = await fetch('https://randomuser.me/api/?gender=male&results=100');
const newData = await req.json();
return setData(newData.results);
};
const handleClick = (event) => {
event.preventDefault();
fetchData();
};
return (
<Layout>
<button onClick={handleClick}>FETCH DATA</button>
{data.map((user) => {
return (
<div>
{user.email}
<img src={user.picture.medium} alt="" />
</div>
);
})}
</Layout>
);
}
Users.getInitialProps = async () => {
const req = await fetch('https://randomuser.me/api/?gender=female&results=10');
const data = await req.json();
return { initialData: data.results };
};
Sidenote: Times have changed and it would seem that user1665355 is indeed correct:
Recommended: getStaticProps or getServerSideProps
If you're using Next.js 9.3 or newer, we recommend that you use
getStaticProps or getServerSideProps instead of getInitialProps.
These new data fetching methods allow you to have a granular choice
between static generation and server-side rendering.
import { useState } from 'React';
export default function Users({ initialData }) {
const [data, setData] = useState(initialData);
const fetchData = async () => {
const req = await fetch('https://randomuser.me/api/?gender=male&results=100');
const newData = await req.json();
setData(newData.results);
};
const handleClick = (event) => {
event.preventDefault();
fetchData();
};
return (
<Layout>
<button onClick={handleClick}>FETCH DATA</button>
{data.map(user => {
return (
<div key={user.login.uuid}>
{user.email}
<img src={user.picture.medium} alt="" />
</div>
);
})}
</Layout>
);
}
Users.getInitialProps = async () => {
const req = await fetch('https://randomuser.me/api/?gender=female&results=10');
const data = await req.json();
return { initialData: data.results };
};
I would like to list my notes about George's code. At least, it should pay attention to them.
First of all, it should attach any key to a div element otherwise a warning will have appeared in the browser console. Here is an article about using keys: https://reactjs.org/docs/lists-and-keys.html#keys
As well, the keyword return can be removed from the fetchData function that doesn't return a response.
It is recommended to use getStaticProps or getServerSideProps now. https://nextjs.org/docs/api-reference/data-fetching/getInitialProps
I am building a Simple ToDoList App.
I fetch List Using React Hook.
When I add a new Todo or delete an existing one the request sends and works but the component doesn`t rerender.
I tried 2 ways to solve the problem to create
1.async functions(delete and add). Took getToDoList outside the hook and call it after requests(post/delete)
useEffect(() => {
getToDoList();
}, []);
const getToDoList = async () => {
const result = await axios.get('http://localhost:1200/');
setToDoList(result.data);
};
const addNewTodo = async () => {
await axios.post('http://localhost:1200/create', {
item: newToDo.current.value
});
getToDoList();
};
const deleteToDo = async (id) => {
await axios.delete(`http://localhost:1200/delete?id=${id}`);
getToDoList();
};
2.Took getToDoList inside the hook and gave it 2 dependecies which change in deleteToDo/addNewToDo
const [add, setAdd] = useState(false);
const [remove, setRemove] = useState(false);
useEffect(() => {
const getToDoList = async () => {
const result = await axios.get('http://localhost:1200/');
setToDoList(result.data);
};
getToDoList();
}, [add, remove]);
const addNewTodo = async () => {
await axios.post('http://localhost:1200/create', {
item: newToDo.current.value
});
setAdd(!add);
};
const deleteToDo = async (id) => {
await axios.delete(`http://localhost:1200/delete?id=${id}`);
setRemove(!remove);
};
Both don`t work. Tell me please where Im wrong
JSX
return (
<div>
<Jumbotron>
<Container>
<h1>Hello!</h1>
<p>This is a simple ToDoList created by Vadik</p>
</Container>
</Jumbotron>
<Container>
<Container>
<InputGroup>
<FormControl placeholder="What needs to be done" ref={newToDo}/>
</InputGroup>
<Button onClick={addNewTodo} variant="primary" size="sm">
Add new Todo
</Button>
</Container>
<Container className="cards">
<Card>
<ListGroup>
{toDoList.todos.map((elem) => <ListGroup.Item
key={elem._id}>{elem.item}<CloseButton onClick={() => deleteToDo(elem._id)}/> </ListGroup.Item>)}
</ListGroup>
</Card>
</Container>
</Container>
</div>
);
Try the below code.This should work.In addTodo there are 2 ways either update the resultList after post is successful or use the getList to get the latest data and update the resultList. Both of them are shown.
function Example() {
const [resultList, setResult] = useState({todos: []});
useEffect(() => {
const todoList = getToDoList();
setResult(prev => todoList);
}, []);
const addTodo = async () => {
await axios.post('http://localhost:1200/create', {
item: newToDo.current.value
});
//Addition successful, hence update our resultList.
return setResult(prev => {
return {todos: [...prev.todos, {item: newToDo.current.value, id: prev.todos.length+1}]}
});
//Now since add api doesn't return a
//value call Get api again and update the todoList to render
//const todoList = getToDoList();
//setResult(prev => todoList);
};
const getToDoList = async () => {
const result = await axios.get('http://localhost:1200/');
return result.data;
};
return (
<div id='container'>
{resultList.todos.map(todo => <div>{todo.item}</div>
)}
<button onClick={addTodo}>Add</button>
</div>
)
}
The problem was that I didn`t send any response to those requests.enter image description here
I'm trying to take out the fetchImages function from the following component and put it inside a new component:
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import UnsplashImage from './UnsplashImage';
const Collage = () => {
const [images, setImages] = useState([]);
const [loaded, setIsLoaded] = useState(false);
const fetchImages = (count = 10) => {
const apiRoot = 'https://api.unsplash.com';
const accessKey =
'<API KEY>';
axios
.get(`${apiRoot}/photos/random?client_id=${accessKey}&count=${count}`)
.then(res => {
console.log(res);
setImages([...images, ...res.data]);
setIsLoaded(true);
});
};
useEffect(() => {
fetchImages();
}, []);
return (
<div className="image-grid">
{loaded
? images.map(image => (
<UnsplashImage
url={image.urls.regular}
key={image.id}
alt={image.description}
/>
))
: ''}
</div>
);
};
export default Collage;
For this, I created a new component called api.js, removed the entire fetchImage function from the above component and put it in to api.js like this:
api.js
const fetchImages = (count = 10) => {
const apiRoot = 'https://api.unsplash.com';
const accessKey =
'<API KEY>';
axios
.get(`${apiRoot}/photos/random?client_id=${accessKey}&count=${count}`)
.then(res => {
console.log(res);
setImages([...images, ...res.data]);
setIsLoaded(true);
});
};
export default fetchImages;
Next I took setIsLoaded(true); from api.js and paste it inside Collage component like this:
useEffect(() => {
fetchImages();
setIsLoaded(true);
}, []);
Now I can import fetchImages in to Collage component.
However, I don't know what should I do with this line inside the fetchImages function? This needs to go to Collage component, but res.data is not defined inside Collage component.
setImages([...images, ...res.data]);
How should I handle it?
There is many way to do that, but in your case.
You should use
const fetchImages = (afterComplete, count = 10) => {
const apiRoot = 'https://api.unsplash.com';
const accessKey = '<API KEY>';
axios
.get(`${apiRoot}/photos/random?client_id=${accessKey}&count=${count}`)
.then(res => {
console.log(res);
afterComplete(res.data);
});
};
export default fetchImages;
And in your Collage component:
const afterComplete = (resData) =>{
setImages([...images, ...resData]);
setIsLoaded(true);
}
useEffect(() => {
fetchImages(afterComplete);
}, []);
What you can do is create a custom hook ( sort of like a HOC)... Since I don't have an unsplash API key I'll give you an example with a different API but the idea is the same:
Here is your custom hook:
import { useState, useEffect } from 'react';
export const useFetch = url => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const fetchUser = async () => {
const response = await fetch(url);
const data = await response.json();
const [user] = data.results;
setData(user);
setLoading(false);
};
useEffect(() => {
fetchUser();
}, []);
return { data, loading };
};
Here is how you can use it in your component:
import { useFetch } from './api';
const App = () => {
const { data, loading } = useFetch('https://api.randomuser.me/');
return (
<div className="App">
{loading ? (
<div>Loading...</div>
) : (
<>
<div className="name">
{data.name.first} {data.name.last}
</div>
<img className="cropper" src={data.picture.large} alt="avatar" />
</>
)}
</div>
);
};
Here is a live demo: https://codesandbox.io/s/3ymnlq59xm