I have dynamic JSON data and have a custom method to go through the JSON to dynamically render a form on the page. The reason for the JSON schema is to build various forms that is not predefined.
I have hooked up Redux so that the schema and the formValues below gets assigned as the props of this class. So, the form is rendering correctly with the correct label, correct input field types etc. When an onChange event happens on the fields, the app state(under formData) is being updated correctly. But I am noticing that when the formData changes in the app state, the entire form gets re-rendered, instead of just the "specific fields". Is this because I am storing the form values as an object under formData like this? How do I avoid this issue?
formData = {
userName: 'username',
firstName: 'firstName
}
Example schema
const form = {
"fields":[
{
"label":"Username",
"field_type":"text",
"name":"username"
},
{
"label":"First Name",
"field_type":"text",
"name":"firstName"
}
]
}
Redux state
const reducer = combineReducers({
formSchema: FormSchema,
formData: Form
});
//render method
render() {
const { fields } = this.props.form,
forms = fields.map(({label, name, field_type, required }) => {
const value = value; //assume this finds the correct value from the "formData" state.
return (
<div key={name}>
<label>{label}</label>
<input type={field_type}
onChange={this.onChange}
value={value}
name={name} />
</div>
);
})
}
//onchange method (for controlled form inputs, updates the fields in the formData app state)
onChange(event) {
this.props.dispatch(updateFormData({
field: event.target.name,
value: event.target.value
}));
}
From your example I'm not sure, but if you're rendering the whole thing in a single render() method, yes, the component will be rendered again. And that is the problem, THE component. If you are trying to have multiple components, then they should be split up as much as possible. Otherwise if the state changes, it triggers a re-render of the only component there is.
Try breaking it as much as you can.
Hints: (dont know if they apply but maybe)
use ref={}s
implement shouldComponentUpdate()
EDIT: Just thought about this, but are you storing the fields' values in your state? This doesnt feel correct. Be sure to read carefully the React guide about controlled components. (Eg try to render using plain <span>s instead of inputs, and listen to onKeyPress. Would it still work? If not you might be misusing the value attribute)
Related
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 need to create a form in React made of multiple components, such as <TextField>, <DropDown>, <DatePicker> where each one has a different props, some of them are common such as Id, Label
Question is how should I program this, I have come with a few ideas ->
It would be easier to split it into separate arrays: dropdowns, textFields, datePickers and then render them via .map(), but it wouldn't work as they are not in order in the form
Put all of components into array and render them via .map() with if/switch/function where I would decide which component is which
const App = (props) => {
const forms = props.forms
const getItem(type) {
if (type === Type.DropDown) {
return <Dropdown dropDownProps>
}
....
}
return (
forms.map(item => {
return getItem(item.type)
})
)
}
Next question is how/where to store all props - there is about 10 of these fields in the form, so should I do some file like InitData.ts with
const FirstField = {id: 0, label: "FirstField"}
...
but then how would I pass props such as onChange etc?
Also how should I store all those inputs using hooks? Array of strings, or some object?
Always base your components on abstraction and try to make them reusable, so assume I want to create a login form.
I would create a folder called common having common components, like text field and dropdowns as a way of reusing them.
and then another component for wrapping up most of the functionalities: handling submit events, handling form validation, render input, render dropdown. Most of these functionalities are core in my form, so abstract them away into a separate component called form component.
finally, I create another component called loginForm, in which I call these functionalities to render them.
I store the initial field values of type empty strings in a state hook inside loginform and pass it as an argument to the props of the form component.
I'm having trouble wording my question, so hopefully I can explain it well enough here!
I'm making a generic form component, which handles form validation on the top level. The end result would look like this:
<Form action='/api/endpoint'>
... form inputs go here
</Form>
Everything is working really well. If I go through and fill out the form, I can submit it just fine and the validation works.
The form has state which looks like this:
{
values: { name: 'Johnny', age: 18, email: 'email#example.com' },
used: { name: true, age: true, email: true },
validators: { name: (Yup Validator), age: (Yup Validator), email: (Yup Validator) }
}
The state is populated using custom onChange and onBlur functions in each of the components (they are passed using react context). For example, onBlur the used part of the state is updated to true for the current element, onChange the values part of the state is updated to the elements value, etc.
As aforementioned, this works well when I go through and use every element. What I want to happen is that when the user clicks the Submit button, it checks EVERY field to ensure it's valid before sending the POST request.
An easy solution I thought to do would be to just programmatically blur every element which calls the onBlur function. However, because setState is asynchronous, it only adds the last element to the state (because it's being overridden, if that makes sense).
I'm wondering if I can either modify how I'm setting my form state or if there's a way I can wait before setting the form state again.
I'm using functional components, and I know that useEffect exists for this purpose, but I'm not sure how to use it in my situation.
Here's how I'm setting the state onChange and onBlur:
const handleBlur = (e) => {
e.persist();
setFormState({...formState, used: {...formState.used, [e.target.name]: true}});
}
(hopefully you can infer onChange from that, it's the same but with values instead of used)
My idea of blurring everything was as follows:
const blurAll = async () => {
let inputs = [];
// Get all inputs on the current page
const inputTags = document.getElementsByTagName("input");
// Get all text areas (because they are <textarea> instead of <input>)
const textAreas = document.getElementsByTagName("textarea");
// Concat the arrays
inputs.push(...inputTags, ...textAreas);
// Trigger a blur event for each (to initialize them in formstate)
inputs.map(input => { input.focus(); input.blur(); });
}
However because setFormState uses the existing form state, it all happens too fast and so only the last element is added to the state.
I understand that this blurAll is probably not a great solution, but I was just trying to get something working (and then I was going to get only the inputs in the form itself).
This leads me to my question, how do I wait for the previous setState to complete before setting the state again (in the case on my onChange and onBlur)?
OR is there a way in JavaScript to simply update one key in my state object instead of replacing the entire object?
If you need any more information, comment and I'll provide as much as I can. I'd prefer not to use a form library, I'm trying to learn React, and this seemed like a good thing to further my knowledge! Thank you!
I have all my states in my parent App.js. I have initialized states like so.
this.state = {
name: ''
}
I have a child component Child.js which is stateless.
Now I have a form in Child.js and when submitted, calls function onSubmit() which then sends data to my database.
There are two ways by which the data can be passed from the child component to the onSubmit function. I can pass the data as argument from Child.js and then use it as parameter in my App.js like this.
Child.js
<Button
title="Save"
onPress={() => this.props.onSubmit(myformvalue)}
/>
After this, I can just use the values as parameter in my onSubmit function like this.
onSubmit (value) {
fetch('myip', {
method: 'POST',
body: JSON.stringify({
customername: value,
}),
}
}
OR,
I can create a function in App.js that tracks the state change in Child.js's form and then use the current state when submitting like this.
App.js
changingstatefunc(NameParameter) {
this.setState ({
name: NameParameter,
})
}
onSubmit () {
fetch('myip', {
method: 'POST',
body: JSON.stringify({
customername: this.state.name,
}),
}
}
And In Child.js,
<TextInput
placeholder="Customer Name"
onChangeText={(customername) => this.props.changingstatefunc(customername)}
/>
I am not sure which one to choose as both work. In React.js they want you to use controlled input but in react-native, would it be okay to just use the first method to pass parameters instead of changing states if I do not need anything special to happen on each press of a button?
I think the best solution - create stateful Child component and control all inputs in this component, and onSubmit just pass data from Child state to App. It'll allow you to validate inputs in the Child component and the App component will be much readable
I have a very basic comment form that takes some text input from a user and sends a POST request via AJAX to create a new comment.
var CommentForm = React.createClass({
propTypes: {
// ...
// ...
},
handleFormSubmit: function(e) {
e.preventDefault();
var component = this;
return $.ajax({
type: "POST",
url: this.props.someURL,
data: // ???? - Need to figure out how to serialize data here,
dataType: "json",
contentType: "application/json",
success: (function(response){ alert("SUCESS") }),
error: (function(){ alert("ERROR"); })
});
},
render: function() {
var component = this;
return (
<form onSubmit={component.handleFormSubmit} className="comments__form" id="new_comment" accept-charset="UTF-8">
// ...
</form>
);
}
});
I need to serialize the form data to send along with my POST request, but I'm not sure how. I know in JQuery I can select the form element and do something like $("#my_form").serialize(), but I can't seem to call that from inside the React component (not sure why?)
Some other stackoverflow answers suggested adding a ref='foo' to each relevant DOM element and then I can access them with React.findDOMNode(this.refs.foo).getValue();. This works fine but it leaves me to manually construct the whole serialized form data string, which isn't pretty if the form is a lot longer and complex.
// Sample serialized form string
"utf8=✓&authenticity_token=+Bm8bJT+UBX6Hyr+f0Cqec65CR7qj6UEhHrl1gj0lJfhc85nuu+j2YhJC8f4PM1UAJbhzC0TtQTuceXpn5lSOg==&comment[body]=new+comment!"
Is there a more idiomatic way to approach this - perhaps a helper that will let me serialize my form data within ReactJS?
Thanks!
You would usually use the component state to store all the information.
For example, when an inputs text is changed, you would change the state to reflect that:
<input type="text" onChange={ (e) => this.setState({ text: e.target.value }) }
Then, when it comes to submit the form you can access the value using this.state.text.
This does mean that you would have to build the object yourself however, although, this could be achieved via a loop:
this.state.map((value, index) => {
obj[index] = value;
});
and then send obj as the post data.
UPDATE
The only performance issue with updating the state is that the component is re-rendered each time. This isn't an issue and is the way react components work, as long as you watch for the size of the render method and what it is doing.
Forms in the usual HTML <form> tag sense, don't exist within React. You use the form components to change the state. I suggest reading up on React Forms
In terms of the UTF8 flag, that would be the value of a hidden field, so you could use refs in this case to get that value for your object:
<input type="text" ref="utf8" value="✓" />
obj.utf8 = this.refs['utf8'].value
For those who want to use Serialize form in ReactJs with functional component try this:
1. Create a state
const [form, setForm] = useState({});
2. Create a function handler
const handleForm = (name, value) => {
setForm({...form, [name]: value});
}
Now you got your object here:
const handleSubmit = () => {
console.log(form);
}
3. In your render
<form>
<input onChange={(e)=>handleForm('name', e.target.value)} />
<input onChange={(e)=>handleForm('phone', e.target.value)} />
<button onClick={()=>handleSubmit()}>Submit</button>
</form>
UPDATE 2022
Or use useForm
useForm is a custom hook for managing forms with ease. It takes one object as optional argument. The following example demonstrates all of its properties along with their default values.