Why, when the user enters data into the form for the first time, 'users' remains an empty array, as it was, and only after the second time button is pressed, the data is written to 'setUser?
import Card from "../UI/Card";
import Button from "../UI/Button";
const UserForm = (props) => {
const [users, setUsers] = useState([]);
const [data, setData] = useState({ username: "", age: "" });
const submitHandler = (e) => {
e.preventDefault();
setUsers([...users, data]);
console.log("2", users);
props.getUsers(users);
};
return (
<Card className={classes.container}>
<div>Username</div>
<input
type="text"
onChange={(e) => {
setData({ ...data, username: e.target.value });
}}
></input>
<div>Age (Years)</div>
<input
type="number"
onChange={(e) => setData({ ...data, age: e.target.value })}
></input>
<Button onClick={submitHandler}>Add User</Button>
</Card>
);
};
export default UserForm;
....................
React State update takes time. So you need a useEffect hook with state as a argument in it. so whenever the state change, this hook triggers and perform your state related functions.
useEffect(() => {
console.log(users)
props.getUsers(users);
}, [users]);
it does change but how react works ?
when you update the value of one react hook and if this value is different from the previous one the component re-render with the new value of the hook.
in you code you are trying to :
console.log("2", users);
this is just before the component re-render so the new value is not available yet, but directly after submitHandler the component will re-render with new value of users
you can understand better if you try to console log() from inside your jsx
return (
<Card className={classes.container}>
{console.log('this is a new render and this is my users value :',users)}
</Card>
);
learn more here about React component lifecycle
Related
I am new to react and I am trying to make an application that gets the output from a form and displays it in a div. So far, I have only managed to get the result from the form with a simple useState() approach.
To display it, I have tried creating a variable "isSubmitted" to keep track whether or not the variable was submitted, in order to display it only when the user stops typing!
const Example0 = () => {
var isSubmitted;
const [email, setEmail] = useState("");
const handleEmail = (event) => {
setEmail(event.target.value);
isSubmitted = false;
console.log(isSubmitted);
};
const handleSubmit = (event) => {
event.preventDefault();
isSubmitted = true;
console.log(isSubmitted);
};
return (
<Fragment>
<h1>Form example</h1>
<form onSubmit={handleSubmit}>
<div>
<label htmlFor="email">Email</label>
<input id="email" type="text" value={email} onChange={handleEmail} />
</div>
<button type="submit">Submit</button>
</form>
<div>{isSubmitted ? email : "no user"}</div>
</Fragment>
);
};
This part, does not work. The email variable changes but the display doesn't. Does it have something to do with how React works? I come from a HTML background.
<div>{isSubmitted ? email : "no user"}</div>
Help is much appreciated.
Your page needs to be rendered, so you can see the data already in your page.
In reactJS, there are states,
The state is a built-in React object that is used to contain data or
information about the component. A component's state can change over
time; whenever it changes, the component re-renders. source
So you need to use a state to make your component renders again and see the data.
simply create a new state like this:
const [isSubmitted, setIsSubmitted] = React.useState(false);
then change the states when you need, In your example you have to use it like this:
const handleEmail = (event) => {
setEmail(event.target.value);
setIsSubmitted(false);
console.log(isSubmitted);
};
const handleSubmit = (event) => {
event.preventDefault();
setIsSubmitted(true);
console.log(isSubmitted);
};
You should be using a state to control the boolean for if the user has submitted their email or not.
Why can't you use a normal variable?
Every time you're updating a state variable, the component will re-render, and each time it re-renders, you're setting your isSubmitted flag to an undefined. Additionally, even if it wasn't undefined, it wouldn't update your component to show the email because non-state variables won't trigger a re-render, so updating isSubmitted to true won't trigger your ternary statement you have, and re-rendering it will just take it back to undefined, which leaves both scenarios without a render of your div tag content.
Solution
If you want something to conditionally render, you should use useState, its the whole point of react, to "react" accordingly and update your DOM. It's one of the biggest issues I see new learners grasp when first touching React.
Here's my approach to it, which won't update the end tag to whatever the value is after you submit it (I assume you'd want this implemented):
const [email,setEmail] = useState("");
const [isSubmitted, setIsSubmitted] = useState(false);
/**
* handles the Email input, does not update if the form has been submitted or if the string is empty.
* #param {*} e
*/
const handleEmail = (e) => {
if(e.target.value.length > 0 && isSubmitted === false) {
setEmail(e.target.value);
}
};
const handleSubmit = (e) => {
e.preventDefault();
setIsSubmitted(true);
};
return (
<>
<h1>Form example</h1>
<form onSubmit={(e) => handleSubmit(e)}>
<div>
<label htmlFor="email">Email</label>
<input id="email" type="text" onChange={handleEmail} />
</div>
<button type="submit">Submit</button>
</form>
{isSubmitted ? <p>Thank you for submitting your email: {email}</p> : null}
</>
Below is my code for a personal project where i can keep track of my monthly subscriptions, if i have to add a subscription i just have a add an object to an existing array. however for testing purposes when i tried to console.log(value.startDate) in handleSubmit it gives me undefined and causes further problems. How would i fix it?
import React from 'react';
import PropTypes from 'prop-types';
const List = () => {
const [ mylist, setList ] = React.useState([]);
const [ value, setValue ] = React.useState({ subscription: '', startDate: '', paymentTime: 0 });
const handleSubmit = (e) => {
console.log(value.startDate);
setList(mylist.push(value));
e.preventDefault();
};
const handleOnChange = (event) => {
setValue({ [event.target.name]: event.target.value });
};
return (
<div>
<div className="for_list">
<ul className="list">{mylist.map((obj) => <li key={obj.subscription}>{obj.subscription}</li>)}</ul>
</div>
<div className="for_form">
<form>
<input type="text" name="subscription" onChange={handleOnChange} value={value.subscription} />
<input type="text" name="startDate" onChange={handleOnChange} value={value.startDate} />
<input type="number" name="paymentTime" onChange={handleOnChange} value={value.paymentTime} />
</form>
</div>
<button onClick={handleSubmit}>Add Item</button>
</div>
);
};
// it just removes the error above.
List.propTypes = {
list: PropTypes.node
};
export default List;
You are replacing your state every time. This might be because of the miss in understanding the difference between setState in traditional class based React components and useState.
You need to append the value to the existing data. Something similar would work
const handleOnChange = (event) => {
setValue({ ...value, [event.target.name]: event.target.value });
};
The setState in class based components always accepts partial state and merges with the existing one. While useState setter function replaces the value you provide in the respective state.
On handleChange function you need to pass the old value of value
const handleOnChange = (event) => {
setValue({ ...value , [event.target.name]: event.target.value });
};
I am new and still practicing in reactjs. I am having a problem in my input form, whenever I type any key I always get TypeError: Cannot read property 'value' of null
this is my code:
import React, { useState } from 'react';
export default function FormPractice() {
const [isDefault, setIsDefault] = useState('');
const [balance, setBalance] = useState({amount: null});
return (
<div className="input">
<input placeholder="enter balance here" onChange={e => setBalance(form => ({...form, [e.target.value]: e.target.value}))} value={isDefault}/>
</div>
)
}
Thanks for the help.
React re-uses events. When you use a functional update, by the time the function is called, the event has already been wiped for reuse. To use the event within the functional update you would need to call e.persist() which will take that event out of the reuse pool and let it keep its values.
To keep it inline, it would look like this:
onChange={e => {e.persist(); setBalance(form => ({...form, [e.target.value]: e.target.value}))}}
Or to make it more readable, move it into its own function:
const onChange = (e) => {
e.persist();
setBalance(form => ({...form, [e.target.value]: e.target.value}));
}
However, the simplest available solution would be to not use the functional update at all. You are replacing state values, but you are not setting any values derived from the previous state. So it is safe to use a normal update:
onChange={e => setBalance({...balance, [e.target.value]: e.target.value})}
Now the event reuse is a non-issue.
Side note: [e.target.value]: e.target.value this doesn't really make sense. You're setting an object key with the name of the new value to the same value.
It seems like maybe you've seen [e.target.name]: e.target.value before and modified it. I would suggest using the name, and then giving the input the name of the property you want to update.
Here is a simplified example:
const {useState, useEffect} = React;
const Example = () => {
const [state, setState] = useState({ input1: '' });
const onChange = (e) => {
setState({[e.target.name]: e.target.value});
}
return (
<input
name="input1"
placeholder="enter balance here"
onChange={onChange}
value={state.input1}
/>
);
}
ReactDOM.render(<Example />, 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>
I'm currently using this plugin for my react application: https://www.npmjs.com/package/react-editext.
I have multiple fields:
<EdiText
value={contact.addressLine1}
type="text"
onSave={handleSave('addressLine1')}
onCancel={(e) => setEditing(v => !v)}
inputProps={{
placeholder: 'Address Line 1',
}}
/>
<EdiText
value={contact.addressLine2}
type="text"
onSave={handleSave('addressLine2')}
onCancel={(e) => setEditing(v => !v)}
inputProps={{
placeholder: 'Address Line 2',
}}
/>
With a save handle
const handleSave = (e) => value => {
setContact({...contact, [e]: value})
};
But, I need to be able to save all fields with one button.
Now, if these were controlled form fields, I would be able to grab the value, and submit. But they're not as there is no onChange event.
Any ideas?
I didn't find in the plugin a possibility to do that. I suggest that you use a form with refs to achieve what you want.
here is an example code
import React, { useState, useRef } from "react";
import "./styles.css";
export default function App() {
const [editing, setEditing] = useState(true);
const [contact, setContact] = useState({
addressLine1: "Address 1",
addressLine2: "Address 2"
});
const [adress1, setAdress1] = useState("adress 1");
const [adress2, setAdress2] = useState("adress 2");
const form = useRef(null);
const handleSave = () => {
const adresses = {
addressLine1: form.current["adress1"].value.toString(),
addressLine2: form.current["adress2"].value.toString()
};
setContact(adresses);
console.log(contact);
};
const handleEdit = () => {
const edit = editing;
setEditing(!edit);
};
return (
<div className="App">
<form ref={form}>
<input
type="text"
value={adress1}
name="adress1"
onChange={e => setAdress1(e.target.value)}
disabled={editing}
/>
<input
type="text"
value={adress2}
name="adress2"
onChange={e => setAdress2(e.target.value)}
disabled={editing}
/>
</form>
<button onClick={handleSave}>save</button>
<button onClick={handleEdit}>edit</button>
</div>
);
}
explanation
I used state variable editing to make the fields editable or not on button edit click
I used a state variable for each field and used the react onChange function to save the value of each field when it changes.
on save button click the values of all fields states get saved to contact state
you can change the code to make it suitable for your needs. Here is a sandbox for my code:https://codesandbox.io/s/eloquent-pine-wnsw3
I have been trying to submit a login request using a form with controlled input. A submit function is passed down the React components to be triggered upon onClick of a material-ui Button. This error is only thrown when I send a mutation request using Apollo Client.
index.js:1375 Warning: A component is changing an uncontrolled input of type text to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component.
From my understanding of controlled components in React's docs, input components are "controlled" by using React value and setValue state hooks in value and onChange attributes.
This is the top level Login component that contains the submit function and useMutation hook. submit is first passed down to a LoginForm component.
const Login = () => {
const [login, { data }] = useMutation(LOGIN);
console.log(data);
const submit = async form => {
console.log(form); // form object looks correct
await login({ variables: form });
};
...
<Container>
<LoginForm submit={submit} />
</Container>
This is the LoginForm component, which renders a GeneralForm component. Again, submit is passed down to GeneralForm.
const fields = [
{
id: "username",
label: "Username",
required: true,
placeholder: "example: 98sean98"
},
...
const LoginForm = props => {
const { submit } = props;
...
<Container>
<GeneralForm fields={fields} submit={submit} />
</Container>
This is the GeneralForm component.
const GeneralForm = props => {
const { fields, submit } = props;
const [form, setForm] = useState({});
useEffect(() => {
fields.forEach(field => {
form[field.id] = "";
});
setForm(form);
}, [form, fields]);
const handleChange = event => {
form[event.target.id] = event.target.value;
setForm(setForm);
};
const handleSubmit = () => {
if (validateForm(form)) { // returns a boolean indicating validity
submit(form); // trigger the submit function that is passed down from <Login />
} else {
alert("invalid form");
}
};
return (
<FormGroup>
{fields.map(field => (
<FormControl key={field.id} required={field.required}>
<InputLabel htmlFor={field.id}>{field.label}</InputLabel>
<Input
required={field.required}
id={field.id}
type={field.type ? field.type : "text"}
aria-describedby={
field.helperText ? `${field.id}-helper-text` : null
}
placeholder={field.placeholder}
value={form[field.id]}
onChange={handleChange}
/>
{field.helperText ? (
<FormHelperText id={`${field.id}-helper-text`}>
{field.helperText}
</FormHelperText>
) : null}
</FormControl>
))}
<Button type="submit" onClick={handleSubmit}>
Submit
</Button>
</FormGroup>
);
};
My dev environment
partial packages list:
"#apollo/react-hooks": "^3.1.3",
"#material-ui/core": "^4.7.0",
"#material-ui/icons": "^4.5.1",
"apollo-boost": "^0.4.4",
"graphql": "^14.5.8",
"react": "^16.10.2",
"react-dom": "^16.10.2",
Machine: MacOS X Catalina 10.15.1
The peculiar behaviour I'm observing now is that without calling the Apollo Client mutation request,
const submit = async form => {
console.log(form);
// await login({ variables: form });
};
the above error does not get triggered. So, I wonder if Apollo Client is altering my form object incorrectly in some way.
I have spent some time digging around the internet, and this resource seems to be quite helpful. Apparently, all I had to do was to switch the value attribute of Input to listening to value that is exposed by React state in the same component instead of form[field.id] passed down from another component.
// GeneralForm.js
...
const [value, setValue] = useState(form[field.id] ? form[field.id] : "");
...
<Input value={value} ... />
So, I modularised the Input component along with its parent FormControl into another file called FormInput.js, and arrived at this solution.
// FormInput.js
const FormInput = props => {
const { field, form, setForm } = props;
const [value, setValue] = useState(form[field.id] ? form[field.id] : "");
const handleChange = event => {
setValue(event.target.value);
setForm({
...form,
[event.target.id]: event.target.value
});
};
return (
<FormControl key={field.id} required={field.required}>
<InputLabel htmlFor={field.id}>{field.label}</InputLabel>
<Input
required={field.required}
id={field.id}
type={field.type ? field.type : "text"}
aria-describedby={field.helperText ? `${field.id}-helper-text` : null}
placeholder={field.placeholder}
value={value}
onChange={handleChange}
/>
{field.helperText ? (
<FormHelperText id={`${field.id}-helper-text`}>
{field.helperText}
</FormHelperText>
) : null}
</FormControl>
);
};
Then, I import FormInput into GeneralForm, passing down all necessary props.