I onpress method triggered the pressed useEffect api using promise in useEffect and according to incoming data set myDataStt and assign data to getData function I want to take action accordingly but I logged to getData func. triggered useEffect myDataStt wont change, press the buton second time this time succes myDataStt is change but first time press the button wont work (use effect triggered and value is correct) I dont understand why, thanks for comments
const [pressed, setPressed] = useState(false);
const [myDataStt, setMyData] = useState(announcements);
useEffect(() => {
CallApi.then(
values => {
setMyData(values);
const data = getData();
}),
}, [pressed]);
const getData = () => {
return myDataStt.dataFirst || [];
};
<Button
onPress={() => {
setPressed(true);
}}>
You shouldn't use useEffect in manner. Have your button actually perform the API call. If you want the API to load data on the component mount, then have it called in useEffect and on click. There is no need for a pressed state. You can assign your function to a callback, so it doesn't trigger an infinite loop.
const getData = useCallback(async () => {
const values = await CallApi();
setMyData(values);
}, [setMyData]);
useEffect(()=> {
getData();
}, [getData])
<Button onClick={() => getData()}>Refresh API</Button>
Look if this complete component works for you.
import React, { useState, useEffect } from 'react';
const CallAPI = () => {
//do your request/fetch
};
export const Component = () => {
const [data, setData] = useState([]);
useEffect(() => {
const initialCall = async () => {
const values = await CallAPI();
setData(values);
};
initialCall();
return () => {
//clean your fetch
};
}, []);
const handleClick = async () => {
const values = await CallAPI();
setData(values);
};
return (
<>
<button onClick={handleClick}>Add</button>
{data.map(item => {
return <div>{item}</div>;
})}
</>
);
};
2 remarks, you dont need 2 states for call your api, try to use onClick in buttons.
Related
Below code i try to make get response from api and put in Movie Component. but the problem is that api hit again and again i don't why this happened.here screen shot of api call
const [loading, setloading] = useState(false);
const [movielist, setmovielist] = useState([]);
const [err, seterr] = useState("");
const fetchMoviesHandler = useCallback(async () => {
setloading(true);
try {
const reponse = await fetch("https://swapi.dev/api/films");
if (reponse.status != 200) {
seterr("something is wrong");
}
const data = await reponse.json();
const result = data.results;
setmovielist(result);
} catch (errr) {
}
});
useEffect(() => {
fetchMoviesHandler();
}, [fetchMoviesHandler]);
return (
<div>
{movielist.map((movie) => {
return <Movie key={movie.episode_id} title={movie.title} />;
})}
</div>
);
};
This is returning a new instance of the function on every render:
const fetchMoviesHandler = useCallback(async () => {
// your function
});
Which will trigger the useEffect on every render, since this function is in its dependency array.
To tell useCallback to memoize the function and keep the same instance across multiple renders, it also needs a dependency array. For example:
const fetchMoviesHandler = useCallback(async () => {
// your function
}, [setloading, setmovielist, seterr]);
Or, at the very least, an empty dependency array:
const fetchMoviesHandler = useCallback(async () => {
// your function
}, []);
Which would essentially create one instance of the function and always use it for the life of the component.
I'm still beginner to ReactJS and I'm having trouble rendering a list.
I don't know why, all the time calls are being made to my API. Since I don't put any dependency on useEffect, that is, I should only render my function once.
I don't understand why this is happening. Can you tell me what I'm doing wrong?
Here's my code I put into codesandbox.io
import React from "react";
import axios from "axios";
import "./styles.css";
const App = () => {
const BASE_URL = "https://pokeapi.co/api/v2";
const [pokemons, setPokemons] = React.useState([]);
const getAllPokemons = async () => {
const { data } = await axios.get(`${BASE_URL}/pokemon`);
data.results.map((pokemon) => getPokeType(pokemon));
};
const getPokeType = async (pokemon) => {
const { data } = await axios.get(pokemon.url);
setPokemons((prev) => [...prev, data]);
};
React.useEffect(() => {
getAllPokemons();
}, []);
console.log(pokemons);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
{pokemons.map((pokemon) => (
<p key={pokemon.id} style={{ color: "blue" }}>
{pokemon.name}
</p>
))}
</div>
);
};
export default App;
Thank you very much in advance.
Your issue is that you are calling setPokemons inside getPokeType (which is called for each data in part). Your useEffect is called just once (as expected) and the ${BASE_URL}/pokemon call is executed just once too. But getPokeType is called 20 times and the pokemons state is changed 20 times as well (once for each instance from data.results).
What I would recommend in your case (instead of what you have now) is:
Create a list of all the pokemons and
Set the state just once at the end.
So something like:
...
const getPokeType = async (pokemon) => {
const { data } = await axios.get(pokemon.url);
return data;
};
const getAllPokemons = async () => {
const { data } = await axios.get(`${BASE_URL}/pokemon`);
const pokemons = await Promise.all(
data.results.map((pokemon) => getPokeType(pokemon))
);
setPokemons(pokemons);
};
React.useEffect(() => {
getAllPokemons();
}, []);
...
I was just having the same issue in my project the way I solved is by moving the function definition inside the useEffect
React.useEffect(() => {
const getAllPokemons = async () => {
const { data } = await axios.get(`${BASE_URL}/pokemon`);
data.results.map((pokemon) => getPokeType(pokemon));
};
getAllPokemons();
}, []);
If this solves your problem please accept the answer.
From what I understand, async code inside useEffect runs without blocking the rendering process. So if I have a component like this:
const App = () => {
useEffect(() => {
const log = () => console.log("window loaded");
window.addEventListener("load", log);
return () => {
window.removeEventListener("load", log);
};
}, []);
useEffect(() => {
const getData = async () => {
console.log("begin");
const response = await fetch(
"https://jsonplaceholder.typicode.com/todos/1"
);
const data = await response.json();
console.log("end");
};
getData();
}, []);
return null;
};
The console output is (in order):
begin
window loaded
end
However if I use ES2020 dynamic import inside the useEffect:
const App = () => {
useEffect(() => {
const log = () => console.log("window loaded");
window.addEventListener("load", log);
return () => {
window.removeEventListener("load", log);
};
}, []);
useEffect(() => {
const getData = async () => {
console.log("begin");
const data = await import("./data.json");
console.log("end");
};
getData();
}, []);
return null;
};
The output is (in order):
begin
end
window loaded
This is a problem because when data.json is very large, the browser hangs while importing the large file before React renders anything.
All the necessary and useful information is in Terry's comments. here is the implementation of what you want according to the comments:
First goal:
I would like to import the data after window has loaded for SEO reasons.
Second goal:
In my case the file I'm trying to dynamically import is actually a function that requires a large dataset. And I want to run this function whenever some state has changed so that's why I put it in a useEffect hook.
Let's do it
You can create a function to get the data as you created it as getData with useCallback hook to use it anywhere without issue.
import React, {useEffect, useState, useCallback} from 'react';
function App() {
const [data, setData] = useState({});
const [counter, setCounter] = useState(0);
const getData = useCallback(async () => {
try {
const result = await import('./data.json');
setData(result);
} catch (error) {
// do a proper action for failure case
console.log(error);
}
}, []);
useEffect(() => {
window.addEventListener('load', () => {
getData().then(() => console.log('data loaded successfully'));
});
return () => {
window.removeEventListener('load', () => {
console.log('page unmounted');
});
};
}, [getData]);
useEffect(() => {
if (counter) {
getData().then(() => console.log('re load data after the counter state get change'));
}
}, [getData, counter]);
return (
<div>
<button onClick={() => setCounter((prevState) => prevState + 1)}>Increase Counter</button>
</div>
);
}
export default App;
Explanation:
With component did mount, the event listener will load the data.json on 'load' event. So far, the first goal has been met.
I use a sample and simple counter to demonstrate change in the state and reload data.json scenario. Now, with every change on counter value, the getData function will call because the second useEffect has the counter in its array of dependencies. Now the second goal has been met.
Note: The if block in the second useEffect prevent the getData calling after the component did mount and first invoking of useEffect
Note: Don't forget to use the catch block for failure cases.
Note: I set the result of getData to the data state, you might do a different approach for this result, but the other logics are the same.
I'm exploring hooks with react-redux-firebase but my "setDataProducts" is behaving oddly.
I'm using useEffect() like I could use componentDidMount() but not sure if this is the right way.
export default function ProductList() {
const [dataProducts, setDataProducts] = useState([]);
const firestore = useFirestore();
const fetchProducts = async () => {
const response = firestore.collection("products");
const data = await response.get();
data.docs.forEach((product) => {
setDataProducts([...dataProducts, product.data()]);
console.log(product.data());
});
};
useEffect(() => {
fetchProducts();
}, []);
return (
<div>
{isLoaded &&
dataProducts.map((product) => {
return (
<div>
<h4>{product.title}</h4>
<h3>{product.price}</h3>
</div>
);
})}
</div>
);
}
I cannot render the both products I have in Firestore. Only One is rendering... So I dont understand. Should not it rerender when state is updated ?
Thanks for reply
We can see there was not rerendering
I think it is because you called setDataProducts again before dataProducts updated.
Please replace fetchProducts method with my code following:
const fetchProducts = async () => {
const response = firestore.collection("products");
const data = await response.get();
const newProducts = data.docs.map((product) => product.data());
console.log(newProducts);
setDataProducts([...dataProducts, ...newProducts]);
};
I have a function that gets some data from my backend and then I want simply to assign it to the state and display it in my browser. Everything works correctly, but I don't know why when I run a request the function keeps calling the API without stopping. What is the reason for this?
It seems that the function is stuck in some kind of while-true loop.
function App() {
const [orders, setOrders] = useState();
const getOrders = async () => {
const response = await axios.get("/api/orders/");
setOrders(response);
console.log(response);
};
getOrders();
return <div className="App">{JSON.stringify(orders)}</div>;
}
export default App;
What is the reason for this?
This happens because you are calling a function every render inside the functional component.
const getOrders = async () => {
const response = await axios.get("/api/orders/");
setOrders(response); // this will re render the component
console.log(response);
};
getOrders(); // this will be called every render and cause the infinity loop
When you render the component, you call getOrders and this functions calls setOrders wich will rerender the component, causing a infinity loop.
First render => call getOrders => call setOrders => Rerender =>
Second render => call getOrders => call setOrders => Rerender =>
...
You need to use useEffect hook or call the function on some event (maybe button click)
e.g. using useEffect
function App() {
const [orders, setOrders] = useState(null);
useEffect(() => {
const getOrders = async () => {
const response = await axios.get("/api/orders/");
setOrders(response);
console.log(response);
};
getOrders();
}, []);
return <div className="App">{JSON.stringify(orders)}</div>;
}
You want to use the Effect hook React offers:
https://reactjs.org/docs/hooks-effect.html
function App() {
const [orders, setOrders] = useState();
const getOrders = async () => {
const response = await axios.get("/api/orders/");
setOrders(response);
console.log(response);
};
useEffect(() => {
getOrders();
}, []); //Empty array = no dependencies = acts like componentDidMount / will only run once
return <div className="App">{JSON.stringify(orders)}</div>;
}
export default App;
I think you are using react-hooks here, so your getOrders function is getting render all the time.
Use useEffect from react to avoid this.
function App() {
const [orders, setOrders] = useState();
useEffect(() => {
const getOrders = async () => {
const response = await axios.get("/api/orders/");
setOrders(response);
console.log(response);
};
getOrders();
}, [])
return <div className="App">{JSON.stringify(orders)}</div>;
}
export default App;