How to detect only the changed fields in Angular 2 forms? - javascript

I am using FormGroup and FormControls toghether with ngrx to build a reactive form. Also I'm using the Inspector of the Chrome redux redux dev-tools. I want to properly render the history of actions while skipping some form change actions. Currently skipping any form action before the last one will not project as if that specific form change was not made. The form sends a full object with all the fields applied. Thus any change from previous actions is obscured because each actions replaces all previous properties of the form's state.
A bit of context: I am stashing in state store a person object while the user fills up a form inside a modal. Then on submit I send the person data to the server.
The form component
// Emit events when form changes
this.personForm.valueChanges
.debounceTime(500)
.subscribe(person => {
// Block #Input person update from triggering a form change
if (this._personFormInputUpdated === true) {
// Reset #Input safe-guard
this._personFormInputUpdated = false;
return;
}
// Ignore unchaged form
if (!this.personForm.dirty) { return; }
debug('Person form changed');
this.personChange.emit(Object.assign({}, person));
});
The reducer:
case AccountsDataActions.STASH_ACCOUNT_PERSON:
newState = Object.assign({}, state, {
stashedAccountPerson: Object.assign({}, state.stashedAccountPerson, action.payload)
});
debug('STASH_ACCOUNT_PERSON:', [newState.stashedAccountPerson]);
return newState;
I am considering using some diff-checking library in order to select only the changed fields for the next STASH_ACCOUNT_PERSON action. Is there a simpler method that doesn't require an additional library? Something built-in into ng2 forms?
Thanks!
Edit
ngOnChanges() has a similar effect for #Input decorators. Is there something similar for forms?

Yes. try to use distinctUntilChanged method.
Returns an observable sequence that contains only distinct contiguous elements according to the keySelector and the comparer.
this.personForm.valueChanges
.debounceTime(500)
.distinctUntilChanged()
.subscribe(person => {
// only what changed!
});

You can have look at KeyValueDiffers and KeyValueDiffer in the #angular/core

Related

Need some suggestions for a dynamic form using React

