so I am getting the error too many re-renders when trying to update the state of an array.
I declare an array called sampleFriends, made up of objects with fields "name", "location", and "picture" (each element of the array looks similar to: {name: 'John', location: 'Boston', picture: TestImage}).
Then I use useState as follows:
const [searchQuery, setSearchQuery] = useState('')
const [friendMatches, setFriendMatches] = useState(sampleFriends)
I also have a TextInput component:
<TextInput
value={searchQuery}
onChangeText={setSearchQuery}
onChange={search()}
placeholder="Search Contacts"
keyboardType="default"
/>
And a search() function that looks like this:
const search = () => {
const coincidences = sampleFriends.filter(({ name }) => name.includes(searchQuery))
setFriendMatches(coincidences)
}
I want to render the users whose name matches searchQuery, and coincidences gives me the users, but I can't use setFriendMatches to update the matchesState (which is what I'd like to render). Hope it makes sense and thanks for your help!!
UPDATE:
Some responses told me to change onChange={search()} to onChange={search} and that eliminated the error. However, now search never runs.
In the onChange event, just mention the function name like this onChange={search} or if you need to put some parameters, call it within a function as below.
This is what your final code block should look like.
<TextInput
value={searchQuery}
onChangeText={setSearchQuery}
onChange={() => search()}
placeholder="Search Contacts"
keyboardType="default"
/>
Let me know if it works
Replace your code with this :
<TextInput
value={searchQuery}
onChangeText={setSearchQuery}
onChange={search} // <- This One is Important
placeholder="Search Contacts"
keyboardType="default"
/>
Change Line 4 in TextInput from onChange={search()} to onChange={search}.
By calling the function you are triggering an infinite re-render at each re-render.
There are probably other issues since it's unlikely that your component wants a double onChange event listener, I made a simple demo to show you how to possibly manage these scenarios in a cleaner way: https://stackblitz.com/edit/react-eijegp
Related
Today I was practicing with React and found two ways to get input values when submitting form.
First:
Using hooks, for example:
...
const [name, setName] = useState('');
...
return <input onChange={(value) => setName(value)} value={name} />
Second:
Get by using event.target.children[i].value, for example:
const handleSubmit = (event: BaseSyntheticEvent) => {
event.preventDefault();
const formInputs = event.target.children;
for (const input in formInputs) {
console.log(formInputs[input].value);
}
};
The question is, which one should I use and why? Is it optional, or I don't know, it does have some performance impact.
Don't use vanilla DOM manipulation in React when you can avoid it, within reason. The first approach is far better in most situations.
Working with the DOM is slow. State and the virtual DOM help a lot.
In the React paradigm, the appearance and functionality of the application should ideally stem from state as much as possible. Non-stateful side-effects can make things difficult to reason about and hard to work with given how re-rendering works and should be avoided.
If the reason you're tempted to go with the second approach is that you have a whole lot of inputs and making a separate state for each is tedious, one option is to make the state be a single object (or Map) instead.
const MyInput = ({ name, state, onChange }) => (
<Input name={name} onChange={onChange} value={state[name]} />
);
const onChange = (e) => {
setState({ ...state, [e.target.name]: e.target.value });
};
<MyInput name="name" state={state} onChange={onChange}/>
Im using MUIs input field for a lot of input sections of my questionnaire and want to save it to my state variable that is an object which holds all my form values. How can I manipulate my formData object in a reusable MUI component?
Im currently passing formData as a prop to the component but am unsure how to use the setFormData inside the component since it will be a different property each time I use it.
I was thinking each question is a property of the state object formData so itll look like
formData{question1: 'foo', question2: 'bar'}
This is how the form looks at the moment (i dont mind changing the structure if it makes sense to do so
Take a look at InputField on the form (i also attached the component code)
<QuestionLabel>Do you have a "Nickname":</QuestionLabel>
<InputField value={props.formData.nickname}/>
<QuestionLabel>Preferred Language:</QuestionLabel>
<InputField value={props.formData.language}/>
<QuestionLabel>Occupation:</QuestionLabel>
<InputField value={props.formData.occupation}/>
This is how the component looks (im aware i will have to change this)
export default function InputField(props){
return(
<TextField
fullWidth
value={props.value}
variant='standard'
/>
)
}
Disclaimer
First post so sorry if the format isn't perfect or if i attached the code snippets in a inconvenient way, please give me some pointers if thats the case
Since there is no two-way binding in react, the normal way of processing form input with controlled components is to pull the text from the specific dom element every time it changes. We do this using event.target.value to pull the data, but how do we then add it correctly to your state in a reusable component?
For that we would want to add a name tag to the input field, which we would then use when adding the value to our state. As you correctly stated, formData.someName = 'some text' We update our state in a changeHandler function that updates our state every time the input changes.
For example, assume we are passing setFormData, formData and a fieldName into the props:
export default function InputField(props){
const {setFormData, formData, fieldName} = props //destructure the props
const changeHandler = (event) => {
// updates the formData state of formData.name to equal event.target.value
setFormData({...formData, [event.target.name]: event.target.value})
}
return(
<TextField
fullWidth
name={fieldName}
value={formdata.fieldName}
onChange={changeHandler}
variant='standard'
/>
)
}
Using the above component would look something like this:
<InputField setFormData={setFormData} formData={formData} fieldName="question1">
I was able to get it working by passing the property into the component that needed to be changed; here is my handleChange function on the textfield component:
const handleChange = (event) => {
var formData = {...props.formData}
formData[props.property] = event.target.formData
props.onChange(formData)
}
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 am using Redux to create a quiz app that includes a form with some nested fields. I have just realized (I think) that every key press to my input fields triggers a re-render if I use the children prop, i.e. designing the app like this:
const keys = Object.keys(state)
<QuizContainer>
{keys.map(key =>
<QuizForm key={key}>
{state[key].questions.map(({ questionId }) =>
<Question key={questionId} questionId={questionId}>
{state[key]questions[questionId].answers.map(({ answerId })=>
<Answer answerId={answerId} key={answerId} />
)}
</Question>
)}
</QuizForm>
)}
</QuizContainer>
QuizContainer is connected to redux with mapStateToProps and mapDispatchToProps and spits out an array of arrays that all have objects inside them. The store structure is designed according to Dan Abramov's "guide to redux nesting" (using the store kind of like a relational database) and could be described like this.
{
['quizId']: {
questions: ['questionId1'],
['questionId1']:
{ question: 'some question',
answers: ['answerId1', 'answerId2']
},
['answerId1']: { some: 'answer'},
['answerId2']: { some: 'other answer'}
}
The code above works in terms of everything being updated etc, etc, no errors but it triggers an insane amount of re-renders, but ONLY if I use the composition syntax. If I put each component inside another (i.e. not using props.children) and just send the quizId-key (and other id-numbers as props) it works as expected - no crazy re-rendering. To be crystal clear, it works when I do something like this:
// quizId and questionId being passed from parent component's map-function (QuizForm)
const Question ({ answers }) =>
<>
{answers.map(({ answerId }) =>
<Answer key={answerId} answerId={answerId} />
)}
</>
const mapStateToProps = (state, { questionId, quizId }) => ({
answers: state[quizId][questionId].answers
})
export default connect(mapStateToProps)(Question)
But WHY? What is the difference between the two? I realize that one of them is passed as a prop to the parent instead than being rendered as the child of that very parent, so to speak, but why does that give a different result in the end? Isn't the point that they should be equal but allow for better syntax?
Edit: I can now verify that the children prop is causing the problem. Setting
shouldComponentUpdate(nextProps) {
if (nextProps.children.length === this.props.children.length) {
return false
} else {
return true
}
}
fixes the problem. However, seems like a pretty black-box solution, not really sure what I am missing out on right now...
When you use the render prop pattern you are effectively declaring a new function each time the component renders.
So any shallow comparison between props.children will fail. This is a known drawback to the pattern, and your 'black-box' solution is a valid one.
Okay so I figured it out:
I had done two bad things:
I had my top component connected to state like so: mapStateToProps(state) => ({keys: Object.keys(state)}). I thought the object function would return a "static" array and prevent me from listening to the entire state but turns out I was wrong. Obviously (to me now), every time I changed the state I got a fresh array (but with the same entries). I now store them once on a completely separate property called quizIds.
I put my map-function in a bad place. I now keep render the QuizContainer like so:
<QuizContainer>
{quizIds.map(quizId =>
<QuizForm>
<Question>
<Answer />
</Question>
</QuizForm>
)}
</QuizContainer>
And then I render my arrays of children, injecting props for them to be able to use connect individually like so:
{questions.map((questionId, index) => (
<React.Fragment key={questionId}>
{React.cloneElement(this.props.children, {
index,
questionId,
quizId
})}
</React.Fragment>
))}
That last piece of code will not work if you decide to put several elements as children. Anyway, looks cleaner and works better now! :D