Updating nested object in react hooks - javascript

I'm currently trying to implement a dynamic + multi-step form and was wondering how to update values of a map within a map.
For example: I have three fields "First Name", "Last Name", "Email". The name values I want to store within a key called "General" while the email value I would like to store within a key called "Contact".
Currently, I have implemented a method called onChange that is given to each field and listens for changes and stores the field and its value within the state.
function App() {
const [state, setState] = useState(false);
const onChange = (field, value) => {
console.log("Values:", value);
setState({
...state,
[field]: value
});
console.log("State:", state);
};
return (
<div className="App">
<EuiForm>
<EuiFormRow label="First Name">
<EuiFieldText
name="first"
onChange={event => onChange("firstName", event.target.value)}
/>
</EuiFormRow>
<EuiSpacer />
<EuiFormRow label="Last Name">
<EuiFieldText
name="last"
onChange={event => onChange("lastName", event.target.value)}
/>
</EuiFormRow>
<EuiSpacer />
<EuiFormRow label="Email">
<EuiFieldText
name="email"
onChange={event => onChange("email", event.target.value)}
/>
</EuiFormRow>
<EuiSpacer />
<EuiButton type="submit" fill>
Save form
</EuiButton>
</EuiForm>
</div>
);
}
What is the correct way of updating values so that the data in my state looks like this?
{
"general": {
"firstName": "ABCD",
"lastName": "EFGH"
},
"contact": {
"email": "abcd#efgh.com"
}
}

To simplify, you can define two different states and then merge them upon submit. Here is an example:
function App() {
const [general, setGeneral] = React.useState({});
const [contact, setContact] = React.useState({});
const onChange = (set, field, value) => {
set(state => ({
...state,
[field]: value
}));
};
const onSubmit = (e) => {
e.preventDefault();
console.log({
general,
contact
});
}
return (
<div className="App">
<form onSubmit={onSubmit}>
<label htmlFor="First Name">First Name
<input
name="first"
onChange={event => onChange(setGeneral, "firstName", event.target.value)}
/>
</label>
<hr />
<label htmlFor="Last Name">Last Name
<input
name="last"
onChange={event => onChange(setGeneral, "lastName", event.target.value)}
/>
</label>
<hr />
<label htmlFor="Email">Email
<input
name="email"
onChange={event => onChange(setContact, "email", event.target.value)}
/>
</label>
<hr />
<button type="submit">
Save form
</button>
</form>
</div>
);
}
ReactDOM.render(<App />, document.getElementById('root'));
<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>

