Can't Update state after fetch request - reactjs

I'm trying to make an app where I fetch data from a Graphql API, and update state after fetching, but after the successful request I'm getting the data but I can't update state with that data. when I console log the response data it's showing the array with required data but when I update state and console log the state it's showing empty array.
I need to update the state to use it in the component, when i'm doing this it's throwing error that currList is undefined.
here are pictures of code and console.
export default function App() {
const [search, setSeach] = useState("");
const [currList, setCurrList] = useState([]);
const fetchShips = async () => {
const response = await request(gql`
{
ships {
name
home_port
image
}
}
`);
console.log("request response", response.data);
setCurrList(response.data);
console.log("currlist:", currList);
};
useEffect(() => {
fetchShips();
}, [currList]);
const Searchitems = (event) => {
setSeach(event.target.value);
setCurrList(
currList.filter((item) => {
return item.name.includes(search) || item.home_port.includes(search);
})
);
};
return (
<div className="App">
<div className="container">
<header></header>
<div className="body">
<input
type="text"
id="search"
value={search}
onChange={Searchitems}
className="input"
placeholder="Search Ships"
/>
<p>Total Count: {currList.length}</p>
<div className="ships">
{currList.map((item, index) => {
return (
<Ship
key={index}
name={item.name}
port={item.port}
img={item.image}
/>
);
})}
</div>
</div>
</div>
</div>
);
}

the State is Updated just not when the code is running that's why it logs that the state is an empty array, try to console.log it once again and you will see that there is something in the List.

That's normal, everything is happening asynchronously. React will trigger a re-render once your currList is updated and you will get its new value. If you want to listen to this change, you have to use useEffect with currList as a dependency.
useEffect(() => {
console.log("List is updated", currList);
}, [currList]);

The function fetchShips has closure over the currList state variable that blongs to specific render call, so when you log it after calling setCurrList it shows you the previous state not the updated state, you can log it in the useEffect to see the changes.

I had the same problem once. I couldn't find efficient solution but my solution saved me anyway.
I used useRef instead of useState.
Try this:
const currList = useRef([]);
useEffect=(()=>{
const fetchShips = async () => {
const response = await request(gql`
{
ships {
name
home_port
image
}
}
`);
console.log("request response", response.data);
// setCurrList(response.data);
if(response)
currlist.current = response.data
console.log("currlist:", currList);
};
fetchShips()
// Also as far as I know ,you should use useEffect like this
},[])
//... codes
return(
//... codes
currList.current.map(...
)
//... codes
Before using useRef, try to define your fetchShips function inside useEffect so maybe you don't need my solution.
Why is not efficient my solution for your case?
When you want to update your currList data, useRef does not trigger re-render. Even if your data updated, you cannot see it on your screen.
So setCurrList(currList.current) can save you but as I said earlier it may not efficient way.

Related

React hooks not showing correct data when component is rendered

I have created a hook in a component as below.
useEffect(() => {
axios
.get("http://127.0.0.1:5000/v1/matches")
.then((response) => {
getStatusCode(response.data.code);
console.log("responseCode",responseCode);
getMatchdata(response.data.result);
setInfo(<MatchData responseCode={responseCode} matchdata={matchdata} />);
})
.catch((error) => console.log(error));
},[]);
This is a state function used in the above effect
const [info, setInfo] = useState();
I expected the above useEffect should return me some data in the below block
<div> {info} </div>
but it is showing wrong data, whereas I have created another function to trigger on Refresh button as
function refresh() {
setInfo(<MatchData responseCode={responseCode} matchdata={matchdata} />);
}
this function is returning me correct data. I want to create a functionality that will dynamically update the div element with change in state of {info}, by default when the page is loaded first, it should fetch data from the endpoint used here only. I'm new to React. Where I'm going wrong and how do I achieve it?
I don't want to say this is wrong, but this seems like an atypical approach from what I've seen in the wild. Specifically I am talking about storing a JS/JSX or TS/TSX element in a state object. I have more commonly seen a value stored in that type of variable and that value changing when necessary via the set dispatch function. Then the state object is passed to the component who needs it to do something. In react, when the value of that state object changes, it will cause the component who uses it to re-render. If I were coding this, this is what my code would look like.
const [info, setInfo] = useState();
const getData = () => {
axios
.get("http://127.0.0.1:5000/v1/matches")
.then((response) => {
setInfo(response.json())
})
.catch((error) => console.log(error));
}
const divComponent = ({info}) => (
<div>
<p>{info.data.code}</p>
<p>{info.data.result}</p>
</div>
)
const refreshButton = () => (
<button onClick(()=>getData())>Refresh</button>
)
Unless you only specifically want something to happen once at component mount, you would not use useEffect() like you did in your code. If the decision to refresh were coming from an external object with state instead of the refresh button, you could add that object whose state changes to the dependency array of the useEffect function. This would cause the refresh to run any time that object's state value changes. In the code above, getData() (which might need to be async) will only run when called. Then you have a component called divComponent which is expecting info to have value. When rendering this component you would want a null check like I coded below. Finally the refreshButton component will call getData() when it is clicked.
Then in your code that renders this, I would have something like this:
<>
{info ? <divComponent info={info} /> : <p>There is no info</p>}
<refreshButton />
</>
The code above will check if the state object info has value, and if it does it will render the divComponent with your data values. If it does not, instead it will show the p tag explaining that there is no data. Either way it will render the refreshButton, which would run the getData() function again when clicked.
** EDIT **
Based on your comment, here is another approach so you can have a value on page load and update when necessary:
import {useState, useEffect} from "react";
const [info, setInfo] = useState();
const getData = () => {
axios
.get("http://127.0.0.1:5000/v1/matches")
.then((response) => {
setInfo(response.json())
})
.catch((error) => console.log(error));
}
useEffect(()=> {
getData();
}, [])
const divComponent = ({info}) => (
<div>
<p>{info.data.code}</p>
<p>{info.data.result}</p>
</div>
)
const refreshButton = () => (
<button onClick(()=>getData())>Refresh</button>
)
export const Page = () => (
<>
{info ? <divComponent info={info} /> : <p>There is no info</p>}
<refreshButton />
</>
);
your method is quite complex. I believe you need to add your MatchData Component Inside the div in this way.Also Don't Need To Call State Method setInfo() in useEffect hook.Only responseCode and matchdata Needed that is already adjusted by you in useEffect Hook.

Why non-conditional JSX doesn't works after setting state value from another variable

This is the code where I set the values and update after an api call
function Expense() {
const [details, setDetails] = useState({});
const [login, setLogin] = useState(false);
const [expyear, setExpyear] = useState();
useEffect(() => {
try {
(//Api call...)
.then((res) => {
const details = res.data[0];
// Also when I do setDetails(res.data[0]) the non-conditional render works fine but I want to set some value immediately after the Api call
setDetails(details);
setLogin(true);
setExpyear(details.year);
});
}
} catch (err) {
console.log(err);
}
}, []);
But when I try to render the following JSX, that gives an error as TypeError: Cannot read properties of undefined (reading 'Fullname') also when I log props {details} in LastData, is shows empty object
return (
<div>
<div>
<LastData details={details} />
{details.Fullname}
{expyear}
</div>
</div>
);
}
Only untill I change it to conditional rendering, it works fine
return login ? (
<div>
<div>
<LastData details={details} />
{details.Fullname}
{expyear}
</div>
</div>
) : (
<div>Hello</div>
);
}
I need to know why I have to put a condition to render the JSX and why does it render non-conditionally after setting direct api value to state?
It happens because setState is asynchronous operation, and initial value of details state is empty object (that leads to details.Fullname === undefined at initial render).
Only when this group of state settings are done:
setDetails(details);
setLogin(true);
setExpyear(details.year);
and login set to true you can see all rest data rendered.
You can also add initial state to details instead of empty object set some data for used fields, for example useState({FullName: ""}).
At first render you will have empty string and when state updates, you 'll get your fetched FullName value.

