React re renders everything every time key is pressed - reactjs

I have a chart and an input field below. Basically the user can ask a question and the chart will change accordingly. Here is how it looks like
Here is the sample of the code below (ignore CoreUI syntaxes)
<CRow>
<CCol xs="12" sm="12" lg="12">
<CCard id="answerScreen" style={{height: "500px"}}>
{
!tempResponse.loading? tempResponse.data.map(value => (
<ChartRender
key={uuidv4()}
data={value}
/>
))
:
<Loader/>
}
</CCard>
</CCol>
</CRow>
<CRow>
<CCol xs="12" sm="12" lg="12">
<CCard>
<CCardBody>
<CForm className="form-horizontal">
<CFormGroup>
<CInputGroup size="lg" className="input-prepend">
<CInputGroupPrepend>
<CInputGroupText className="">#Ask</CInputGroupText>
</CInputGroupPrepend>
<CInput
size="16"
type="text"
value={userInput || ""}
onChange={e=> handleTextBoxInput(e)}
onClick={e=> handleTextBoxClick(e)}
onKeyPress={e => handleTextBoxEnter(e)}
id="userQuery"
/>
<CInputGroupAppend>
<CButton color="primary">Send</CButton>
</CInputGroupAppend>
</CInputGroup>
</CFormGroup>
</CForm>
</CCardBody>
</CCard>
</CCol>
</CRow>
This is how I have defined my states
const [userInput, setUserInput] = React.useState("");
const [tempResponse, setTempResponse] = React.useState({
data: []
})
I suspect the problem in this part of the code
<CInput
size="16"
type="text"
value={userInput || ""}
onChange={e=> handleTextBoxInput(e)}
onClick={e=> handleTextBoxClick(e)}
onKeyPress={e => handleTextBoxEnter(e)}
id="userQuery"
/>
I even tried adding useCallback to onChange function like this
const handleTextBoxInput = useCallback(e =>{
e.preventDefault();
setUserInput(e.target.value)
}, [])
But no help. I even read about memo but not sure where or how to apply it in my situation. What am I doing wrong?
OBSERVATION
As mentioned by #Matthew ,arrow syntax creates a different callback each time which contributes to re rendering and hence must be removed.
But even after removing it, the chart is getting re rendered every time a key is pressed. I am using Chartjs which uses canvas internally. Is Chartjs a problem?

You are correct about the problemetic code.
<CInput
size="16"
type="text"
value={userInput || ""}
onChange={e=> handleTextBoxInput(e)} // performance issue
onClick={e=> handleTextBoxClick(e)} // performance issue
onKeyPress={e => handleTextBoxEnter(e)} // performance issue
id="userQuery"
/>
When the above code is run multiple times, the functions will be re-created every time. So instead of doing that, the following is enough
<CInput
size="16"
type="text"
value={userInput || ""}
onChange={handleTextBoxInput}
onClick={handleTextBoxClick}
onKeyPress={handleTextBoxEnter}
id="userQuery"
/>
The useCallback hook returns, well, a callback function. You can simply use the return values as normal event callbacks.

On your input, you have two events firing on every keypress - onKeyPress and onChange - remove the onKeyPress.
I suspect that handleTextBoxEnter calls setTempResponse which updates tempResponse. Setting state that the UI depends on will trigger a re-render.
You should also handle form submissions using the onSubmit event. If an element has focus inside of a form and the enter button is pressed - it will fire onSubmit.
<form onSubmit={handleTextBoxEnter}></form>
Also, if a key changes React will re-render. You are calling a function in your key so it is updated on every update.
tempResponse.data.map((value, i) => (
<ChartRender key={`chart-${i}`} data={value} />
))
FYI manually creating a UUID for a key overkill.
export const YourComponent = (): JSX.Element => {
const [userInput, setUserInput] = useState('');
const [tempResponse, setTempResponse] = useState({ data: [], loading: true });
useEffect(()=>{
// handle initial data loading and set loading to false
}, [])
const handleSubmit = (e) => {
e.preventDefault();
setTempResponse(your_state_data);
};
// e.preventDefault shouldn't be used here and is not required
const handleChange = ({ target }) => setUserInput(target.value);
if (tempResponse.loading) {
return <Loading />;
}
// action is set to # for iOS - an action is required to show the virtual submit button on the keyboard
return (
<>
<form action="#" onSubmit={handleSubmit}>
<input defaultValue={userInput} onChange={handleChange} type="text" />
<button type="submit">Submit</button>
</form>
{!!tempResponse.length &&
tempResponse.data.map((value, i) => (
<ChartRender key={`chart-${i}`} data={value} />
))}
</>
);
};

