This question already has answers here:
The useState set method is not reflecting a change immediately
(15 answers)
Closed 2 years ago.
I had read a lot of answers of questions about it, but it didn't helped me.
Functional component need once subscribe to events and get data from API to state.
But state don't changes correctly.
const [games, setGames] = useState(null);
const [activeGame, setActiveGame] = useState(null);
const [pending, setPending] = useState(false);
useEffect(() => {
if (games === null) {
setPending(true);
API.Game.get({}).then((res) => {
if (res.success) {
console.log(res.msg.games) // Returning array of games [{...}, {...}]
setGames(res.msg.games);
setActiveGame(res.msg.activeGame);
// Rendered games, but didn't changed state :/
setTimeout(() => console.log(games), 1000) // Returning null (initial state)
}
setPending(false);
});
API.Socket.emit('subscribe', 'game');
API.Socket.on('addGame', (game) => {
setGames((games) => [...games, game]);
});
API.Socket.on('open', (isReconnect) => {
if (isReconnect) API.Socket.emit('subscribe', 'game');
});
}
}, [games, pending, activeGame]);
Depending on answers you provided, I tried this, but it still don't work.
const [games, setGames] = useState([null]);
const [activeGame, setActiveGame] = useState(null);
const [pending, setPending] = useState(false);
const fetchGames = async () => {
setPending(true);
const res = await API.DurakGame.get({});
if (res.success) {
setGames(res.msg.games);
setActiveGame(res.msg.activeGame);
}
setPending(false);
return true;
};
useEffect(() => {
fetchGames();
API.Socket.emit('subscribe', 'durak');
API.Socket.on('addGame', (game) => {
setGames((games) => [...games, game]);
});
API.Socket.on('open', (isReconnect) => {
if (isReconnect) API.Socket.emit('subscribe', 'durak');
});
}, []);
Possibility that problem can be caused by :
Maybe API function that you is used is async ?
If yes, useEffect parameter callback is not async and can't be async maybe :D. Reference React Hooks.
Solution :
You can use sperate function API async and call on useEffect function.
Maybe you can refer on this link Example For useEffect async
The data state updated is Array types
State data type is immutable, that means you can't modify directly on the state (Different allocation memmory). And array type is one of data type that if the variable is assigmented on other can have different allocation. So you should be destruct the data for manipulating the data that can be same allocation.
React State with array
I hope that answered your question. Thanks before
Related
Below is my custom hook, I'm trying to handle everything from the hook. I have seen similar questions but none seems to work for my case and I have been made to believe there's a solution for this approach, jus can't figure it out.
const useResource = (baseUrl) => {
const [resources, setResources] = useState([]);
const create = async (resource) => {
const response = await axios.post(baseUrl, resource)
setResources(resources.concat(response.data));
console.log(resources)
return response.data
}
const get = async () => {
const response = await axios.get(baseUrl);
setResources(response.data)
return response.data
}
const service = {
create,
get
}
return [
resources, service
]
}
Here is my approach to use the custom hook, but request keeps looping nonstop, please how do I stop it running after every render?
const App = () => {
const content = useField('text');
const name = useField('text');
const number = useField('text');
const [notes, noteService] = useResource('http://localhost:3005/notes');
const [persons, personService] = useResource('http://localhost:3005/persons');
useEffect(() => {
noteService.get();
}, [noteService])
useEffect(() => {
personService.get();
}, [personService])
const handleNoteSubmit = (event) => {
event.preventDefault();
noteService.create({ content: content.value });
}
const handlePersonSubmit = (event) => {
event.preventDefault();
personService.create({ name: name.value, number: number.value});
}
Edit: I just had to disable ESLint for the dependency line, because I just need it to run once after every render. Works well!
useEffect(() => {
noteService.get();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
useEffect(() => {
personService.get();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
As correctly pointed out in comments, each time the component renders and calls your useResource hook, a new service object is created. If this service object is used as a dependency for any other hooks this will trigger their callbacks.
The solution is to memoize the service object so it's being provided as a stable reference. This can be accomplished via the useMemo hook. Because service will be memoized, the create callback will also be memoized and contain stale resources state. To address this update create to use a functional state update when appending new response data to the existing state.
Example:
import { useEffect, useMemo, useState } from 'react';
const useResource = (baseUrl) => {
const [resources, setResources] = useState([]);
const create = async (resource) => {
const response = await axios.post(baseUrl, resource);
// Functional update to update from previous state
setResources(resources => resources.concat(response.data));
return response.data;
}
const get = async () => {
const response = await axios.get(baseUrl);
setResources(response.data);
return response.data;
}
const service = useMemo(() => ({
create,
get
}), []);
return [resources, service];
};
This question already has answers here:
How to use `setState` callback on react hooks
(22 answers)
How to use callback with useState hook in react [duplicate]
(8 answers)
Closed 1 year ago.
In getData() function, I'm console.logging data to see if it set in the state properly after an asynchronous call. It's returning Array[] which I'm guessing means that the data set but console.log is running before the fetch finishes.
The console.log in the useEffect works properly although it will log it twice.
Is there a way to console.log inside of getData() function or is it the proper way to do it in the useEffect?
useEffect console.log runs twice because I'm guessing once after the data is retrieved and set into state, and then after it's set, it console logs it again after the re-render.
const TestComponent = () => {
// State for Data
const [data, setData] = useState([])
// URL for Data
const url = 'https://randomuser.me/api/?results=20'
// Retrieve Data - Function
const getData = async() => {
const { results } = await (await fetch(url)).json()
setData(results)
console.log(data)
}
useEffect(() => {
getData()
console.log(data)
},[])
return (JSX)
Run an effect whenever state data changes
useEffect(() => {
if(data.length) {
console.log(data)
}
}, [data])
const getData = async() => {
const { results } = await (await fetch(url)).json()
setData(results)
console.log(data)
}
useEffect(() => {
getData()
console.log(data)
},[])
useEffect only execute once, you are seeing console.log twice simply because one in getData while another in useEffect
This question already has answers here:
Is there a synchronous alternative of setState() in Reactjs
(13 answers)
Closed 1 year ago.
I'm using React and Firestore.
I want to get the value recorded in Firestore and setState to the retrieved value.
The current code is as follows.
const [value, setValue] = useState("");
useEffect(() => {
db.collection("value")
.doc(uid)
.collection("subvalue")
.onSnapshot(async (snapshot) => {
snapshot.forEach((doc) => {
const rawValue = doc.data().raw_value;
setValue(rawValue);
console.log(value);
});
});
}, []);
return <p>{value}</p>
In this code, value is empty.
Checking the console, it is also empty. (It doesn't show up as null or undefind, just empty.
console
However, when I ran console.log(rawValue) in forEach, the value was displayed in the console.
useEffect(() => {
db.collection("value")
.doc(uid)
.collection("subvalue")
.onSnapshot(async (snapshot) => {
snapshot.forEach((doc) => {
const rawValue = doc.data().raw_value;
console.log(rawValue);
setValue(rawValue);
});
});
}, []);
console
foo
This shows that the value is not empty, but exists, and that we are setState the value that exists.
I feel like I'm making some elementary mistakes, but I think what I'm doing is simple.
Get the value ← Yes.
value is not empty ← That's right.
Set the value ← Not done.
What's going on in the last step?
state updation in React is async. So setting it inside a for Each won't work as expected
useEffect(() => {
db.collection("value")
.doc(uid)
.collection("subvalue")
.onSnapshot(async (snapshot) => {
const finalSnapShot = [];
snapshot.forEach((doc) => {
const rawValue = doc.data().raw_value;
finalSnapShot.push(rawValue);
console.log(rawValue)
});
setValue(finalSnapShot.join(',')); // would set a string with comma seperated value. You can update the way you want to set data to state
});
}, []);
I invoke a GET API using axios in React. Backend (Python-Flask) is returning data by writing return jsonify(animals=animals, cars=cars) and these (cars and animals) are arrays.
The response has data in the following format:
{
"animals": ["elephant", "lion", "dog"],
"cars": ["range rover", "fortuner", "land cruiser"]
}
I want to update the cars state using the cars array and the animals state using the animals array. I tried the following but only animals state is updating, cars state is remaining empty
App
export default function App() {
const [animals, setAnimals] = useState([])
const [cars, setCars] = useState([])
useEffect(()=> {
axios.get("http://127.0.0.1:5000/animals_cars").then(
response=> setAnimals(response.data.animals),
resp => setCars(resp.data.cars)
);
console.log(cars)
console.log(animals)
}, []);
}
Any help on the responses, that is where I have no idea how can I split the response to update different states.
.then() takes up to two arguments; callback functions for the success and failure cases of the Promise. In this case, your second callback (resp => setCars(resp.data.cars)) is only called if the Promise is rejected.
You want to use the first callback which is called if the Promise is fulfilled. You can update two state variables in one single function like this:
export default function App() {
const [animals, setAnimals] = useState([]);
const [cars, setCars] = useState([]);
useEffect(() => {
axios.get('http://127.0.0.1:5000/animals_cars')
.then((response) => {
setCars(response.data.animals);
setAnimals(response.data.cars);
});
// state updates are asynchronous
// state is not updated yet, you can't log these
// well, you can but chances are it's the default value []
console.log(cars);
console.log(animals);
}, []);
}
Please note though that useState is asynchronous. You can't update the state on one line and assume it's already changed on the next one. You'll likely log the unchanged state.
You need an async function when you are loading data from an API to wait for the completion of the function. To do that in a useEffect you need to define a new function and execute it only when a condition is met. In this case I create a new boolean constant loading, I check against it's value and if it's true I execute the load function. Upon completion of the function I set loading to false and that would prevent the load function from executing again. This has the advantage that if you want to fetch your data again all you have to do is to set loading to true.
Another way to do that is to define a memoised load function with useCallback outside of the useEffect but I don't want to complicate things for you.
A clean way to write that is the following.
export default function App() {
const [loading, setLoading] = useState(true);
const [animals, setAnimals] = useState([])
const [cars, setCars] = useState([])
useEffect(()=> {
const load = async () => {
const responseData = await axios.get("http://127.0.0.1:5000/animals_cars")
.then((response) => {
return response.data;
})
.catch((error) => {
console.log(error);
});
setAnimals(responseData.animals);
setCars(responseData.cars);
setLoading(false);
}
if (loading) {
load();
}
},[loading]);
}
You Can hadle that with function like this
useEffect(() => {
axios.get("http://127.0.0.1:5000/animals_cars")
.then(function (response) {
// handle success
setAnimals(response.data.animals);
setCars(response.data.cars);
});
console.log(cars);
console.log(animals);
}, []);
console.log(data) gives me an object with the correct data but, I set rates to that data but when console logging rates I get an empty object {}. Thanks in advance.
const [rates, setRates] = useState({});
useEffect(() => {
search();
}, []);
const search = async () => {
const response = await axios.get('https://api.exchangeratesapi.io/latest');
const data = response.data.rates;
console.log(data);
setRates(data);
console.log(rates);
};
As someone said in the comment, state updates will be reflected in the next render. Also, there are some problems with your code I'll address.
const [rates, setRates] = useState({});
useEffect(() => {
// move this in here unless you plan to call it elsewhere
const search = async () => {
const response = await axios.get('https://api.exchangeratesapi.io/latest');
const data = response.data.rates;
setRates(data);
};
search();
}, [/* no dependencies means it runs once */]);
If you do plan on calling search elsewhere, wrap it in a useCallback hook so you can set it as a dependency of your useEffect (you'll get a lint warning without). useCallback with an empty dependency array will always return the same function reference, so your useEffect will only ever run the one time like it does now.
If you leave search as a normal function in the component, the reference changes each render, so your effect would run every render if you included it as a dependency.
const [rates, setRates] = useState({});
const searchCallback = useCallback(async () => {
const response = await axios.get('https://api.exchangeratesapi.io/latest');
const data = response.data.rates;
setRates(data);
}, [])
useEffect(() => {
// move this in here unless you plan to call it elsewhere
search();
}, [searchCallback]);