React component unnecessarily re-renders when I enter input in forms - javascript

I have a react component which manage user logging in and out, when user type email and password in the login field the whole component (Navbar) re-render to Dom in every keystroke unnecessarily thus reduces speed.
How can I prevent Navbar from re-rendering when user type their credential in login fild ?
import React, { useContext,useState } from 'react';
import { Postcontext } from '../contexts/Postcontext';
import axios from 'axios';
const Navbar = () => {
const { token,setToken } = useContext(Postcontext);
const [email,setEmail] = useState(''); **state manages user email for login**
const [password,setPassword] = useState(''); **state manages user password for login**
const[log,setLog] = useState(true) **state manages if user logged in or not based on axios post request**
const login=(e)=>{
//function for login using axios
})
}
const logout=(e)=>{
//function for logout using axios
}
return (
<div className="navbar">
{log?(
<form>
<input value={email} type="text" placeholder="email" onChange={(e)=>setEmail(e.target.value)}/>
<input value={password} type="text" placeholder="password" onChange={(e)=>setPassword(e.target.value)}/>
<button onClick={login}>login</button>
</form>
):(
<button onClick={logout}>logout</button>
)}
</div>
);
}
export default Navbar;

It is because it is same component which needs re-render to reflect input text changes. If you want your email to change but not effect Navbar then create a child component and move inputs into that component, manage input values using useState() there in child component and when you finally submit and user is logged in then you can either update some global state like redux store or global auth context to reflect and rerender Navbar.