Initially take the state as an object , like this
const [state, setState] = useState({ general :{}, contact:{});
than do some thing like this
const onChange = (field, value) => {
var temp = {...state}
if(field == 'firstName'){
temp.general.firstName = value
setState({
...state,
general:temp
});
} else if(field == 'lastName'){
temp.general.lastName= value
setState({
...state,
general:temp
});
} else if(field == 'email'){
temp.contact.email= value
setState({
...state,
contact:temp
});
}
console.log("State:", state);// this will not work as the setState is asynchronous
};
// so you can view the state like this
useEffect(() => {
console.log('State', state); // so this block of code will watch for any changes in state
}, [state]);

Related

Unable to type anything in textbox after state empty Reactjs

I am working on Reactjs/nextjs,Right now i am integrating newsletter(submit form) but once i submit form then unable to type anything in textbox,How can i fix this ? I tried with make "state" empty but still unable to type anything, Here is my current code
const [state, setState] = useState({});
const handleSubmit = (e) => {
e.preventDefault();
const data = {
name: e.target.name.value,
country: e.target.country.value,
msgs: e.target.msgs.value,
};
axios
.post("xxxxxxxxxxxxxxxxxxxxxxxx",data)
.then(function (response) {
if (response.data.msg == "exist") {
$("#msg4").show("slow").delay(5000).fadeOut();
setState({
...state,
name: ""
});
} else {
}
});
return (
<>
<form className="row" id="home_contact_form" onSubmit={handleSubmit}>
<input
type="text"
name="name"
id="name"
placeholder="Enter Your Email"
value={state.name}
// onChange={handleChange}
className="input-group"
/>
<input type="submit" value="send" className="sendbtn" id="sendbtn" />
</form>
</>
)
if you want to couse the input be a controlled component you can do
so:
const [state, setState] = useState({ name: '' });
and provide an event handler for onChange
const handleChange = (event) => setState({...state, name: event.target.value })
<input
type="text"
name="name"
id="name"
placeholder="Enter Your Email"
value={state.name}
onChange={handleChange}
className="input-group"
/>

How to get all inputs value in react

How to get all input values by click to count and multiply them all? Without useref, just somehow add result of all event.target.values to quantity?
const [quantity, setQuantity] = useState(0);
function handleChange(event) {
setQuantity(event.target.value);
}
function countCups() {
setQuantity(hours * l * degrees * capacity);
}
return(
<>
<input type="number" placeholder="hours" onChange={handleChange}/>
<input type="number" placeholder="l" onChange={handleChange}/>
<input type="number" placeholder="degrees" onChange={handleChange}/>
<input type="number" placeholder="capacity" onChange={handleChange}/>
<button id="countButton" onClick={count}>COUNT</button>
<span>{quantity}</span>
</>
);
Currently whenever you change an input value it's replacing the existing value in state. Ideally you want to store each input state separately. Now you can do that with separate states or you can use an object as in the example below. You can destructure the input name and value from the event.target (i.e. the changed element), and use those values to update the each object key/value.
When you want to calculate the total quantity that doesn't necessarily need to be stored in state. You can call a function that multiplies the object values together, and use the returned value in the JSX.
const { Fragment, useState } = React;
function Example() {
// Initialise the inputs state with an object
const [inputs, setInputs] = useState({});
// Destructure the name and value from the
// changed element, and then use those to update
// the state
function handleChange(event) {
const { name, value } = event.target;
setInputs(prev => {
return { ...prev, [name]: Number(value) };
});
}
// Returns a value by multiplying all the state
// values together
function getQuantity() {
let quantity = 1;
for (const key in inputs) {
const val = inputs[key];
if (val) quantity *= val;
}
return quantity > 1 ? quantity : null;
}
// Each input now has a name attribute
// and is "controlled" by including the value
// from the state
return (
<Fragment>
<input
type="number"
name="hours"
placeholder="hours"
value={inputs.hours || ''}
onChange={handleChange}
/>
<input
type="number"
name="l"
placeholder="l"
value={inputs.l || ''}
onChange={handleChange}
/>
<input
type="number"
name="degrees"
placeholder="degrees"
value={inputs.degrees || ''}
onChange={handleChange}
/>
<input
type="number"
name="capacity"
placeholder="capacity"
value={inputs.capacity || ''}
onChange={handleChange}
/>
<div>Quantity: {getQuantity()}</div>
</Fragment>
);
}
ReactDOM.render(
<Example />,
document.getElementById('react')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>
You can do this by adding a name to your inputs which you can use as a key in a state object to save your input states
const [state, setState] = useState({});
const [quantity, setQuantity] = useState(0);
function handleChange(event) {
const name = event.target.name;
const value = event.target.value;
setState((prevState) => {
return {
...prevState,
[name]: value,
};
});
}
function countCups() {
setQuantity(state.hours * state.l * state.degrees * state.capacity);
}
return (
<>
<input
type="number"
name="hours"
placeholder="hours"
value={state.hours ?? ""}
onChange={handleChange}
/>
<input
type="number"
name="l"
placeholder="l"
value={state.l ?? ""}
onChange={handleChange}
/>
<input
type="number"
name="degrees"
placeholder="degrees"
value={state.degrees ?? ""}
onChange={handleChange}
/>
<input
type="number"
name="capacity"
placeholder="capacity"
value={state.capacity ?? ""}
onChange={handleChange}
/>
<button id="countButton" onClick={count}>
COUNT
</button>
<span>{quantity}</span>
</>
);

How to add input validation in react?

I am having a simple form that has firstName and lastName.
<label htmlFor="firstName">First Name: </label>
<input
type="text"
className="form-control"
id="firstName"
name="firstName"
value={basicDetails.firstName}
onChange={(event) => handleInputChange(event)}
/>
<label htmlFor="lastName">Last Name: </label>
<input
type="text"
className="form-control"
id="lastName"
name="lastName"
value={basicDetails.lastName}
onChange={(event) => handleInputChange(event)}
/>
For this I am trying to add validation.
The validation rules are,
Both fields should accept only text
First name is required and should have at least 4 characters.
If Last name field has value, then it needs to be at least 3 characters.
Things I have tried to achieve this,
components/utils.js
export function isLettersOnly(string) {
return /^[a-zA-Z]+$/.test(string);
}
components/basic_details.js
const handleInputChange = (event) => {
const { name, value } = event.target;
if (!isLettersOnly(value)) {
return;
}
setValue((prev) => {
const basicDetails = { ...prev.basicDetails, [name]: value };
return { ...prev, basicDetails };
});
};
On handle input field, I am making the validation to check whether the input has value but I am unable to get the point how to catch the actual validation error and display below respective input box.
Kindly please help me to display the validation message on the respective fields.
Working example:
I suggest adding an errors property to the form data in form_context:
const [formValue, setFormValue] = useState({
basicDetails: {
firstName: '',
lastName: '',
profileSummary: '',
errors: {},
},
...
});
Add the validation to basic_details subform:
const ErrorText = ({ children }) => (
<div style={{ color: 'red' }}>{children}</div>
);
const BasicDetails = () => {
const [value, setValue] = React.useContext(FormContext);
const { basicDetails } = value;
const handleInputChange = (event) => {
const { name, value } = event.target;
if (!isLettersOnly(value)) {
setValue((value) => ({
...value,
basicDetails: {
...value.basicDetails,
errors: {
...value.basicDetails.errors,
[name]: 'Can have only letters.',
},
},
}));
return;
}
switch (name) {
case 'firstName': {
const error = value.length < 4 ? 'Length must be at least 4.' : null;
setValue((value) => ({
...value,
basicDetails: {
...value.basicDetails,
errors: {
...value.basicDetails.errors,
[name]: error,
},
},
}));
break;
}
case 'lastName': {
const error = value.length < 3 ? 'Length must be at least 3.' : null;
setValue((value) => ({
...value,
basicDetails: {
...value.basicDetails,
errors: {
...value.basicDetails.errors,
[name]: error,
},
},
}));
break;
}
default:
// ignore
}
setValue((prev) => {
const basicDetails = { ...prev.basicDetails, [name]: value };
return { ...prev, basicDetails };
});
};
return (
<>
<br />
<br />
<div className="form-group col-sm-6">
<label htmlFor="firstName">First Name: </label>
<input
type="text"
className="form-control"
id="firstName"
name="firstName"
value={basicDetails.firstName}
onChange={(event) => handleInputChange(event)}
/>
</div>
<br />
{basicDetails.errors.firstName && (
<ErrorText>{basicDetails.errors.firstName}</ErrorText>
)}
<br />
<br />
<div className="form-group col-sm-4">
<label htmlFor="lastName">Last Name: </label>
<input
type="text"
className="form-control"
id="lastName"
name="lastName"
value={basicDetails.lastName}
onChange={(event) => handleInputChange(event)}
/>
</div>
<br />
{basicDetails.errors.lastName && (
<ErrorText>{basicDetails.errors.lastName}</ErrorText>
)}
<br />
</>
);
};
Lastly, check the field values and errors to set the disabled attribute on the next button in index.js. The first !(value.basicDetails.firstName && value.basicDetails.lastName) condition handles the initial/empty values state while the second condition handles the error values.
{currentPage === 1 && (
<>
<BasicDetails />
<button
disabled={
!(
value.basicDetails.firstName && value.basicDetails.lastName
) ||
Object.values(value.basicDetails.errors).filter(Boolean).length
}
onClick={next}
>
Next
</button>
</>
)}
This pattern can be repeated for the following steps.
First you must be getting converting controlled component to uncontrolled component error in your console. For controlled component it is always preferred to use state to set value for the input. And with onChange handler you set the state. I will try to put into a single component so you would get the idea and apply your case
import React, {useState} from 'react';
import {isLettersOnly} from './components/utils'; // not sure where it is in your folder structure
const MyInputComponent = ({value, ...props}) => {
const [inputValue, setInputValue] = useState(value || ''); // input value should be empty string or undefined. null will not be accepted.
const [error, setError] = useState(null);
const handleChange = event => {
const { name, value } = event.target;
if (!isLettersOnly(value)) {
setError('Invalid Input');
}
setInputValue(value);
}
return (
<>
<input
value={inputValue}
onChange={handleChange}
{...props}
/>
{error && (
<span className={"error"}>{error}</span>
)}
</>
)
}
export default MyInputComponent;
This is a very rudimentary component. just to show the concept. You can then import this component as your input field and pass necessary props like name, className etc from parent.
import React from 'react';
import MyInputComponent from 'components/MyInputComponent';
const MyForm = (props) => {
return props.data && props.data.map(data=> (
<MyInputComponent
name="lastName"
className="form-control"
value={data.lastName}
));
}

How to add user input object of an array with react useState, when I type in the field and send it it does not update the questions

I have an object that has quizzes which is an array of objects. I would like the user to enter questions, correct_answer and incorrect_answers and send it to my database but its not doing it, it is sending it empty.
const [questions, setQuestions] = useState({
quizes:[{
question : '',
correct_answer: '',
incorrect_answers:[]
}]
});
useEffect(() => {
loadQuestions(id);
}, []);
const loadQuestions = async ( id ) => {
const result = await api.get(`/assignments/${id}`);
setQuestions(result.data);
};
// const {quizes,question, correct_answer} = questions
const onInputChange = e => {
const value = e.target.value
setQuestions({...questions, [e.target.name]: value });
};
// const addQuestion = async (id) => {
// await api.post(`/assignments/addquestion/${id}`);
// loadQuestions();
// };
const onSubmit = async e => {
e.preventDefault();
await api.post(`/assignments/addquestion/${id}`, questions);
history.push("/dashboard");
};
return (
<div className="container">
{/* {console.log('this is the name of the question ** '+ questions.quizes.map(x=>x.question))} */}
<div className="py-4">
<form onSubmit={e => onSubmit(e)}>
{/* {questions.quizes.map(x=>{ */}
<div className="form-group">
<input
type="text"
className="form-control form-control-lg"
placeholder="Enter question"
name= {questions.quizes.question}
value = {questions.quizes.question}
onChange={e => onInputChange(e)}
/>
</div>
<div className="form-group">
<input
type="text"
className="form-control form-control-lg"
placeholder="Enter question"
name='correct_answer'
value={
questions.quizes.correct_answer
}
onChange={e => onInputChange(e)}
/>
</div>
this is the output, it leaves the quizzes part empty
{
"createdAt": "2021-01-13T02:54:39.710Z",
"_id": "5ffe666824f06bfd45f6bcd0",
"title": "klkplk",
"quizes": [
{
"incorrect_answers": [],
"_id": "5ffe668f24f06bfd45f6bcd1",
"question": "",
"correct_answer": ""
}
],
"__v": 0
}
]

Bind child component click handler to parent state

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>

Categories

Resources