I am trying to validate data in a redux store for a form. What is the best way to go about this. I have had a few ideas so far:
A validate action has to be bound to some sort of listener like onBlur or onChange. Once a form component blurs it is then validated. However, the validate function then can 1) only validate that field in isolation or 2) must be passed the full form state. Also, action creators cannot/should not have access to the new state, so I can only update existing data, and I cannot bind the validators to something that updates the state. One upside, is that I can perform asynchronous validation by calling a validator action creator, and returning a promise (using redux-promise/thunk).
Reducer validates it own data. Part 1 of the reduce would update the data, and Part 2 would analyze the state and update the errors. However, this goes against reducer purity, as updating one part of the state then updates another part the state. So I have to ensure the validator runs after the state is updated.
Use selectors to derive errors from state. The data itself does not change if its invalid/valid, just errors are shown. However, since selectors are synchronous, I cannot do any asynchronous validation.
You can store all your fields in reducer with shape like:
"fields": {
"first_field": {
"error": null, //stores error that should be shown in form
"touched": false, //becomes true when onBlur event fired for this field,
"value": "", //input value
},
...
}
In this case you can run validation for whole form and validate all fields that have touched: true. Actually, all problems that you describe are solved in redux-form and I'd suggest you to use it.
Related
I have a userContext wrapped my app, I have a custom hook useAnalytics used to send analytics events and I have a Login component which performs the log in and changes the user value in the context and send an event.
My problem is when I do a setUser inside my Login component the updating user data is not reflected in useAnalytics hook which it sends always the user data in every event
Here a sandbox: https://codesandbox.io/s/romantic-perlman-wzxti?file=/src/Login.js
You can open the console and see the results after clicking SEND
I know this is probably an anti-pattern but it would be good to know why I get this behavior
Thanks!
The problem is that all set states are async, while the sending of the event is in sync. So you dispatch the change, send the analytic and after a timeout, the state is updated.
There are 2 main ways to fix this.
Set a timeout to wait for react to update the state and push the analytic after the state is updated:
setUser({ type: "login", data: { user: "pippo" } });
setTimeout(sendEvent, 16);
Send the analytic with a middleware:
Add a middleware to intercept all reducer changes and push those to the analytic without the hook itself. This will guarantee that you get all changes that you need to track by checking for the action.type and sending the new data from action.payload.
I have form with search criteria and result table. we have implemented redux. so the search will go through reducers and return the updated state with response.
Do we need to keep the property of search criteria (name, number) also be part of the redux state ? if so how we handle the onChange input fields.
Else still need to keep the local state and onChange will handle using setState for input form fields?
I am looking for right approach, where we need to keep the input form fields value in redux state/local state
I'm wondering what is the best way to implement CRUD operations in React Redux in case of List of entities.
Requirements:
1. Every row contain an entity
2. Changing a value in a text input trigger a PUT call to BE
3. Every row has Delete operation trigger a DELETE call to BE
4. Form has an ADD action trigger a POST call to BE
Notes:
1. No single Submit button
I thought about Redux Form for that but since I don't have a single submit operation I find it less appropriate (Feel free to correct me)
You can use Redux Form for that. If you setup a validator for the form, you can define if a field is dependent on another. Redux Form will run this validator for each change in any field.
You can setup something like Redux Observable or Redux Thunk to submit the form on Redux Form's CHANGE action when there are no validation errors in the form.
This way, you will submit the form on change, only if all the related fields are also filled.
I'm new to Redux. This is how I would do it. I will keep a state in the reducer which is called "personList". I will put all components in a container and map the personList to a property of the container called "propPersonList". When I create the ListItemComponent, I would also pass the index of the person in the list to the component as a property "propIndex". The onChange handler of the two inputs will dispatch an action with payload {index: this.props.propIndex, value: event.target.value}, then trigger a PUT request to backend (use lodash module to throttle it instead of triggering it everytime the input changes). The reducer will take care of the actions and update the state according to the payload. The add button will dispatch another action and the reducer will simply add an empty entity to the state, then trigger a request to the backend. The remove button will dispatch an action with the index as the payload, and the reducer will remove the corresponding entity from the list.
So, I have a form with multiple inputs that update state onChange.
What I want to happen, is let the user change the values and when they change, update the the form's state to save these changes. My code using ES6 classes:
super(props);
this.state = {
post: this.props.content // this is an object {title: '', description: ''}
}
// I have all the methods below this.method = this.method.bind(this) up here.
}
handleTitle(title) { // gets title from input onChange event
let post = update(this.state.post, {title: {$set: title}}); // using import update from 'immutability-helper' for changing object
this.setState({ post });
this.checkPostApproval();
} // this all works, post is updated. I have the same code for description.
checkPostApproval() {
// here I want to check the post values and make sure they are valid.
// and either allow or deny submission.
if (this.state.post['title'] && this.state.post['description']) {
this.setState({isApproved: true});
} else {
this.setState({isApproved: false});
}
}
The problem:
When users set the title and then the description, isApproved switches to true (what I want). However, if the user then changes the title to "", the approval remains true (not what I want). If the user then starts typing in the description (the approval switches to false). I believe this has to do with the component lifecycle and when the state is being set, but I'm not sure.
I don't mind using jQuery to check for validation, but I was hoping to just check the values after the inputs change and then update the post using methods and props.
What is the best practices approach to updating an object in state and then validating it using onChange event handlers in various input fields?
Ultimately, I would like isApproved to be false, if there are any invalid inputs.
Please run the post approval test in setState callback (second parameter).
As you already noticed - the setState method is asynchronous and component state isn't updated immediately.
this.setState({post}, this.checkPostApproval)
Please see this note from React's documentation about setState:
setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall. Instead, use componentDidUpdate or a setState callback (setState(updater, callback)), either of which are guaranteed to fire after the update has been applied. If you need to set the state based on the previous state, read about the updater argument below.
If I'm doing field validation in say a login form field, if it's missing a required field should I trigger the error message in the rendered view by either
a. update this.state.errorMessage in the component and then trigger a render somehow?
OR
b. raise a loginError action, which then updates the store with an errormessage and let that trigger a state update and then the render in the component
Both ways will work. It depends on whether you have other components that should also listen to the changes in your field validation states:
If so, then you should do the full flux cycle by calling the loginError
action, which will update the stores, causing state changes to properly
propagate to all relevant components that cares about the field
validation states.
If not, where your field validation state changes are localized to
just the current component, then it's not wrong to just
setState() within the component and only triggers the render() of
itself.
However I recommend that you will do the full flux cycle (action->store->view) since it will be easier to make modifications later.