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.
Related
I have a project where I'm displaying cards that contain attributes of a person in a textfield, and the user can edit the textfield to directly change that person's attribute values. However every time they're editing the textfield it causes a rerender of all cards which slows down the app. Here is an example:
export default Parent() {
const [personList, setPersonList] = useState(/* list of person objects*/);
const modifyPerson(index, property, value) {
const newPersonList = _.cloneDeep(personList);
newPersonList[index][property] = value;
setPersonList(newPersonList);
}
const children = personList.map((person, index) => {
<Person
modifyPerson={modifyPerson}
index=index
/*properties on the person */
/>
});
return <div> {children} </div>
}
export default Person(props) {
const fields = /* assume a list of these textfields for each property */
<TextField
value={props.name}
onChange={(e) => modifyPerson(props.index,"name",e.target.value)}
value={props.name} >
return {fields};
}
So essentially when the child's text field is updated, it triggers a state change in the parent that stores the new value, then refreshes what the Child looks like. There's no button that the user clicks to "save" the values-- as soon as they edit the textfield it's a permanent change. And also the parent needs to know the new values of every Person because there's some functions that require knowledge of the current state of the person list. The Person component contains images that slow down the rendering if done inefficiently.
Is there a better way to make this design more performant and reduce rerenders? I attempted to use useCallback to preserve the functions but I don't it works in this specific design because the property and values are different-- do I have to create a new "modifyPerson" for each exact attribute?
Use React.memo()
React.Memo will check the props passed to the component and only if the props changes , it will re-render that particular component.
So, if you have multiple Person component which are getting props which are explicit to those Person, and when you change a particular Person component which leads to Parent state getting updated, only that Person component which you had modified will re-render because its props will change(assuming that you pass the changed value to it).
The other components will have the same props and wont change.
export default React.memo(Person(props) {
const fields = /* assume a list of these textfields for each property */
<TextField
value={props.name}
onChange={(e) => modifyPerson(props.index,"name",e.target.value)}
value={props.name} >
return {fields};
})
As others have already said React.memo() is the way to go here, but this will still re-render because you recreate modifyPerson every time. You can use useCallback to always get the same identity of the function, but with your current implementation you would have to add personList as dependency so that doesn't work either.
There is a trick with setPersonList that it also accepts a function that takes the current state and returns the next state. That way modifyPerson doesn't depend on any outer scope (expect for setPersonList which is guaranteed to always have the same identity by react) and needs to be created only once.
const modifyPerson = useCallback((index, property, value) {
setPersonList(currentPersonList => {
const newPersonList = _.cloneDeep(currentPersonList);
newPersonList[index][property] = value;
return newPersonList;
})
}, []);
I have a parent Component A that computes some initial value for his child Component B. Actually i compute the initial value in componentDidMount() of Component A and pass it to B through props:
<ComponentB initialValue={this.state.value} handleChange={this.handleChange}/>
And in component B i do
this.state = {value: this.props.initialValue}
After this, Component B should manage his state independently. But every time it's value change, i want it to update A's state but with less information.
In Component B:
const onChange = (event) => {
this.setState({value: event.target.value + "only in B"});
this.props.handleChange(this.state.value);
}
In ComponentA:
const handleChange = (value) => {
this.setState({value: value});
}
The problem is that A always has the last word and value in B doesn't keep the "only in b" suffix. A reset the value of B's state to his internal state value through the initialValue props when it renders again.
So basically, what i want to do is to inherit the initial value from A at first render, and then update A state when a change occurs in B without erasing B's state.
Hey even though you may do this better way if I understood correctly I think you can achieve what you want by just using:
this.props.value
on B Component:
<input defaultValue={this.props.value} onChange={this.onChange}/>
and then when you handle update just pass in event.target.value to parent (A component)
I'm not 100% sure if that's what you are asking but here's example:
https://codepen.io/pegla/pen/qBNxqBd?editors=1111
Any change in the parent component will trigger a re-render of the child component. I believe the best approach for your situation is after inheriting the initial value from A, set some local state variable to the B component to manage that state specifically so it's separate from A.
I have got 2 pieces of code one is using independent state in child and the other one with state being send out as props like this in my " sent as props " version.
function App() {
const [isChanged, setIsChanged] = React.useState(false);
const [i, setI] = React.useState(0);
React.useEffect(() => {
console.log("USEEFFECT");
setTimeout(() => {
setIsChanged(true);
}, 2000);
}, []);
return (
<div className="App zx">
{isChanged && <h1>Hello CodeSandbox with outter states</h1>}
<Change set={setI} i={i} />
</div>
);
}
the second one with the exception of having states inside <Change /> as such :
function Change() {
const [i, setI] = React.useState(0);
let rnd = 9;
if (i !== rnd) {
setI(i + 1);
}
console.log(i);
The version in which state is managed inside the child, the component runs twice and I get the log two times in a row but in the one with passed down state as props I get the component running once as desired.
Why is this happening and which one is the correct practice ?
When can I safely use state in a child component without worrying about re-renders ?
Is there a way to avoid the re-render in the first version so I get
the same results despite using state in the child component ? If so please provide me with a sample.
To reproduce,
Props version : https://codesandbox.io/s/heuristic-driscoll-9m1pl
state in child version :
https://codesandbox.io/s/confident-shockley-btjg6
Usually you put the state in the component where you want to use it. For example, if just one child uses the state, put it right in the child component. But let's say you want to use the same state in some different child components, then it's better to have it in the parent component. (in this case, it would be better to use useContext() hook).
Honestly, I don't understand what you want to accomplish with your code but in general, rendering and re-rendering happens when you update your state. The reason it re-render agin and you see 0 to 9 and again 0 to 9 is that your default state is 0 and each time it get's re-rendered, the state changes to 0. (I assume)
Hope this answers some of your questions.
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
I'm reading the Fullstack React book, and in their example on form validation, they create their own Field component (pg. 204 - 212), and then store the field value in both the Field state and parent state, which is confusing to me. Their Field component has a value prop as well as a state containing value. The parent component needs to know about each field value, so that it can do form validation as a whole, and so it also has a state containing value.
Within Field, they handle value changes by both setting the Field state when the input value changes, and by using getDerivedStateFromProps when the value prop changes:
//(within Field)
getDerivedStateFromProps(nextProps) {
return {value: nextProps.value}
}
onChange = evt => {
const name = this.props.name;
const value = evt.target.value;
const error = this.props.validate ? this.props.validate(value) : false;
this.setState({value, error});
this.props.onChange({name, value, error});
};
They also sync the value state in the other direction to the parent by calling the parent's onInputChange function (passed as the onChange prop):
//(within parent component)
onInputChange = ({name, value, error}) => {
const fields = Object.assign({}, this.state.fields);
const fieldErrors = Object.assign({}, this.state.fieldErrors);
fields[name] = value;
fieldErrors[name] = error;
this.setState({fields, fieldErrors});
};
The book doesn't really explain why they duplicate the state like this, except to say,
"There are only two pieces of data that Field will need, the current
value and error. Like in previous sections where our form component
needed that data for its render() method, so too does our Field
component."
and also
"One key difference is that our Field has a parent, and sometimes this
parent will want to update the value prop of our Field. To allow this,
we’ll need to create a new lifecycle method,
getDerivedStateFromProps() to accept the new value and update the
state."
I'm just a beginner, but in my mind, it would make more sense to ditch the value state altogether within Field, and have it just passed in as a prop. When the input changes, call the onChange method with Field, and call parent's onInputChange within that. Have onInputChange update the parent state about the field's value, and pass down the field value as a prop to the field. The way it's done now seems sort of redundant and more error prone. Any insight as to why they do it this way?
Haven't read the book, but here I will explain why I would write such a code.
The main point in having the two states is to make the Field component more generic.
In that specific case, the parent happens to also save the value in his state, and the Field component becomes a controlled component by updating his state from the received props on getDerivedStateFromProps.
However there is still the possibility to use the Field component as an uncontrolled component, then the Field's state would be the only source of truth.
In both cases there's only a single source of truth, which maintains React's way of doing things, however the Field component can be used in both a controlled and uncontrolled form.