How to set several state at the same time with useState hook? - javascript

I would like to combine several states and handle them at the same time with useState hook, check the following example which updates some text on user input:
const {useState} = React;
const Example = ({title}) => {
const initialState = {
name: 'John',
age: 25
};
const [{name, age}, setFormState] = useState(initialState);
const handleNameChange = (e) => {
setFormState({
name: e.target.value,
age
});
};
const handleAgeChange = (e) => {
setFormState({
name,
age: e.target.value
})
};
return (
<form onSubmit={e=>e.preventDefault()}>
<input type='text' id='name' name='name' placeholder={name} onChange={handleNameChange} />
<p>The person's name is {name}.</p>
<br />
<label htmlFor='age'>Age: </label>
<input type='text' id='age' name='age' placeholder={age} onChange={handleAgeChange} />
<p>His/her age is {age}.</p>
</form>
);
};
// Render it
ReactDOM.render(
<Example />,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="react"></div>
The code works well, but as you can see I'm using 2 functions to handle name and age separately., which was against my intention to save some code. Is it possible to just use 1 function to change name and age separately? Tried this but obviously it would update both with the same value.
const {useState} = React;
const Example = ({title}) => {
const initialState = {
name: 'John',
age: 25
};
const [{name, age}, setFormState] = useState(initialState);
const handleChange = (e) => {
setFormState({
name: e.target.value,
age: e.target.value
});
};
return (
<form onSubmit={e=>e.preventDefault()}>
<input type='text' id='name' name='name' placeholder={name} onChange={handleChange} />
<p>The person's name is {name}.</p>
<br />
<label htmlFor='age'>Age: </label>
<input type='text' id='age' name='age' placeholder={age} onChange={handleChange} />
<p>His/her age is {age}.</p>
</form>
);
};
// Render it
ReactDOM.render(
<Example />,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="react"></div>

It's not possible to set several different states at the same time with a single useState hook. You can either set them separately,
example:
const [userName, setUserName] = useState('');
const [password, setPassword] = useState('');
or put all of the states into an object, and update that object using the useState hook.
Source: https://daveceddia.com/usestate-hook-examples/
Example:
const [form, setState] = useState({
username: '',
password: ''
});
const updateField = e => {
setState({
...form,
[e.target.name]: e.target.value
});
};
return (
<form onSubmit={printValues}>
<label>
Username:
<input
value={form.username}
name="username"
onChange={updateField}
/>
</label>
<br />
<label>
Password:
<input
value={form.password}
name="password"
type="password"
onChange={updateField}
/>
</label>
<br />
<button>Submit</button>
</form>
);

You have a couple options to get your intended effect.
You could, for example, make a single function factory.
const {useState} = React;
const Example = ({title}) => {
const initialState = {
name: 'John',
age: 25
};
const [{name, age}, setFormState] = useState(initialState);
const handleChange = key => e => {
const newValue = e.target.value
setFormState(oldState => ({
...oldState,
[key]: newValue
}));
};
return (
<form onSubmit={e=>e.preventDefault()}>
<input type='text' id='name' name='name' placeholder={name} onChange={handleChange('name')} />
<p>The person's name is {name}.</p>
<br />
<label htmlFor='age'>Age: </label>
<input type='text' id='age' name='age' placeholder={age} onChange={handleChange('age')} />
<p>His/her age is {age}.</p>
</form>
);
};
// Render it
ReactDOM.render(
<Example />,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="react"></div>
Alternatively, if you need a function factory with more flexibility (e.g. if you were trying to store other state in there), you could use useReducer() instead of useState(), then curry the dispatch function. As a rough, untested example
const reducer = (state, {type, payload}) => {
switch (type) {
case 'name': return {...state, name: payload}
case 'age': return {...state, age: payload}
default: throw new Error()
}
}
const [{name, age}, dispatch] = useReducer(reducer, { name: 'john', age: 25 })
const makeEventListener = type => e => dispatch({type, payload: e.target.value})
// Now, you can use either use makeEventListener, or dispatch() directly
return <input type="text" value={name} onChange={makeEventListener('name'))
Lastly, it is possible to do exactly what you're saying, have one function that'll just update the state no matter who called it, by using of refs. But this is not a recommended approach.

Related

react input form returns undefined

it updates only the lastly typed input box value in the state and other are undefined
i get this in console
Object { Name: undefined, Age: "123", City: undefined }
second time
Object { Name: undefined, Age: undefined, City: "city" }
Form.jsx
import React, {useState} from 'react';
const Form = (props) => {
const [formData, setFormData] = useState({ Name:'', Age:'', City:''});
const infoChange = e => {
const { name,value} = e.target;
setFormData({
[e.target.name]: e.target.value,
})
}
const infoSubmit = e =>{
e.preventDefault();
let data={
Name:formData.Name,
Age:formData.Age,
City:formData.City
}
props.myData(data);
}
return (
<div className="">
<form onSubmit={infoSubmit} autoComplete="off">
<div className="form-group mb-6">
<label className="">Name:</label>
<input type="text" onChange={infoChange} name="Name" value={formData.Name} className=""placeholder="Enter Name" />
</div>
<div className="form-group mb-6">
<label className="">City:</label>
<input type="text" onChange={infoChange} name="City" value={formData.City} className=""
placeholder="Enter Age" />
</div>
<button type="submit" className="">Submit</button>
</form>
</div>
);
};
export default Form;
App.jsx
this is App.jsx file, here i get the data prop and display it in console.log
import React from 'react';
import Form from './components/Form';
import Table from './components/Table';
const App = () => {
const create = (data) => {
console.log(data);
}
return (
<div className='flex w-full'>
<div className=''>
<Form myData={create} />
</div>
<div className=''>
<Table />
</div>
</div>
);
};
export default App;
You're stomping the previous state with the most recent change. If you want to preserve the existing state you have to include it in the update.
setFormData({
...formData,
[e.target.name]: e.target.value,
})
with react-hooks you need to set the entire object again.
const [formData, setFormData] = useState({ Name:'', Age:'', City:''});
const infoChange = e => {
const { name,value} = e.target;
setFormData({
// spread the current values here
...formData,
// update the current changed input
[name]: value,
})
or, even better IMHO. You have one state for each prop
const [name, setName] = useState('');
const [age, setAge] = useState('');
const [city, setCity] = useState('');
// ...
<input onChange={({target: {value}}) => setName(value)} />
<input onChange={({target: {value}}) => setAge(value)} />
<input onChange={({target: {value}}) => setCity(value)} />
Change this
const infoChange = e => {
const { name,value} = e.target;
setFormData({...formData
[e.target.name]: e.target.value,
})
}

Using regex with react hooks

I am basically trying to save the phone number entered by the user without braces, spaces or dashes but I somehow fail to do that. I am calling the regex after submitting the form in handleSubmit function through the setting of state and it prints out (and renders) without any change. Any idea what went wrong?
import React, { useContext, useState, useEffect } from "react";
import DataContext from "../store/data-context";
function Form() {
const [name, setName] = useState("");
const [secName, setSecName] = useState("");
const [tel, setTel] = useState("");
const [note, setNote] = useState("");
const [state, setState] = useState({
name: "",
secName: "",
tel: "",
note: "",
});
const { dispatchDataState } = useContext(DataContext);
const handleSubmit = (e) => {
e.preventDefault();
setTel((tel)=>tel.replace(/[^+\d]+/g, ""))
console.log(name);
dispatchDataState({ type: "ADD_DATA", payload: state });
setState(
{
name: "",
secName: "",
tel: "",
note: "",
}
)
console.log(state);
};
return (
<div>
<form onSubmit={handleSubmit}>
<label>
Jméno
<input
type="text"
required
value={state.name}
onChange={(e) => setState({ ... state, name: e.target.value })}
/>
</label>
<label>
Příjmení
<input
type="text"
required
value={state.secName}
onChange={(e) => setState({ ... state, secName: e.target.value })}
/>
</label>
<label>
Telefonní číslo
<input
type="text"
required
value={state.tel}
onChange={(e) => setState({ ... state, tel: e.target.value })}
/>
</label>
<label>
Poznámka
<input
type="text"
value={state.note}
onChange={(e) => setState({ ... state, note: e.target.value })}
/>
</label>
<input type="submit" value="Odeslat" />
</form>
</div>
);
}
export default Form;

how to sum two inputs with React hooks?

i’m trying to sum two inputs and give a result with a button , i have defined the state hooks and they work but i don’t know how to pass the current state to a function and sum it.
Can you please help me?
i’m a beginner
here’s my code:
import React from 'react';
export default function Suma (){
//hook defined
const [input, setInput] = React.useState({
num1: "",
num2: "",
});
//handle input change
const handleInput = function(e){
setInput({
...input,
[e.target.name]: e.target.value
});
};
//suma function
const suma = function(){
}
return (
<div>
<input onChange={handleInput} name="num1" value={input.num1} type="text"></input>
<input onChange={handleInput} name="num2" value={input.num2} type="text"></input>
<button>+</button>
<span>resultado</span>
</div>
)
};
If you only want to show the result on click, I think this should be enough
export default function Suma (){
//hook defined
const [input, setInput] = React.useState({
num1: "",
num2: "",
});
const [result, setResult] = React.useState("")
//handle input change
const handleInput = function(e){
setInput({
...input,
[e.target.name]: e.target.value
});
};
//suma function
const suma = function(){
const { num1, num2 } = input;
setResult(Number(num1) + Number(num2));
}
return (
<div>
<input onChange={handleInput} name="num1" value={input.num1} type="number"></input>
<input onChange={handleInput} name="num2" value={input.num2} type="number"></input>
<button onclick={suma}>+</button>
<span>resultado: {result}</span>
</div>
)
};
import React from 'react';
export default function Suma (){
//hook defined
const [input, setInput] = React.useState({
num1: "",
num2: "",
});
const [sum, setSum] = React.useState(undefined)
useEffect(() => {
setSum(parseInt(input.num1) + parseInt(input.num2))
}, [input])
//handle input change
const handleInput = function(e){
setInput({
...input,
[e.target.name]: e.target.value
});
};
return (
<div>
<input onChange={handleInput} name="num1" value={input.num1} type="text"></input>
<input onChange={handleInput} name="num2" value={input.num2} type="text"></input>
<button>+</button>
{sum !== undefined && <span>{sum}</span>}
</div>
)
};
function AddForm() {
const [sum, setSum] = useState(0);
const [num, setNum] = useState(0);
function handleChange(e) {
setNum(e.target.value);
}
function handleSubmit(e) {
setSum(sum + Number(num));
e.preventDefault();
}
return <form onSubmit={handleSubmit}>
<input type="number" value={num} onChange={handleChange} />
<input type="submit" value="Add" />
<p> Sum is {sum} </p>
</form>;
}

What is best way to create forms in react?

I am beginner in react. I have following code:
import React, { useState, useEffect } from 'react';
import { Card, Form, Button } from 'react-bootstrap';
import Axios from 'axios'
export function StudentForm({ student, onSuccess, onError, setState }) {
const url = `http://localhost:9899/api/StudentData`;
const intialStudent = { Firstname: '', Middlename: '', Lastname: '', DOB: '', Gender: '' };
const [Student, setStudent] = useState(intialStudent);
useEffect(() => {
setStudent(student ? student : intialStudent);
}, [student]);
const SaveData = function (studentData) {
if (student._id) {
Axios.post(url, { ...studentData }, { headers: { 'accept': 'application/json' } })
.then(res => {
setState(null);
onSuccess(res);
})
.catch(error => {
alert('Error To Edit data');
});
}
else {
Axios.post(url, studentData, { headers: { 'accept': 'application/json' } })
.then(res => {
setState(null);
onSuccess(res);
})
.catch(err => onError(err));
}
}
return (
<Card>
<Card.Header><h5>{student ? "Edit" : "Add"} Student</h5></Card.Header>
<Card.Body>
<Form onSubmit={(e) => { e.preventDefault(); SaveData(Student); }}>
<Form.Group><Form.Control type="text" name="Firstname" placeholder="Firstname" value={Student.Firstname} onChange={e => { setStudent({ ...Student, Firstname: e.target.value }) }} /></Form.Group>
<Form.Group><Form.Control type="text" name="Middlename" placeholder="Middlename" value={Student.Middlename} onChange={e => setStudent({ ...Student, Middlename: e.target.value })} /></Form.Group>
<Form.Group><Form.Control type="text" name="Lastname" placeholder="Lastname" value={Student.Lastname} onChange={e => setStudent({ ...Student, Lastname: e.target.value })} /></Form.Group>
<Form.Group><Form.Control type="date" name="DOB" placeholder="DOB" value={Student.DOB} onChange={e => setStudent({ ...Student, DOB: e.target.value })} /></Form.Group>
<Form.Group><Form.Control type="text" name="Gender" placeholder="Class" value={Student.Gender} onChange={e => setStudent({ ...Student, Gender: e.target.value })} /></Form.Group>
<Button variant="primary" type="submit">Submit</Button>
</Form>
</Card.Body>
</Card>
);
}
In above code I am setting state on change event on each field. So it will render again and again when I change any of the field.If it is large form so it may take a lot of time to re-render so is there a better way to create to handle this kind of situation, or any best practices for using forms with react?
You can use only one Function for all onChanges. Looks like this;
<Form.Group>
<Form.Control
type="text"
name="Firstname"
placeholder="Firstname"
value={Student.Firstname}
onChange={handleChange}
/>
</Form.Group>
And this is your handleChange function;
const handleChange = e => {
const {name, value} = e.target
setValues({...values, [name]: value})
}
This is your state;
const [values, setValues] = useState({
Firstname: "",
Middlename: "",
Lastname: "",
DOB: "",
Gender: ""
})
I think this way is more effective with less code.
Managing forms in react is a task complex enough to delegate it to a library.
Alo, big forms are not a good candidate for functional components because the problems that you outlined. You can, of course, spend the time to tune it up, but I think the effort may not worth the benefit.
My personal recommendation is to try one of the many react form libraries out there. One that I personally like is Formik
If you want to manage the form yourself I recommend to encapsulate the form on stateful component and use the key property for easier reset when you need it.
Another alternative will be the usage of memoization, for example using react.memo. But that will not guarantee success unless your data has the proper shape. This means, simple values that can be compared between themselves, not arrays, not functions, not objects.
You have to re render the form when an input changed but you don't need to re render every input when you make sure the onChange function doesn't change reference on every render and your input is a pure component (using React.memo for functional component and inherit from React.PureComponent for class components).
Here is an example of optimized inputs.
const {
useEffect,
useCallback,
useState,
memo,
useRef,
} = React;
function App() {
return <StudentForm />;
}
//put initial student here so it doesn't change reference and quits the linter
// in useEffect
const initialStudent = {
Firstname: '',
Middlename: '',
};
function StudentForm({ student }) {
const [Student, setStudent] = useState(initialStudent);
//useCallback so onChange is not re created and causes re rendering
// of components that didn't change
const onChange = useCallback(
(key, value) =>
setStudent(student => ({ ...student, [key]: value })),
[]
);
useEffect(() => {
setStudent(student ? student : initialStudent);
}, [student]);
const SaveData = function(studentData) {
console.log('saving data:', studentData);
};
return (
<form
onSubmit={e => {
e.preventDefault();
SaveData(Student);
}}
>
<InputContainer
type="text"
name="Firstname"
placeholder="Firstname"
value={Student.Firstname}
stateKey="Firstname" //provide state key
onChange={onChange}
/>
<InputContainer
type="text"
name="Middlename"
placeholder="Middlename"
value={Student.Middlename}
stateKey="Middlename"
onChange={onChange}
/>
<button type="submit">Submit</button>
</form>
);
}
//make this a pure component (does not re render if nothing changed)
const InputContainer = memo(function InputContainer({
type,
name,
placeholder,
value,
onChange,
stateKey,
}) {
const rendered = useRef(0);
rendered.current++;
return (
<div>
<div>{rendered.current} times rendered.</div>
<input
type={type}
name={name}
value={value}
placeholder={placeholder}
onChange={e =>
//pass state key and new value to onChange
onChange(stateKey, e.target.value)
}
/>
</div>
);
});
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

React Form with form data held as Object within Form state - reset not working

I have a React Form app with name and description fields.
The form data is held in a local state object using Hooks:
const [data,setData] = useState({name: '', description: ''}).
The <Form /> element creates inputs and passes their value using <Field initialValue ={data.name} />
Within the <Field /> element, this initialValue is passed to the state, which controls the input value (updated onChange):
const [value,setValue] = useState(initialValue).
But if I reset the data object (see handleResetClick function), the inputs don't clear (even though the data object clears). What am I doing wrong? I thought that changing the data would cause a re-render and re-pass initialValue, resetting the input.
Codepen example here - when I type in the inputs, the data object updates, but when I click Clear, the inputs don't empty.
function Form() {
const [data, setData] = React.useState({name: '', description: ''});
React.useEffect(() => {
console.log(data);
},[data]);
const onSubmit = (e) => {
// not relevant to example
e.preventDefault();
return;
}
const handleResetClick = () => {
console.log('reset click');
setData({name: '', description: ''})
}
const onChange = (name, value) => {
const tmpData = data;
tmpData[name] = value;
setData({
...tmpData
});
}
return (
<form onSubmit={onSubmit}>
<Field onChange={onChange} initialValue={data.name} name="name" label="Name" />
<Field onChange={onChange} initialValue={data.description} name="description" label="Description" />
<button type="submit" className="button is-link">Submit</button>
<button onClick={handleResetClick} className="button is-link is-light">Clear</button>
</form>
)
}
function Field(props) {
const {name, label, initialValue, onChange} = props;
const [value, setValue] = React.useState(initialValue);
return (
<div>
<div className="field">
<label className="label">{label}</label>
<div className="control">
<input
name={name}
className="input"
type="text"
value={value}
onChange={e => {
setValue(e.target.value)
onChange(name, e.target.value)
}}
/>
</div>
</div>
</div>
)
}
class App extends React.Component {
render() {
return (
<div className="container">
<Form />
</div>
);
}
}
ReactDOM.render(
<App />,
document.getElementById('app')
)
On handleResetClick you change the data state of Form, but it doesn't affect its children.
Try adding a listener for initialValue change with useEffect:
function Field(props) {
const { name, label, initialValue, onChange } = props;
const [value, setValue] = React.useState(initialValue);
useEffect(() => {
setValue(initialValue);
}, [initialValue]);
return ...
}
You may be better off having Field as a controlled component (ie it's state is managed by the parent component rather than maintaining its own state). In this example I've swapped in value instead of initialValue and simply passed that down as props to the field. onChange then calls the parent method and updates the state there (which is automatically passed back down to the field when it renders):
const { useState, useEffect } = React;
function Form() {
const [data, setData] = React.useState({
name: '',
description: ''
});
useEffect(() => {
console.log(data);
}, [data]);
const onSubmit = (e) => {
e.preventDefault();
return;
}
const handleResetClick = () => {
setData({name: '', description: ''})
}
const onChange = (e) => {
const { target: { name, value } } = e;
setData(data => ({ ...data, [name]: value }));
}
return (
<form onSubmit={onSubmit}>
<Field onChange={onChange} value={data.name} name="name" label="Name" />
<Field onChange={onChange} value={data.description} name="description" label="Description" />
<button type="submit" className="button is-link">Submit</button>
<button onClick={handleResetClick} className="button is-link is-light">Clear</button>
</form>
)
}
function Field(props) {
const {name, label, value, onChange} = props;
return (
<div>
<div className="field">
<label className="label">{label}</label>
<div className="control">
<input
name={name}
className="input"
type="text"
value={value}
onChange={onChange}
/>
</div>
</div>
</div>
)
}
function App() {
return (
<div className="container">
<Form />
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Categories