Passed the button text to state, but text reverted back during page refresh in React hooks - reactjs

I have a record with two buttons, on click on Accept button should send the data back to server and display the text to Save and should not change the text afterwards ( should change only for clicked buttons).
In the below example, it is changing the text during on click, which is fine, but while refreshing the page it set back the text to Accept. Could someone please advise on how can I fix this ?
import { useState, useEffect } from "react";
import "./styles.css";
export default function App() {
const [acceptPlayer, setAcceptPlayer] = useState("Accept");
const postData = (e) => {
const text = "Save";
setAcceptPlayer(text);
};
return (
<div className="App">
<div>
<button onClick={postData} value="accept">
{acceptPlayer}
</button>
</div>
</div>
);
}
https://codesandbox.io/s/admiring-austin-1w1g38?file=/src/App.js

There is nothing wrong with this code. It's the expected behavior of the react. React or any other framework does not persist state on refresh That's the responsibility of the BE.
Solution:
When you can get API to show the data on the page, In the API response, you can return the data in the following format:
{
isAccepted: false,
//...something else
}
When you click on accept button and call the API BE is responsible for updating the isAccepted flag from the next time the API is called. You will get the following response:
{
isAccepted: true,
//...something else
}
Based on isAccepted flag you can toggle the button text.

Of course, it would go back to Accept because react only stores state locally. So every time you refresh the page it will go back to its initial state.
So if you want to get the updated state in your component then you have to fetch the latest data from the database in useEffect and have to set it to a state.

Related

Components aren't re-rendered after setState update, requires updating twice before re-render