I'm building an enterprise-level application and I need some tips and suggestions for handling dynamic form.
The fields of the form are totally dynamic and they come differently for each user.
I loop through each field(fields come from an API call) on a file called renderUiType.js and based on a property of the field called uitype, we render different Inputs.
For example if uitype===1{render TextField}, if uitype===2{ render Checkbox } and so on...
So far the displaying part is correct but now I want to save the values of each field rendered and have them all in an object so I can do a POST API Call
So my question is, how can I do that? Should I create an onChange handler function for each form-element at the main file renderUiType.js and then pass it with props to the form-elements components or should I use Redux?
Any suggestion/article or anything is welcomed. Thank you
The folder structure looks like the image below(just in case it helps to understand what I ask)
..
You can use one callback function and use it in each onChange component specific handlers. You could have everything in state of the Form if you would like hidden under the unique keys/id, so you don't need to have Redux. f.e.
if (uitype===1)
{render <TextField value={this.state[fieldId]} onChange={this.onChange}/>}
if (uitype===2)
{ render <Checkbox value={this.state[fieldId]} onChange={this.onChange}/>}
or to simplify:
const getComponentByUIType = (uiType) => {
switch(uiType) {
case 1: return TextField
case 2: return Checkbox
}
}
// ...
onChange = fieldId => value => this.setState(state => ({fieldId: value}))
//...
render() {
getComponentByUIType(uiType).map(Component => <Component value={this.state[fieldId]} onChange = {this.onChange(fieldId)} />
}
Using Redux for this shouldn't be necessary unless you need to access this form's state somewhere outside the form. If you only need the form info to do a POST, I would keep all the data inside one component's state.
Just use the unique ID provided by the IP (the one you were gonna use for the POST) to build that state object. Every field will have an onChange that updates the main form component's state, and then that same value from the state is passed in to each field as a prop.

React - Mutating form object for each change . Is that anyway we can do it in a better way

handleChange(evt, field) {
let form = this.state.form;
form[field] = evt.value;
console.log('change triggered');
this.setState({
form: form
});
}
render(){
return(
<div>
<input type="text" name="name" id="name"
text={this.state.form.name}
onChange={event => { this.handleChange(event, 'name'); }} />
<br />
<input type="text" name="email" id="email"
text={this.state.form.email}
onChange={event => { this.handleChange(event, 'email'); }} />
</div>
);
}
I have added simple form for reference . In the above content Whenever form field changes handleChange method invoked and In that form object mutated updated as per field changes and setting to state object.
I want to know whether we can avoid mutation of object of each field text changes or Is there any other better way to address the same . Also want to know object mutation affects the performance in any way because here I mentioned couple of fields and In my original project I am working contains atleast 15 to 20 fields for each form.
Added the same working module.
https://stackblitz.com/edit/react-ssgcnu?file=Hello.js
Please help me out . Thanks in advance
If you don't want to update each character change you can use onBlur event.
onBlur={event => { this.handleChange(event, 'name'); }}
So that on leaving the field only you can update the state.
'...' Spread operator produces a shallow copy of an object
handleChange(evt, field) {
let form = {...this.state.form}; //object spread notation
form[field] = evt.value;
console.log('change triggered');
this.setState({ form: form });
}
When it comes to forms with React, there are two approaches.
1) Uncontrolled components, which basically making use of refs and getting values from the DOM (please check the official documentation for more details https://reactjs.org/docs/uncontrolled-components.html) or
2) Controlled components making use of states and getting/handling values from React states (please check the official documentation for more details https://reactjs.org/docs/forms.html#controlled-components)
With controlled components => React, state is the single source of truth (as official document suggests), that means, you need to provide methods to handle state changes as user provides the input
With uncontrolled components => Instead of updating state on every single change, you can get values inside of onSubmit method and handle them before submit. Since you won't need to update the state, you won't need additional functions to handle state changes.
For you seeking of better way to handling things and avoid mutations, it actually depends on your use case, but official documentation suggests that
In most cases, we recommend using controlled components to implement
forms. In a controlled component, form data is handled by a React
component
When it comes to mutating object, indeed mutations is not good, but there are ways to avoid mutations during state changes. as #mhkit already suggested, you can use spread operator to create a new object (shallow copy, that means it only copies the values) or you could use Object.assign() method.
Let's say you have the following state
state = {
form: {
email: '',
name: '',
lastName: '',
}
}
When you user provides the email, you basically need to update the email field,
in that case in your handleChangeEmail() method, what you can do is the following
this.handleChangeEmail = (value) => {
this.setState(({form}) => {
return {
form: {
...form,
email: value
}
})
}
So with this method, what I basically do is,
1) I utilize functional setState and extracted the current value of form via ES6 object destructuring, then I say that, okay my new form object inside of the state, will have all the existing field that former form has, BUT, the email field will have a new value based on the new input user provided.
By this way, instead of mutating form object, we created a shallow copy of it with some values are exactly the same, but some values are updated. THUS we prevent the mutation

Why is ugly hack required for react-redux to re-render redux-form?

I have a react app that is using a redux store and this component also uses redux-form. I am seeing a strange behavior on one particular type of update where I want to trigger a follow-on update.
Basically a user is updating permissions for something on the form, then saves. The save submits the form and in the processing of the action, it triggers another action (getting a list of all the somethings). When the list is returned from the server, this dispatches an action to update the list UI. At this point I am updating a property in the redux store for the form to say trigger an update of the permissions for the currently editing thing. This is the only point where I can do this because we now have the information to trigger this request.
The problem is that a boolean flag is being set as the trigger, and the mapStateToProps is getting called with the updated flag, but the component is not subsequently getting called (shouldComponentUpdate and componentWillUpdate never get called). It is like it ignores the state of the boolean variable in the props.
I found that if, in mapStateToProps I set another property based on the
boolean trigger, then the component is updated (re-rendered).
function mapStateToProps(store, ownProps) {
...
let triggerFetchSomethingOwnershipHash = 0;
if (store.triggerFetchSomethingOwnership) {
triggerFetchSomethingOwnershipHash = 100000 * Math.random();
}
return {
...
triggerFetchSomethingOwnership,
triggerFetchSomethingOwnershipHash
}
}
...
const ConnectedSomethingDetail = connect(mapStateToProps)(SomethingForm);
export default ConnectedSomethingDetail;
Why do I need to do this and how can I avoid it?
Yes I have checked a few relevant SO questions like react-redux-component-does-not-rerender-on-store-state-change and react-redux-update-item-in-array-doesnt-re-render

Submit button from outside Redux Form v6.0.0

This question relates to Redux Form v6.0.0 (in time of writing this question it is v6.0.0-alpha.15).
How can I get form validation status (like pristine, submitting, invalid) from outside of form component ?
Let me give an example. This is "classical redux-form" pseudo-structure:
<Form(MyExampleForm)>
<MyExampleForm>
<input name="title" ... />
<submit-button />
...where <submit-button> in JSX looks like this:
<button type="submit" disabled={pristine || submitting || invalid} >Save</button>
But in my application, my submit button must be outside of the form, placed on different place in the application (let's say in application header, on the top of whole application).
How can I get those pristine, submitting, invalid from outside of Redux-Form? (Without really nasty hacking, if possible :-))
How can I submit that form?
Just decorate another component with same form name and you have access to same state variables there. Also you can pass it onSubmit function from parent and be able to submit all the Field values from wherever you define them as they are all from redux state, not HTML of current form instance. (it is kind of "hacky" way, but it feels right)
The submit function is defined from parent, not shared in state, so you can have it different for every instance.
class MySubmitForm extends React.Component {
render() {
return (
<button
onClick={this.props.handleSubmit}
>
{this.props.pristine ? 'pristine' : 'changed'}
</button>
)
}
}
export default reduxForm({
form: 'myFormName'
})(MySubmitForm);
redux-form works with React Redux to enable an html form in React to use Redux to store all of its state.
If "outside of Redux-Form" means still redux application, you can try to store those properties in state by dispatching some actions.
In forms: you're detecting whats happening (when its invalid etc), dispatch an action to modify a state,
In "outside" part: you're passing a proper property to component (with those you need) and depends on that you disable a button.
in latest redux-form version 6.0.2:
it is possible to access form state pristine, submitting, invalid via selectors http://redux-form.com/6.0.2/docs/api/Selectors.md/
it is possible to export redux-form action creators http://redux-form.com/6.0.2/docs/api/ActionCreators.md/
Maybe you can have a look at the Instance API of redux-forms. It provides access to a submit() method on an instance of your decorated form component. There is also a pristine Boolean property and an invalid Boolean property available (there is a request to expose the submitting property too).
There is an example here : http://redux-form.com/5.3.1/#/examples/submit-from-parent?_k=jgv0m4 (example is for 5.3.1, but the process is similar with v6 using the Instance API)
The basic idea is that by adding a ref="myExampleForm" to your form, you can pass it around with this.refs.myExampleForm. You can then check properties of the instance or call the submit() method (or any other method exposed).
Now is easier to do this. You can call the Submit action in the standalone component that has the button.
See this example:
https://redux-form.com/7.1.2/examples/remotesubmit/
If we are talking about just submitting the form, then you should provide {withRef: true} option to your redux connect() function.
Consider Row component that has child RowDetail component which has information that should be saved from Row.
RowDetail in this case could be created like this:
import { connect } from 'react-redux';
import { reduxForm } from 'redux-form';
const RowDetailForm = reduxForm({form: 'row-detail-form'})(RowDetail);
export default connect(mapStateToProps, null, null, {withRef: true})(RowDetailForm);
Then, in your parent component (Row) you create your form with ref attribute:
<RowDetailForm ref={'rowDetailForm'} .../>
Submitting now is 'easy':
onSave() {
this.refs.rowDetailForm.getWrappedInstance().submit();
}
If we are talking about pristine and other form properties, then you could try to get them from your mapStateToProps function in your parent component.
const rowDetailFormName = 'row-detail-form';
const mapStateToProps = (state) => ({
rowDetailForm: state.form[rowDetailFormName]
});
But this way seems a bit hacky because as I understand redux-form API all form states were never meant to be accessed directly. Please correct me if I am wrong
I had to tackle this issue recently. I ended up passing a callback to the form, which is invoked every time the properties I'm interested in change.
I use the componentDidUpdate life cycle method to detect changes.
This should work on any version of the library.
Here is a post with sample code - http://nikgrozev.com/2018/06/08/redux-form-with-external-submit-button/

React: update a field value based on another field in dynamically generated form

I'm trying to find out the "proper" way to do the following in React:
I have a form with two fields, url and title.
Whenever url's value changes, I make an API call to Embedly to retrieve metadata about the link.
Once the metadata has been retrieved, I want to update the title field with the result.
The difficulty here is that the url and title fields are not in the same component. Here's the basic structure of the form (I'm using Formsy):
<Formsy.Form onSubmit={this.submitForm} onChange={this.updateState} ref="form">
{fields.map(field => <FormComponent
name={field.name}
type={field.type}
value={field.value}
/>)}
<Button type="submit">Submit</Button>
</Formsy.Form>
As you can see, the form loops over an array of fields and calls a generic <FormComponent/> component for each of them, which is basically a big switch that then calls the appropriate Formsy Component based on the field's type.
The logic for querying Embedly is already working inside the component for the url field, but I'm not sure if there's a way to accomplish what I want while still using the default Input component for title?
[Edit: thanks to Dan I was able to come up with a better solution for step 4 and after]
So here's the solution I came up with. I have no clue if it's the best pattern or not, but at least so far it seems to work.
Step 1: add a addToPrefilledValues method to the <Form/> component that takes a property and adds it to the prefilledValues object on that component's state.
Step 2: add addToPrefilledValues to the form component's context so that the method is passed on to all child components (note: I could also pass it as a prop but context seems easier to pass it on to grandchild components).
Step 3: make my <URLField/> component call addToPrefilledValues whenever it receives new metadata from Embedly:
this.context.addToPrefilledValues({title: result.title, body: result.description});
Step 4 [Wrong, see below]: in the <TitleField/> component's shouldComponentUpdate method, watch for changes to the context and update the field's value (if it's empty):
shouldComponentUpdate(nextProps, nextState, nextContext) {
const nextTitle = nextContext.prefilledValues && nextContext.prefilledValues.title;
const currentTitle = this.context.prefilledValues && this.context.prefilledValues.title;
if (!!nextTitle && nextTitle != currentTitle && !this.input.getValue()) {
this.input.setValue(nextTitle);
}
return true;
}
Step 4 (better): whenever the form changes, store all of the form's values in a currentValue object on the form's state.
Step 5: Look at:
the original value of the field (i.e. in the database), as passed through this.props).
the current value being typed in the field, as found in this.state.currentValue.
the prefilled value generated by the Embedly API call, as found in this.state.prefilledValue.
Figure out which one is correct (i.e. if the user hasn't entered anything in the field prefill it, if not don't) and pass that on to the form field child component.
This achieves the desired result:
<URLField/> and <TitleField/> don't have to know about or communicate with each other.
<Form/> doesn't have to know about <URLField/> or <TitleField/> either.
As #ffxsam suggested though, I should probably just use Redux Form…

Categories

Resources