So, I had the same issue and I was able to solve it using useRef and useCallback and I will try to explain in Q&A form. Sorry if I am not that clear, this is my first StackOverFlow comment and I am a beginner in React :)
Why useRef?
React re-renders every time it sees a component has updated by checking if previous and current object are same or not. In case of useRef it checks the object Id only and not the content inside it i.e. value of current inside the Ref component. So if you change the value of current React will not consider that. (and that's what we want)
Why useCallback?
Simply because it will run only when we call it or one (or more) of the dependencies have changed. As we are using Ref so it won't be called when the current value inside it has changed.
More info: https://reactjs.org/docs/hooks-reference.html
Based on above info your code should look like this (only doing login part):
import React, { useContext, useRef } from 'react';
const App = () => {
const emailRef = useRef(null);
const passwordRef = useRef(null);
const logRef = useRef(null);
const loginUpdate = useCallback( async (event) => {
event.preventDefault();
// Your logic/code
// For value do:
// const email = emailRef.current.value;
}, [emailRef, passwordRef, logRef]);
return (
<div className="navbar">
{log?(
<form>
<input
ref={emailRef}
type="text"
placeholder="email"
/>
<input
ref={passwordRef}
type="text"
placeholder="password"
/>
<button onClick={loginUpdate}>login</button>
</form>
):(
// Not doing this part because I am lazy :)
<button onClick={logout}>logout</button>
)}
</div>
);
}
export default App;

Had a few typos. It works for me
https://codesandbox.io/s/cold-sun-s1225?file=/src/App.js:163-208
import React, { useContext,useState } from 'react';
// import { Postcontext } from '../contexts/Postcontext';
// import axios from 'axios';
const App = () => {
// const { token,setToken } = useContext();
const [email,setEmail] = useState('');
const [password,setPassword] = useState('');
const[log,setLog] = useState(true)
const login=(e)=>{
//function for login using axios
}
const logout=(e)=>{
//function for logout using axios
}
return (
<div className="navbar">
{log?(
<form>
<input value={email} type="text" placeholder="email" onChange={(e)=>setEmail(e.target.value)}/>
<input value={password} type="text" placeholder="password" onChange={(e)=>setPassword(e.target.value)}/>
<button onClick={login}>login</button>
</form>
):(
<button onClick={logout}>logout</button>
)}
</div>
);
}
export default App;

Related

Show warning message before unmouting React component

I have a form. I want to show warning before unmounting the form component.
Component is something like this -
import React from "react";
export default function FormComp() {
const sub = (e) => {
e.preventDefault();
const formData = new FormData(e.target);
console.log(formData.get("name"));
//API CALL HERE
};
return (
<div className="test">
<form onSubmit={sub}>
<input name="name" />
<button type="submit">Submit</button>
</form>
</div>
);
}
If the component is unmounted when user goes to a different route, how can i show a warning that form changes will not be saved (along with Yes and No option).As soon as FormComp component is unmounted, form data is cleared.
Are you using react-router? This can be easy with that.
If yes, then you can do something like this:
import { Prompt } from 'react-router'
const FormCompComponent = () => (
<>
<Prompt
when={formIsHalfFilledOut}
message='You have unsaved changes, are you sure you want to leave?'
/>
{/* Component JSX here */}
</>
)
For more details check this out: https://v5.reactrouter.com/core/api/Prompt

React js - useState returns different values inside and outside of a onChange Form function

I would like to know why loginPassword.length and loginPasswordError is different inside and outside of loginFormPasswordHandler
import React, {useState} from 'react';
import './styles.css'
const App = () => {
const [loginPassword, setLoginPassword] = useState('');
const [loginPasswordError, setLoginPasswordError] = useState();
const [submitController, setSubmitController] = useState(false);
const loginFormSubmitHandler = (e) => {
e.preventDefault();
}
const loginFormPasswordHandler = (e) => {
setLoginPassword(e.target.value);
setLoginPasswordError(loginPassword.length < 8);
console.log('login password length is(inside):'+loginPassword.length+' and the state is '+loginPasswordError)
loginPassword.length > 8 ? setSubmitController(true) : setSubmitController(false);
}
console.log('login password length is(outside):'+loginPassword.length+' and the state is '+loginPasswordError)
return(
<React.Fragment>
<div className="form-wrapper">
<form onSubmit={loginFormSubmitHandler}>
<input className={`${loginPasswordError && 'error'}`} type="password" id="password" name="password" placeholder="Password" onChange={loginFormPasswordHandler} />
<div className={`submit-btn ${submitController ? '' : 'disable'}`}>
<input type="submit" />
</div>
</form>
</div>
</React.Fragment>
);
}
export default App;
I know useState re-runs the entire code when the state is changed. But I can't understand this behavior. I am not sure whether this is a Javascript property or React property.
setState is asynchronous, meaning your login password and error state values might not update immediately after you run setLoginPassword and setLoginPasswordError.
The other line below re-runs on every render, so it will output up to date values.
console.log('login password length is(outside):'+loginPassword.length+' and the state is '+loginPasswordError)

ReactJS Form Submission

I am a newbie in ReactJS and I am making a simple contact manager.
In my InputContact component I take name and email of contact and after submission I store in state variables and pass to parent.
To check my state var is updated , i check my console.
The problem is that, after I submit the form after giving data, I only see a blank line in console. After again clicking on submit, then I see my input in console.
My question is
Why I have to click submit twice , in order to see my state variable getting updated ??
My InputContact.js file
import React from 'react'
import { useState } from 'react';
const InputContact = (props)=>{
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const validateInput = (e)=>{
e.preventDefault();
setName(e.target.fname.value);
setEmail(e.target.femail.value);
console.log(name)
props.addContact(name,email);
}
return(
<>
<form onSubmit={validateInput}>
<label>Name
<input type="text" name='fname' ></input>
</label>
<br/>
<label>Email
<input type="text" name='femail' ></input>
</label>
<button type="submit">Save</button>
</form>
</>
)
};
export default InputContact;
My App.js file is
import Header from './components/Header/Header'
import InputContact from './components/InputContact/InputContact';
import { v4 as uuidv4 } from 'uuid';
function App(){
const [contacts, setContacts] = useState([]);
const addContactFn= (name,email)=>{
setContacts([...contacts, {id:uuidv4(), name:name, email:email}]);
}
return(
<>
<Header />
<InputContact addContact = {addContactFn}/>
</>
)
}
export default App; ```
Your setName call is asynchronous. You cannot guarantee that
console.log(name)
right after
setName(e.target.fname.value);
How you are using state isn't really the normal way. You want to use onChange handlers on the inputs to set the state for each name and email, e.g.
onChange={(e)=> setEmail(e.target.value)}
Then onSubmit of your form should refer to the state variables for name and email, not e.target.value
const validateInput = (e)=>{
e.preventDefault();
props.addContact(name,email);
}
To check the updated value, you can use useEffect hook as
import React from 'react'
import { useState, useEffect } from 'react';
.
.
.
useEffect(() => {
console.log('Name', name);
}, [name]);
const validateInput = (e)=>{
e.preventDefault();
setName(e.target.fname.value);
setEmail(e.target.femail.value);
props.addContact(name,email);
}
.
.
.
I do it like this, so the state updates on typing and when you send it, is already updated.
<form onSubmit={validateInput}>
<label>Name
<input type="text" name='fname' onChange={e => setName(e.target.value) ></input>
</label>
<br/>
<label>Email
<input type="text" name='femail' onChange={e => setEmail(e.target.value) ></input>
</label>
<button type="submit">Save</button>
</form>

Run some code in React after multiple async useState setters

I have a functional React component in which I am using useState to manage state. Normally, it's just a form with two fields - code and env - which the user can manually fill out and submit. However, when the component loads, I also want to check any querystring params and if the appropriate ones exist, I want to populate and submit the form automatically. That way, users can bookmark specific form submissions.
The problem I'm having is that, as we all know, useState setters are async, just like setState in class components. As both form fields are controlled by state, setting both values will kick off multiple renders, so where should I put the code to submit the form so that I'm guaranteed that both state updates have completed?
Here is the form:
And here is a simplified, sanitized version of the code I have:
import React, { useState, useEffect } from "react";
import axios from "axios";
import queryString from "query-string";
import Form from "react-bootstrap/Form";
import Button from "react-bootstrap/Button";
import ToggleButtonGroup from "react-bootstrap/ToggleButtonGroup";
import ToggleButton from "react-bootstrap/ToggleButton";
import Card from "react-bootstrap/Card";
/*
* this component will show a spinner or the results from the API when complete
*/
const PortalDisplay = ({ data: portal, isLoading }) => {
if (Object.keys(portal).length === 0 && !isLoading) {
return null;
} else if (isLoading) {
return (
<div>
<p>loading…</p>
</div>
);
} else if (!!portal.id && !isLoading) {
return <div className="card-portal">data goes here</div>;
}
};
/*
* main component
*/
const PortalConfiguration = () => {
const [validated, setValidated] = useState(false);
const [code, setCode] = useState("");
const [env, setEnv] = useState("prod");
const [portalInfo, setPortalInfo] = useState({});
const [isLoading, setIsLoading] = useState(false);
const params = queryString.parse(location.search);
const onSubmitForm = (event) => {
const form = event.currentTarget;
setValidated(true);
if (form.checkValidity() === false) {
event.preventDefault();
event.stopPropagation();
return;
}
//re-initialize
setIsLoading(true);
setPortalInfo({});
axios
.get(`http://www.example.com/api`)
.then((response) => {
setIsLoading(false);
setPortalInfo({ ...response.data, ...{ baseUrl: baseUrl[env] } });
})
.catch((error) => console.log(error));
event.preventDefault();
event.stopPropagation();
};
useEffect(() => {
if (!!params && !!params.portal && !!params.env) {
if (!/^[a-zA-Z]{3}$/.test(params.portal) || (params.env !== "prod" && params.env !== "demo")) {
console.log(`Specified portal parameters {portal: ${params.portal}} and {env: ${params.env}} are incorrect`);
} else {
// this is where i want to set the portal and env states and submit the form
}
}
}, [params.portal]);
return (
<>
<h1>Your Portal Configuration</h1>
<Card>
<Card.Body>
<Form noValidate validated={validated} inline className="form-portal" onSubmit={onSubmitForm}>
<Form.Group className="form-group-code">
<label className="sr-only" htmlFor="code">
Portal Code
</label>
<Form.Control
type="text"
id="code"
value={code}
required
placeholder="Enter Portal Code (e.g. 'FWU')"
maxLength="3"
onChange={(e) => setCode(e.target.value)}
/>
<Form.Control.Feedback type="invalid">Portal Code must be three letters (a-z)</Form.Control.Feedback>
</Form.Group>
<Form.Group>
<ToggleButtonGroup type="radio" name="env" value={env} onChange={(val) => setEnv(val)}>
<ToggleButton type="radio" name="env" value="prod" variant="primary" className="btn-inline">
Production
</ToggleButton>
<ToggleButton type="radio" name="env" value="demo" variant="primary" className="btn-inline">
Demo
</ToggleButton>
</ToggleButtonGroup>
</Form.Group>
<Button variant="secondary" block="true" className="btn-inline" type="submit">
Submit
</Button>
</Form>
</Card.Body>
</Card>
<PortalDisplay data={portalInfo} isLoading={isLoading} env={env} />
</>
);
};
export default PortalConfiguration;
The line which is commented out and says "this is where i want to set the portal and env states and submit the form" is where I know I have querystring params passed and need to set both states, then submit the form.
FWIW, I have considered the usual answer to the question of how to deal with useState's asynchronicity, which is to handle it in useEffect, scoped to the particular state variable you are interested in. The two problems with that is that 1) I have two state variables that need to be updated so I don't think there's any guarantee that they will be updated in the order I called the setters, creating a possible race condition, and 2) I don't want to call this code every time that code or env updates, which can happen when the user manually interacts with the form. I only want it to be auto-submitted when the component detects the querystring upon loading.

