useState not updating for some reason? - reactjs

when I try to get some data from my backend API using axios, and set the state after I've gotten the result for some reason the state is not updated and when I try to use the state it will only show me an empty array. but what's so interesting is that when I console.log(res.data) it will show me my array of lists with no problem, so I guess the problem is with the setCategories() state function. What am I doing wrong?
const Home = (props) => {
const [categories, setCategories] = useState([]);
useEffect(() => {
getCats();
}, []);
const getCats = async () => {
const data = await axios.get(`${myUrl}/allItems`, {
withCredentials: true,
});
const cats = await data.data;
console.log(cats); //this one works perfectly
setCategories(cats);
console.log(categories) //this one doesn'nt work which means the setState didn't work
};
return (
<>
<div className="card-div mt-5">
{categories.map((cat) => {
<li>{cat.name}</li>;
})}
</div>
</>
);
};

the state is set asynchronously, so the data is not updated instantly. that's why you are not getting the output on console.log(categories) right after setCategories(cats);
here is a small example of asynchronous behaviour of useState state update:
Link to working example: stackblitz
import React, { useEffect, useState } from "react";
import "./style.css";
import axios from "axios";
const url = "https://jsonplaceholder.typicode.com/users";
export default function App() {
const [users, setUsers] = useState([]);
useEffect(() => {
axios.get(url).then(result => {
console.log("1. when data is fetched sucessfully: ", result.data);
setUsers(result.data);
console.log("2. Just after setting state: ", users);
});
}, []);
// secons useEffect for logging out upadated todos useState
useEffect(() => {
console.log("todos upadated: ", users);
}, [users]);
return (
<div>
<h1>Hello StackBlitz!</h1>
<p>Start editing to see some magic happen :)</p>
{users.map(user => (
<p>{user.name}</p>
))}
</div>
);
}
Here is what is happening in the above example:
You can see the flow of data fetching and async update of state.

The useState function is asynchronous, so you will never get the new state in the same function, the best way is to use it in another function or useEffect.
Example:
useEffect(() => {
console.log(categories);
}, [categories]);

Related

React hook "useMemo" with array as dependency

