I'm fetching data from a json. And i want to display that data in my React component.But every time I try to pass the objects that I return from the json to my state, it returns only one and delete the previous, instead of the entire elements inside the json.
This is my code.
const [state, setState] = useState({});
const connection = new Connection("devnet");
const { publicKey } = useWallet();
useEffect(() => {
(async () => {
//if not public key, close
if(!publicKey) return;
//get tokens
let response = await connection.getTokenAccountsByOwner(
publicKey!, // owner here
{
programId: TOKEN_PROGRAM_ID,
}
);
response.value.forEach((e) => {
const accountInfo = SPLToken.AccountLayout.decode(e.account.data);
//get only tokens with value of 1
if ( parseInt(`${SPLToken.u64.fromBuffer(accountInfo.amount)}`) === 1 ) {
const tokenPublicKey = `${new PublicKey(accountInfo.mint)}`
//get the metadata of each NFT
const run = async () => {
const ownedMetadata = await programs.metadata.Metadata.load(connection, await programs.metadata.Metadata.getPDA(tokenPublicKey));
//get only tokens of the collection ...
if (ownedMetadata.data.updateAuthority === "Address_authority") {
//show the json data from arweave
let url= ownedMetadata.data.data.uri;
fetch(url)
.then(res => res.json())
.then((out) => {
setState(prevState => {
// THIS IS NOT WORKING FOR ME :(
return {...prevState, ...out};
});
})
.catch(err => { throw err });
}
};
run();
}
});
})()
}, [connection, publicKey]);
console.log(state)
CodePudding user response:
{...prevState, ...out};
creates a new object, puts all of prevState
's own properties on the new object, then puts all of out
's own properties on the new object (overwriting the values from prevState
if prevState
also had properties with those names).
It sounds like you want an array, not a single object:
const [state, setState] = useState([]);
Then setting:
setState(prevState => [...prevState, out]);
Possibly unrelated, but that's potentially a bunch of distinct state changes (one for each element in response.value
). Because the work is asynchronous, that could also result in a number of interim re-renders. Maybe you want that, but if you don't, you can do all the fetching and then update state once. Also, any time you're doing async work in a useEffect
, you should allow for the possibility the effect's dependencies have changed in the meantime or the component has unmounted. Something like this (see ***
comments):
const [state, setState] = useState({});
const connection = new Connection("devnet");
const { publicKey } = useWallet();
useEffect(() => {
// *** Use a controller to stop when the component unmounts, etc.
const controller = new AbortContoller();
const {signal} = controller;
(async () => {
if (signal.aborted) return; // ***
// If not public key, stop
if (!publicKey) return;
// Get tokens
let response = await connection.getTokenAccountsByOwner(
publicKey!, // owner here
{
programId: TOKEN_PROGRAM_ID,
}
);
// *** Build up the new data in this array (since we skip some elements,
// so we won't have a simple 1:1 mapping of `response.value` elements
// to result elements.
const newState = [];
// *** Wait for all the operations to finish and add their elements to `newState`
await Promise.all(
response.value.map(async (e) => {
const accountInfo = SPLToken.AccountLayout.decode(e.account.data);
// Skip tokens with value other than 1
if (parseInt(`${SPLToken.u64.fromBuffer(accountInfo.amount)}`) !== 1) {
return;
}
const tokenPublicKey = `${new PublicKey(accountInfo.mint)}`;
const ownedMetadata = await programs.metadata.Metadata.load(connection, await programs.metadata.Metadata.getPDA(tokenPublicKey));
// Get only tokens of the collection ...
if (ownedMetadata.data.updateAuthority !== "Address_authority") {
return;
}
// Show the data from arweave
let url = ownedMetadata.data.data.uri;
const response = await fetch(url, {signal}); // *** Pass the signal to `fetch`
if (!response.ok) { // *** This check was missing
throw new Error(`HTTP error ${response.status}`); // Or ignore if you prefer
}
const out = await response.json();
newState.push(out);
})
);
// *** Now we have all of them, do one state update
setState(prevState = [...prevState, ...newState]);
})();
return () => {
// Stop if our dependencies change or the component unmounts
controller.abort();
};
}, [connection, publicKey]);