I am creating a custom blog listing page in React for a HubSpot site (I've already searched there, couldn't find anything relevant to my issue). I'm still somewhat new to React but it is my first project from scratch in over a year. Basically what I am trying to do is combine three separate blogs into one page and add filtering options to show only posts from certain blogs and/or posts with certain tags attached to them. Currently I am successfully pulling the post data and sorting it. I am almost finished with the filtering functionality, but I am hitting a snag when trying to reset all filters.
I understand that setState is asynchronous and you can't expect the state value to updated immediately. My issue is that even after I have successfully updated the state, the useEffect hook doesn't fire consistently. I have tabs at the top of the page to switch between blogs and that works perfectly, as soon as I click one it filters out every other blog post and I can go back and forth between them easily. The problem is on the reset, I have to click the button twice for it to work. I'm completely at a loss as to why this is. In the console I see that the useEffect function for blogActive fires after the first time I click the reset button, but the blog view doesn't change. The second time I click the reset button, the blog view changes but the useEffect function doesn't fire.
I know that useEffect isn't supposed to fire if the state isn't actually updated (its being set to undefined when the button is clicked, so the second time it won't change) but I am still confused as to why this happens with the reset button and not with the blog switch function. I was originally resetting the blog from the reset function instead of just the sort function but that wasn't working either and calling it from the sort function is simpler in terms of displaying results with resetting the other filters (I have removed the tag filtering functions and components and still get the same results). I've also tried calling the handleBlogSwitch method instead of using the setState hook but I get the same results.
I've pasted the most relevant code below, but I've also included a sandbox link with a skeleton of the whole project including the code to fetch the post data.
Any help would be much appreciated!
function Blog({ blogName, blogID, handleBlog }) {
return (
<div className="blog">
<button className={"blog-" + blogName} onClick={() => handleBlog(blogID)}>
{blogName}
</button>
</div>
);
}
export default Blog;
function Archive() {
const [page, setPage] = useState(1);
const [blogActive, setBlog] = useState();
const [resetCheck, setChecked] = useState([]);
const [postState, setPosts] = useState(postArray);
let postArray = [];
function handleBlogSwitch(id) {
setBlog(id);
}
function sortPosts(sortme) {
let resetSort = false;
if (sortme) {
sortme.sort((a, b) => b["dataset"]["date"] - a["dataset"]["date"]);
postArray = [...postArray, ...sortme];
} else if (sortme === false) {
resetSort = true;
setBlog(() => {
return undefined;
});
}
let filtered = postArray;
if(!resetSort) {
if (blogActive) {
filtered = blogFilter(filtered);
setLoader("spinner-loaded");
}
}
setPosts(filtered);
setLoader("spinner-loaded");
}
function resetFilters(resetThis) {
setChecked([]); // Uncheck everything
sortPosts(resetThis);
}
// Handle logic for blog and tag filters
useEffect(() => {
setBtnLoad("btn-load-more"); // add this to the useEffect hook for tags too.
if(blogActive) {// Don't run this on page load.
sortPosts();
console.log("test blogactive effect");
}
}, [blogActive]);
return (
<div className="results__container">
<div className="tabs__container">
{Blogs.blogs.map((blog, key) => (
<Blog
key={key}
blogName={blog.name}
blogID={blog.id}
handleBlog={handleBlogSwitch}
></Blog>
))}
</div>
<div className="filter__container">
<button onClick={() => resetFilters(false)}>Reset Filters</button>
<div className="posts__container">
{postState.slice(0, page * resultsPerPage).map((html, key) => (
<Posts key={key} postHtml={html.outerHTML}></Posts>
))}
<img className={loader} src={logoSrc} />
<button className={btnClass} onClick={() => setPage(page + 1)}>
Load More
</button>
</div>
</div>
</div>
);
}
export default Archive;
https://codesandbox.io/s/wonderful-lalande-0w16yu?file=/src/App.js
Update: I didn't really fix this bug I'm facing, but I found a workaround. Instead of relying on the value of blogActive in sortPosts(), I created a new boolean in sortPosts which is set to true when it is called from the reset function. This way I know that the states should be resetting and skip that code, even if they aren't caught up yet.

React useState reruns the component function on no change

in this very simple demo
import { useState } from 'react';
function App() {
const [check, setCheck] = useState(false);
console.log('App component Init');
return (
<div>
<h2>Let's get started! </h2>
<button
onClick={() => {
setCheck(true);
}}
>
ClickMe
</button>
</div>
);
}
export default App;
i get one log on app init,
upon the first click (state changes from false to true) i get another log as expected.
But on the second click i also get a log , although the state remains the same.(interstingly the ReactDevTools doesn't produce the highlight effect around the component as when it is rerendered)
For every following clicks no log is displayed.
Why is this extra log happening.
Here is a stackblitz demo:
https://stackblitz.com/edit/react-ts-wvaymj?file=index.tsx
Thanks in advance
Given
i get one log on app init,
upon the first click (state changes from false to true) i get another
log as expected.
But on the second click i also get a log , although the state remains
the same.(interstingly the ReactDevTools doesn't produce the highlight
effect around the component as when it is rerendered)
For every following clicks no log is displayed.
And your question being
Why is this extra log happening?
Check the useState Bailing Out of a State Update section (emphasis mine):
If you update a State Hook to the same value as the current state,
React will bail out without rendering the children or firing effects.
(React uses the Object.is comparison algorithm.)
Note that React may still need to render that specific component again
before bailing out. That shouldn’t be a concern because React won’t
unnecessarily go “deeper” into the tree. If you’re doing expensive
calculations while rendering, you can optimize them with useMemo.
The answer to your question is essentially, "It's just the way React and the useState hook work." I'm guessing the second additional render is to check that no children components need to be updated, and once confirmed, all further state updates of the same value are ignored.
If you console log check you can see...
The first click you will get check is true -> check state change from false (init) => true (click) => state change => view change => log expected.
The second click => check state is true (from frist click) => true => state not change => view not render.
So. you can try
setCheck(!check);

SetState Updates but does not save

Below is my code, I am attempting to setState a string global context value,
the string exists by itself and is not apart of an array of variables,
When I click login, I can see the h1 containing the global value login.loggedin updating to true as requested, however when the login finishes and auth0 redirects back to the app, the value is no longer changed to login and goes back to false,
On the tutorial I followed, https://www.youtube.com/watch?v=35lXWvCuM8o
Dev ed talks about using prevState to make sure it correctly assigns the value,
however I am unsure how to use prevState for a string instead of the array he uses in his example, can someone please explain to me how set state can be showing as working but not saving and a possible fix? I even placed the loginwithredirect after the set state but is it possible the loginwithredirect is happening before the state change can finish processing?
import React, { useState, useContext, useEffect} from 'react';
import { useAuth0 } from '#auth0/auth0-react';
import { ValueContext } from './ValueContext'
const LoginButton = () => {
const { loginWithRedirect, isAuthenticated } = useAuth0();
const [login, setLogin] = useContext(ValueContext);
const updateLogin = () => {
setLogin({loggedIn:'true'});
console.log(login.loggedIn + " = login.loggedinvalue after update")
console.log('true log')
loginWithRedirect()
};
return (
!isAuthenticated && (
<>
<button onClick={() => updateLogin() }>
Log In
</button>
<h1>{login.loggedIn}</h1>
</>
)
)
}
----------
is this happening because I am not submitting the page? It seems that would make hooks quite limiting if you need to submit, so I am guessing that's not it either
I'm only guessing here.
I'm assuming that when you call loginWithRedirect the app navigates away from your app (to Auth0? site), then user authenticates, and then the browser redirects back to your app. If this happens, whatever you saved in your ValueContext context will be reset to its initial state. During the initialisation of the ValueContext you need to take into account current user authentication.
Also, it seems that isAuthenticated flag provided by useAuth0 hook has pretty much the same meaning as your ValueContext.login flag. Do you actually need login flag? If you need it, you have to keep the value of ValueContext.login in sync with isAuthenticated at all time to avoid any odd behaviours.

How to create new page without react router

I am creating a react project where I have a button in my home page and when I click it, it should open up a new page that contains a form to fill. Because it is a form and it should only appear after clicking the button on the home page, I don't want to use react router because I don't want the user to just type 'mywebsite.com/form'. I also want the functionality that, when the user submits the form, the page then goes back to the homepage and the data from the form should be available in the home page. For example, if the form had text fields, lists, date pickers, etc. I want all that data in the homepage after the user submits the form.
P.S. I am using material-ui components for my whole app so the text fields, datepickers, etc. are all mui components, so the data fetching from these components has to be according to that.
In your Home component do a conditional render based on if the button was clicked. If it was, render your Form component. Ideally your Home component should be your smart component and the Form should be a dumb component. Meaning that Home should manage all the state and Form is purely just for visuals(UI).
import React, {useState, useEffect} from 'react';
import myForm from './myForm';
const Home = () => {
const [isBtnClicked, setIsBtnClicked] = useState(false);
const [formData, setFormData] = useState(undefined);
useEffect(() => {
if(formData){
setIsBtnClicked(false); // or you can pass this setter to form component and set to false when form is submitted.
}
},[formData])
return (
<>
{isBtnClicked
? <myForm setFormData={setFormData} />
: (<h2>Home With Button</h2>
<Button variant="contained" onClick={() => setIsBtnClicked(true)})
}
};
import React from 'react';
const myForm = ({setFormData}) => {
//assuming you have refs to all inputs
const handleFormSubmit = (e) => {
e.preventDefault();
// construct form data and call setFormData()
}
return (
<>
<form onSubmit={handleFormSubmit()}>
...
</form>
</>
};
Since you are using material-ui, I would recommend you use one of their Dialog components. In case you want to make it look like a completely different page opens up to fill the form, you could use their full-screen dialog component. Build your form within this component and change the props to your liking. Conditionally render the form when the user clicks a button on your home page.
Use the fullscreen prop from the Dialog API and add others that you need.
If i can give my opinion, i think you can use NextJS with server side rendering for this. NextJS provides a structured folder called "pages". You can add your page to this. The redirecting can also be done using router in next package. An easy way without any need for react router. I used to use react router until i found out about this.

Render Component By Clicking Text Passed as Props

I am new to React and am trying to render a component when a user clicks on some text. The text is a state that's updated in a parent component using a form input and the useState React hook. I can find articles on rendering 'onClick' events, but this isn's a form with type submit or a button, it's just text that's passed down to it as a prop from a state higher up. Also a lot of tutorials seem to be outdated. What I would like to do is be able to click on the text and have it render a new component, with the effect of taking the user to a new 'page' in the browser. The rendered text I want to be able to click on is {user.contacts.username} in the code below. Any help much appreciated as I'm not sure which direction to go in.
import './Contacts.css';
import CreateTimer from './CreateTimer';
function Contacts(user) {
return (
<div className="Contacts">
{user.contacts.username}
</div>
);
}
export default Contacts;

Resources