Update a component with onChange. React-Hooks

I'm building a dropdown with suggestions that fetch data from an API. The input from the search bar is being stored using setState and it is updated when i change the value in the text input.
The thing is that I'm not managing to update the users lists from the dropdown each time I enter a new character in the text input. Can I somehow force the component to be rendered every time the props change? Or is there a better approach?
import React, {useState, useEffect} from 'react';
import Dropdown from '../Dropdown/Dropdown';
import './SearchBar.css';
// Component created with arrow function making use of hooks
const SearchBar = (props) => {
const [input, setInput] = useState('');
const [dropdownComponent, updateDropdown] = useState(<Dropdown input={input}/>)
useEffect(() => {updateDropdown(<Dropdown input={input}/>)}, [input])
const onChange = (e) => {
setInput(e.currentTarget.value)
updateDropdown(<Dropdown input={input}/>)
console.log("=(")
}
return(
<div className="search-bar">
<input type="text" placeholder={props.placeholder} onChange={onChange}/>
{dropdownComponent}
</div>
)
}
export default SearchBar;
I can't make the problem happen using your code in a simple test, but your onChange does has a problem: It's using input to update the dropdown, but it's not using useCallback to ensure that input isn't stale when it does. Either:
Don't update the dropdown in your onChange, allowing your useEffect callback to do it; or
Use e.target.value instead of input and get rid of the useEffect updating the dropdown; or
Don't memoize the dropdown (e.g., don't put it in state) since you want to update it when the input changes anyway, just render it directly in the JSX
Of those, with what you've shown, #3 is probably the simplest:
const SearchBar = (props) => {
const [input, setInput] = useState('');
const onChange = (e) => {
setInput(e.currentTarget.value);
};
return(
<div className="search-bar">
<input type="text" placeholder={props.placeholder} onChange={onChange}/>
<Dropdown input={input}/>
</div>
);
}
Live Example:
const {useState, useEffect} = React;
function Dropdown({input}) {
return <div>Dropdown for "{input}"</div>;
}
const SearchBar = (props) => {
const [input, setInput] = useState('');
const onChange = (e) => {
setInput(e.currentTarget.value);
};
return(
<div className="search-bar">
<input type="text" placeholder={props.placeholder} onChange={onChange}/>
<Dropdown input={input}/>
</div>
);
}
ReactDOM.render(<SearchBar />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>

Categories

Resources