I am new to react (that I use with typeScript) and I am facing an issue with the use of the useMemo hook.
Here is my fetching service:
export default class FetchingService {
datas: Data[] = [];
constructor() {
this.fetch();
}
async fetch(): Promise<Data[]> {
const d = // await an async array from an api, using Array.flat()
this.datas = d;
console.log(this.datas);
return d;
}
}
In a component, I try to watch for change of the datas attribute of my service:
import fetchingService from '../services/fetchingService.ts';
const Home: React.FC = () => {
const ds: Data[];
const [datas, setDatas] = useState(ds);
const fetchDatas = useMemo(() => {
console.log('Render datas', fetchingService.datas?.length)
setDatas(fetchingService.datas);
return fetchingService.datas;
}, [fetchingService.datas]);
return (
<ul>{datas.map(d => {
return (
<li key={d.id}>{d.id}</li>
);
</ul>
);
}
The problem I am facing is that the useMemo hook is not recompouted when the datas attribute changes within my fetchService. I am pretty sure that my FetchingService.fetch() function works because the console.log within the fetch function always display the fetched datas.
The observed behavior is that sometimes datas are well rendered (when fetch ends before rendering ...), but sometimes it isn't.
The expected one is that datas are rendered every time and only on refresh, exept when datas are modified
I also tried to put the length of the data array as a dependency in useMemo, but in both cases it doesn't work and I have a warning in my IDE, telling me it is an unnecessary dependency.
I don't really understand if it is a typescript or a specific react behavior issue. I think the reference of the datas attribute should change at the end of the fetch (or at least its length attribute ...), but tell me if I am wrong.
I do appreciate every help !
in fetchingService, when datas change, probably the dependency cannot be accepted. You can use a custom hook in stead of it.
You can use this source about useMemo: useMemo with an array dependency?
import { useState, useLayoutEffect, useCallback } from "react";
export const useFetchingService = () => {
const [fetchedData, setFetchedData] = useState([]);
const fetch = useCallback(async () => {
const d = await new Promise((res, rej) => {
setTimeout(() => {
res([1, 2, 3]);
}, 5000);
}); // await an async array from an api, using Array.flat()
setFetchedData(d);
}, []);
useLayoutEffect(() => {
fetch();
}, []);
return [fetchedData];
};
useLayoutEffect runs before rendering
using:
const [fetchData] = useFetchingService();
const fetchDatas = useMemo(async () => {
console.log("Render datas", fetchData.length);
setDatas(fetchData);
return fetchData;
}, [fetchData]);
You can also use this directly without 'datas' state.
I hope that this will be solution for you.
So I put together a codesandbox project that uses a context to store the value:
App.tsx
import React, { useState, useEffect, createContext } from "react";
import Home from "./Home";
export const DataContext = createContext({});
export default function App(props) {
const [data, setData] = useState([]);
useEffect(() => {
const get = async () => {
const d = await fetch("https://dummyjson.com/products");
const json = await d.json();
const products = json.products;
console.log(data.slice(0, 3));
setData(products);
return products;
};
get();
}, []);
return (
<div>
Some stuff here
<DataContext.Provider value={{ data, setData }}>
<Home />
</DataContext.Provider>
</div>
);
}
Home.tsx
import React, { FC, useMemo, useState, useEffect, useContext } from "react";
import { DataContext } from "./App";
import { Data, ContextDataType } from "./types";
const Home: FC = () => {
const { data, setData }: ContextDataType = useContext(DataContext);
return (
<>
<ul>
{data.map((d) => {
return (
<li key={d.id}>
{d.title}
<img
src={d.images[0]}
width="100"
height="100"
alt={d.description}
/>
</li>
);
})}
</ul>
</>
);
};
export default Home;
This was my first time using both codesandbox and typescript so I apologize for any mistakes

React fetch stops working after page refresh

I am fetching a single object. When the page loads for the first time the fetched elements does render to the DOM. When i refresh the page the fetch does not work anymore and i get an error - Uncaught TypeError: Cannot read properties of undefined (reading 'name')
import { useState, useEffect } from "react";
import axios from "axios";
function DataFetching() {
const [products, setProducts] = useState([]);
useEffect(() => {
const loadProducts = async () => {
const response = await axios.get("https://CENSORED/");
setProducts(response.data);
};
loadProducts();
}, []);
return (
<>
<div className="App">
<p>{products.product.name}</p>
</div>
</>
);
}
Thats because for first time render there is no product yet , and after fetching and rerender component it is available , so in first render you have no access to object and it throw error
you can do {products?.product?.name}
Or You can also write your code like
import { useState, useEffect } from "react";
import axios from "axios";
function DataFetching() {
const [products, setProducts] = useState(null);
useEffect(() => {
const loadProducts = async () => {
const response = await axios.get("https://CENSORED/");
setProducts(response.data);
};
loadProducts();
}, []);
if(!products) return <div>loading...</div>
return (
<>
<div className="App">
<p>{products.product.name}</p>
</div>
</>
);
}
It's because the 'products' state have no children yet, so the 'name' is undefined.
A simple solution is to check if the 'products' state have 'product' child inside before rendering.
Like this :
<p>{products.product ? products.product.name : ''}</p>

How to synchronously get data using Axios?

I'm trying to get data from Axios but while the data is being fetch the return statement gets executed, I think.
import axios from 'axios'
import React, { useState, useEffect} from 'react'
export default function Overall() {
const [post, setPost] = useState({
date:null,
total:null
});
useEffect(() => {
async function fetchData(){
await axios.get(`https://data.covid19india.org/v4/min/data.min.json`).then((response) => {
console.log("Inside Then block");
setPost({'total':Object.entries(response.data).map(([k,v])=>
// console.log(k,v.meta.last_updated,v.total.confirmed)
v.total.confirmed
).reduce((prev, curr)=>prev+curr,0)
})
setPost({'date':Object.entries(response.data)[0][1].meta.last_updated})
});
}
fetchData()
console.log("Data Fetched");
}, []);
console.log("Post: ",post);
return (
<div className='overall-container'>
<div>
<h2>India</h2>
</div>
<div>
<h2>Total: {post.total}</h2>
<h2>Date: {post.date}</h2>
</div>
</div>
)
}
The problem is that in return only {post.date} is working while {post.curr} is not. I don't know why? I'm new to react so I don't know much. I would appreciate if someone could explain this.
I'm assuming that you meant {post.total} instead of {post.curr}
You could change from:
setPost({'total':Object.entries(response.data).map(([k,v])=>
// console.log(k,v.meta.last_updated,v.total.confirmed)
v.total.confirmed
).reduce((prev, curr)=>prev+curr,0)
})
setPost({'date':Object.entries(response.data)[0][1].meta.last_updated})
To:
setPost({
total: Object.entries(response.data).map(([k,v])=>
// console.log(k,v.meta.last_updated,v.total.confirmed)
v.total.confirmed
).reduce((prev, curr)=>prev+curr,0),
date: Object.entries(response.data)[0][1].meta.last_updated
});
And it should work. However, maybe it'd be better to seperate date and total into two different states?
This is a bad practice to do async stuff in useEffect. You should create a separate function and call it from useEffect.
Your error is caused because you call setPost to update total and then once again to update date.
When you call setPost it will erase previous value for post. If you need to keep previous value you can do:
setPost((prev) => ({
...prev,
date: new Date()
}));
In your code you should call setPost only once
import React, { useState, useEffect} from 'react'
import axios from 'axios'
export default function Overall() {
const [post, setPost] = useState({});
const fetchData = async () => {
try {
const response = await axios.get(`https://data.covid19india.org/v4/min/data.min.json`)
// tranform your response and then set it to state at once
const total = 1 // replace with your total
const date = new Date() // replace with your date
setPost({ total, date })
} catch(err) {
console.log(err);
}
}
useEffect(() => {
fetchData()
}, []);
console.log("Post: ",post);
return (
<div className='overall-container'>
<div>
<h2>India</h2>
</div>
<div>
<h2>Total: {post?.total}</h2>
<h2>Date: {post?.date}</h2>
</div>
</div>
)
}

please explain the error to me, Error: Rendered more hooks than during the previous render

Iam newbie and now learning to make customize react hooks
here i am trying to call the function i made in app.js file, i want to use it onClick button. but fail to do so. please help me to find the error and understand it.
import React, {
useEffect,
useState
} from "react";
const useRandomJoke = () => {
const [jokes, setJokes] = useState();
useEffect(() => {
const jokeFetch = async() => {
await fetch("https://api.icndb.com/jokes/random")
//we'll run 2 "then"
.then(
// this will give us response and will return inform of res.json
(res) => res.json()
) //.json is a format
.then((data) => {
setJokes(data.value.joke);
}); // now calling data from te returned values in res.json
};
jokeFetch();
}, []);
return jokes;
};
export default useRandomJoke;
//With onClick function
function App() { const [jokes, setJokes] = useState();
return (
<div className="App">
<h1>Random Jokes</h1>
<p>{jokes}</p>
<button onClick={()=>{setJokes(useRandomJoke)}}>
Click for Jokes</button>
</div>
); } export default App;
`
useRandomJoke is a custom hook. Hooks should only be called at the top level of a component and as the custom hook already has the joke state, you don't need an additional state in the App component.
If you want to get a new joke after the component renders and every time the button gets clicked, you can do this:
const useRandomJoke = () => {
const [joke, setJoke] = useState("");
const fetchJoke = useCallback(() => {
fetch("https://api.icndb.com/jokes/random")
.then((res) => res.json())
.then((data) => {
setJoke(data.value.joke);
});
}, []);
return [joke, fetchJoke];
};
export default function App() {
const [joke, fetchJoke] = useRandomJoke();
useEffect(() => {
fetchJoke();
}, [fetchJoke]);
return (
<div className="App">
<h1>Random Jokes</h1>
<p>{joke}</p>
<button onClick={fetchJoke}>Click for a random joke</button>
</div>
);
}
You can't conditionally call React hooks, like in the onClick handler of the button, as this breaks the rules of hooks. I suggest refactoring the useRandomJoke hook to return the fetched joke and a function to fetch the next random joke. You also shouldn't mix async/await with Promise chains as this is an anti-pattern.
const useRandomJoke = () => {
const [jokes, setJokes] = useState(null);
const jokeFetch = async () => {
const res = await fetch("https://api.icndb.com/jokes/random");
const data = await res.json();
setJokes(data.value.joke)
};
return [jokes, jokeFetch];
};
Then use the hook in the app.
function App() {
const [joke, getJoke] = useRandomJoke();
return (
<div className="App">
<h1>Random Jokes</h1>
<p>{joke}</p>
<button onClick={getJoke}>Click for Joke</button>
</div>
);
}
Well, there is more than one point to talk about here:
1- in React.js, you can only call custom hooks at the top level of your function's body (react recognizes any function starting with the keyword use as a hook)
function App() {
// top level is here
const randomJokes = useRandomJoke()
const [jokes, setJokes] = useState();
return (
<div className="App">
<h1>Random Jokes</h1>
<p>{jokes}</p>
<button onClick={()=>{setJokes(useRandomJoke)}}>
Click for Jokes
</button>
</div>
); }
export default App;
2- In your example I understand you want to have a new joke each time onClick triggers, in order to do so, I don't think using a custom hook is the ideal solution here, since your custom hook runs the fetchJokes method only once on initial render (as you described in your useEffect hook), I understand a lot of people mention that useEffect is the place to make API calls, but it doesn't necessarily applies to all use cases, in your example it is simple, you don't have to use useEffect neither create a custom hook.
a possible simple solution:
function App() {
// we always call hooks at the top level of our function
const [jokes, setJokes] = useState();
const fetchNewJoke = () => {
fetch("https://api.icndb.com/jokes/random")
//we'll run 2 "then"
.then(
// this will give us response and will return inform of
res.json
(res) => res.json()
) //.json is a format
.then((data) => {
setJokes(data.value.joke);
}); // now calling data from te returned values in res.json
};
};
return (
<div className="App">
<h1>Random Jokes</h1>
<p>{jokes}</p>
<button onClick={fetchNewJoke}>Click for Joke</button>
</div>
);
} export default App;

I receive "TypeError: items is undefined" when trying to map items from a JSON

I am currently trying to setup a React web app using React hooks. I try to pull the items from the JSON with Map but I receive this error.
TypeError: items is undefined
Shop.js
import React, {useState, useEffect} from 'react';
import './App.css';
function Shop() {
useEffect(() => {
fetchItems();
}, []);
const [items, setItems] = useState([]);
const fetchItems = async () => {
const data = await fetch('https://fortnite-api.theapinetwork.com/upcoming/get');
const items = await data.json();
console.log(items.items);
setItems(items.items);
};
return (
<div>
{items.map(item => (
<h1>{item.map}</h1>
))}
</div>
);
}
export default Shop;
I'm not an expert on React, but I'm pretty sure its because you get to the return statement before you define items.
That function is async, so while it takes its turn running each line, the program itself will move on, thus getting to the return with "items" in it before items is actually defined.
What might fix it is doing an if/else that checks if items is defined, then returns either blank html or the html with items. React dynamically updates so that should then return the correct html once items is loaded.
This should solve your question. I have checked the response from the GET request you provided and you have used the incorrect data structuring when pulling fields out. Try the code below.
import React, {useState, useEffect} from 'react';
import './App.css';
function Shop() {
useEffect(() => {
fetchItems();
}, []);
const [items, setItems] = useState([]);
const fetchItems = async () => {
const response = await fetch('https://fortnite-api.theapinetwork.com/upcoming/get');
const deserialisedResponse = await response.json();
setItems(deserialisedResponse.data);
};
return (
<div>
{items.map((item, index) => (
<h1 key={index}>{item.map}</h1>
))}
</div>
);
}
export default Shop;
I've tried on my end and confirmed it works.
Let me show the codes.
function Shop() {
useEffect(() => {
fetchItems();
}, []);
const [items, setItems] = useState([]);
const fetchItems = async () => {
const response = await fetch('https://fortnite-api.theapinetwork.com/upcoming/get');
const deserialisedResponse = await response.json();
console.log("result: ", deserialisedResponse)
setItems(deserialisedResponse.data);
};
return (
<div>
{items.map((item, idx) => (
<h1 key={idx}>{item.item.name}</h1>
))}
</div>
)
}
Please have a check and let me know if it works or not.

Resources