Today I was practicing with React and found two ways to get input values when submitting form.
First:
Using hooks, for example:
...
const [name, setName] = useState('');
...
return <input onChange={(value) => setName(value)} value={name} />
Second:
Get by using event.target.children[i].value, for example:
const handleSubmit = (event: BaseSyntheticEvent) => {
event.preventDefault();
const formInputs = event.target.children;
for (const input in formInputs) {
console.log(formInputs[input].value);
}
};
The question is, which one should I use and why? Is it optional, or I don't know, it does have some performance impact.
Don't use vanilla DOM manipulation in React when you can avoid it, within reason. The first approach is far better in most situations.
Working with the DOM is slow. State and the virtual DOM help a lot.
In the React paradigm, the appearance and functionality of the application should ideally stem from state as much as possible. Non-stateful side-effects can make things difficult to reason about and hard to work with given how re-rendering works and should be avoided.
If the reason you're tempted to go with the second approach is that you have a whole lot of inputs and making a separate state for each is tedious, one option is to make the state be a single object (or Map) instead.
const MyInput = ({ name, state, onChange }) => (
<Input name={name} onChange={onChange} value={state[name]} />
);
const onChange = (e) => {
setState({ ...state, [e.target.name]: e.target.value });
};
<MyInput name="name" state={state} onChange={onChange}/>
Related
Background
So I have a simple example, a Form component (Parent) and multiple Input components (Children). Each individual Input component will have an useState hook to initialize and manage its value's state.
Issue
As with most forms, I would like to submit all of the data to a backend for processing. However, the issue is that I cannot retrieve the state of value from each Child Input component.
// app.jsx
import Form from "./Form";
export default function App() {
return <Form />;
}
// Form.jsx
import React from "react";
import Input from "./Input";
const handleSubmit = (e) => {
e.preventDefault();
console.log("Wait, how do I retreive values from Children Inputs?");
};
const Form = () => {
console.log("Form render");
return (
<form onSubmit={handleSubmit}>
Sample Form
<Input initial="username" name="user" />
<Input initial="email" name="email" />
<button type="submit">Submit</button>
</form>
);
};
export default Form;
// Input.jsx
import React from "react";
import useInputValue from "./useInputValue";
const Input = ({ name, initial }) => {
const inputState = useInputValue(initial);
console.log(`${name}'s value: ${inputState.value}`);
return <input {...inputState} />;
};
export default Input;
Plausible Solution
Of course, I can lift the Input states up to the Form component, perhaps in an obj name values. However, if I do that, every time I change the Inputs, the Form will re-render, along with all of the Inputs.
To me, that is an undesirable side-effect. As my Form component gets bigger, this will be more costly to re-render all inputs (inside the form) every time one input changes.
Because of that, I would like to stick with my decision of having each individual input manage its own state, that way if one input changes, not all other input will re-render along with the Parent component.
Question
If each of the Child components manages its own state, could the Parent component access the value of that state and do actions (like form submission)?
Update
Many answers and comments mentioned that this is premature optimization and the root of all known evil; which I agree with, but to clarify, I am asking this question with a simple example because I wanted to find a viable solution to my current and more complex project. My web app has a huge form (where all states are lifted to the form level), which is getting re-rendered at every change in its inputs. Which seemed unnecessary, since only one input is changed at a time.
Update #2
Here is a codesandbox example of the issue I am having. There are two forms, one with all states managed by individual Child input components, and the other has all states lifted up in the form level. Try entering some values and check the console for log messages. One can see that with all states lifted to the form level, every change will cause both inputs to be re-rendered.
I think yes, you can share state. Also there are 3 options:
I recommend you to use such library as Formik. It will help you in your case.
You can share state using useState() hook as props.
Use such tools as Redux Toolkit (if we are speaking about memoisation), useContext() and etc.
If the thing you want is getting final values from input, assign ref to each input and access using emailRef.current.value in the submit function.
import { useState, useRef, forwardRef } from 'React';
const Input = forwardRef((props, ref) => {
const [value, setValue] = useState('');
return <input ref={ref} value={value} onChange={(e) => {setValue(e.target.value)}} {...props} />;
});
const App = () => {
const emailRef = useRef(null);
const submit = () => {
const emailString = emailRef.current.value
};
return (
<>
<Input ref={emailRef} />
<button onClick={submit}>Submit</button>
</>
);
};
If the parent needs to know about the childs state, you can
move the state up. This is resolved by passing down a setState function that the child calls (the states data is available in the parent)
use a context https://reactjs.org/docs/context.html
use a state management library e.g. redux
In your simple case I'd go with 1)
so I am getting the error too many re-renders when trying to update the state of an array.
I declare an array called sampleFriends, made up of objects with fields "name", "location", and "picture" (each element of the array looks similar to: {name: 'John', location: 'Boston', picture: TestImage}).
Then I use useState as follows:
const [searchQuery, setSearchQuery] = useState('')
const [friendMatches, setFriendMatches] = useState(sampleFriends)
I also have a TextInput component:
<TextInput
value={searchQuery}
onChangeText={setSearchQuery}
onChange={search()}
placeholder="Search Contacts"
keyboardType="default"
/>
And a search() function that looks like this:
const search = () => {
const coincidences = sampleFriends.filter(({ name }) => name.includes(searchQuery))
setFriendMatches(coincidences)
}
I want to render the users whose name matches searchQuery, and coincidences gives me the users, but I can't use setFriendMatches to update the matchesState (which is what I'd like to render). Hope it makes sense and thanks for your help!!
UPDATE:
Some responses told me to change onChange={search()} to onChange={search} and that eliminated the error. However, now search never runs.
In the onChange event, just mention the function name like this onChange={search} or if you need to put some parameters, call it within a function as below.
This is what your final code block should look like.
<TextInput
value={searchQuery}
onChangeText={setSearchQuery}
onChange={() => search()}
placeholder="Search Contacts"
keyboardType="default"
/>
Let me know if it works
Replace your code with this :
<TextInput
value={searchQuery}
onChangeText={setSearchQuery}
onChange={search} // <- This One is Important
placeholder="Search Contacts"
keyboardType="default"
/>
Change Line 4 in TextInput from onChange={search()} to onChange={search}.
By calling the function you are triggering an infinite re-render at each re-render.
There are probably other issues since it's unlikely that your component wants a double onChange event listener, I made a simple demo to show you how to possibly manage these scenarios in a cleaner way: https://stackblitz.com/edit/react-eijegp
I came across the following two form designing approaches in react-hook-form documentation.
Connect Form ref
When we are building forms, there are times when our input lives inside of deeply nested component trees, and that's when FormContext comes in handy. However, we can further improve the Developer Experience by creating a ConnectForm component and leveraging React's renderProps. The benefit is you can connect your input with React Hook Form much easier.
export const ConnectForm = ({ children }) => {
const methods = useFormContext();
return children({ ...methods });
};
export const DeepNest = () => (
<ConnectForm>
{({ register }) => <input {...register("deepNestedInput")} />}
</ConnectForm>
);
export const App = () => {
const methods = useForm();
return (
<FormProvider {...methods} >
<form>
<DeepNest />
</form>
</FormProvider>
);
}
Somehow, I am not able to get how this "improves developer experience"? Without ConnectForm, we would have required only two lines which are inside ConnectForm in above example. But with ConnectForm, we are requiring extra lines to define ConnectForm and two extra lines for <ConnectForm>','</ConnectForm>. So the number of lines have increased.
I guess I am not getting the idea from the above example, and maybe for a more complex or large form, it might result in a lesser number of lines. Q1. Is it so?
Q2. Or is it that the doc wants to say "<ConnectForm>...</ConnectForm> looks more elegant" by "improving Developer Experience"?
I'm having a hard time understanding the 'exhaustive-deps' lint rule.
I already read this post and this post but I could not find an answer.
Here is a simple React component with the lint issue:
const MyCustomComponent = ({onChange}) => {
const [value, setValue] = useState('');
useEffect(() => {
onChange(value);
}, [value]);
return (
<input
value={value}
type='text'
onChange={(event) => setValue(event.target.value)}>
</input>
)
}
It requires me to add onChange to the useEffect dependencies array. But in my understanding onChange will never change, so it should not be there.
Usually I manage it like this:
const MyCustomComponent = ({onChange}) => {
const [value, setValue] = useState('');
const handleChange = (event) => {
setValue(event.target.value);
onChange(event.target.value)
}
return (
<input
value={value}
type='text'
onChange={handleChange}>
</input>
)
}
Why the lint? Any clear explanation about the lint rule for the first example?
Or should I not be using useEffect here? (I'm a noob with hooks)
The reason the linter rule wants onChange to go into the useEffect hook is because it's possible for onChange to change between renders, and the lint rule is intended to prevent that sort of "stale data" reference.
For example:
const MyParentComponent = () => {
const onChange = (value) => { console.log(value); }
return <MyCustomComponent onChange={onChange} />
}
Every single render of MyParentComponent will pass a different onChange function to MyCustomComponent.
In your specific case, you probably don't care: you only want to call onChange when the value changes, not when the onChange function changes. However, that's not clear from how you're using useEffect.
The root here is that your useEffect is somewhat unidiomatic.
useEffect is best used for side-effects, but here you're using it as a sort of "subscription" concept, like: "do X when Y changes". That does sort of work functionally, due to the mechanics of the deps array, (though in this case you're also calling onChange on initial render, which is probably unwanted), but it's not the intended purpose.
Calling onChange really isn't a side-effect here, it's just an effect of triggering the onChange event for <input>. So I do think your second version that calls both onChange and setValue together is more idiomatic.
If there were other ways of setting the value (e.g. a clear button), constantly having to remember to call onChange might be tedious, so I might write this as:
const MyCustomComponent = ({onChange}) => {
const [value, _setValue] = useState('');
// Always call onChange when we set the new value
const setValue = (newVal) => {
onChange(newVal);
_setValue(newVal);
}
return (
<input value={value} type='text' onChange={e => setValue(e.target.value)}></input>
<button onClick={() => setValue("")}>Clear</button>
)
}
But at this point this is hair-splitting.
The main purpose of the exhaustive-deps warning is to prevent the developers from missing dependencies inside their effect and lost some behavior.
Dan abramov – developer on Facebook core – strongly recommend to keep that rule enabled.
For the case of passing functions as dependencies, there is a dedicated chapter in the React FAQ:
https://reactjs.org/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies
tl;dr
If you have to put a function inside your dependencies array:
Put the function outside of your component, so you are sure that the reference won't be changed on each render.
If you can, call the function outside of your effect, and just use the result as dependency.
If the function must be declared in your component scope, you have to memoize the function reference by using the useCallback hook. The reference will be changed only if the dependencies of the callback function change.
I'm rewriting a form generator from a class based to a functional based approach. However, in both approaches I'm running into the same problem.
The form receives a field template, and values, loops the field specifications, and renders the appropriate inputs. Each is given it's value and a handler to carry the value in a state object of the form (which later can be submitted).
This works fine while the form is small, but of course those forms are not small and can grow to be quite large and many types of elaborate fields in them. When the form field specification grows, the form slows down to the point where there is a delay between key press and visible input. Interestingly, that delay is very noticable while in development but is much better when compiled to a production build.
I would like to render the form elements as few times as possible and prevent the whole building of the form every time a key is pressed. However, if i pre-generate the fields, the event handlers don't retain the modified values. If I rebuild it on every render - it just slows things down.
A simplified example of this is here:
https://codesandbox.io/s/black-meadow-wqmzt
Note that this example starts by pre-rendering the form content into state and rendering it later. However, you can change the renders return line (in main.js) from :
return <div>{formContent}</div>;
to
return <div>{build()}</div>;
to have the form re-build on each render. You will notice in this case that the build process runs a lot.
Is it possible to pre-render a set of inputs with event handlers attached and retain the event handler's behaviour?
Edit: The slowness of a large form rendering is manifested in the input - typing some text into a text field for example sees a delay between keypress to rendering of the input because each key press triggers a rebuild of the form.
You can just use local state [and handler] to force item update/rerenderings. This of course duplicates data but can be helpful in this case.
export default function Text({ spec, value, onChange }) {
const [val, setVal] = useState(value);
const handleChange = ev => {
onChange(ev);
setVal(ev.target.value);
};
return (
<React.Fragment>
<label>{spec.label}</label>
<input type="text" name={spec.name} value={val} onChange={handleChange} />
</React.Fragment>
);
}
working example
BTW - use key (and not just index value) on outer element of item rendered from an array:
return (
<div key={spec.name}>
<FormElement
spec={spec}
value={values[spec.name] || ""}
onChange={handleChange}
/>
</div>
You should defer eventHandlers and all the behavior to React. I've simplified your code a bit here: https://codesandbox.io/s/solitary-tree-1hxd2. All the changes are in main.js file. Below I explain what I changed and why.
Removed useEffect hook and trigger of build() in there. Your hook was called only once on the first render and wasn't called on re-renders. That caused values don't update when you changed state.
Added unique key to each field. This is important for performance. It let's React internally figure out what field has updated and trigger DOM update only for that input. Your build() function is super fast and don't have side-effects. You shouldn't worry that it is being called more than once. React may call render multiple times and you have no control over it. For heavy functions you can use useMemo (https://usehooks.com/useMemo/) hook, but it isn't the case here, even if you have 50 fields on a form.
Inlined calls to handleChange and fields. That's minor and personal preference.
I don't see any delay in the code now and render called once or twice on each field update. You can't avoid render because it is controlled component: https://reactjs.org/docs/forms.html#controlled-components. Uncontrolled components isn't recommended when using React.
Final code for form component:
export default function Main({ template, data }) {
const [values, setValues] = useState(data);
console.log("render");
return (
<div className="form">
{template.fields.map((spec, index) => {
const FormElement = Fields[spec.type];
const fieldName = spec.name;
return (
<div>
<FormElement
spec={spec}
value={values[fieldName] || ""}
key={spec.name}
onChange={e => {
setValues({
...values,
[fieldName]: e.target.value
});
}}
/>
</div>
);
})}
</div>
);
}