React Material UI onFocusOut - reactjs

I currently use
<TextField onChange={e => this.change(e, items)}
This gets fired at every single letter I put into the TextField, consequently, text I type gets filled in slow motion. I was thinking it'd be better if this request goes out once the user types everything and focuses away. What kind of event I can use in this scenario with React & Material UI TextField ?

Having a debounced version of your function is useful when any DOM event is attached. It reduces the number of calls to this function to the bare minimum and thereby improve the performance.
So, you can do this:
import _ from 'lodash';
constructor(props) {
super(props)
this.onChangeDebounce = _.debounce(e => this.change(e, items), 300);
}
render() {
...
onChange={e => this.onChangeDebounce(e)}
...
}
In this case, I am passing to debounce only two parameters:
The function to be rate-limited
The rate limit in milliseconds, ie., the time to wait before the function is fired.
Or you can use onBlur event, that is available for any DOM element. The onBlur event happens whenever an input loses focus on. In other words: when you remove your cursor from within the input, it loses "focus", or becomes "blurry".
The caveat is that it doesn't have an associated event, so, to reach what you want, you can update the state with the field value and on onBlur, retrieve this value from state.
Here you have a fiddle doing this.

After one and a half year, here is a contemporary approach mostly for functional components:
Keep the value of the field as a React state.
Set it onChange
Persist (or do whatever expensive process) onBlur
So, a component containing this text field would, in a way, look like similar to this:
import React, { useState } from 'react'
import { TextField } from '#material-ui/core'
const MyFunctionalComponent = () => {
const [textFieldValue, setTextFieldValue] = useState('')
// ...
return (
<React.Fragment>
{/** ... */}
<TextField
value={textFieldValue}
onChange={(e) => setTextFieldValue(e.target.value)}
onBlur={() => {
console.log(`I am blurred and ready to process ${textFieldValue}`)
}}
/>
{/** ... */}
</React.Fragment>
)
}

Related

React - Checking for a value in an uncontrolled input?

