None of the other SO answers have helped, so I think I'm missing something conceptually.
I have a Parent (Wrapper) component, and a Child (Input) component. The Parent passes a function down to the child:
const Wrapper = () => {
const [dictionary, setDictionary] = useState([{ word: "init", definition: "def init" }]);
const handleWordChange = (e, value) => {
e.preventDefault();
/// IS NEVER TRIGGERED
};
return (
<Input setDictionary={{ setDictionary }} onChange={handleWordChange} />
)
}
The child component handles its own state, but is supposed to update the Parent props by calling the setDictionary function:
const Input = props => {
const [definition, setDefinition] = useState("");
const [word, setWord] = useState("");
const handleSubmit = e => {
const { setDictionary } = props.setDictionary;
e.preventDefault();
setDictionary([{ word, definition }]);
}
return (
<form onSubmit={handleSubmit}>
<input
name='word'
onChange={e => setWord(e.target.value)}
onFocus={() => setWord("")}
placeholder='Word'
type='text'
value={word}
/>
<input
name='definition'
onChange={e => setDefinition(e.target.value)}
onFocus={() => setDefinition("")}
placeholder='Definition'
type='text'
value={definition}
/>
<input type='submit' value='Submit' />
</form>
)
}
Other answers I have seen suggest to pass a callback to the Child (setDictionary), but the onChange handler is never called on change. I've also tried to use onSubmit instead.
How do I successfully update dictionary?
I know the above creates a dependency of the Child to the Parent, is there a better programmatic way to achieve this, considering that I eventually need to pass down dictionary to a 2nd child?
You cannot assign child's onChange() event handler this way.
Instead, you refer to child event handlers as props and bind parent callbacks to those props.
The concept is known as lifting state up.
Complete live-demo of your use case you may find below:
const { render } = ReactDOM,
{ useState } = React
const Input = ({onInput}) => {
const [word, setWord] = useState(''),
[definition, setDefinition] = useState('')
return (
<form onSubmit={e => (e.preventDefault(), onInput(word, definition))}>
<label>
Word:
<input onChange={({target:{value}}) => setWord(value)} />
</label>
<label>
Definition:
<input onChange={({target:{value}}) => setDefinition(value)} />
</label>
<input type="submit" value="Submit" />
</form>
)
}
const List = ({list}) => (
<ul>
{
list.map(({word,definition},key) => <li {...{key}}><strong>{word}</strong> - {definition}</li>)
}
</ul>
)
const Parent = () => {
const [dictionary, setDictionary] = useState([]),
onDicionaryItemSubmit = (word,definition) => setDictionary([...dictionary, {word,definition}])
return (
<div>
<Input onInput={onDicionaryItemSubmit} />
<List list={dictionary} />
</div>
)
}
render (
<Parent />,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><div id="root"></div>
//there is not props.onChange here in this component
const Input = props => {
const [definition, setDefinition] = useState("");
const [word, setWord] = useState("");
const handleSubmit = e => {
const { setDictionary } = props.setDictionary;
e.preventDefault();
setDictionary([{ word, definition }]);
//like here
props.onChange(any arguments);
}
return (
<form onSubmit={handleSubmit}>
<input
name='word'
onChange={e => {
setWord(e.target.value)
props.onChange();
}}
onFocus={() => setWord("")}
placeholder='Word'
type='text'
value={word}
/>
<input
name='definition'
onChange={e => {
setDefinition(e.target.value)
props.onChange();
}}
onFocus={() => setDefinition("")}
placeholder='Definition'
type='text'
value={definition}
/>
<input type='submit' value='Submit' />
</form>
)
}
Use parent onChange() method in your Input Component than it will be triggered if you didn't call that method than how it will triggered i hope this will help you.
You're not even triggering onChange passed to component
<Input setDictionary={{ setDictionary }} onChange={handleWordChange} />
you have to do exactly as you named the prop like props.onChange
//there is no props.onChange here in this component
const Input = props => {
const [definition, setDefinition] = useState("");
const [word, setWord] = useState("");
const handleSubmit = e => {
const { setDictionary } = props.setDictionary;
e.preventDefault();
setDictionary([{ word, definition }]);
//like here
props.onChange(any arguments);
}
return (
<form onSubmit={handleSubmit}>
<input
name='word'
onChange={e => setWord(e.target.value)}
onFocus={() => setWord("")}
placeholder='Word'
type='text'
value={word}
/>
<input
name='definition'
onChange={e => setDefinition(e.target.value)}
onFocus={() => setDefinition("")}
placeholder='Definition'
type='text'
value={definition}
/>
<input type='submit' value='Submit' />
</form>
)
}
If i rename
<Input setDictionary={{ setDictionary }} onInputChanged={handleWordChange} />
i'd call it like
const handleSubmit = e => {
const { setDictionary } = props.setDictionary;
e.preventDefault();
setDictionary([{ word, definition }]);
//like here
props.onInputChanged(any arguments);
}
Error:- not calling onChange function in <Input/> and setting state of dictionary in <Wrapper />. This is the working solution of your query.
const {useState} = React;
const Wrapper = () => {
const [dictionary, setDictionary] = useState([
{ word: "computer", definition: "an electronic device for storing and processing data" }
]);
const handleWordChange = (e, value) => {
e.preventDefault();
let updateDictionary = [...dictionary];
updateDictionary.push(value);
setDictionary(updateDictionary);
// console.log(updateDictionary);
/// IS NEVER TRIGGERED
};
return (
<React.Fragment>
<Input onChange={handleWordChange} />
{dictionary.length > 0 ? (
<table>
<tr>
<th>WORD</th>
<th>DEFINITION</th>
</tr>
{dictionary.map(datum => (
<tr>
<td>{datum.word}</td>
<td>{datum.definition}</td>
</tr>
))}
</table>
) : null}
</React.Fragment>
);
};
const Input = props => {
const [definition, setDefinition] = useState("");
const [word, setWord] = useState("");
const handleSubmit = e => {
e.preventDefault();
props.onChange(e, { word, definition });
};
return (
<form onSubmit={handleSubmit}>
<input
name="word"
onChange={e => setWord(e.target.value)}
onFocus={() => setWord("")}
placeholder="Word"
type="text"
value={word}
/>
<input
name="definition"
onChange={e => setDefinition(e.target.value)}
onFocus={() => setDefinition("")}
placeholder="Definition"
type="text"
value={definition}
/>
<input type="submit" value="Submit" />
</form>
);
};
ReactDOM.render(<Wrapper />, document.getElementById('root'));
table,
th,
td {
border: 1px solid black;
}
table {
margin-top: 20px;
}
<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>
Related
I want to pass the states into a object, because after I'll post an API. How can I store the states into the object? If I call the function setAll() causes a infinite loop.
Like an example the console.log returns infinite times in the console.
import React,{useState} from 'react';
import Signup1 from '../pages/Signup/steps/Signup1';
import Signup2 from '../pages/Signup/steps/Signup2';
import Signup3 from '../pages/Signup/steps/Signup3';
function Forms() {
const [alert,setAlert]=useState();
const [page,setPage] = useState(0);
const [formData,setFormData]= useState({
email :"",
FirstName:"",
LastName:"",
CountryNumber:"",
Number:"",
JobTitle:"",
People:"",
Company:"",
TaxID:""
})
const [email,setEmail] = useState("");
const [firstName,setFirstName] = useState("");
const [lastName,setLastName] = useState("");
const [countryNumber,setCountryNumber] = useState("");
const [number,setNumber] = useState("");
const [jobTitle,setJobTitle] = useState("");
const [people,setPeople] = useState("");
const [company,setCompany] = useState("");
const [taxID,setTaxID] = useState("");
const setAll = ()=>{
setFormData({
email :email,
FirstName:firstName,
LastName:lastName,
CountryNumber:countryNumber,
Number:number,
JobTitle:jobTitle,
People:people,
Company:company,
TaxID:taxID
})
console.log(formData)
}
const PageDisplay = () =>{
if(page===0){
return <Signup1
email={email}
setEmail={setEmail}
setPage={setPage}
/>;
}
if(page===1){
return <Signup2
firstName={firstName}
setFirstName={setFirstName}
lastName={lastName}
setLastName={setLastName}
countryNumber={countryNumber}
setCountryNumber={setCountryNumber}
number={number}
setNumber={setNumber}
jobTitle={jobTitle}
setJobTitle={setJobTitle}
setPage={setPage}/>;
}
if(page===2){
return <Signup3
people={people}
setPeople={setPeople}
company={company}
setCompany={setCompany}
taxID={taxID}
setTaxID={setTaxID}
page={page}
setPage={setPage}
/>
}
if(page===3){
return( <div>sucess</div>)
}
}
/*
if(page===3){
setAll()
return<div>sucess</div>
}
*/
return (
<div className='Forms'>
{PageDisplay()}
</div>
);
}
export default Forms;
Don't call setAll() like this, functions that set state like this should be called on the occurrence of an event (like an onClick) or in a useEffect, whenever you set a state in the component body, the component re-renders which triggers another state set, thus causing an infinite loop, in your case, it's better to set the form body when you call the API. Setting states is a costly operation, so it's better to set form body state finally when you are going to hit the API.
Making a working example always helps someone to create a better and more precise answer to your question. This is how you set your data.
Also if you want to send this data to another component/function you can do it by passing formData object, you don't need to set it one by one.
Please try to ask any questions you may have.
To see the document clearly, expand the snippet to fill the entire page.
const {useEffect, useState} = React;
const Forms = () => {
const [alert,setAlert]=useState();
const [page,setPage] = useState(0);
const [formData,setFormData]= useState({
email :"",
FirstName:"",
LastName:"",
CountryNumber:"",
Number:"",
JobTitle:"",
People:"",
Company:"",
TaxID:""
})
const [email,setEmail] = useState("");
const [firstName,setFirstName] = useState("");
const [lastName,setLastName] = useState("");
const [countryNumber,setCountryNumber] = useState("");
const [number,setNumber] = useState("");
const [jobTitle,setJobTitle] = useState("");
const [people,setPeople] = useState("");
const [company,setCompany] = useState("");
const [taxID,setTaxID] = useState("");
useEffect(() => {
console.log(formData);
}, [formData]);
useEffect(() => {
setAll();
}, [email, firstName, lastName, countryNumber, number, jobTitle, people, company, taxID]);
const setAll = () => {
setFormData({
email :email,
FirstName:firstName,
LastName:lastName,
CountryNumber:countryNumber,
Number:number,
JobTitle:jobTitle,
People:people,
Company:company,
TaxID:taxID
})
}
const Signup2 = ({data}) => {
return <div>Email: {data.email}</div>;
}
const Signup3 = ({data}) => {
return "Signup3 Page Content";
}
const PageDisplay = () =>{
if(page===0){
return <form>
<input onChange={(e) => setEmail(e.target.value)} value={email} type="text" />
<input onChange={(e) => setFirstName(e.target.value)} value={firstName} type="text" />
<input onChange={(e) => setLastName(e.target.value)} value={lastName} type="text" />
<input onChange={(e) => setCountryNumber(e.target.value)} value={countryNumber} type="text" />
<input onChange={(e) => setNumber(e.target.value)} value={number} type="text" />
<input onChange={(e) => setJobTitle(e.target.value)} value={jobTitle} type="text" />
<input onChange={(e) => setPeople(e.target.value)} value={people} type="text" />
<input onChange={(e) => setCompany(e.target.value)} value={company} type="text" />
<input onChange={(e) => setTaxID(e.target.value)} value={taxID} type="text" />
</form>;
}
if(page===1){
return <Signup2 data={formData} />;
}
if(page===2){
return <Signup3 data={formData} />
}
if(page===3){
return( <div>sucess</div>)
}
}
return (
<div>
<button onClick={() => setAll()}>Trigger SetAll</button>
<div>Current Page {page + 1}</div>
<button onClick={() => setPage(0)}>Page 1</button>
<button onClick={() => setPage(1)}>Page 2</button>
<button onClick={() => setPage(2)}>Page 3</button>
<div>{PageDisplay()}</div>
</div>
);
};
ReactDOM.render(
<Forms />,
document.getElementById("root")
);
input[type="text"]{
display:block;
}
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root"></div>
I am trying to print real-time user input from input tags by the user. I am even getting multiple user inputs from the child element to the parent element as a form of an object using useState. But whenever the user tries to fill the second input field, then the first input is re-render and it's replaced by the primary state which is an empty string.
code:-
Child Element
import React, { useState } from "react";
const Child = (props) => {
const [name, setName] = useState("");
const [age, setAge] = useState("");
let userData = {
name: "",
age: ""
};
const nameChangeHandler = (e) => {
setName(e.target.value);
userData.name = e.target.value;
};
const ageChangeHandler = (e) => {
setAge(e.target.value);
userData.age = e.target.value;
};
const formOnChageHandler = (e) => {
e.preventDefault();
props.getData(userData);
};
const fromOnSubmitHandler = (e) => {
e.preventDefault();
};
return (
<React.Fragment>
<form onChange={formOnChageHandler} onSubmit={fromOnSubmitHandler}>
<label htmlFor="name">Name:</label>
<input
id="name"
placeholder="Enter Name"
value={name}
onChange={nameChangeHandler}
/>
<br />
<label htmlFor="age">Age:</label>
<input
id="age"
placeholder="Enter Age"
value={age}
onChange={ageChangeHandler}
/>
</form>
</React.Fragment>
);
};
export default Child;
Parent Element
import React, { useState } from "react";
import Child from "./components/Child";
function App() {
const [name, setName] = useState("");
const [age, setAge] = useState("");
let userData = (data) => {
setName(data.name);
setAge(data.age);
};
return (
<React.Fragment>
<Child getData={userData} />
<h1>Your name is:{name}</h1>
<h1>Your age is:{age}</h1>
</React.Fragment>
);
}
export default App;
code sandbox Link- https://codesandbox.io/s/from-traversing-child-to-parent-to-another-child-ynwyqd?file=/src/App.js:0-441
How I can get both data being reflected by using onChange from child to parent element?
I suggest you accumulate the user data in one state.
Like this.
const [user, setUser] = useState({
name: "",
age: null
});
And put the state on the parent and pass as props, also just have one handleChange function to update both the name and age by the input id
Child.js
import React, { useState } from "react";
const Child = ({ user, setUser }) => {
const handleChange = (e) => {
setUser((prev) => ({
...prev,
[e.target.id]: e.target.value
}));
};
const formOnChageHandler = (e) => {
e.preventDefault();
};
const fromOnSubmitHandler = (e) => {
e.preventDefault();
};
return (
<React.Fragment>
<form onChange={formOnChageHandler} onSubmit={fromOnSubmitHandler}>
<label htmlFor="name">Name:</label>
<input
id="name"
placeholder="Enter Name"
value={user.name}
onChange={handleChange}
/>
<br />
<label htmlFor="age">Age:</label>
<input
id="age"
placeholder="Enter Age"
value={user.age}
onChange={handleChange}
/>
</form>
</React.Fragment>
);
};
export default Child;
App.js
import React, { useState } from "react";
import Child from "./components/Child";
function App() {
const [user, setUser] = useState({
name: "",
age: null
});
return (
<React.Fragment>
<Child user={user} setUser={setUser} />
<h1>Your name is:{user.name}</h1>
<h1>Your age is:{user.age}</h1>
</React.Fragment>
);
}
export default App;
CODESANDBOX
Try using the child component as below,
import React, { useState } from "react";
const Child = (props) => {
const [name, setName] = useState("");
const [age, setAge] = useState("");
let userData = {
name: name, // the value "name" comes for the local state will be listen to the onChange event every time
age: age // same applies here as well
};
const nameChangeHandler = (e) => {
setName(e.target.value);
};
const ageChangeHandler = (e) => {
setAge(e.target.value);
};
const formOnChageHandler = (e) => {
e.preventDefault();
props.getData(userData);
};
const fromOnSubmitHandler = (e) => {
e.preventDefault();
};
return (
<React.Fragment>
<form onSubmit={fromOnSubmitHandler}>
<label htmlFor="name">Name:</label>
<input
id="name"
placeholder="Enter Name"
value={name}
onChange={nameChangeHandler}
/>
<br />
<label htmlFor="age">Age:</label>
<input
id="age"
placeholder="Enter Age"
value={age}
onChange={ageChangeHandler}
/>
</form>
</React.Fragment>
);
};
export default Child;
just use simple one state to manage data. just take a look below example component created from your child component.
we simply use single object state.
use name prop as key to store value in state.
import React, { useState } from "react";
const Child = (props) => {
const [formData, setFormData] = useState({});
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setFormData({
...formData,
[e.target.name]: e.target.value,
});
};
const formOnChageHandler = (e) => {
e.preventDefault();
props.getData(userData);
};
const fromOnSubmitHandler = (e) => {
e.preventDefault();
};
return (
<React.Fragment>
<form onChange={formOnChageHandler} onSubmit={fromOnSubmitHandler}>
<label htmlFor="name">Name:</label>
<input
id="name"
placeholder="Enter Name"
value={name}
onChange={handleChange}
name="name"
/>
<br />
<label htmlFor="age">Age:</label>
<input
id="age"
placeholder="Enter Age"
value={age}
onChange={handleChange}
name="age"
/>
</form>
</React.Fragment>
);
};
export default Child;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
it happens because you dont watch to the state, try this:
Child.js
import React, { useState } from "react";
const Child = (props) => {
const [name, setName] = useState("");
const [age, setAge] = useState("");
let userData = {
name,
age
};
const nameChangeHandler = (e) => {
setName(e.target.value);
userData.name = e.target.value;
};
const ageChangeHandler = (e) => {
setAge(e.target.value);
userData.age = e.target.value;
};
const formOnChageHandler = (e) => {
e.preventDefault();
props.getData(userData);
};
const fromOnSubmitHandler = (e) => {
e.preventDefault();
};
return (
<React.Fragment>
<form onChange={formOnChageHandler} onSubmit={fromOnSubmitHandler}>
<label htmlFor="name">Name:</label>
<input
id="name"
placeholder="Enter Name"
value={name}
onChange={nameChangeHandler}
/>
<br />
<label htmlFor="age">Age:</label>
<input
id="age"
placeholder="Enter Age"
value={age}
onChange={ageChangeHandler}
/>
</form>
</React.Fragment>
);
};
export default Child;
Try this, i check in codesandbox and it works:
In App.js:
import React, { useState } from "react";
import Child from "./components/Child";
function App() {
const [name, setName] = useState("");
const [age, setAge] = useState("");
return (
<React.Fragment>
<Child name={name} age={age} setName={setName} setAge={setAge} />
<h1>Your name is:{name}</h1>
<h1>Your age is:{age}</h1>
</React.Fragment>
);
}
export default App;
In Child.js:
import React, { useState } from "react";
const Child = ({ name, age, setName, setAge }) => {
const nameChangeHandler = (e) => {
setName(e.target.value);
};
const ageChangeHandler = (e) => {
setAge(e.target.value);
};
const fromOnSubmitHandler = (e) => {
e.preventDefault();
};
return (
<React.Fragment>
<form onSubmit={fromOnSubmitHandler}>
<label htmlFor="name">Name:</label>
<input
id="name"
placeholder="Enter Name"
value={name}
onChange={nameChangeHandler}
/>
<br />
<label htmlFor="age">Age:</label>
<input
id="age"
placeholder="Enter Age"
value={age}
onChange={ageChangeHandler}
/>
</form>
</React.Fragment>
);
};
export default Child;
If you want to improve your code, you can research and use state management like: redux, zustand, react context,...
Hope it useful for you.
I have multiple rows, each row contains two text inputs and a button. When user focuses on one of the inputs, the button should be shown. When elements lose focus, the button should become invisible once again. My best attempt:
const Input = ({inputRef}) => {
return (
<>
<h1>Input</h1>
<input type="text" ref={inputRef}/>
</>
)
}
export default () => {
const firstRef = useRef(null);
const secondRef = useRef(null);
const it = useRef(null);
const [editing, setEditing] = useState(false);
function handleClick(e) {
firstRef.current.focus();
}
function handleSave() {
console.log("saving!");
}
function checkFocus(e) {
if (!it.current.contains(document.activeElement)) {
setEditing(false);
} else {
setEditing(true);
}
}
useEffect(() => {
document.body.addEventListener("focus", checkFocus, true);
return () => {
document.body.removeEventListener("focus", checkFocus, true);
}
}, []);
return (
<div ref={it}>
<Input inputRef={firstRef}/>
<Input inputRef={secondRef}/>
<button type="button" onClick={handleSave} style={{visibility: editing ? "visible" : "hidden"}}>Save</button>
<button type="button" onClick={handleClick}>Edit</button>
</div>
)
}
Is there any better/more elegant and efficient way of achieving this?
Here's a solution to what you're attempting using only CSS, which in my opinion makes it more elegant (and ever so slightly more performant, but really this is negligible).
https://codepen.io/danny_does_stuff/pen/QWMprMJ
<div>
<input id="input1" />
<input id="input2" />
<button id="save-button">Save</button>
<button id="edit-button">Edit</button>
</div>
<style>
input#input2:focus + button#save-button {
visibility: hidden;
}
</style>
If you wanted to do it in a more React way, you could do what Marco B suggested in his answer
You can use onBlur and onFocus events.
This should work as expected, just adapt the logic on your component
EDIT
Edited the onBlur method.
const INITIAL_STATE = {
input: ''
}
export default function App() {
const [show, setShow] = useState(false);
const [value, setValue] = useState(INITIAL_STATE);
const handleChange = (e) => {
const { value, name } = e.target;
setValue(prevState => ({ ...prevState, [name]: value }))
}
const onBlur = () => {
if (!value.input) {
setShow(false)
}
}
return (
<>
<Input name="input" onChange={handleChange} value={value.input} onFocus={() => setShow(true)} onBlur={onBlur} />
{show && <button>TEST</button>}
</>
);
}
const Input = (props) => {
return (
<>
<h1>Input</h1>
<input {...props} type="text" />
</>
);
};
I'm making a recipe box App, and I decided to start by my handleTitle and handleInput methods. I was able to pull that out, but now I want to make an array of objects (the objects contain the title and the description) and then I would map through this array and display the data.
but my handleSubmit function is not working the way I wanted. I want the user to be able to write several titles and descriptions, and those will keep being added to the recipes array in the state. Take a look at the code:
import React, { useState } from "react";
import "./styles.css";
import { Form } from "./components/containers/Form";
export default function App() {
const [title, setTitle] = useState("");
const [description, setDescription] = useState("");
const [recipe, setRecipe] = useState([]);
const handleTitle = e => {
setTitle(e.target.value);
};
const handleDescription = e => {
setDescription(e.target.value);
};
const handleSubmit = e => {
e.preventDefault();
if (title !== "" && description !== "") {
setRecipe(prevState => {
const data = { title: title, description: description };
return {
...prevState,
recipe: prevState.recipe.concat(data)
};
});
}
};
return (
<div className="App">
<Form
title={title}
description={description}
handleTitle={handleTitle}
handleDescription={handleDescription}
handleSubmit={handleSubmit}
/>
</div>
);
}
import React from "react";
export const Form = ({
handleTitle,
handleDescription,
handleSubmit,
title,
description
}) => {
return (
<div>
<form onSubmit={handleSubmit}>
<input
type="text"
onChange={handleTitle}
placeholder="title"
value={title}
/>
<input
type="text"
onChange={handleDescription}
placeholder="description"
value={description}
/>
<button>Add</button>
</form>
</div>
);
};
When you set the recipes, you're changing the primitive type of recipes state to an object. Instead you should just return a new array with the previous recipes and the new recipe.
I've attached a runnable example below:
const Form = ({
handleTitle,
handleDescription,
handleSubmit,
title,
description
}) => {
return (
<div>
<form onSubmit={handleSubmit}>
<input
type="text"
onChange={handleTitle}
placeholder="title"
value={title}
/>
<input
type="text"
onChange={handleDescription}
placeholder="description"
value={description}
/>
<button>Add</button>
</form>
</div>
);
};
function App() {
const [title, setTitle] = React.useState("");
const [description, setDescription] = React.useState("");
const [recipes, setRecipes] = React.useState([]);
const handleTitle = e => {
setTitle(e.target.value);
};
const handleDescription = e => {
setDescription(e.target.value);
};
const handleSubmit = e => {
e.preventDefault();
if (title !== "" && description !== "") {
setRecipes(prevState => {
const data = { title: title, description: description };
return [...prevState, data];
});
setTitle("");
setDescription("");
}
};
return (
<div className="App">
<Form
title={title}
description={description}
handleTitle={handleTitle}
handleDescription={handleDescription}
handleSubmit={handleSubmit}
/>
<h5>Recipes</h5>
{recipes.length === 0
? (
<div>No recipes</div>
)
: (
<ul>
{recipes.map(({ title, description }) => (
<li>
{title} : {description}
</li>
))}
</ul>
)}
</div>
);
}
ReactDOM.render(<App/>, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
This is a pretty common pattern in React components:
handleTextFieldChange(event)
{
const name = event.currentTarget.name;
this.setState({[name]: event.currentTarget.value})
}
What Javascript syntax could be used to do the same with React hooks?
i.e. something possibly along the lines of:
handleTextFieldChange(event)
{
const name = event.currentTarget.name;
this.set[name](event.currentTarget.value);
}
You could use a single useState with a default value of an object that contains all your input values and update that like you are used to with class components.
Example
const { useState } = React;
function App() {
const [state, setState] = useState({ email: "", password: "" });
function onChange(event) {
const { name, value } = event.target;
setState(prevState => ({ ...prevState, [name]: value }));
}
return (
<div>
<input value={state.email} name="email" onChange={onChange} />
<input value={state.password} name="password" onChange={onChange} />
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root"></div>
How about something like this?
function handleTextFieldChange(mySetFunction, event) {
const value = event.currentTarget.value;
mySetFunction(value);
}
<TextField
placeholder="Email"
name="passwordResetEmailAddress"
onChange={(e) => handleTextFieldChange(setPasswordResetEmailAddress, e)}
>
{passwordResetEmailAddress}
</TextField>
I've tested it and it works.
class Yup extends React.Component {
state = {
first: "",
second: ""
};
handleTextFieldChange = ({ target: { name, value } }) =>
this.setState({ [name]: value });
render() {
const { first, second } = this.state;
return (
<div>
<p>{first}</p>
<p>{second}</p>
<input type="text" name="first" onChange={this.handleTextFieldChange} />
<input
type="text"
name="second"
onChange={this.handleTextFieldChange}
/>
</div>
);
}
}
same with hook
function Yup() {
const [{ first, second }, setState] = useState({ first: "", second: "" });
function handleTextFieldChange({ target: { name, value } }) {
setState(prevState => ({ ...prevState, [name]: value }));
}
return (
<div>
<p>{first}</p>
<p>{second}</p>
<input type="text" name="first" onChange={handleTextFieldChange} />
<input type="text" name="second" onChange={handleTextFieldChange} />
</div>
);
}
You can dynamically update a state for the target field by receiving an update state function as an argument in onChange function.
Example
import React, { useState } from "react";
const App = () => {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const onChangeHandler = (setIdentifierState, event) => {
setIdentifierState(event.target.value);
};
return (
<div>
<p>{"Email: " + email}</p>
<p>{"Password: " + password}</p>
<input
type="text"
placeholder="email"
onChange={onChangeHandler.bind(null, setEmail)}
/>
<input
type="text"
placeholder="password"
onChange={onChangeHandler.bind(null, setPassword)}
/>
</div>
);
};
export default App;
I recently tackled this same problem while converting my components from classes to functions. I ended up creating an object that could then point to the separate state hooks:
const textStates = {};
const setTextStates= {};
for (var name of names) {
let [textState, setTextState] = useState("");
textStates[name] = textState;
setTextStates[name] = setTextState;
}
I solved it this way (a slightly more dynamic solution than what #VikR offered)
const [title, setTitle] = useState("");
const [desc, setDesc] = useState("");
const [video, setVideo] = useState("");
const handleChange = setter => event => {
const value = event.target.value;
//special cases
if (setter === setVideo) {
setInvalidVideo(!ReactPlayer.canPlay(value))
}
setter(value)
}
Then in my code:
<TextField fullWidth value={title} type="date"
label={t('service.ticket.add.title')}
placeholder={t('service.ticket.add.titlePh')}
onChange={handleChange(setTitle)} variant="outlined"/>