How can i optimize 'form' rendering. For each key pressed, the component is rendered
Any idea to solve or improve it?
const Example = () => {
const [inputForm, setInputForm] = useState('');
const inputHandler = event => {
setInputForm(event.target.value);
};
console.log('Rendering');
return (
<div>
<div>
<span>Text: {inputForm}</span>
<input value={inputForm} onChange={inputHandler} />
</div>
</div>
);
};
log of component rendering
Thanks guys!
Try to use Uncontrolled Components, from the documentation:
https://pt-br.reactjs.org/docs/uncontrolled-components.html
As #andergtk mentioned, if you want to avoid rendering the input on each key press you'll have to resort to an input that React does not control.
To write an uncontrolled component, instead of writing an event
handler for every state update, you can use a ref to get form values
from the DOM.
but the rendering on every key press that you notice is expected in the case of a controlled input where React has control over the value of the field and should not worry you
A controlled input is more "powerful" because you let React sync your data with your input value
more on this subject in the docs or in this article (there are lots of other resources on this): https://goshakkk.name/controlled-vs-uncontrolled-inputs-react/
improving your case is not about stop rendering, every time input value is changed it needs to be re-rendered, if not changes wouldn't apply
to improve performance you may:
useCallback to the event handler if you gonna do calculations there
maybe split label out of the component returning only input element
useState may be declared outside Input component to make sense
Input props: { value, setValue } ,. then setup callback
react renders its components based on props or state changes, so always something changes on screen react has to re-render that component
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)
I have created a controlled component that constrains an input field to only 4 numbers. These numbers can be changed at any time and any time the number is four digits I want the result to be passed to a sibling component to action.
I am sending the value to the parent state and this re-renders and the sibling gets the value. But my component also re-renders and the input fields loses the value.
Should I just send the value to the parent let the parent re-render on every input change?
Or use Context?
Any insight would be great as I am new to React.
const NumberComp = ({setPostcode})=>{
const [ fieldValue, setFieldValue ] = useState('')
useEffect(()=>{
if(fieldValue.length == 4){
setPostcode(fieldValue)
}
}, [fieldValue])
const updateNumber = (e)=>{
const value = e.target.value
if(value.length > 4 || isNaN(value)) return
setFieldValue(value)
}
return <input onChange={updateNumber} value={fieldValue} type="text" />
}
If your project is small then uplift the state and pass it down to sibling components and component memoize else use context API if you want to avoid prop drilling.
You need to let the re-render happen on the parent as well. A child render is possible only when the parent is re-rendered. However, this won't re-render unchanged parts, as React updates only what is necessary.
You can also check out this question to help clear your doubts up.
You can also try using react memos, but it would need some fine control to ensure that your component updates when it is supposed to.
I have a simple input field like this:
<TextField
required
variant="standard"
type="string"
margin="normal"
fullWidth = {false}
size="small"
id="orgName"
placeholder="Organisation Name"
label="Name"
name="orgName"
// defaultValue={orgData ? orgData.orgName : ""}
//inputRef={(input) => this.orgName = input}
value={this.state.orgName || ""}
onChange={this.handleChange("orgName")}
error={this.state.errors["orgName"]}
/>
I want to use the same input field for new and update? For new I just set the state to empty, and save the values. Which works fine. Now I have a select dropdown to edit the previously saved objects.
My problem is with editing, and I am tearing my head out trying to find the any way to do this. All these are the corresponding issues:
If i set the state from props - any edited changes are being reset
If i don't set the state from props, I get all blank fields, which is incorrect.
If I use defaultValue to load the form inputs from props, then its only called once. And it does not reload when I change the object to be edited.
If i just use onChange handler for, the form gets creepy slow, with many inputs on page
If I use refs, I am not able to reset the refs to reload the input when the object to be edit changes.
I have managed to make it work with componentWillReceiveProps, but it is deprecated, and react website is saying its dangerous to use it.
componentWillReceiveProps(nextProps) {
if (nextProps.orgData !== this.props.orgData) {
this.setState({
"orgName": nextProps.orgData.orgName,
"orgPath": nextProps.orgData.orgPath,
"orgAddress": nextProps.orgData.orgAddress,
"orgPhone": nextProps.orgData.orgPhone,
"orgEmail": nextProps.orgData.orgEmail,
})
}
}
So how can I actually create an editable form where the values have to be loaded from props for different instances from db, but they have to controlled by the state. There has to someplace where I have to check saying "hey if the props have changed, reset the state with the new props for edit???
This has been the most frustrating experience using react for me. How are there no examples anywhere on how to build a simple form to create new, and editable object using react and redux. It just seems overly complicated to do such a simple thing, the whole thing just sucks!
There has to someplace where I have to check saying "hey if the props have changed, reset the state with the new props for edit???
Yes you can use React.useEffect hook on the special prop or Array of props, then when that/those prop(s) change the internal hook function will be fire.
e.g. :
const MyComponent = (props) => {
let [myState,setMyState]= React.useState(null);
React.useEffect(()=>{
setMyState(props.prop1);
},[props.prop1]);
return ...
}
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>
);
}
I have a component that has an input field bound to the application state, like this
import {updateTitle} from '../actions/sales';
class Sale extends Component {
onTitleChange(event) {
const {value} = event.target;
const {id} = this.props;
this.props.updateTitle(id, value);
}
render() {
return (
<input
placeholder="The Title"
value={this.props.title}
onChange={::this.onTitleChange} />
);
}
}
export default connect(({sales}) => ({
title: sales.title
}), {
updateTitle
}))(Sale);
but a lot heavier in markup.
Everything is nice except that when I try to type something into this field fast, the browser lags significantly since every input change, which implies every key press, runs through the whole loop from event handler, to action creator, to the actual action, to the reducer, to the store update, to component props update and render. It's super slow. Is there any way to optimize that?
Am I missing something obvious?
I tried using debounced function passed as onChange prop value but, this way, the app state wouldn't update at all. I also tried using component-level state and setState along with app-level state but I think this approach contradicts the idea of Redux and therefore shouldn't be used.
React components can be controlled or uncontrolled. A controlled input receives its value as a prop and fires an event handler for each change in the value. An uncontrolled control keeps the user input in the local state and fires event handlers for changes.
Your input is currently controlled, but if you'd like to keep changes more local, why not make it uncontrolled? You can then call updateTitle in the onBlur event when the user is done typing, or debounce the onChange event to call updateTitle less often while the user is typing.
You have at least two options. The first: try connecting to the redux store closer to the input component (so less components that aren't affected by the value update on the change)
Generally though we store the intermediate values in the parent component state, and either flush the value occasionally (like a normal debounce) to the redux store, or do it on something like onBlur. So update the value in state on every change and flush the value to the store occasionally. It involves more care to make sure the values are in-sync but those are some of the trade-offs for optimizing hot paths.