I am using the following code to render a Material UI AutoComplete field with options retrieved from an API. I would like to prevent re-rendering of the input field but the chosen country is used as a prop for the Country component which should be updated onChange. set_alpha3Code doesn't seem to update the state from within useCallback. How do I get around that?
let AC = memo(AutocompleteCountries)
function Show(props: {})
{
let [alpha3Code_, set_alpha3Code_] = useState<string>('gbr');
let onChange = useCallback((alpha3Code) => {
console.log(alpha3Code_);
set_alpha3Code_(alpha3Code);
}, []);
return (
<div>
<AC onChange={onChange}/>
{alpha3Code_ ? <Country cca3_={alpha3Code_}/> : null}
</div>
)
}
Two things jump out about that code:
onChange without value
Stale state
onChange without value
In the normal case, if you specify onChange with an input control, you have to specify value to tell the control what the current (updated) value is. The documentation for Material-UI's Autocomplete definitely suggests you need both:
Controlled states
The component has two states that can be controlled:
the "value" state with the value/onChange props combination. This state represents the value selected by the user, for instance when pressing Enter.
The same thing with a standard input prevents the input from ever changing. I haven't used the Material-UI Autocomplete, but I suspect it's similar.
Stale state
Your code is updating the value of alpha3Code_ (if onChange is called with a new value), but the console.log in that code looks at at an old one:
let onChange = useCallback((alpha3Code) => {
console.log(alpha3Code_); // <−−− Uses the *first* `alpha3Code_` only
set_alpha3Code_(alpha3Code);
}, []);
// ^^−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− Because you haven't included
// `alpha3Code_` in these dependencies
But even though that console.log shows you outdeated information, the rest of the code is fine and will work. It will re-render the AC with the updated code.
If you want to see the alpha3Code_ used for rendering, put a breakpoint on the return and look at it in the debugger (or move the console.log outside the onChange handler to above the return).
Related
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 am developing a Web Application using React and Redux. I am new to React and Redux. Now, I am having an issue using it together, React and Redux. The problem is I cannot change the input field value when I set the value of the field with the state value.
In my component, I have a property called, event in the state which is coming from connect like this.
function mapStateToProps(state)
{
return {
submittingForm : state.editEvent.submittingForm,
formErrors : state.editEvent.formErrors,
event: state.editEvent.event,
};
}
As you can see the event filed is coming from the reducer. In my component, I can retrieve the event like this.
this.props.event.name
Now, what I am trying to do is when I edit the event, I like to maintain the values of the event in the input field.
Therefore, I render the state value in the input field like this.
<TextField
name="location"
label="Location"
value={this.props.event? this.props.event.location: ""} />
It is maintaining the value in the input field. But the problem is I cannot change the input field now. I can focus on the text input field, but when I type in anything using keyboards, the value is not changing and just keeps displaying the state value. How can I fix the issue?
If it is slow, I would use something like debounce function (see here: https://lodash.com/docs/#debounce). Essentially, it is useful, when you have groups of often events, and you want to call some function, only when there is a certain timeout elapsed since the last event.
So, instead of emitting a new event every time something is changed, you can emit it only after 300-500ms after user finished typing. This looks like batching of all the changes into a single update. If this is something acceptable for your problem, here is how you can do it:
var debouncedOnChange = _.debounce(current_onChange_method, 300 or another time in ms);
Then in <TextField> set onChange to debouncedOnChange.
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.
I want to do simple data injection inside a simple Input redux form Field.
Let me explain :
I have one big form, and when the user is writing inside "LastName" Input, I just want to inject the value to the second Field as "LastNameFather".
Pretty simple imo,
I made a formvalueSelector, and get the first Input value.
const selecor = formValueSelector('MyForm')
const mapStateToProps = (state,props) => {
const name = selector(state,"nameOne")
return {
name : name
}}
And I was using componentWillReceiveProps to handle the writing and use props change to inject my value.
componentWillReceiveProps = (props) => {
props.name != null ? props.change("name2",props.name) : null
}
Now, when I write inside my first Field, there's infinite rerender. As you can see, I have to test props.name != null before change my second Field.
anyway, what I missed ?
Thanks
Mr. GreGGus, according to the React Js official documentation "Note that React may call this method (componentWillReceiveProps) even if the props have not changed, so make sure to compare the current and next values if you only want to handle changes. This may occur when the parent component causes your component to re-render."
You may try that.
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.