Related

React Controlled Input Validation

I noticed a weird behavior where controlled components are not respecting the minLength and allowing a form to submit even though that condition is not met.
Validation works fine for uncontrolled components.
What is the reason for this behavior?
export default function App() {
const [uncontrolledSubmitCount, setUncontrolledSubmitCount] = useState(0);
const [controlledSubmitCount, setControlledSubmitCount] = useState(0);
const [content, setContent] = useState("");
return (
<div className="App">
<h2>Uncontrolled (minLength: 10)</h2>
<form
onSubmit={(e) => {
console.log(e);
e.preventDefault();
setUncontrolledSubmitCount(uncontrolledSubmitCount + 1);
}}
>
<textarea
type="text"
minLength={10}
maxLength={30}
required
/>
<br />
<button type="submit">Submit</button>
<br />
<span>Submit Count: {uncontrolledSubmitCount}</span>
</form>
<h2>Controlled (minLength: 10)</h2>
<form
onSubmit={(e) => {
console.log(e);
e.preventDefault();
setControlledSubmitCount(controlledSubmitCount + 1);
}}
>
<textarea
type="text"
minLength={10}
maxLength={30}
required
value={content}
onChange={(e) => setContent(e.currentTarget.value)}
/>
<br />
<button type="submit">Submit</button>
</form>
<span>Submit Count: {controlledSubmitCount}</span>
</div>
);
}
Here's a reproduction link:
https://codesandbox.io/s/kind-violet-zwcct?file=/src/App.js
This behavior happens only on Chrome as far as I tested it.
Based on React official documentation adding value to textarea it overrides HTML validations and it makes it "controlled" input and it ignores it overall. But only in Chrome.
The documentation also says there is an option to use defaultValue. I tried it at it works.
<textarea
minLength={10}
maxLength={30}
required
defaultValue=""
onChange={(e) => setContent(e.currentTarget.value)}
/>
Working example from your codesanbox
With this solution you will solve Chrome problem but still keep content state with value that you enter inside textarea
NOTE:
If you try to use children value, React will throw an error that it is not supported for controlled textarea

In React with Formik how can I build a search bar that will detect input value to render the buttons?

New to Formik and React I've built a search component that I'm having issues with the passing of the input value and rendering the buttons based on input length.
Given the component:
const SearchForm = ({ index, store }) => {
const [input, setInput] = useState('')
const [disable, setDisable] = useState(true)
const [query, setQuery] = useState(null)
const results = useLunr(query, index, store)
const renderResult = results.length > 0 || query !== null ? true : false
useEffect(() => {
if (input.length >= 3) setDisable(false)
console.log('input detected', input)
}, [input])
const onReset = e => {
setInput('')
setDisable(true)
}
return (
<>
<Formik
initialValues={{ query: '' }}
onSubmit={(values, { setSubmitting }) => {
setInput('')
setDisable(true)
setQuery(values.query)
setSubmitting(false)
}}
>
<Form className="mb-5">
<div className="form-group has-feedback has-clear">
<Field
className="form-control"
name="query"
placeholder="Search . . . . ."
onChange={e => setInput(e.currentTarget.value)}
value={input}
/>
</div>
<div className="row">
<div className="col-12">
<div className="text-right">
<button type="submit" className="btn btn-primary mr-1" disabled={disable}>
Submit
</button>
<button
type="reset"
className="btn btn-primary"
value="Reset"
disabled={disable}
onClick={onReset}
>
<IoClose />
</button>
</div>
</div>
</div>
</Form>
</Formik>
{renderResult && <SearchResults query={query} posts={results} />}
</>
)
}
I've isolated where my issue is but having difficulty trying to resolve:
<Field
className="form-control"
name="query"
placeholder="Search . . . . ."
onChange={e => setInput(e.currentTarget.value)}
value={input}
/>
From within the Field's onChange and value are my problem. If I have everything as posted on submit the passed query doesn't exist. If I remove both and hard code a true for the submit button my query works.
Research
Custom change handlers with inputs inside Formik
Issue with values Formik
Why is OnChange not working when used in Formik?
In Formik how can I build a search bar that will detect input value to render the buttons?
You need to tap into the props that are available as part of the Formik component. Their docs show a simple example that is similar to what you'll need:
<Formik
initialValues={{ query: '' }}
onSubmit={(values, { setSubmitting }) => {
setInput('')
otherStuff()
}}
>
{formikProps => (
<Form className="mb-5">
<div className="form-group has-feedback has-clear">
<Field
name="query"
onChange={formikProps.handleChange}
value={formikProps.values.query}
/>
</div>
<button
type="submit"
disabled={!formikProps.values.query}
>
Submit
</button>
<button
type="reset"
disabled={!formikProps.values.query}
onClick={formikProps.resetForm}
>
</Form>
{/* ... more stuff ... */}
)}
</Formik>
You use this render props pattern to pull formiks props out (I usually call them formikProps, but you can call them anything you want), which then has access to everything you need. Rather than having your input, setInput, disable, and setDisable variables, you can just reference what is in your formikProps. For example, if you want to disable the submit button, you can just say disable={!formikProps.values.query}, meaning if the query value in the form is an empty string, you can't submit the form.
As far as onChange, as long as you give a field the correct name as it corresponds to the property in your initialValues object, formikProps.handleChange will know how to properly update that value for you. Use formikProps.values.whatever for the value of the field, an your component will read those updates automatically. The combo of name, value, and onChange, all handled through formikProps, makes form handing easy.
Formik has tons of very useful prebuilt functionality to handle this for you. I recommend hanging out on their docs site and you'll see how little of your own code you have to write to handle these common form behaviors.