How to prevent loading data from API before user input in react?

I want to display data from an API, which user input will be part of the API URL. To be more precise, the goal is to let user enter an ETH wallet address, and display the NFT assets this wallet has, using Opensea API.
My problem is that the data is fetched and displayed before user submit input. (It's also a valid api url but not the data I want to fetch).
How to fix this issue? I think one way is to keep a boolean state of submitted, and display only if it is true. But this way it makes the api call regardless, although not rendered. Is there a better way? Does it matter what I set as the initial state for owner?
My guess is that there needs an async function, and the api fetch is callback. Trigger is the user input event. I'm not sure how to construct this.
Below is my code.
import { useState, useEffect } from "react";
// example user input: 0x147412d494731cbb91dbb5d7019464a536de04dc
function App() {
const [data, setData] = useState({ assets: [] });
const [enteredWallet, setEnteredWallet] = useState("");
const [owner, setOwner] = useState("");
const walletChangeHandler = (event) => {
setEnteredWallet(event.target.value);
};
const submittedHandler = (event) => {
event.preventDefault();
setOwner(enteredWallet);
console.log(enteredWallet);
};
useEffect(() => {
fetch(
`https://api.opensea.io/api/v1/assets?owner=${owner}&order_direction=desc&offset=0&limit=10`
)
.then((res) => {
return res.json();
})
.then((data) => {
setData(data);
});
}, []);
return (
<div className="App">
<header className="App-header">
<h3>Show me assets in this wallet</h3>
<form onSubmit={submittedHandler}>
<input
placeholder="wallet address"
value={enteredWallet}
onChange={walletChangeHandler}
/>
<button>Submit</button>
</form>
<div>
{data.assets.map((i, index, k) => (
<li key={index}>{i.name}</li>
))}
</div>
</header>
</div>
);
}
export default App;
p.s. I know this fetch api call is not the best approach. it's just for my exercise.
On a separate note, i also got a warning message "react Hook useEffect has a missing dependency: 'owner'. Either include it or remove the dependency array react-hooks/exhaustive-deps" Any clue?
It could look something like
const [data, setData] = useState()
const [owner, setOwner] = useState()
useEffect(() => {
if(owner){
fetch(...).then(setData)
}
}, [owner])
return data ? <>something with data goes here</> : <>Loading...</>

How to update React state once useMutation is done?

I add an user to an api with react-query's useMutation hook. It works. Now, I need to add the new user to my array of users, which is in my state.
I know I'm supposed to query all the users with useQuery and then use onSuccess inside useMutation to modify the cache. But in certain cases, I don't fetch the users with useQuery, so I need to update a local state as I would do with a normal promise.
For the moment, I simply check if the prop "success" is true and if so, I update the array. But it only works on the second click. Why and how to fix this?
It seems the success condition inside onAddUser() is only reached on a second click.
export default function App() {
const { updateUser } = userService();
const { update, loading, error, data, success } = updateUser();
const [users, setUsers] = useState(allUsers);
const onAddUser = async () => {
await update(newUser);
if (success) {
return setUsers((users) => [...users, data]);
}
};
return (
<>
<div>
{users.map((user) => (
<div key={user.id}>
{user.name} - {user.job}
</div>
))}
</div>
{loading && <div>sending...</div>}
{error && <div>error</div>}
<button onClick={() => onAddUser()}>add user</button>
</>
);
}
Here is also a sandbox: https://codesandbox.io/s/usemutation-test-co7e9?file=/src/App.tsx:283-995
The success prop returned from updateUser (I assume this is somehow the result returned by useMutation) will only update on the next render cycle. Functions will still keep the reference to whatever they closured over, even if you have an async await in there. This is nothing react-query specific, this is just how react works.
I would suggest to use the onSuccess callback of the mutation function, or use mutateAsync and check the result, though you have to keep in mind to catch errors manually if you use mutateAsync. You can read about mutateAsync here, I'm gonna show you a mutate example:
const { mutate, loading } = useMutation(() => ...);
const onAddUser = () =>
mutate(newUser, {
onSuccess: (newData) => setUsers((users) => [...users, data]);
});
};
also, please don't violate the rules of hooks. You can only call hooks from functional components or other hooks (=functions that start with use), but in your codesandbox, you call useMutation from the updateUser function ...

