I am learning React and trying to write an asynchronous hook. Using setResult inside of useEffect doesn't seem to work. When I tried to render the result, there was nothing, so I added some console logging to see what is going on. The setter function in the useState hook doesn't seem to be doing anything. I've been following this video for some guidance, and my code does not differ too much.
I have the following component:
import React, { useState, useEffect } from 'react'
const Search = () => {
const [search, setSearch] = useState('')
const [query, setQuery] = useState('')
const [result, setResult] = useState([])
useEffect(() => {
async function fetchData() {
try {
const response = await fetch(
`https://api.spotify.com/v1/search?q=${encodeURIComponent(
query
)}&type=track`,
{
method: 'GET',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
Authorization: 'Bearer ' + auth.access_token
}
}
)
const json = await response.json()
console.log({ json })
console.log(
json.tracks.items.map(item => {
return item.id
})
)
setResult(
json.tracks.items.map(item => {
return item.id
})
)
console.log({result})
} catch (error) {
console.log(error)
}
}
if (query !== '') {
fetchData()
}
}, [query])
return (
<div>
<input
value={search}
placeholder='Search...'
onChange={event => setSearch(event.target.value)}
onKeyPress={event => {
if (event.key === 'Enter') {
setQuery(search)
}
}}
></input>
<br />
{result.map(item => (
<h3 key={item}></h3>
))}
</div>
)
}
export default Search
From console.log ({ json }), I see the response from the server looks OK.
console.log(
json.tracks.items.map(item => {
return item.id
})
)
The above console output looks OK as well.
setResult(
json.tracks.items.map(item => {
return item.id
})
)
console.log({result})
Why is result empty?
EDIT: Thanks Patrick and Talgat. I understand now. So, when I console.log outside of useEffect, I could see result is set correctly. I then realized I was missing a reference to {item} in my render:
{result.map(item => (
<h3 key={item}>{item}</h3>
))}
Now I see the IDs rendered on the page. Thanks for your help.
setTimeout(()=>{
console.log(result);
},0);
Please try this instead using console.log(result).
Setting state is not updated as soon as you insert value via setter( on your side setResult ).
So to do it, delay is needed.
Related
Dears,
Due to some reason, I need access to the PUT request body after the PUT request settled.
Please check the sandbox example I tried to prepare.
My question is - is it ok to return the PUT params in onMutate and then do some logic in onSettled based on these params, for example selectively setting a loading state to false.
And then, why the PUT params are the 3rd argument of the onSettled function?
p.s. please don't argue about state management, the question is about onSettled usage :)
Best regards,
MJ
import React from "react";
import { useMutation } from "react-query";
const someProps = { prop1: "key1" };
export default function App() {
const [isLoading, setIsLoading] = React.useState(false);
const { mutate } = useMutation({
mutationFn: async (someProps) =>
await fetch("https://httpbin.org/put", {
method: "PUT",
body: JSON.stringify(someProps)
}).then((response) => response.json()),
onSuccess: (responseData) => {
console.log("RESPONSE ON SUCCESS: " + JSON.stringify(responseData));
},
onMutate: (data) => {
setIsLoading(true);
console.log(
"Yes, I have access to props before I send the request: " +
JSON.stringify(data)
);
// I return the data so I can use it in on settled
return data;
},
onSettled: (arg1NotUsed, arg2NotUsed, data) => {
console.log(
"Yes, I have access to props after I receive the response: " +
JSON.stringify(data)
);
if (data) {
setIsLoading(false);
}
}
});
return (
<div>
<p>is loading: {isLoading ? "LOADING" : "IDLE"}</p>
<button onClick={() => mutate(someProps)}>trigger mutation</button>
</div>
);
}
variables are available in onSettled even if you don't return them from onMutate. What onSettled receives is:
onSettled(data, error, variables, context)
where context is what you return from onSettled. In your example, you're using the 3rd parameter, which is not the value returned from onMutate, so you can safely leave that out.
There is also no need to separately track an isLoading boolean, because useMutation does this for you and also returns a loading state.
export default function App() {
const { mutate, isLoading } = useMutation({
mutationFn: async (someProps) =>
await fetch("https://httpbin.org/put", {
method: "PUT",
body: JSON.stringify(someProps)
}).then((response) => response.json()),
onSuccess: (responseData) => {
console.log("RESPONSE ON SUCCESS: " + JSON.stringify(responseData));
},
onSettled: (arg1NotUsed, arg2NotUsed, data) => {
console.log(
"Yes, I have access to props after I receive the response: " +
JSON.stringify(data)
);
}
});
return (
<div>
<p>is loading: {isLoading ? "LOADING" : "IDLE"}</p>
<button onClick={() => mutate(someProps)}>trigger mutation</button>
</div>
);
}
Here's a fork of your sandbox with these changes: https://codesandbox.io/s/usequery-forked-vq8kcr?file=/src/App.js
I am building a simple fan made page for a streamer using felix twitch api . 2 days now i cant get around with a problem im facing when trying to display fetched data to front end . My code is as follows.
import React, { useState, useEffect } from "react";
import axios from 'axios';
export const Home = () => {
const [loading, setLoading] = useState(true);
const [data, setData] = useState([])
const client_id = "3fjsnq21qnimeiel9b773vwbhmjurk";
const token = "5z79quc5s7z0qbiiqmalqd4ueh4lue";
const url = "https://api.twitch.tv/helix/clips?broadcaster_id=42365125"
const requestOptions = {
method: "GET",
headers: {
"client-id": client_id,
Authorization: "Bearer " + token,
},
};
useEffect(() => {
const fetchData = async () =>{
setLoading(true);
try {
const {data: response} = await axios.get(url,requestOptions);
setData(response.data);
console.log(response.data)
} catch (error) {
console.error(error.message);
}
setLoading(false);
}
fetchData();
console.log(data);
}, []);
return (
<div>
hi
{loading && <div>Loading</div>}
{!loading && (
<div>
<h2>Doing stuff with data</h2>
{data.map(item => (<span>{item.id}</span>))}
</div>
)}
</div>
);
};
What I'm trying to do here is to create an array of the data twitch returns. Which in Cli mode returns an object and inside of it and array . Then with the function map to go trhough all videos of the streamer in order to display them in my front end. Thats the console in chrome with the code above.
Check chrome console image here
In API response there is no need to stringify the data which is set in setData state....
There is no need when use axios to stringify the data
useEffect(() => {
const fetchData = async () =>{
setLoading(true);
try {
const {data: response} = await axios.get(url,requestOptions);
setData(response.data);
console.log(response.data)
} catch (error) {
console.error(error.message);
}
setLoading(false);
}
fetchData();
console.log(data);
}, []);
I have a page in my React Gatsby project where I retrieve data from an API and the page renders the details from the API.
I am trying to add a search feature where the API re-renders when the input search field is updated, but it does not work.
Below is the code:
const [search, setSearch] = useState("")
const [people, setPeople] = useState()
let myHeaders = new Headers();
const getPeople = async () => {
myHeaders.append("Access-Control-Request-Headers", process.env.GATSBY_PEOPLE_ACCESS_CONTROL);
myHeaders.append("Authorization", process.env.GATSBY_PEOPLE_BEARER);
const requestOptions = {
method: 'GET',
headers: myHeaders,
redirect: 'follow'
};
try {
let response = await fetch(
process.env.GATSBY_PEOPLE_API + "&search=" + search, requestOptions);
let result = await response.json();
setPeople(result)
} catch (err) { console.error(err); }
};
const searchUpdate = (e) => {
setSearch(e.target.value)
}
useEffect(() => {
getPeople()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
return (
<Fragment>
<input placeholder="Search.." onChange={searchUpdate}></input>
<div className="people">
{people ? Object.values(people).map(person => {
return (
<div id={"person-"+person.id} key={person.id} >
{person.name}
</div>
)
}) : "Not available.."}
</div>
</Fragment >
)
How do I make the search feature work?
Add search as a dependency in the useEffect method because when the search state change useEffect is called.
useEffect(() => {
getPeople()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [search])
On form submit, a new event is added in the Database and the details of this new event are returned back via JSON. I want to concatenate this new JSON in the 'events' data so that the 'events' data gets updated automatically. But it is not working. Here is my code. Please see the line commented as //Here. It is where I am trying to update the event state variable but it is not getting updated.
import React, { useState,useEffect } from 'react';
async function getAllEvents() {
return fetch('http://localhost:8080/get-all-events/', {
method: 'GET',
//body: JSON.stringify(credentials)
})
.then(
data => data.json()
)
}
export default function Dashboard() {
const [events, setEvents ] = useState({});
const [eventName, setEventName] = useState();
useEffect(() => {
getEventsWithFetch();
}, []);
const getEventsWithFetch = async () => {
const response = await fetch("http://localhost:8080/get-all-events/");
const jsonData = await response.json();
setEvents(jsonData);
};
const addEvent = async (name) => {
const response = await fetch("http://localhost:8080/create-event/",
{
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
"title": name,
"day": "1",
"month": "2"
})
}
).then((response) => response.json())
.then((json) => {
events.push(json);
var events_array = events;
setEvents(events_array); //Here
})
};
const handleSubmit = async e => {
e.preventDefault();
var data = new FormData(e.target)
}
return(
<div>
<p>EVENTS DASHBOARD</p>
{JSON.stringify(events)}
<form onSubmit={handleSubmit}>
<label>
<p><b>Event Name</b></p>
<input type="text" name="event_name" onChange={e => setEventName(e.target.value)} />
</label>
<div>
<button type="submit">Submit</button>
</div>
</form>
</div>
);
}
You're modifying the read-only state events, instead of using setEvents, which is already a mistake.
Furthermore, the diff-check React does is by reference for objects, so you have to clone events, instead of setting the state to itself:
setEvents([...events, json]); // Using spread to clone the array and add a new element in the end
Otherwise, React sees the same reference being set and ignores it.
So I have a 40+ loop that's calling another component to display images. Each image has an ID and with that ID I can get more information about the image like Name and description via another API call.
When DisplayImage gets called I want it to call another callback function that will send out API calls for that image's metadata, store it in a variable and display it as an H1 tag.
return (
<div>
{array.map(index) => {
// Some Other Code That return a TokenID //
<>
{displayImage(tokenId)}
</>
</div>
})
const displayImage = (tokenId) => {
const imageName = GetURI(tokenId)
return (
<div className="token-container">
<h1>{imageName}</h1>
<img className="artwork" width="250px" src={`https://ipfs-asdf/${tokenId}`} />
</div>
)
}
const GetURI = async (tokenId) => {
const res = await fetch("https://api"+tokenId , {
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
}).then(data => {
console.log(data)
return data.json();
})
.then(data => {
return (data.name || [])
})
.catch(err => {
console.log(err);
});
}
The data is being displayed on the console but now I'm running into an infinite loop issue that I know UseEffect can solve but I can't quite figure it out. I managed to display the data on the console with UseEffect using the [] attribute but don't know how to display the data. Any help would be amazing. Thank you!
Two things useful to your situation
functions declared outside the component aren't recreated each render
useState and useEffect pairing limits calls to API to only when tokenId changes
// Put this function outside the component
// so it does not need a useCallback
// i.e not reconstructed each render of DisplayImage
const GetURI = async (tokenId) => {
...
});
const DisplayImage = (tokenId) => {
const [imageName, setImageName] = useState()
// limit calls to API to when tokenId changes
// and if eslint complains add GetURI to dependency list
// - but GetURI never changes, so no un-needed calls from it
useEffect(() => {
setImageName(GetURI(tokenId))
}, [tokenId, GetURI])
return (
<div className="token-container">
<h2>{imageName}</h2>
<img className="artwork" width="250px" src={`https://ipfs-asdf/${tokenId}`} />
</div>
)
};
You can also abstract to custom hook useImageName()
const GetURI = async (tokenId) => {
...
});
const useImageName = (tokenId) => {
const [imageName, setImageName] = useState()
useEffect(() => {
setImageName(GetURI(tokenId))
}, [tokenId, GetURI])
return imageName
})
const DisplayImage = (tokenId) => {
const imageName = useImageName(tokenId)
return (
<div className="token-container">
<h2>{imageName}</h2>
<img className="artwork" width="250px" src={`https://ipfs-asdf/${tokenId}`} />
</div>
)
};
BTW in GetURI this
return (data.name || [])
looks like should be
return data.name || ''
Is a different approach ok? I'd put display image into its own component.
const DisplayImage = ({tokenId: {_tokenId}}) => {
const imageName = GetURI(_tokenId)
const GetURI = useCallback(async () => {
await fetch("https://api"+tokenId , {
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
}).then(data => {
console.log(data)
return data.json();
})
.then(data => {
return (data.name || [])
})
.catch(err => {
console.log(err);
});
})
});
useEffect(() => {
if (_tokenId) GetURI();
}, [GetURI]);
return (
<div className="token-container">
<h2>{imageName}</h2>
<img className="artwork" width="250px" src={`https://ipfs-asdf/${_tokenId}`} />
</div>
)
};
and then
return (
<div>
{array.map(index) => {
//Some Other Code//
<DisplayImage tokenId={tokenId} />
</div>
})
You should probably cache the response from GetURI(tokenId). No need to ask twice for the same URI when using the same tokenId.
An easy way is using react-query:
Setup in App.js:
// App.js
import { QueryClient, QueryClientProvider } from 'react-query'
const queryClient = new QueryClient()
export default function App() {
return (
<QueryClientProvider client={queryClient}>
<Example />
</QueryClientProvider>
)
}
Then use in a DisplayImage component (instead of inline function):
// DisplayImage.js
import { useQuery } from 'react-query'
export function DisplayImage(tokenId) {
const { isLoading, error, data: imageName } = useQuery(['images', tokenId], GetURI(tokenId))
return (
<div className="token-container">
<h1>{isLoading ? 'loading...' : imageName}</h1>
<img className="artwork" width="250px" src={`https://ipfs-asdf/${tokenId}`} />
</div>
)
}
I found the best way to go about it with everyones help on here so thanks!
I put the GetURI function inside the show image component, and had a useEffect method call GetURI every time there was a new token ID, then I set a state variable to whatever was returned.
No loops, no errors 👌
const DisplayImage = (data) => {
const [nftMetadata, setNftMetadata] = useState();
const GetURI = async (data) => {
const nftURI = await data.drizzle.contracts.Contract.methods.tokenURI(data.tokenId).call()
await fetch(nftURI , {
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
"Access-Control-Allow-Origin": "*"
},
})
.then(data => {
return data.json();
})
.then(data => {
return setNftMetadata(data || []);
})
.catch(err => {
return console.log(err);
});
});
useEffect(() => {
GetURI(data);
}, [data.tokenId])
return (
<div className="token-container">
<h2>{nftMetadata.name}</h2>
<img className="artwork" width="450px" src={`https://ipfs:/whatever/${nftMetadata.image}`} />
</div>
);
};
return (
<div>
{array.map(index) => {
// Some Other Code That returns a TokenID //
<>
<DisplayImage address={drizzle.contractList[0].address} tokenId={tokenId} drizzle={drizzle} drizzleState={drizzleState}/>
</>
</div>
})