change in state does not re-renders the component - reactjs

const [list, setList] = useState([]);
useEffect(() => {
const getData = async () => {
let storeList = [];
for (let i = 0; i<50; i++) {
try {
const token = await contract.methods.tokenOfOwnerByIndex(accountId , i).call();
let data = await contract.methods.tokenURI(token).call();
const receivedData = await fetch(data);
let jsonData = await receivedData.json();
storeList.push(jsonData);
setList(storeList);
} catch(error) {
cogoToast.error(`Error caused in fetching data of ${i} number token`);
}
}
};
if (contract) getData();
}, [contract]);
the initial state of "list" is empty. When the loop is run for 1st time and the list is updated for the first time, the component re-renders, but after that, even though the state is changing the component does not re-render.
Ideally, the component should re-render when the state changes.

You're mutating the array that already exists, not creating a new one. When you set state in a function component, react does a === between the old state and the new state. If they're the same, it skips rendering.
To fix this, create a new array and set state with that:
let jsonData = await receivedData.json();
setList(prev => [...prev, jsonData]);

Related

React state is never up to date in my callback

In a react functional component, I setState from an api call. I then create an EventSource that will update the state each time an event is received.
The problem is that in the EventSource callback, the state is not updated.
My guess is, state is printed at the creation of the callback, so it can't be updated.
Is there any other way to do it ?
function HomePage() {
const [rooms, setRooms] = useState<Room[]>([]);
const getRooms = async () => {
const data = await RoomService.getAll(currentPage);
setRooms(data.rooms);
}
const updateRooms = (data: AddRoomPayload | DeleteRoomPayload) => {
console.log(rooms); // rooms is always empty
// will append or remove a room via setRooms but i need actual rooms first
}
useEffect(() => {
// setState from api response
getRooms();
// set up EventSource
const url = new URL(process.env.REACT_APP_SSE_BASE_URL ?? '');
url.searchParams.append('topic', '/room');
const eventSource = new EventSource(url);
eventSource.onmessage = e => updateRooms(JSON.parse(e.data));
}, [])
...
}
Try using a functional update when using setRooms like this:
const updateRooms = (data: AddRoomPayload | DeleteRoomPayload) => {
setRooms((rooms) => {
if (data.type === 'add') {
return [...rooms, data.room];
} else if (data.type === 'remove') {
return rooms.filter(/* ... */);
}
});
}
Here is a reference to the React Docs on functional updates in useState: https://reactjs.org/docs/hooks-reference.html#functional-updates
If that doesn't work then try checking the React Developer Tools to make sure that the component's state rooms is being updated.

React component does't display options after setting state, firestore returning empty array with object in it? I'm confused

Have this problem going on where I fetch data from firestore, then setState with that data, for some reason my component won't re render to then display that data. It'll just be blank. fetchData is my firebase function, it returns an empty array which my console is showing as empty, but there's objects in there which is confusing.
photo of empty array with objects from firestore
const dataFetching = (data) => {
const x = fetchData(data);
setData(x);
};
// fetchData firestore function
export const fetchData = (applicants) => {
const applicantData = [];
for (let i = 0; i < applicants.length; i++) {
const x = firestore.collection("users").doc(applicants[i]).get();
x.then((user) => applicantData.push(user.data()));
}
return applicantData;
};
firestore.collection("users").doc(applicants[i]).get() returns a promise that doesn't get awaited. The for loop therefore finishes iterating before the promises get resolved. At the time of the return applicantData; the array is still empty so setData is receiving an empty array.
I would change the fetchData function to something like this:
export const fetchData = async (applicants) => {
const applicantData = [];
for (let i = 0; i < applicants.length; i++) {
const user = await firestore.collection("users").doc(applicants[i]).get();
applicantData.push(user.data())
}
return applicantData;
};
And the dataFetching function to something like this:
const dataFetching = async (data) => {
const x = await fetchData(data);
setData(x);
};

Why fetched data do not populate the state after i set them?

Data fetched successfully but my state does not change.
I initialize the state categories as an empty [] then I want to set it to the data I fetched. Response is OK and I can see the data in the console.
It is an object with one pair: categories:Array(14) which is what I am looking for.
const [categories,setCategories] = useState([])
useEffect(() => {
const fetchMeals = async () => {
const res = await fetch(
"https://www.themealdb.com/api/json/v1/1/categories.php"
);
const data = await res.json();
console.log(data.categories); /*array (14)*/
setCategories(data.categories.map((cat) => cat.strCategory));
console.log(categories); /*empty array*/
};
fetchMeals();
},[]);
Why doesn't work?
setState is async like operation so you would never see updated state synchronously (after invoking setState).
You can do alternatively:
const updatedCategiories = data.categories.map((cat) => cat.strCategory);
setCategories(updatedCategiories);
console.log(updatedCategiories);

How to save data from axios.get to hooks right away

How will I able to save in hooks that data from database. Since I need to display the data that I get to dropdown.
Here's my code
const [dataSystem, setdataSystem] = useState([])
const getAllSystems = async() => {
......
}
const getDependentSystems = async() => {
const response = await axios.get('/API' + ID)
console.log('LIST OF SYSTEM', response.data)
setdataSystem(response.data)
}
Since upon setState, data is not yet saved to dataSystem I need to trigger getDeoendetSystems() twice to display the list on my dropdown.
Result of console.log
LIST OF SYSTEM [{...},{...}]
0: {ID: 1, SYSTEMID: 12 ...},
1: {ID: 2, SYSTEMID: 13 ...}
Thank you
You need to load the data inside an useEffect like
function Component() {
const [dataSystem, setdataSystem] = useState([])
useEffect(() => {
getDependentSystems()
}, [])
const getDependentSystems = async() => {
const response = await axios.get('/API' + ID)
console.log('LIST OF SYSTEM', response.data)
setdataSystem(response.data)
}
return ...
}
Basically you want to call the function in the useEffect so you only call it once; cause if you call it in the component context, everytime the state updates it will call the api again which will trigger an infinite loop.

Why do I need to put the function inside a setState method for it to work?

When a socket emits an event from the server side the App reloads for some reason and posts is emptied out. But when I define the function inside the setPosts it works perfectly. Why is this?
const App = () => {
let [user, setUser] = useState(null)
let [posts, setPosts] = useState({})
console.log('app')
useEffect(() => {
console.log('use effect')
socket.on('post', (post) => {
// THIS DOES NOT WORK:
// let newPosts = { ...posts }
// newPosts[post._id] = post
// setPosts(newPosts)
//THIS WORKS
setPosts((posts) => {
let newPosts = { ...posts }
newPosts[post._id] = post
return newPosts
})
})
async function getUser() {
let user = await actions.getUser()
if (user) {
setUser(user?.data)
}
}
getUser()
actions
.getAllPosts()
.then((res) => {
console.log('WE GOT ALL POSTSTFOM API', res.data)
const postsById = {}
for (let post of res.data) {
postsById[post._id] = post
}
console.log('wired')
setPosts(postsById)
//filterPosts(res.data)
})
.catch((err) => console.error(err))
return () => {
socket.off('post')
}
}, [])
This is how enclosures work in javascript. When you use a non-functional state update you are referencing the posts state value ({}) from the render cycle the callback was instantiated in, i.e. the initial render when the effect callback ran when mounted (note the empty dependency array). It's a stale enclosure of the posts state value.
When using a functional state update you are accessing and updating from the previous state, not the state from the previous render cycle (or enclosure).

Resources