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!
Related
Consider i have multiple Vue.js components.
<loginFields></loginFields>
<submitButton></submitButton>
Now, after clicking submitButton (which in fact is a div with unique id) i want a method to be started to check values of loginFields.
Component loginFields has v-for directive inside which creates multiple input fields like username or password.
What i want to achieve is to get value of user loginField and password loginField and pass it to method from submitField.
With jQuery it would be easy - call $('#userLoginField').val() and it is done.
I believe there are two ways to do it in vue, however i guess they are both not best approaches:
create Bus and on clicking submitButton emit an event which starts cascade of events which finally comes back to submitButton with value of all input fields. I think it is quite messy.
Call this.$parent.$refs of submitField and then get refs of inputFields component, search it for all other refs, grab value. It is messy too.
Is there a short and clean solution like jQuery version to easily get data from other children of same parent or siblings? Each sibling may have unique id, be a part of same/different class so it can be easily identified.
Kalreg.
I think the simplest way (i.e., least amount of "Vue wiring") is using the native form's submit-event, which includes the form's elements (including children of <login-fields>) in event.target.elements. The submit-event handler could validate those elements' values as needed.
// in <login-form>
methods: {
submit(e) {
// Get elements that have a "name" attr
const elems = Array.from(e.target.elements).filter(el => el.name);
// Create dictionary of those elements' values
const values = elems.reduce((p,c) => {
p[c.name] = c.value;
return p;
}, {});
if (this.validate(values)) {
// submit form
} else {
// don't submit...show error
e.preventDefault();
}
}
}
demo
I have been working with react for about 6 months now and something that always used to bother me is the way re-renders work.
Below is a traditional component that has one input box and sends data to the server to whose value is used by some other forms along with multiple almost static HTML elements that are never used or change very rarely. I am saying very rarely because static elements can be built and stored in a variable in the componentWillMount() method. But for this question to be a little more than that, render should contain a call to buildComplexHTMLFromData method.
buildComplexHTMLFromData = (data) => {
// Lot of javascript to build the boxes based on the initial or data
// that changes so rarely
// array.map.filter.find etc.
return (
<div>
//Complex HTML element 1
//Complex HTML element 2
//....
//....
//Complex HTML element n
</div>
)
}
sendDataToBackend = (event) => {
this.setState(
{ value: event.target.value },
() => this.functionThatSendsDataToBackend()
)
}
render() {
<div>
// Active input that sends data to the backend
<input
value={this.state.value}
onChange={this.sendDataToBackend}
/>
{this.buildComplexHTMLFromData()}
</div>
}
Now setting state upon input box change will trigger even the buildComplexHTMLFromData method that does complex javascript all over again. I heard React does something smart by diffing across DOM to efficiently re-render but this javascript is executed anyway.
On the other hand the same functionality can be achieved using two varieties of sendDataToBackend method as shown in the snippet below. This however ensures that only the target input element is changed without touching the already rendered elements or executing any javascript on buildComplexHTMLFromData method.
buildComplexHTMLFromData = (data) => {
// Lot of javascript to build the boxes based on the initial or data
// that changes so rarely
// array.map.filter.find etc.
return (
<div>
//Complex input box 1
//Complex input box 2
//....
//....
//Complex input box n
</div>
)
}
sendDataToBackend = (event) => {
//First strategy
var element = document.getElementById("persistable-input");
element && element.value = event.target.value
//Second strategy
this.persistableInput.value = event.target.value
}
render() {
<div>
// Active input that sends data to the backend or for other forms
<input
id="persistable-input"
ref={(elem) => { this.persistableInput = elem }}
value={this.state.value}
onChange={this.props.persistedValue}
/>
{this.buildComplexHTMLFromData()}
</div>
}
I don't know if I am missing something or if this is very minimal on performance but I feel it could be quite taxing for complex components. I looked multiple articles on React's reconciliation paradigm but it does not seem to address this.
I would really appreciate if anyone could shed some light into this area of React because I am looking for some cool tips and inputs on performant reconciliation in React in most cases.
Thanks in advance.
This is exactly what the shouldComponentUpdate lifecycle hook was created for. If you know that your component shouldn't always re-render, then you can add this lifecycle hook to detect which piece of state is changing. If it something that you don't care about, you can return false and the buildComplexHTMLFromData function won't ever run.
EDIT:
They also expose a base class called PureComponent that handles shouldComponentUpdate under the hood for you.
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)
I am confused about why I cannot clear <input> field values using this simple pattern with React:
onComponentDidMount: function () {
this.clearFields();
},
clearFields: function () {
document.getElementById('username_field').value = '';
document.getElementById('password_field').value = '';
},
I don't think it's a problem with React, I think there some other issue at hand but I am not sure what's going on. But the fields definitely do not clear out. Later on, I can call this.clearFields() and that function does work as expected, but not when the component first mounts.
The correct React lifecycle function is called componentDidMount, not onComponentDidMount
However, you don't want to do it this way if the inputs are also rendered with React. It's usually better and more relevant to the application to change values stored in the state, and let the render function deal with setting the value of the input fields.
I'm trying to 'reset' a ReactJS element.
In this case, the element is 90%+ of the contents of the page.
I'm using replaceState to replace the state of the element with with its initial state.
Unfortunately, sub-elements which have their own 'state' do not reset. In particular, form fields keep their contents.
Is there a way of forcing a re-render of an element, which will also cause sub-elements to re-render, as if the page had just loaded?
Adding a key to the element forces the element (and all its children) to be re-rendered when that key changes.
(I set the value of 'key' to simply the timestamp of when the initial data was sent.)
render: function() {
return (
<div key={this.state.timestamp} className="Commissioning">
...
The this.replaceState(this.getInitialState()) method doesn't actually reset children that are inputs, if that's what you're looking for. For anyone looking to just reset their form fields, there is a standard DOM reset() function that will clear all the inputs in a given element.
So with React, it'd be something like this:
this.refs.someForm.getDOMNode().reset();
Doumentation:
https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/reset
If it is a form you want to reset, you simply can use this
// assuming you've given {ref: 'form'} to your form element
React.findDOMNode(this.refs.form).reset();
While I don't personally think you should store local, interim component state (like in-progress input boxes) in a centralized location (like a flux store) in most cases, here it may make sense, depending on how many you have, especially since it sounds like the inputs already have some server interaction/validation around them. Pushing that state up the component hierarchy or into some other central location may help a lot in this case.
One alternative idea off the top of my head is to use a mixin in components that might need to reset local state, and do some kind of event triggering, etc. to make it happen. For example, you could use Node's EventEmitter or a library like EventEmitter3 with a mixin like this (warning: not tested, maybe best this as pseudocode :)
var myEmitter = new EventEmitter(); // or whatever
var ResetStateMixin = {
componentWillMount: function() {
myEmitter.on("reset", this._resetState);
},
componentWillUnmount: function() {
myEmitter.off("reset", this._resetState);
},
_resetState: function() {
this.replaceState(this.getInitialState());
},
triggerReset: function() {
myEmitter.emit("reset");
}
};
Then you could use it in components like so:
React.createClass({
mixins: [ResetStateMixin],
getInitialState: function() {
return { ... };
},
onResetEverything: function() {
// Call this to reset every "resettable" component
this.triggerReset();
}
});
This is very basic and pretty heavy handed (you can only reset all components, every component calls replaceState(this.getInitialState()), etc.) but those problems could be solved by extending the mixin a bit (e.g. having multiple event emitters, allowing component-specific resetState implementations, and so forth).
It's worth noting that you do have to use controlled inputs for this to work; while you won't need to push your state all the way up the component hierarchy, you'll still want all your inputs to have value and onChange (etc.) handlers.
You could also use document.forms[0].reset()