I am working on like functionality for my web-app. The problem here is my setLike is not changing the state even after using setLike(!like). I checked this with console.log() by using it before and after the setlike() but both the console.log() statements were giving me the same value. Here is my Post component.
import React , {useState,useEffect} from 'react'
import './Post.css'
import Avatar from '#material-ui/core/Avatar';
import {Button, IconButton, Input, Typography } from '#material-ui/core';
import {DataBase} from './firebase'
import firebase from 'firebase';
import FavoriteIcon from '#material-ui/icons/Favorite';
import {useStateValue} from '../contexts/StateProvider'
function Post({postId}) {
//get the user from the provider
const [{user}, dispatch] = useStateValue();
//number of likes
const [likeCount,setLikeCount] = useState(likesCount)
//if like=true or not
const [like, setLike] = useState(false);
I am using firebase firestore database for backend. The like document only exists in the database collection when the user interacts with it for the first time (likes it or likes it first and then does undo like). So first check whether user previously liked the document or not. If user previously liked the document, then get the value of the like for that particular user (true/false) and set it using setLike(like). Also get the number of documents in the collection who have like==true and set it equal to setLikeCount(likeCount).
//=======Get likes from the database ===========================
useEffect(() => {
//check if the user already liked the doc or not (first time or not)
DataBase.collection('posts').doc(postId).collection('postLikes')
.doc(user.uid).get().then((doc) => {
if (doc.exists) {
console.log(doc.data().like)
//set like to value of like from database
setLike(doc.data().like)
} else {
// doc.data() will be undefined in this case
console.log("Not liked");
}
}).catch((error) => {
console.log("Error getting document:", error);
});
//grab the docs which have like=true
DataBase.collection('posts').doc(postId).collection('postLikes').where("like", "==",
true).get()
.then((querySnapshot) => {
setLikeCount((querySnapshot.docs.map(doc =>doc.data())).length)
console.log(likeCount +" likes count")
})
.catch((error) => {
console.log("Error getting documents: ", error);
});
}
//when postId changes or page loads fire the code above
},[postId])
Here is my postLike function. The setLike(!like) is supposed to toggle the previous like value the user has for the document from firebase. I guess this is not updating.
//=============Post likes to the database=======
const postLike = (e) => {
//if already liked i.e. doc exists
console.log(like+"like before")
setLike(!like)
console.log(like+"like after")
like?(setLikeCount(likeCount+1)):(setLikeCount(likeCount-1))
console.log("likeCount"+likeCount)
DataBase.collection('posts').doc(postId).collection('postLikes').doc(user.uid).set(
{
like:like,
username:user.displayName,
timestamp:firebase.firestore.FieldValue.serverTimestamp(),
}
).catch((err)=>{console.log("something wrong happened "+err.message)})
}
return (
<div className="post">
<div className="post__likes">
{/*like button*/}
{
like?
(<Button onClick={postLike} ><FavoriteIcon fontsize="small" cursor="pointer" onClick=.
{postLike} style={{color:'red'}}/></Button> ):
(<Button onClick={postLike} ><FavoriteIcon fontsize="small" cursor="pointer" />
</Button>)
}
<Typography style={{color:'aliceblue'}}>Liked by {likeCount}</Typography>
</div>
</div>
)
}
export default Post
Edit:
Yeah, the useEffect might have been a bad idea. It's getting called on initial load and after like is updated from the server request. I've made a snippet that I think does most of what you want. It's still brittle though because a user can click the like button before we get back the likeCount from the server. You might want to disable the button while the request is pending.
const {
useState,
useEffect
} = React;
function Post({
postId
}) {
//number of likes
const [likeCount, setLikeCount] = useState(5)
//if like=true or not
const [like, setLike] = useState(false);
//=======Get likes from the database ===========================
useEffect(() => {
// This is a call to the server to check if the user already liked the doc or not (first time or not)
setTimeout(() => {
if (true) {
setLike(true);
}
}, 500);
// This is a call to the server to grab the docs which have like=true
setTimeout(() => {
setLikeCount(15);
}, 400);
//when postId changes or page loads fire the code above
}, [postId]);
//=============Post likes to the database=======
const postLike = (e) => {
//if already liked i.e. doc exists
const newLikeValue = !like;
const newLikeCount = like ? likeCount - 1 : likeCount + 1;
setLike(!like);
setLikeCount(newLikeCount);
setLike(newLikeValue);
// Update like value on server here
/*
DataBase
.collection('posts').doc(postId)
.collection('postLikes').doc(user.uid).set({
like: newLikeValue, // <-- Use newLikeValue here
username: user.displayName,
timestamp: firebase.firestore.FieldValue.serverTimestamp(),
}).catch((err) => {
console.log("something wrong happened " + err.message)
})
*/
}
return (
<div className = "post" >
<div className = "post__likes" > { /*like button*/ }
{
like
? (<button onClick={postLike}><div style={{color:'red'}}>Icon here</div></button>)
: (<button onClick={postLike}><div>Icon here</div></button>)
}
<p>Liked by {likeCount}</p>
</div>
</div>
);
}
ReactDOM.render(
<Post postId={5}/>,
document.getElementById("react")
);
<script crossorigin src="https://unpkg.com/react#17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#17/umd/react-dom.development.js"></script>
<div id="react"></div>
Initial answer:
Personally, I would use a local copy as Dave suggested in a comment. But you could also set up another useEffect to listen for changes to like and make your server update inside there.
useEffect(() => {
like?(setLikeCount(likeCount+1)):(setLikeCount(likeCount-1));
DataBase
.collection('posts').doc(postId)
.collection('postLikes').doc(user.uid)
.set(
{
like:like,
username:user.displayName,
timestamp:firebase.firestore.FieldValue.serverTimestamp(),
}
).catch((err)=>{console.log("something wrong happened "+err.message)})
}, [like]);
const updateLike = (e) => {
setLike(!like);
}
Related
I have an array of Notes that I get from my database, the notes objects each have a category assigned to it. There are also buttons that allow the user to filter the notes by category and only render the ones with the corresponding one.
Now, it's all working pretty well but there's one annoying thing that I can't get rid of: whenever I click on any of the buttons: <button onClick={() => {handleClick(categoryItem.category)}}>{categoryItem.category}</button>, the filterNotes() function is only called on the second click. I suspect it has to do something with me calling setState() twice, or maybe with the boolean that I set in the functions, but I tried various combinations to call the function on the first click, but to no avail so far.
Here's my MainArea code:
import React, { useState, useEffect } from "react";
import Header from "./Header";
import Footer from "./Footer";
import ListCategories from "./ListCategories";
import Note from "./Note";
import axios from "axios"
function CreateArea(props) {
const [isExpanded, setExpanded] = useState(false);
const [categories, setCategories] = useState([])
const [notes, setNotes] = useState([])
const [fetchB, setFetch] = useState(true)
const [filterOn, setFilter] = useState(false)
const [note, setNote] = useState({
title: "",
content: "",
category: ''
});
useEffect(() => {
fetch('http://localhost:5000/categories')
.then(res => res.json())
.then(json => setCategories(json))
}, [])
useEffect(() => {
if(fetchB) {
fetch('http://localhost:5000/notes')
.then(res => res.json())
.then(json => {
console.log(json)
setNotes(json)
setFetch(false)
})
}
}, [fetchB])
function handleChange(event) {
const { name, value } = event.target;
console.log("handleChange called")
setNote(prevNote => {
return {
...prevNote,
[name]: value
};
});
}
function submitNote(e){
e.preventDefault();
axios.post("http://localhost:5000/notes/add-note", note)
.then((res) => {
setNote({
category: '',
title: "",
content: ""
})
setFetch(true)
console.log("Note added successfully");
console.log(note)
})
.catch((err) => {
console.log("Error couldn't create Note");
console.log(err.message);
});
}
function expand() {
setExpanded(true);
}
function filterNotes(category){
if(filterOn){
fetch('http://localhost:5000/notes')
.then(res => res.json())
.then(json => {
console.log("filter notes")
setNotes(json)
setNotes(prevNotes => {
console.log("setNotes called with category " + category)
return prevNotes.filter((noteItem) => {
return noteItem.category === category;
});
});
setFilter(false)
})
}
}
return (
<div>
<Header/>
<ListCategories categories={categories} notes={notes} filterNotes={filterNotes} setFilter={setFilter} filterOn={filterOn} setFetch={setFetch}/>
<form className="create-note">
{isExpanded && (
<input
name="title"
onChange={handleChange}
value={note.title}
placeholder="Title"
/>
)}
<textarea
name="content"
onClick={expand}
onChange={handleChange}
value={note.content}
placeholder="Take a note..."
rows={isExpanded ? 3 : 1}
/>
<select
name="category"
onChange={handleChange}
value={note.category}>
{
categories.map(function(cat) {
return <option
key={cat.category} value={cat.value} > {cat.category} </option>;
})
}
</select>
<button onClick={submitNote}>Add</button>
</form>
<Note notes={notes} setFetch={setFetch}/>
<Footer/>
<button onClick={()=>{setFetch(true)}}>All</button>
</div>
);
}
export default CreateArea;
And ListCategories where I get call the function and get the chosen category from the buttons:
import React, { useState } from "react";
import CreateCategory from "./CreateCategory";
export default function ListCategories(props) {
function handleClick(category){
props.setFilter(true)
props.filterNotes(category)
}
return (
<div className="category-group">
<CreateCategory/>
<div className="btn-group">
{props.categories.map((categoryItem, index) =>{
return(
<button onClick={() => {handleClick(categoryItem.category)}}>{categoryItem.category}</button>
)
})}
</div>
</div>
)
}
I'm not sure what the best practice is with such behaviour - do I get the notes from the database each time as I'm doing now or should I do something completely different to avoid the double-click function call?
Thanks in advance for any suggestions!
Your issue is this function:
function handleClick(category){
props.setFilter(true)
props.filterNotes(category)
}
Understand that in React, state is only updated after the current execution context is finished. So in handleClick() when you call setFiler(), that linked filterOn state is only updated when the rest of the function body finishes.
so when your filterNotes() function is called, when it evaluates filterOn, it is still false, as it was initially set. After this function has executed, the handleClick() function has also finished, and after this, the filterOn state now equals true
This is why on the second click, the desired rendering effect occurs.
There are multiple ways to get around this, but I normally use 'render/don't-render' state by including it as an embedded expression in the JSX:
<main>
{state && <Component />}
</main>
I hope this helps.
You diagnosed the problem correctly. You shouldn't be using state like you would a variable. State is set asynchronously. So, if you need to fetch some data and filter it, do that and THEN add the data to state.
function filterNotes(category){
fetch('http://localhost:5000/notes')
.then(res => res.json())
.then(json => {
const filtered = json.filter((noteItem) => (noteItem.category === category));
setNotes(filtered);
})
}
}
It's not clear to me why you would need the filterOn state at all.
Depending on how your frequently your data is updated and if you plan on sharing data across users, the answer to this question will vary.
If these notes are specific to the user then you should pull the notes on load and then store them in a local state or store. Write actions that can update the state or store so that this isn't coupled with your react UI rendering. Example: https://redux.js.org/ or https://mobx.js.org/README.html.
Then update that store and your remote database accordingly through dispatching actions. This avoids lots of calls to the database and you can perform your filtering client-side as well. You can then also store data locally for offline use through this method so if it's for a mobile app and they lose internet connection, it'll still render. Access the store's state and update your UI based on that. Specifically the notes and categories.
If you have multiple users accessing the data then you'll need to look at using websockets to send that data across clients in addition to the database. You can add listeners that look for this data and update that store or state that you will have created previously.
There are many approaches to this, this is just an approach I would take.
You could also create a context and provider that maintains your state on the first load and persists after that. Then you can avoid passing down state handlers through props
This is my first post here, so nice to meet you all. I have recently started my adventure with React, so my question probably will not be very challenging (it could also be very basic stuff), however I got stuck with this problem for a while.
So I am working with WebSocket that when connected, sends one-time message with all products currently in stock. Then, every few seconds, it sends object with update of product stock changes.
I managed to update state with first recieved message, but then, when I try to access state in a function handleData, state is empty array. This is happening despite the fact that the data rendered on the page using state is still visible, state is visible in Firefox Developer Edition React DevTools, and the useEffect that is associated with the state change fires only once - at the start, so it doesn't change.
I want to be able to access data I put before in state, in function handleData, so that I can update the state with stock changes. It is interesting that when the websocket loads again, the "products" status variable is extended by the previous, unloaded products.
Here is code of my component:
import React, { useState, useEffect, useRef } from 'react';
import Product from './Product';
export default function ProductsList() {
const [products, setProducts] = useState([]);
const ws = useRef(null);
useEffect(() => {
ws.current = new WebSocket(<HERE IS WEBSOCKET URL BUT I CENSORED IT>);
ws.current.onmessage = (message) => handleData(message);
return () => ws.current.close();
},[]);
useEffect(() => {
console.log("Products useEffect:", products)
}, [products]) //prints fine here
const handleData = (message) => {
const data = JSON.parse(message.data);
switch (data.operation) {
case 'product.stock.decreased': {
console.log("Products Switch:", products) // prints empty array here
break;
}
default: {
setProducts(prevState => [...prevState, ...data]) // here state updates
break;
}
}
}
return (
<div className="ProductsList">
{products.map(p => <Product key={p.productId} productId={p.productId} name={p.name} price={p.price} stock={p.stock} />)}
</div>
);
}
Many thanks in advance for your help.
I want to build a dashboard for a blog. I have a page, listing all blog posts using a component for each list item. Now, inside each list item, I have a button to delete the post.
So far, everything is working. The post gets deleted, and if I reload the page, it is gone from the list. But I can't get it to re-render the page automatically, after deleting a post. I kind of cheated here using window.location.reload() but there has to be a better way?
This is my Page to build the list of all Posts
import {
CCol,
CContainer,
CRow,
CTable,
CTableHead,
CTableRow,
CTableHeaderCell,
CTableBody,
} from "#coreui/react";
import { useState, useEffect } from "react";
import DashboardSidebar from "../../components/dashboard/Sidebar";
import { getAllBlogPosts } from "../../services/blogService";
import BlogListItem from "../../components/dashboard/blog/BlogListItem";
import "./Dashboard.scss";
const AdminBlogListView = () => {
const [blogposts, setBlogposts] = useState([]);
useEffect(() => {
getBlogPosts();
}, []);
async function getBlogPosts() {
const response = await getAllBlogPosts();
setBlogposts(response.data);
}
// console.log(blogposts);
return (
<div className="adminContainer">
<div className="adminSidebar">
<DashboardSidebar />
</div>
<div className="adminContent">
<CContainer fluid>
<CRow className="mb-3">
<CCol>
<CTable>
<CTableHead>
<CTableRow>
<CTableHeaderCell scope="col">#</CTableHeaderCell>
<CTableHeaderCell scope="col">Titel</CTableHeaderCell>
<CTableHeaderCell scope="col">Content</CTableHeaderCell>
<CTableHeaderCell scope="col"></CTableHeaderCell>
</CTableRow>
</CTableHead>
<CTableBody>
{blogposts.map((post) => {
return <BlogListItem key={post._id} post={post} />;
})}
</CTableBody>
</CTable>
</CCol>
</CRow>
</CContainer>
</div>
</div>
);
};
export default AdminBlogListView;
And this is the BlogListItem Component
import { useState, useEffect } from "react";
import {
CTableRow,
CTableHeaderCell,
CTableDataCell,
} from "#coreui/react";
import CIcon from "#coreui/icons-react";
import * as icon from "#coreui/icons";
import {
deleteBlogPost,
getBlogPostById,
// updateBlogPost,
} from "../../../services/blogService";
import { useNavigate } from "react-router-dom";
const BlogListItem = (props) => {
const id = props.post._id;
const [visible, setVisible] = useState(false);
const [post, setPost] = useState({
title: "",
content: "",
});
useEffect(() => {
getBlogPostById(id)
.then((response) => setPost(response.data))
.catch((error) => console.log(error));
}, []);
const handleDelete = async (event) => {
event.preventDefault();
const choice = window.confirm("Are you sure you want to delete this post?");
if (!choice) return;
await deleteBlogPost(post._id);
window.location.reload();
};
return (
<>
<CTableRow>
<CTableHeaderCell scope="row">1</CTableHeaderCell>
<CTableDataCell>{post.title}</CTableDataCell>
<CTableDataCell>{post.content}</CTableDataCell>
<CTableDataCell>
<CIcon
icon={icon.cilPencil}
size="lg"
onClick={() => setVisible(!visible)}
/>
<CIcon
icon={icon.cilTrash}
className="deleteButton"
size="lg"
color=""
onClick={handleDelete}
/>
</CTableDataCell>
</CTableRow>
</>
);
};
export default BlogListItem;
What can I do instead of window.location.reload() to render the AdminBlogListView after deleting an item? I tried using useNavigate() but that doesn't do anything
Thanks in advance :)
You can pass a reference to a function from the parent component AdminBlogListView into the child component BlogListItem, such that it is invoked when a blog post is deleted. That function will have the effect of either repopulating the blog posts or manually removing it from the data (that implementation bit is up to you).
Solution 1: Repopulate all blog posts on deletion
This is a quick fix with a bit of code smell (because you're essentially querying the server twice: once to delete the post and another to fetch posts again). However it is an escape-hatch type of situation and is simple to implement.
When you are rendering BlogListItem, we can pass a function, say onDelete, which will invoke getBlogPosts() to manually repopulate the blog posts from your server:
<BlogListItem key={post._id} post={post} onDelete={getBlogPosts} />
Then it is a matter of ensuring BlogListItem invokes onDelete() when deleting a blog post:
const handleDelete = async (event) => {
event.preventDefault();
const choice = window.confirm("Are you sure you want to delete this post?");
if (!choice) return;
await deleteBlogPost(post._id);
// Invoke the passed in `onDelete` function in component props
props.onDelete();
};
Solution 2: Delete a specific blog post by ID in the parent
Similar to the solution above, but ensure that you are passing a function from the parent that can delete a post by a specific ID (from the argument). This saves you an additional trip to the server.
In your component AdminBlogListView, define a function that can mutate the blogposts state by removing a blog post by ID. This can be done by leveraging functional updates:
const onDelete = (id) => {
setBlogposts((currentBlogPosts) => {
const foundBlogPostIndex = currentBlogPosts.findIndex(entry => entry._id === id);
// If we find the blog post with matching ID, remove it
if (foundBlogPostIndex !== -1) currentBlogPosts.splice(foundBlogPostIndex, 1);
return currentBlogPosts;
})
}
NOTE: The code above assumes that the blog post ID is stored in the _id key. I have simply inferred that from your code, since you have not shared the shape of the data.
Then in your BlogListItem component, it's the same logic as solution #1, but you need to pass the ID into it when invoking it:
const handleDelete = async (event) => {
event.preventDefault();
const choice = window.confirm("Are you sure you want to delete this post?");
if (!choice) return;
await deleteBlogPost(post._id);
// Invoke the passed in `onDelete` function in component props with post ID as an argument
props.onDelete(post._id);
};
Here is the code for infinite scroll load
Mainly two components MainComponent and a custom hook component
everytime i entered something on search item it sends the request and display the data to screen and inside main component i am using lastELementRef to set observer Api on that to send the request again when i scrolled at the end .
Not able to understand when does function passed inside useCallBack(()=>{}) runs
to check how many times it runs i did console.log at line no 21 inside MainComponent.
It will be very nice of folks on this community if anybody can explain me when does it runs.
I have googled and watched some Youtube videos on useCallback and all I can come up with is that it gives the function object only when the dependency inside its dependency array changes else on it memoizes the function on each re-render if dependency does not change.?
i am sharing the code here
have used axios to send request.
//MainComponent
import React,{useState,useRef,useCallback} from 'react'
import useBookSearch from './useBookSearch';
export default function MainComponent() {
//these 2 stataes are here because
//we want them to be used in this component only
//meaning we dont want them to be part
//of any custom logic
const[query,setQuery] = useState('');
const[pageNumber,setPageNumber] = useState(1);
const observer = useRef();
const {books,loading,hasMore,error} = useBookSearch(query,pageNumber);
const lastElementRef = useCallback(node=>{
console.log("How many times did i run ?");
if(loading) return ;
if(observer.current) observer.current.disconnect();
observer.current = new IntersectionObserver(entries=>{
if(entries[0].isIntersecting && hasMore){
setPageNumber(prevPage => prevPage + 1);
}
})
if(node) observer.current.observe(node);
console.log(node);
},[loading,hasMore])
const handleSearch=(e)=>{
setQuery(e.target.value);
setPageNumber(1);
}
return (
<div>
<input value={query} type="text" onChange={handleSearch} ></input>
{books.map((book,index) =>{
if(books.length === index + 1)
{
return <div ref={lastElementRef}key={book}>{book}</div>
}
else{
return <div key={book}>{book}</div>
}
})}
{loading && 'Loading.....................'}
{error && 'Error........'}
</div>
)
}
//custom hook component-useBookSearch
import { useEffect,useState } from 'react';
import axios from 'axios'
export default function useBookSearch(query,pageNumber) {
const[loading,setLoading] = useState('true');
const[error,setError] = useState('false');
const[books,setBooks] = useState([]);
const[hasMore,setHasMore] = useState(false);
//second useEffect which clear the books first
//and then make an api request
useEffect(()=>{
setBooks([]);
},[query])
useEffect(()=>{
setLoading(true);
setError(false);
let cancel ;
axios({
method:'GET',
url:'http://openlibrary.org/search.json',
params:{q:query,page:pageNumber},
cancelToken:new axios.CancelToken(c=>cancel=c)
}).then(res=>{
setBooks(prevBooks=>{
return [...new Set([...prevBooks,...res.data.docs.map(b=>b.title)])]
})
setHasMore(res.data.docs.length > 0);
setLoading(false);
console.log(res.data);
}).catch(e=>{
if(axios.isCancel(e)) return
setError(true);
})
return ()=> cancel();
},[query,pageNumber])
return {loading,error,books,hasMore};
}
screenshot of how the code looks when i entered the string test to fetch data
Screenshot of the console window when entering test into input box
I have a React form for user login, everything works fine except for a setting a successful or unsuccessful message. Once i login, i set the value of a useState variable [res, setRes] to either successful or usuccessful depending on whether the user is registered or not. Problem is, even if the user is registered and username and password is correct, i get the message "invalid credentials" at least and most once. Subsequent calls from the same user result in the correct message being displayed. I searched and found that state is one step behind, and the solution is to use useEffect, but i am already using it. Can anyone help me figure out what the problem is? Code is as follows
export const Login = () => {
const email = useField('string')
const password = useField('password')
const [cred, setCred] = useState({})
const send = (e:any) => {
e.preventDefault()
setCred({'email':email.value, 'password':password.value})
showToast()
}
const [toastIsShown, setToastIsShown] = useState(false);
const showToast = () => {
setToastIsShown(true);
}
const [res,setRes] = useState('')
const hook = () => {
axios
.post('http://localhost:5000/auth', cred)
.then(response => {
console.log('response is ',response.data)
setRes('Login Successful')
})
.catch(err => {
console.log("error is ",err.response)
setRes('Invalid username or password')
})
}
useEffect(hook,[cred])
return (
<>
<form onSubmit = {send}>
<IonText>Enter Name</IonText>
<br />
<input {...email} />
<br />
<IonText>Enter Password</IonText>
<br />
<input {...password} />
<br />
<button>Send</button>
</form>
<IonToast
isOpen={toastIsShown}
onDidDismiss={() => setToastIsShown(false)}
message={res}
duration={3000}
/>
</>
)
}
I am using Ionic, which is why you see Toast there. Also, the language is Typescript.
Thanks
The useEffect hook is always called when a component mounts, and after that every time a value in its dependency array changes. Since an empty object is presumably not a valid log in, you're always going to get an unsuccessful attempt when the component mounts. You could do some simple validation like:
cred.email && cred.password && axios.post('http://localhost:5000/auth', cred)...
However, the root of the problem is that you are misusing useEffect. A log-in attempt is (usually, and in your case) a one time event, not a side-effect that occurs as the result of previous significant action. The side-effect in this scenario happens after the log-in attempt, when you trigger a Toast which contains a notification about the result:
export const Login = () => {
const email = useField('string');
const password = useField('password');
const [res, setRes] = useState('');
const [toastIsShown, setToastIsShown] = useState(false);
const send = (e:any) => {
e.preventDefault();
const cred = {
email: email.value,
password: password.value
};
axios
.post('http://localhost:5000/auth', cred)
.then(response => {
console.log('response is ',response.data)
setRes('Login Successful');
})
.catch(err => {
console.log("error is ",err.response)
setRes('Invalid username or password');
});
};
useEffect(() => {
res && setToastIsShown(true);
}, [res]);
return (
...
)
}
This is just to demonstrate a more reasonable use of useEffect. In reality I would probably not even use one here and instead just call setToastIsShown from inside send after setting res. A useEffect really comes in handy when you have two correlated pieces of data which are updated by multiple uncorrelated methods.