I've got a custom Input control, which I'd like to be uncontrolled. I don't care what the user writes or where this is stored. However, I need to know if the input has some value or not, because depending on that it will have an appearance or another (such as a floating label).
Since it's not possible to check purely by CSS if an input has a value or not (because the attribute value does not get updated when the user types and changes its value), the only way to know if it has a value or not is programatically.
If I use useState to keep a local state of its value just to know if it has some (though it keeps being uncontrolled), whenever some external script changes the value of the input using ref (for example, react-hook-forms reset function), it won't get updated because the state won't notice the change, and thus the input will be empty but its state will think it still has a value.
As it's not a possibility, then, I wanted to access and watch for the ref .value changes. However, it doesn't change (or, at least, it doesn't notify the change). If I access the value prop directly, it has the correct value, but it doesn't trigger the useEffect although what I'm using as a dependency is the value and not the ref itself.
const [hasValue, setHasValue] = useState(false);
useEffect(() => {
setHasValue(!!inputRef?.current?.value);
}, [inputRef?.current?.value]);
Here is Codesandbox a sample of what I'm referring.
Is there any way to check the actual value of the input inside my custom component without having to send a value prop from the parent, which is able to react to external changes by
ref, too? So, no matter how the input value changes, it's aware of it.
You could use Inder's suggestion with vanilla javascript or you could trigger your useEffect with afterValue, which gets set onChange.
import React, { useEffect, useRef, useState } from "react";
const Input = () => {
const inputRef = useRef(null);
const [afterValue, setAfterValue] = useState("");
const [hasValue, setHasValue] = useState(false);
useEffect(() => {
setHasValue(!!inputRef?.current?.value);
}, [afterValue]);
return (
<div>
<input
ref={inputRef}
onChange={({ target: { value } }) =>
setTimeout(() => setAfterValue(value), 1000)
}
/>
<br />
<br />
<div>Has value: {hasValue.toString()}</div>
<div>Actual value: {inputRef?.current?.value}</div>
<div>After value: {afterValue}</div>
</div>
);
};
export default Input;

React-hook form is taking 5+ seconds to shift from isSubmitting to isSubmitted state. How can I reduce this lag to improve performance?

I have written a form which has more than 30 fields(material-ui components) using react-hook form.
All of the fields have different kinds of validation depending upon the type of the field.
The sample folder structure is as shown in this link.
folderStructure
detail_page.js is the root component. In this component useForm is initialized with defaultValues. And the form is wrapped with FormProvider(Context to pass to the deeply nested components). Also handleSave method is written in this component only. Upon clicking SAVE button, handleSubmit(handleSave) is called. From this component TopLevel(top_level.js) component is called.
top_level.js file groups fields(passed as props from detail_page.js) into sections.
A render method(renderSections) is written to display each section in mui-accordion, and all the fields within that section is passed to iterator.js file. the renderSections method is memoized with the dependency array of sectionobjects.
iterator.js file loops through each of the the fields passed as props from top_level.js and calls respective system_component based on the type of the field (Radio,checkbox,textbox,textarea,email,password,file_uploader,address,latlong,phone_number,list,date,date_range etc).
system_component is the actual component which wraps the material-ui's component using useController/Controller. Based on the type of field, respective system_component is called in iterator. for eg:-
SystemTextBox for type input
SystemEmail for type email in input
SystemDate for type date
and so on.
Some system component may be a combination of 2 or more mui's components. for eg
-SystemLatlong- combination of 4 or more text inputs and number input to display address components of the particular latlong.
SystemArray - any combination of rest of the system components(calling iterator again for this purpose)
and so on.
Now the problem is, whenever I click on save button, the save button freezes, In handleSubmit method I am setting MuiBackdrop component to display "Saving" text. This backdrop is called after 5+ seconds upon clicking save button. In detail page I have consoled isSubmitting, isValidating, isSubmitted state of formState. I figured out that, for almost 5+ seconds isSubmitting state holds true value, until which handleSave function is not triggered.
the reason for this is that react-hook-form uses context which is not optimized for performant re-renders. With context all consumers of that context will re-render regardless of if they care of the value that changed. With react hook form having one central context every input re-renders when isSubmitting is changed unfortunately.
I'm building a library that will solve this issues that is currently in beta called #zerry/react-formz. It guarantees isolated re-renders for inputs which means near constant performance no matter how many inputs you are rendering.
Its' in beta and currently being worked on but it might solve the issues you are having.
You can check it out here if you feel so inclined: https://react-formz.zerry.dev/docs/performance/
import {
Form,
TextField,
NumberField,
DependentTextField,
} from "#zerry/react-formz";
const NestedInput = () => {
return (
<TextField
required
name="name"
as={({ input }) => <input {...input} />}
/>
)
}
const MyForm = () => {
return (
<Form initialValues={{ name: "", age: "" }}>
<NestedInput />
<NumberField
required
name="age"
as={({ input }) => <input {...input} />}
/>
</Form>
);
};

Update parent state variable from child React Reactive Form

I am trying to explore react library with next framework. Since I am an angular developer, I like to reuse some of my reactive-form to my new application. I found this library since it has almost the same implementation of reactive-form.
Now, I am using state variable on my parent form; however, whenever I try to update the value from child (which is the reactive form). I cannot accomplish it.
Here's my simple code to replicate this.
import React, { useState } from "react";
import { FieldGroup, FieldControl } from "react-reactive-form";
export default function App() {
const [isRegistered, setIsRegistered] = useState(false);
async function submitForm(e) {
e.preventDefault();
setIsRegistered(state => !state);
console.log(isRegistered);
//await function call .....
}
return (
<div>
<h1>Hello StackBlitz!</h1>
<FieldGroup
control={form}
render={({ get, invalid }) => (
<form onSubmit={submitForm}>
<FieldControl
name="email"
render={TextInput}
meta={{ label: "Email" }}
/>
<button type="submit">Submit</button>
<p>{isRegistered.toString()}</p>
{isRegistered ? <span>Registered Successfully</span> : null}
</form>
)}
/>
</div>
)
}
Just to keep it short, the form and TextInput is just a model and an element.
As you can see., I am updating my state variable on the submitForm function by putting it as an onSubmit function of my form; however, I am able to trigger the submitForm whenever I am trying to submit, but the state variable value doesn't change.
The thing is, when I try to call the submitForm function outside the child (FieldGroup), I am able to update the value.
I created a sample app so you can check as well.
It seems like you need to set strict prop to false for FieldGroup, like described here: https://github.com/bietkul/react-reactive-form/blob/master/docs/api/FieldGroup.md
strict: boolean;
Default value: true
If true then it'll only re-render the component only when any change happens in the form group control irrespective of the parent component(state and props) changes.
I don't know this library, but to me it just looks like the FormGroup is not re-render, because none of it's props are being changed.
The documentation says that passing strict={false} to the <FieldGroup /> component should allow it to re-render when the parent component updates as well. In your given example (thanks for making an example) that also does the trick.

React Material-Table: Typing lag outside of table

I'm using material-table to display a table, with table data being retrieved by calling an api and storing in a state hook. I also have a hidden modal that is opened via material-table's add button full of input fields, where each input field has an associate state variable in the overall component, to be used to add new rows.
My issue is when I type into an input field in the modal, there is a noticeable delay between typing and the change rendering - especially if I mash the buttons.
It appears that Material-Table re-renders itself or checks if anything in the table changes with every change to an input and is the cause of the lag.
The issue can be reduced to the below code structure (it seems to occur if input is associated with a state variable).
const component = () => {
const [data, setData] = useState('');
const [var, setVar] = useState('');
useEffect(// Call API and set data, []);
return (
<div>
<MaterialTable></MaterialTable>
<input value={var} onChange={x => setVar(x.target.value)}></input>
</div>)
}
I'm wondering if there is a solution to my lag issue or is material-table designed this way?
Edit. Included sandbox. Typing really fast in the input has a noticeable lag.
https://codesandbox.io/s/material-table-yjbpr?file=/src/App.js
Edit 2. Updated sandbox with CEich's solution. There appears to be a noticeable lag if you hold backspace/hold a key.
https://codesandbox.io/s/material-table-2-8g98l?file=/src/App.js
Try using useCallback
import React, { useState, useCallback } from "react";
import MaterialTable from "material-table";
import "./styles.css";
export default function App() {
const [name, setName] = useState("");
const handleChange = useCallback((e) => {setName(e.target.value)}, [setName])
const table = (
<MaterialTable
columns={[
{ title: "First Name", field: "Name" },
{ title: "Surname", field: "Surname" }
]}
data={[{ Name: "John", Surname: "Doe" }]}
title="Title"
/>
);
return (
<div className="App">
<h1>Hello {name}</h1>
<div>
<label>Type here fast -> </label>
<input value={name} onChange={handleChange} />
</div>
{table}
</div>
);
}
By putting the callback code directly in the jsx, you were causing the component to re-render every time the value changed.
useCallback keeps your handler the same unless something in the provided array changes. (Here, that's only the setName method). Two different functions are never considered equal, even if they have the same function body. So, when comparing your callback function, react would cause your component to re-render on every keystroke. For a smaller component, this probably might not make a difference. But for a more complex component, the issue is more obvious

React design pattern dilemma - steppers in numeric form components

So we all know uncontrolled components are usually a bad thing, which is why we usually want to manage the state of an input (or group of inputs) at a higher-level component, usually some kind of container. For example, a <Form /> component manages state and passes down state as values to its <Input /> components. It also passes down functions such as handleChange() that allow the Input to update the state.
But while implementing my own <NumericInput /> component, it got me thinking that fundamentally this component is not self-reliant. It's reusable but requires a lot of repetition (opposite of DRY mentality) because everywhere in my app that I want to use this component, I have to implement these state values, a handleChange function, and in the case of my <NumericInput />, two additional functions to control the stepper arrows.
If I (or someone who took over my code) wanted to use this <NumericInput />, but they forget to run to a different container component and copy the stepUp() and stepDown() functions to pass down as props, then there will just be two non-functional arrows. I understand that this model allows our components to be flexible, but they also seem to be more error-prone and dependent on other components elsewhere. Again, it's also repetitive. Am I thinking about this incorrectly, or is there a better way of managing this?
I recognize this is more of a theory/design question, but I'm including my code below for reference:
NumericInput:
const NumericInput = ({label, stepUp, stepDown, ...props}) => (
<>
{label && <Label>{label}</Label>}
<InputContainer>
<Input type={props.type || "number"} {...props} />
<StepUp onClick={stepUp}>
//icon will go here
</StepUp>
<StepDown onClick={stepDown}>
//icon will go here
</StepDown>
</InputContainer>
</>
);
Form.js
const Form = (props) => {
const [value, setValue] = useState('');
const handleChange = (e) => {
setValue(e.target.value);
}
const stepUp = () => {
setValue(value++);
}
const stepDown = () => {
setValue(value--);
}
return (
<NumericInput
stepUp={stepUp}
stepDown={stepDown}
handleChange={handleChange}
label="Numeric"
)
}
Let's try to boil down your questions a bit:
[NumericInput] is reusable but requires a lot of repetition (opposite of DRY mentality) because everywhere in my app that I want to use this component, I have to implement these state values, a handleChange function, and in the case of my , two additional functions to control the stepper arrows.
The only repetition you have is to define a value and a handleChange callback property for <NumericInput />. The value prop contains your numeric input value and is passed in from the parent container component owning that state. You need the callback to trigger a change request from the child (controlled component). That is the ususal approach, when you need to lift state up or divide your components in container and presentational components.
stepUp/stepDown methods are an implementation detail of NumericInput, so you can remove them from Form. Form just wants to know, if a value change has been triggered and what the new changed numeric value is.
Concerning where to store the state: There is probably a reason, why you want to save the state in the Form component. The user enters some input in (possibly) multiple fields before pressing the submit button. In this moment your form needs all the state of its child fields to trigger further processing based on the given input. And the idiomatic React way is to lift state up to the form to have the information.
I understand that this model allows our components to be flexible, but they also seem to be more error-prone and dependent on other components elsewhere. Again, it's also repetitive.
A presentational component is rather less error prone and is independent, because it doesn't care about state! For sure, you write a bit more boilerplate code by exposing callbacks and value props in child. The big advantage is, that it makes the stateless components far more predictable and testable and you can shift focus on complex components with state, e.g. when debugging.
In order to ease up props passing from Form to multiple child components, you also could consolidate all event handlers to a single one and pass state for all inputs. Here is a simple example, how you could handle the components:
NumericInput.js:
const NumericInput = ({ label, value, onValueChanged }) => {
const handleStepUp = () => {
onValueChanged(value + 1);
};
const handleStepDown = () => {
onValueChanged(value - 1);
};
return (
<>
...
<Input value={this.state.value} />
<StepUp onClick={handleStepUp}></StepUp>
<StepDown onClick={handleStepDown}></StepDown>
</>
);
};
Form.js:
const Form = (props) => {
const [value, setValue] = useState(0);
const handleValueChanged = (value) => {
setValue(value);
}
return (
<NumericInput
onValueChanged={handleValueChanged}
value={value}
label="Numeric"
)
}
Move the setValue function to the NumericInput component
Manage your state through the component.
return (
<NumericInput
setValue={setValue}
label="Numeric"
/>
)
I would recommend you use hooks

Resources