Page renders faster than fetching the data and displaying it

How do i display data that i am grabbing using a fetch call and then setting it into an array and displaying in the Dom if it meets the conditions. Currently i am rendering the page faster than i am fetching the data and doing stuff with it.
thanks in advance
function Profile() {
let myReceipe = [];
const [isPopUp, setPopUp] = useState(false);
ReceipeService.getReceipes().then(data => {
myReceipe = data;
})
const popUpHandler = () => {
setPopUp(!isPopUp);
}
return (
<>
<div className='profile'>
<ProfileHeader/>
<div className='createdposts'>
{myReceipe.length !== 0 ?
<Post popUpHandler={popUpHandler} myReceipe={myReceipe}/>
:
null
}
</div>
</div>
{isPopUp === true ?
<Magnify popUpHandler={popUpHandler}/>
:
null
}
</>
)
}
Couple of problems in your code:
You are not using the useEffect hook to make the HTTP request
myReceipe should be in the state of your component
Data will always be loaded after your component has rendered.
The way you are fetching the data is not the correct way to do it. React has useEffect hook that is built exactly for this purpose.
Fetching data from the server is a side-effect and all the side effects belong inside the useEffect hook. So, move the code that makes the HTTP request inside the useEffect hook.
Also make sure that myReceipe is the local state of your component
const [myReceipe, setMyReceipe] = useState([]);
and when the data from the server is available, update the state to trigger a re-render so that you can show the data to the user.
useEffect(() => {
ReceipeService.getReceipes()
.then(data => {
setMyReceipe(data);
});
}, []);
While the data is not available, show some kind of loading spinner to the user to indicate to the user that data is loading.
just use a state variable myReceipe then when myReceipe is set the Component will re render nthen call ReceipeService.getReceipes() in useEffect :
let myReceipe = [];
const [isPopUp, setPopUp] = useState(false);
const [myReceipe , setmyReceipe ] = useState([]);
useEffect(
()=>{
let isMounted=true;
ReceipeService.getReceipes().then(data => {
// isMounted && insures the component is still mounted
// or else this might through an error if the component has unmounted but the api call responded because you cant just update staet of un unmounted Component
isMounted && setmyReceipe(data);
})
return ()=>{isMounted = false }
},[])
const popUpHandler = () => {
setPopUp(!isPopUp);
}
return (
<>
<div className='profile'>
<ProfileHeader/>
<div className='createdposts'>
{myReceipe.length !== 0 ?
<Post popUpHandler={popUpHandler} myReceipe={myReceipe}/>
:
null
}
</div>
</div>
{isPopUp === true ?
<Magnify popUpHandler={popUpHandler}/>
:
null
}
</>
)
}

Resources