React - Change button to loading status whilst fetching data - reactjs

I have tried a few variations of this, but can't seem to get it working. I have a custom hook that posts data once the user clicks a button after having chosen a value from a dropdown.
I want the button to be disabled until the fetch request returns a status of 200. Essentially I want the user to not continue to the next page until the request has completed. Here is some code:
customHook
const postData = () => {
setLoading(true);
axios({
url: -
headers: -,
method: -,
data: -,
responseType: -
})
.then((response) => {
setLoading(false);
}
})
.catch((error) => {
setLoading(false);
setError(error.response.data));
});
};
Button Component
<Button
onClick={onClickHandler}
loading={SOME STATE HERE TO ACHIEVE THIS}
>
{Continue}
</Button>

You could simply use a boolean state variable.
const [loading, setLoading] = useState(false)
const onClickHandler = () => {
setLoading(true)
performNetworkCall()
.finally(() => {
setLoading(false)
}
}
return (
<Button onClick={onClickHandler} loading={loading} >
{Continue}
</Button>
)

Related

React component doesn't re-render after setState

i have state vacations, i set it after fetch within useEffect, i have button approve that will change data in vacation state and i want to re-render component after that happens within function handleApprove , so i made up virtual state componentShouldUpdate with initial value of false and passed it as a dependency for useEffect, and when function handleApprove gets triggered, i setState to the opposite of its value !componentShouldUpdate, but the component only re-render when i click 2 times, why is that happening and why it works fine when i setState componentShouldUpdate from a child component ?
function VacationsComponent() {
const [vacations, setVacations] = useState([{}]);
const [componentShouldUpdate, setComponentShouldUpdate] = useState(false);
useEffect(() => {
const getVacations = async () => {
const response = await fetch("http://localhost:8000/get-vacations");
const data = await response.json();
setVacations(data);
};
getVacations();
}, [componentShouldUpdate]);
const handleApprove = async (e, vactionId) => {
(await e.target.value) === "approve"
? fetch(`http://localhost:8000/approve-vacation/${vactionId}`, {
method: "POST",
})
: fetch(`http://localhost:8000/reject-vacation/${vactionId}`, {
method: "POST",
});
setComponentShouldUpdate(!componentShouldUpdate);
};
<button onClick={(e) => handleApprove(e, item._id)}>
APPROVE
</button>
}
This is most probably caused because useState hook operates asynchronously. Read more here.
You can update your code to use only one state like this
function VacationsComponent() {
const [vacations, setVacations] = useState([{}]);
const getVacations = async () => {
const response = await fetch("http://localhost:8000/get-vacations");
const data = await response.json();
setVacations(data);
};
useEffect(() => {
getVacations();
}, []);
const handleApprove = async (e, vactionId) => {
const slug =
e.target.value === "approve" ? "approve-vacation" : "reject-vaction";
await fetch(`http://localhost:8000/${slug}/${vactionId}`, {
method: "POST",
});
getVacations();
};
<button onClick={(e) => handleApprove(e, item._id)}>APPROVE</button>;
}
put the setComponentShouldUpdate(!componentShouldUpdate) inside a thenable like this, and remove the async/await construct.
Also what was the intended purpose for setting state, I don't see the boolean being used anywhere. Usually when setting state you want the DOM to be updated somewhere, and especially with a boolean its great for toggling elements on the screen.
const handleApprove = (e, vactionId) => {
e.target.value === "approve"
? fetch(`http://localhost:8000/approve-vacation/${vactionId}`, {
method: "POST",
}).then(()=>{
// does this go here if it is approved or when it s rejected
setComponentShouldUpdate(!componentShouldUpdate);
})
: fetch(`http://localhost:8000/reject-vacation/${vactionId}`, {
method: "POST",
}).then(()=>{ setComponentShouldUpdate(!componentShouldUpdate); });
};

Why the flag is showed every time I reload the page after client has been created?

What I have done by far is when a user creates a client, in the top right of the page, is shown a flag(notification), which says "Client has been successfully created".
To do that was a little complex for me, because saving the client to DB, and listing the client to the web page are in two different components. Also, the flag is another component as well.
To save and list the clients I have used Axios since I'm dealing with the backend a lot.
SaveClient.js
export default function SaveClient({}) {
const save = async () => {
const clientParams = {
userName:
currentClient: clientName,
clientId: clientId,
};
await axios
.post(
process.env.REACT_API_CLIENT, clientParams
)
.then((response) => {
navigate("/clientlist", {state: {showFlagCreate: true}}); //passing the state
})
.catch((error) => {;
console.log(error);
});
};
}
ClientList.js
export default function ClientList() {
const { state } = useLocation();
const showFlagCreate = state?.showFlagCreate;
const [clientlist, setClientList] = useState([])
useEffect(() => {
const clientParams = {
userName:
currentClient: clientName,
clientId: clientId,
};
axios
.get(process.env.REACT_API_CLIENT, clientParams)
.then((response) => {
const {data} = response
setClientList(data)
})
.catch((error) => console.log(error));
}, []);
return (
<div>
...
{showFlagCreate && <FlagCreateClient />}
</div>
);
}
FlagCreateClient
export default function FlagCreateClient() {
const [show, setShow] = useState(true);
return (
<div>
<Transition
show={show}
as={Fragment}
<div>
<p>The client is successfully created.</p>
</div>
<div>
<button onClick={() => {setShow(false)}}>
<span>Close</span>
</button>
</div>
</Transition>
<div/>
);
}
The idea is that in the SaveClient component, when a client is saved, in .then() inside the navigate() function, pass a state in a true condition.
Then in the ClinetList component, I call the state using useLocation(), and I passed in the component {showFlagCreate && <FlagCreateClient />}.
By far this logic works, I create a client, the flag is shown after, but when I reload the page after, the flag is shown. When I make other actions in the webpage which might be clicking the edit button and going back to the ClientList component the flag won't show, even if I reload/refresh the page.
How can I fix this bug?

GraphQL endCursor and React State

I'm trying to implement a pagination on my react fe. I'm calling my data on page load and setting the endCursor for the pagination into a react hook variable.
However when I click 'load more' button on my page it goes and fetches new data and should upset the endCursor with the new value retrieved... but it only does this once and then stops working. I'm sure I'm doing something silly but I can't figure out what.
Here's the code..
const LatestDrops = () => {
const [newArrivals, setNewArrivals] = useState([])
const [offset, setOffset] = useState('')
// Run on page load
useEffect(() => {
listNewArrivals()
},[])
// Fetch new arrivals
const listNewArrivals = () => {
getNewArrivals()
.then(data => {
if (data.error){
console.log('Error fetching new arrivals', data.error)
} else {
setNewArrivals(data.edges)
setOffset(data.pageInfo.endCursor)
}
})
.catch(e => console.log('Failed to fetch', e))
}
// Fetch more arrivals on click
const handleLoadMore = (e) => {
console.log('Fetching more with offset: ', offset)
getMarketPlaceListings(offset)
.then((data) => {
if (data.error){
console.log('Error fetching marketplace listings', data.error)
} else {
setNewArrivals(data.edges)
console.log('Page offset from server is: ', data.pageInfo.endCursor)
setOffset(data.pageInfo.endCursor)
}
})
}
return(
<section>
Cursor: {JSON.stringify(offset)}
<button onClick={e => handleLoadMore()}>
View all
</button>
</section>
)
}
export default LatestDrops
I've also tried throwing in the old state when updating it as suggested by another post ie:
setOffset(offset => data.pageInfo.endCursor)

React axios get request without resulting in infinite loop using useEffect

I'm getting an infinite loop with this axios get request, but when I try putting messages or setMessages as the value inside of the [] at the end of useEffect it takes 2 clicks of the button to update the screen or it results in an error saying insufficient network resources. So what should I put instead?
function CommentsPage() {
const { user } = useAuth0();
const [query, setQuery] = useState("");
const [messages, setMessages] = useState([]);
//fetches data from mongodb for comments
useEffect(() => {
fetchComments();
}, [messages]);
//calls backend to fetch data from the messages collection on mongodb
async function fetchComments() {
const response = await axios.get("http://localhost:5000/messages");
setMessages(response.data);
}
// handles when a user types in the comment bar
function handleOnInputChange(event) {
// current value of what user is typing
const { value } = event.target;
setQuery(value);
}
// handles when a user posts a comment
function postComment(comment, user) {
const newMessage = {
username: user.name,
content: comment,
};
// calls backend to send a post request to insert a new document into the collection
axios
.post("http://localhost:5000/messages", newMessage)
.then((res) => console.log(res.data));
setQuery("");
fetchComments();
}
// handles when a user deletes a comment
function deleteComment(id) {
axios
.delete("http://localhost:5000/messages/delete/" + id)
.then((res) => console.log(res.data));
fetchComments();
setMessages(messages.filter((message) => message.id !== id));
}
// handles when a user updates a comment
function updateComment(id) {
// calls a pop up that allows user to input their new comment
const editedContent = prompt("please enter new message");
const newMessage = {
username: user.name,
content: editedContent,
};
axios
.put("http://localhost:5000/messages/update/" + id, newMessage)
.then((res) => console.log(res.data));
fetchComments();
}
console.log(messages);
return (
<Container>
<CommentsContainer>
{messages.length ? (
messages.map((message) => {
{/* if the usernames match then a user comment is returned
otherwise an other comment will be returned */}
return message.username === user.name ? (
<UserComment
key={message._id}
username={message.username}
content={message.content}
deleteComment={deleteComment}
id={message._id}
updateComment={updateComment}
/>
) : (
<OtherComment
key={message._id}
username={message.username}
content={message.content}
/>
);
})
) : (
<div>There are no comments. Make one!</div>
)}
</CommentsContainer>
<CommentBarStyle htmlFor="search-input">
<input
name="commentBar"
type="text"
value={query}
id="search-input"
placeholder="Write a comment..."
onChange={handleOnInputChange}
/>
<AddIcon
className="fas fa-plus"
onClick={() => postComment(query, user)}
/>
</CommentBarStyle>
</Container>
);
}
export default CommentsPage;
Your useEffect will run every time when messages state is change. Change your useEffect like this:
useEffect(() => {
fetchComments();
},[null]);
You are updating the 'messages' state inside the fetchComments... so everytime this state is updated your useEffect will be called because it has the messages as a dependency.
useEffect(() => {
fetchComments();
}, [messages]);
//calls backend to fetch data from the messages collection on mongodb
async function fetchComments() {
const response = await axios.get("http://localhost:5000/messages");
setMessages(response.data);
}
Try this:
useEffect(() => {
fetchComments();
}, []);
If you do it in that way, your component will call the fetchComments in the mount phase instead of calling it everytime that messages state changes.

Why am I unable to set an array in useEffect?

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.

Resources