React async data fetch in useEffect - javascript

I'm trying to load data through an asynchronous method in useEffect. I pass all the necessary dependencies and, in my understanding, useEffect should work when the component is mounted, on the first render, and when dependencies change.
useEffect(() => {
console.log('effect')
if (ids.length === 0) {
api.images.all().then((data) => { console.log(data); setIDs(data) }).catch(console.log)
}
}, [ids])
In my case it's 3 times: mount (it should load data immediately), first render (shouldn't go into if), and due to ids change (should also not go into if). But useEffect fires 4 times and loads data twice, I can't figure out why.
Component code:
//BuildIn
import { useEffect, useState } from 'react'
//Inside
import api from '../services/api.service'
import AsyncImage from '../components/AsyncImage.component'
const ImagesPage = () => {
const [ids, setIDs] = useState([])
useEffect(() => {
console.log('effect')
if (ids.length === 0) {
api.images.all().then((data) => { console.log(data); setIDs(data) }).catch(console.log)
}
}, [ids])
return(
<>
{(ids.length > 0) ? ids.map((id, index) => <AsyncImage guid={id} key={index} />) : <div>No data</div>}
</>
)
}
export default ImagesPage

I've re-implemented the business logic of your example and it works well. The only thing you have to fix is to pass the setIDs to the useEffect as a dependency. The component renders twice which is fine; the first one is the initial render and the second one occurs when the data is present.
You can even get rid of the if condition. Simply do not pass the id to the useEffect hook and it will fetch the images on mount only.
// import { useState, useEffect } from 'react' --> with babel import
const { useState, useEffect } = React // --> with inline script tag
const api = {
images: { all: () => new Promise(res => res(['id1', 'id2'])) }
}
const ImagesPage = () => {
const [ids, setIDs] = useState([])
useEffect(() => {
api.images.all()
.then(data => {
setIDs(data)
})
.catch(console.log)
}, [setIDs])
return(
<ul>
{console.log('reders')}
{ids.map(id => <li key={id}>{id}</li>)}
</ul>
)
}
ReactDOM.render(<ImagesPage />, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.9.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.9.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>

try to do this :
useEffect(() => {
async function fetchData() {
const res = await loadMovies();
setIDs(res)
}
fetchData();
}, []);

Related

How to fetch API as soon as page is loaded in React?

Whenever I visit a page it should automatically fetch the API
import React from 'react'
const Component = () => {
fetch("api url").then((res) => console.log(res))
return (
<div>comp</div>
)
}
export default Component
It is very simple using react hook use effect please learn basics of useffect hook on react docs or any youtube tutorial and as for the answer
import React, { useEffect } from 'react'
const comp = () => {
useEffect(() => {
fetch("api url").then((res)=>console.log(res))
}, [])
return (
<div>comp</div>
)
}
export default comp
here empty dependency means every time page loads only once
use the useEffect for this.
The useEffect method will execute the passed callback on the mount of the component and on every time one of the dependency array parameters is changed. therefore:
const Comp = () => {
useEffect(() => {
fetch("api url").then((res)=>console.log(res))
}, []);
return (
<div>comp</div>
)
}
Will make the callback to fire only once (because the empty dependency array) on the component mount.
You should use the useEffect Hook in your principal component like app.js
import React, {useEffect} from 'react'
useEffect(() => {
fetch("api url").then((res)=>console.log(res))
}, []);
Be careful, this manipulation can consume a lot of resources (a lot of data to fetch etc.)
Thery
import React, { useState, useEffect } from 'react'
const Comp = () => {
const [ data, setData ] = useState([]);
const getData = async () => {
const res = await fetch("api url");
const data = await res.json();
setData(data)
}
useEffect(()=>{ getData() },[]);
return (
<>
<div>comp</div>
// dispaly your data here from data state
</>
)
}
export default Comp;
Fetch and use data with useState
const initialValue = {};
const comp = () => {
const [data, setData] = useState(initialValue);
useEffect(() => {
let ignore = false;
const fetchData = async () => {
const res = fetch("api url");
if (ignore) { return; }
setData(res.json())
return () => {
ignore = true;
}
}
, [])
return (
<div>comp {data.prop}</div>
)
}
More on working with state
More about useEffect life cycle
Hope it helps
You don't need to use the API function like this, it will be called continuously, you need to use useEffect hook, when your component reloads useEffect will be called, and you can learn about the useEffect dependency here,
import React, { useEffect, useState } from 'react'
const comp = () => {
const [data, setData] = useState([]);
useEffect(() => {
fetch("api url").then((res)=> {
console.log(res)
setData(res)
} )
}, [])
return (
// use data state to show the data here
<div>comp</div>
)
}
export default comp;

Infinite console log in react js component

I have made two simple straight forward component is React, used a open source API to test API integration. React is showing this weird behavior of infinite console logs. I don't understand the issue. I'm using the fetch function for making API calls and functional component.
App component:
function App() {
const [characters, setCharac] = useState([])
const URL = "https://swapi.dev/api/";
fetch(URL + "people").then(response => response.json().then(data => {
setCharac(data.results)
console.log('Test');
}))
return (
<div className="App">
{characters.map(charac => {
return <Character {...charac} />
})}
</div>
);
}
Character component:
const Character = (props) => {
console.log(props);
return (
<div key={props.name}>
<h1>{props.name}</h1>
<p>{props.height}</p>
</div>
);
}
console.log('Test'); in App component and console.log(props); in Character component are being executed infinitely.
This is the render method
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
Your components are rendering multiple times because your state is changed every time you fetch data (because of setState).
Try creating a function fetchData(). Make this function async as well to wait for data to be retrieved.
const fetchData = async () => {
const result = await fetch(URL + "people").then(response => response.json().then(data => {
setCharac(data.results)
console.log('Test');
return data;
}));
return result;
}
and then use it inside useEffect (Read more about useEffects: React hooks: What/Why `useEffect`?)
useEffect(() => {
fetchData();
}, []);
Note the usage of [] in useEffect. The data will be fetched only once when you load the component.
Try wrapping it in a useEffect
e.g.
useEffect(()=>{
fetch(URL + "people").then(response => response.json().then(data => {
setCharac(data.results)
console.log('Test');
}))
},[])
otherwise every time the state is set it is firing off the fetch again because a re-render is being triggered.
Because you fetch some data, update the state, which causes a re-render, which does another fetch, updates the state, which causes another render...etc.
Call your fetch function from inside a useEffect with an empty dependency array so that it only gets called once when the component is initially rendered.
Note 1: you can't immediately log the state after setting it as setting the state is an async process. You can, however, use another useEffect to watch for changes in the state, and log its updated value.
Note 2: I've used async/await in this example as the syntax is a little cleaner.
// Fetch the data and set the state
async function getData(endpoint) {
const json = await fetch(`${endpoint}/people`);
const data = await response.json();
setCharac(data.results);
}
// Call `getData` when the component initially renders
useEffect(() => {
const endpoint = 'https://swapi.dev/api';
getData(endpoint);
}, []);
// Watch for a change in the character state, and log it
useEffect(() => console.log(characters), [characters]);
You can do something like this:
import React, { useState, useEffect, useCallback } from "react";
const Character = (props) => {
console.log(props);
return (
<div key={props.name}>
<h1>{props.name}</h1>
<p>{props.height}</p>
</div>
);
};
export default function App() {
const [characters, setCharac] = useState([]);
const makeFetch = useCallback(() => {
const URL = "https://swapi.dev/api/";
fetch(URL + "people").then((response) =>
response.json().then((data) => {
setCharac(data.results);
console.log("Test");
})
);
}, []);
useEffect(() => {
makeFetch();
}, []);
return (
<div className="App">
{characters.map((charac) => {
return <Character {...charac} />;
})}
</div>
);
}

conditional rendering with toast and usestate does not work with react

I have my state and I want to display the component if the value is true but in the console I receive the error message Cannot update during an existing state transition (such as within render). Render methods should be a pure function of props and state my code
import React, { useState} from "react";
import { useToasts } from "react-toast-notifications";
const Index = () => {
const [test, setTest]= useState(true);
const { addToast } = useToasts();
function RenderToast() {
return (
<div>
{ addToast('message') }
</div>
)}
return (
<div>
{test && <RenderToast /> }
</div>
)
}
You cannot set state during a render. And I'm guessing that addToast internally sets some state.
And looking at the docs for that library, you don't explicitly render the toasts. You just call addToast and then the <ToastProvider/> farther up in the tree shows them.
So to make this simple example works where a toast is shown on mount, you should use an effect to add the toast after the first render, and make sure your component is wrapped by <ToastProvider>
const Index = () => {
const { addToast } = useToasts();
useEffect(() => {
addToast('message')
}, [])
return <>Some Content here</>
}
// Example app that includes the toast provider
const MyApp = () => {
<ToastProvider>
<Index />
</ToastProvider>
}
how i can display the toast based on a variable for exemple display toast after receive error on backend?
You simply call addToast where you are handling your server communication.
For example:
const Index = () => {
const { addToast } = useToasts();
useEffect(() => {
fetchDataFromApi()
.then(data => ...)
.catch(error => addToast(`error: ${error}`))
}, [])
//...
}

How can I update state after setting it in UseEffect?

I want to display a list of products in one of my components. I need to use UseEffect to get the results of a function that returns a promise. I then am setting state in UseEffect but am having trouble displaying the products. The state is getting updated but I am not able to render the results on the dom.
Here's the component:
import React, { useEffect, useState } from "react"
import styled from "styled-components"
import pullShopifyData from "../../functions/shopify"
export default function DataPreview(props)
const { apiKey } = props
const [data, setData] = useState([])
useEffect(() => {
pullShopifyData(apiKey).then(response => {
console.log(response.data.shop.products)
setData(response.data.shop.products)
})
}, [])
return (
<Wrapper>
{data.length > 0 ? data.map(product => (
<ProductWrapper>{product}</ProductWrapper>
)): <NoProductsWrapper>No Products Found</NoProductsWrapper>}
</Wrapper>
)
}
const Wrapper = styled.div``
const ProductWrapper = styled.div``
const NoProductsWrapper = styled.div``
Here's the output on the dom:
Move your data fetch function in to async wrapper function. Then just call it in useEffect.
useEffect(() => {
fetchSomething();
}, [])
const fetchSomething = async () => {
const response = await pullShopifyData(apiKey);
console.log(response.data.shop.products)
setData(response.data.shop.products)
}

Too many re-renders React error while fetching data from API

I am building a simple recipe app and I have a problem with fetching my data from the API, because the code seems to run on every render and I do not even understand why it re-runs since I found that if I add the dependency array, it should run only once, right ?
App.js
function App() {
const [recipesList, setRecipesList] = useState([]);
let [scroll, setScroll] = useState(0)
console.log(recipesList,"list");
return (
<div className="App">
<img className="logo" src={logo} alt="Logo"/>
<Recipes recipesList={recipesList} getRecipes={setRecipesList} />
</div>
);
}
export default App;
Recipes.js
import React, {useEffect, useState} from "react";
import Recipe from "../Recipe/Recipe";
import "./Recipes.css";
const Recipes = (props) => {
useEffect( () => {
if (props.recipesList.length === 0) {
fetch("myapi.com/blablabla")
.then(res => res.json())
.then(result => {
props.getRecipes(result.recipes);
}
)
}
else {
console.log("Do not fetch");
}
return () => console.log("unmounting");
}, [props])
const recipeComponent = props.recipesList.map( (item) => {
return <Recipe className="recipe" info={item}/>
})
return(
<div className="recipes">
{recipeComponent}
<h1>Hello</h1>
</div>
)
}
export default Recipes;
Components will re-render every time your the props or state changes inside of the component.
I would recommend keeping the fetching logic inside of the Recipes component, because A: its recipe related data, not app related data. And B: this way you can control the state in Recipes instead of the props. This will give you more control on how the component behaves instead of being dependent on the parent component.
In the useEffect hook, leave the dependency array empty. This will cause the component to render, call useEffect only the first time, load your data and then render the recipes without re-rendering further.
import React, { useEffect, useState } from "react";
import Recipe from "../Recipe/Recipe";
import "./Recipes.css";
const Recipes = () => {
const [recipesList, setRecipesList] = useState([]);
useEffect(() => {
fetch("myapi.com/blablabla")
.then((res) => res.json())
.then((result) => {
setRecipesList(result.recipes);
});
return () => console.log("unmounting");
}, []);
// On the first render recipeComponents will be empty.
const recipeComponents = recipesList.map((item) => <Recipe className="recipe" info={item}/>)
return (
<div className="recipes">
{recipeComponents}
<h1>Hello</h1>
</div>
);
};
export default Recipes;
try this code :
function App() {
const [recipesList, setRecipesList] = useState([]);
let [scroll, setScroll] = useState(0)
const getListPropd = (e) => {
setRecipesList(e)
}
console.log(recipesList,"list");
return (
<div className="App">
<img className="logo" src={logo} alt="Logo"/>
<Recipes recipesList={(e) => getListPropd (e)} getRecipes={setRecipesList} />
</div>
);
}
export default App;
const [checkData , setCheckData ] = useState(true)
useEffect( () => {
if (checkData) {
fetch("myapi.com/blablabla")
.then(res => res.json())
.then(result => {
props.recipesList(result.recipes);
}
if(props.recipesList.length > 0) {
setCheckData(false)
}
)
else {
console.log("Do not fetch");
}
return () => console.log("unmounting");
}, [checkData])
the useEffect hook uses an empty dependency array, [] if it should ONLY run once after component is mounted. This is the equivalent of the old lifecycle method componentDidMount()
If you add a non-empty dependency array, then the component rerenders EVERY time this changes. In this case, every time your component receives new props (i.e. from a parent component, this triggers a reload.
see more info here https://reactjs.org/docs/hooks-effect.html , especially the yellow block at the bottom of the page
Happy coding!

Categories