Warning: A component is changing a controlled input of type file

In an application using react, I have a form with radio input, which chooses which component will render, but when I change the radio option this warning shows up -
"A component is changing an uncontrolled input of type file to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component"
Form
import React, { useState } from "react";
export default () => {
const [link, setLink] = useState("");
const [arquivo, setArquivo] = useState("");
const [forma_envio, setFormaEnvio] = useState("");
return(
<React.Fragment>
<div className="form-check ">
<input
type="radio"
className="form-check-input"
name="forma"
id="forma1"
value="File"
checked={forma_envio === "File"}
onChange={(e) => {
setFormaEnvio(e.target.value);
}}
/>
</div>
<div className="form-check ">
<input
type="radio"
className="form-check-input"
name="forma"
id="forma2"
value="Link"
checked={forma_envio === "Link"}
onChange={(e) => {
setFormaEnvio(e.target.value);
}}
/>
</div>
{forma_envio === "File" ? (
<input
type="file"
className="form-control-file form-control"
id="arquivo"`
onChange={(e) => {
e.preventDefault();
handleUpload(e.target.files[0]);
}}
/>
) :forma_envio === "Link" (
<input
value={link}
type="text"
className="form-control"
id="link"
onChange={(e) => {
e.preventDefault();
setLink(e.target.value);
}}
/>
):
("")}
</React.Fragment>
);
}
All states are starting with "".
Everything works fine, but I still can't figure how to fix this warning.
Make sure the initial value of link is not null or undefined, but an empty string, like:
// const [link, setLink] = React.useState() // 🔴 will cause the warning
const [link, setLink] = React.useState('') // correct
EDIT:
After the question was edited, I noticed the problem:
input tags of type "file" MUST BE UNCONTROLLED. You should not pass value or onChange to it. Instead, you should pass a reference and attach it to the input.
const arquivo = useRef(null);
return (
<React.Fragment>
// ...
<input
type="file"
ref={arquivo}
id="arquivo"
/>
</React.Fragment>
)
This is also a rare situation in which you want to hide a DOM node instead of avoiding the rendering. The File object is stored directly on the DOM, so you must keep this DOM node around, otherwise you will lose the selected files.
<input
type="file"
ref={arquivo}
id="arquivo"
style={{ display: forma_envio !== "File" && "none" }}
/>
Complete code at CodeSandbox, without warnings.

onClick in reactjs not showing items

I have to show a list of defaultValues in the search list and when I click on any of those item then it should take me to that item's component but it's not going anywhere. It's only happening with the defaultValues because as soon as I start typing, then if I click on any search result then it takes me to the desired component. what is wrong with my code?
here's the code
const [search, setSearch] = useState("");
const [showDefaultValues, setShowDefaultValues] = useState(false);
const [defaultValues] = useState({
Mumbai: true
});
{!search.length && showDefaultValues ? (
<div className="result-box">
{data
.filter((item, idx) => defaultValues[item.district])
.map((dist, idx) => (
<div
key={idx}
className="search-result"
onClick={() => {
onResultClick(dist.district);
}}
>
{highlightedText(dist.district, search)}
</div>
))}
</div>
) : null}
Just change the codes at components/search/search.js line 39 to 49
<input
placeholder="Search for a District..."
type="text"
className="search-input"
value={search}
onChange={onSearchInputChange}
onFocus={() => {
toggleDefaultValues(true);
}}
onBlur={onBlurInput}
/>
To
<input
placeholder="Search for a District..."
type="text"
className="search-input"
value={search}
onChange={onSearchInputChange}
onFocus={() => {
toggleDefaultValues(true);
}}
/>
Or simply remove line 48
To compensate this, you can add below inside your useEffect (similar to componentDidMount)
document.addEventListener("mousedown", handleInputClickOutside);
add function handleInputClickOutside to set the state to false/hide
You forgot to implement the onClick logic on the default search result items and that's why the search results work fine, while the default search items do not.
Check this link to the working codesandbox.
All i did was invoke the same onResultClick function onClick of 'District' component.
<div
className="dist"
onClick={() => {
this.props.onResultClick(item.district);
}}
>
...
</div>
Hope this solves your problem.

How to set state for text box in functional component

I am working on React JS. I have one text-box component and I want to show some default value in it. After that, the user should be allowed to change the value. Now I am unable to change the value. The text box is behaving like read-only. Below is my code
const EditStyleFormComponent = ({
submitting,
invalid,
}) => (
<form className={className} onSubmit={handleSubmit}>
<h2>LSPL (Low Stock Presentation Level)</h2>
<Line />
<InputGroup>
<TextFieldWithValidation name="lsplMan" label="LSPL Manual" input={{ onChnage:'', value: 'Current' }} />
</InputGroup>
</form>
);
Below is my TextFieldWithValidation code.
export const TextFieldWithValidationComponent = ({
meta,
input,
noStyles,
...otherProps
}) => (
<TextField
state={noStyles ? textFieldStates.DEFAULT : getState(meta)}
errorMessage={meta.touched ? meta.error : null}
{...input}
{...otherProps}
/>
);
Below is my TextField code.
const TextField = ({
className,
label,
description,
state,
errorMessage,
isEditable,
spaceAtBottom, // Not used, but we don't want it in otherProps
...otherProps
}) => {
const inputId = _.uniqueId();
return (
<div className={className}>
{label &&
<label htmlFor={inputId}>{label}</label>
}
<div className="input-group" id={isEditable ? 'editable' : 'readonly'}>
<input
id={inputId}
readOnly={!isEditable}
{...otherProps}
/>
{getStatusIcon(state)}
{errorMessage &&
<Error>{errorMessage}</Error>
}
{description &&
<Description>{description}</Description>
}
</div>
</div>
);
};
Can someone help me to fix this issue? Any help would be appreciated. Thanks
You can use State Hook for manage state in functional component.
Example :
const Message = () => {
const [message, setMessage] = useState( '' );
return (
<div>
<input
type="text"
value={message}
placeholder="Enter a message"
onChange={e => setMessage(e.target.value)}
/>
<p>
<strong>{message}</strong>
</p>
</div>
);
};
Yu defined onChange as empty string in EditStyleFormComponent component. So on any change input component just do nothing.
onChange should be some function that will update value.
If you want to use functional components there are two possible solutions:
Lift state up to parent component of EditStyleFormComponent (in case parent is class based component)
Use React Hooks like so (just example!)
const EditStyleFormComponent = ({
submitting,
invalid,
}) => {
const [inputValue, setInputValue] = useState ('Current'); // default value goes here
return <form className={className} onSubmit={handleSubmit}>
<h2>LSPL (Low Stock Presentation Level)</h2>
<Line />
<InputGroup>
<TextFieldWithValidation name="lsplMan" label="LSPL Manual" input={{ onChnage: (e) => { setInputValue(e.target.value); }, value: inputValue }} />
</InputGroup>
</form>
};

Resources