How to update a field value after action dispatched with redux - javascript

I would like to populate a form after an ajax call with redux.
My case is pretty simple:
I have a simple user form (with only one text field for now), it's a react view bound to the state with connect().
I call a rest API to fetch the user.
When the api call is done, an action is dispatched with the user.
A reducer update the store state with the user.
I would like to populate/update the form with the retrieved values.
Solution 1:
If I set the value from the props like that:
const Field = ({ field, onFieldChange, value }) => (
<input
value={value}
onChange={(event) => { onFieldChange(field, event.target.value) }}
type="text"
/>
)
It works but I get this warning:
Field is changing an uncontrolled input of type text to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa).
I understand why I get this error as I should not use a component to display something and also be able to update it.
I also tried to use the defaultValue props but this is only used at the component creation (and we don't have the user yet). After the ajax call return, defaultValue cannot be called.
Solution 2:
Use redux-form with a custom plugin to update the form model each time the state get updated. I don't find this solution really clean but maybe I'm wrong.
I really thin that I'm going in the wrong direction and that it should exist a better way.
Does somebody already faced this kind of issue?

I encountered the same problem when I was trying to pass undefined as the input value.
To fix this, ensure that you are passing at least empty string to the input, not undefined
const Field = ({ field, onFieldChange, value }) => (
<input
value={value || ''} // <- add fallback value here
onChange={(event) => { onFieldChange(field, event.target.value) }}
type="text"
/>
)

Actually you might try to make your component statefull - store and manage value of input inside of it (they say it's ok).
Or if you really need this value in the store use redux-form, I have realy good experience of using it (you'll have to write less boilerplate code).
By the way, you will not have to use any custom plugin, you can use initialValues, see more here
The solution above will work for sure, but it doesn't seem to be nice.

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.

SweetAlert2: How to get back values from react component?

With sweetalert2 now there is sweetalert2-react-content, that lets you use React components to show inside Swal.
It's good, but I can't seem to find how to get back values from that component.
For example, I have a component, that has two inputs.
MySwal.fire({
html: <MyComponent />
}).then(value => {
// how to get here my values from MyComponent??
});
I want get whether checkboxes are checked or not from that component.
I tried to have the state for those inputs and onChangeHandler in the component where I call MySwal. But that didn't seem to work at all, the checkboxes would not change.
In the previous version of this library there was swal.setActionValue, that seemed be what I need, but it doesn't work with the current sweetlalert2 version.
To sum up, when I press OK on the prompt, I want to get the value in the promise, that would be set by the MyComponent.
DEMO
You can try using preConfirm() as per the documentation. From perConfirm we can pass custom values.
Please find this example : https://codesandbox.io/s/kk2nv9nmy7
The following trick should do the job:
let returnedValue;
MySwal.fire({
html: <Options onChange={value => (returnedValue = value)} />
}).then(value => {
console.log(returnedValue);
});
Long story short you could pass onChange callback to the component and use it for updating a variable which stores a value. It's not very pretty but it does the job.
You could find the full example here https://codesandbox.io/s/o1005xv1q

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

Understanding ReactJS Controlled form components

I am implementing the following code based on the following page: https://facebook.github.io/react/docs/forms.html
class Reservation extends React.Component {
constructor(props) {
super(props);
this.state = {
isGoing: true,
numberOfGuests: 2
};
this.handleInputChange = this.handleInputChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleInputChange(event) {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
}
handleSubmit(event) {
event.preventDefault();
let data = {
isGoing: this.state.isGoing,
numberOfGuests: this.state.numberofGuests
}
/* Send data in ajax request here */
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Is going:
<input
name="isGoing"
type="checkbox"
checked={this.state.isGoing}
onChange={this.handleInputChange} />
</label>
<br />
<label>
Number of guests:
<input
name="numberOfGuests"
type="number"
value={this.state.numberOfGuests}
onChange={this.handleInputChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
Some questions I have about it:
Why do we need to store the component values in state? Why not
just grab the values we need when the form is submitted as normally
would be done with standard JavaScript? The recommended way would seem to reload the render function for every single character typed in or deleted. To me this makes little sense.
Since the execution of setState is asynchronous and there may be a delay between this.setState() and actually accessing the state with this.state.numberOfGuests does this mean that this code may end up grabbing the state before it has been set? If so, why is this code being suggested in the official React docs? If not, why not?
Regarding point number two, then yes it is logically possible that handleSubmit could run before the state update in handleInputChanged has completed. The reason this isn't mentioned in the React docs, or is generally a concern for anyone is because the setState function runs really quickly. As an experiment I made a codepen to determine the average time taken for setState to run. It seems to take in the order of around 0.02 milliseconds. There is no way someone can change their input, then submit the form in less than that time. In fact, the e.preventDefault() call in handleSubmit takes nearly a quarter of that time anyway.
If you have a situation where it is absolutely crucial that setState has completed before continuing, then you can use a callback function to setState, e.g.
this.setState({
colour: 'red'
}, () => {
console.log(this.state.color)
});
Then red will always be logged, as opposed to the following where the previous value may be logged.
this.setState({
colour: 'red'
});
console.log(this.state.color);
Very good questions! Here's my take on them:
1. Controlled or Uncontrolled - that is the question
You don't have to use controlled form elements. You can use uncontrolled and grab the values as you suggest in your onFormSubmit handler by doing something like event.isGoing.value - plain ole JavaScript (or use refs as some React articles suggest). You can even set a default value with uncontrolled no problem by using, you guessed it, defaultValue={myDefaultValue}.
The above being said, one reason to use controlled components would be if you're looking to give real time feedback while the user is still typing. Say you need to do an autocomplete lookup or provide validation like password strength. Having a controlled component that re-renders with the values in the state makes this super simple.
2. this.setState() asynchronous issues?
[Maybe incorrectly,] I view internally component state updates more like a queue system. No call to this.setState() will be lost and shouldn't overwrite another one when dealing with synchronous code. However, there could be a time where a render is running behind a setState update, but it will eventually have and render the most recent value. Ex: the user types 3 characters, but they only see 2, then a short time later they should see the third. So, there was a point in time where the read to this.state read an "old" value, but it was still eventually updated. I hope I'm making sense here.
Now, I mention synchronous code above because with asynchronous code (like with AJAX) you could potentially introduce a race condition where this.setState() overwrites a newer state value.
Why do we need to store the component values in state?
We don't really need to store the component values in state and it's perfectly fine to access those values on form submit.
However, storing the component values in state has its own advantages. The main reason behind this is to make React state as the single source of truth. When the state is updated (on handleInputChange method), React checks the components (or specifically the parts of components or sub-trees) which needs to be re-rendered.
Using this approach, React helps us to achieve what was earlier achieved via Two Way Binding helpers. In short:
Form updated -> handleInputChange -> state updated -> updated state passed to other components accessing it
For example say, you have a <Card /> component which needs input from the user via a <Form /> component and you wish to display the information as the user types, then the best way would be update your state which would cause React to look for the sub-trees where it was accessed and re-render only those. Thus your <Card /> component will update itself as you type in the <Form />.
Also, React doesn't re-render everything for every single character typed but only those sub-trees which needs to reflect the text change. See the React diffing algorithm for how it is done. In this case, as you type in the characters in a particular field in the form, only those sub-trees in components will be re-rendered which display that field.
Execution of setState and asynchronous behavior
As mentioned by the React docs:
State Updates May Be Asynchronous
React may batch multiple setState() calls into a single update for
performance.
Because this.props and this.state may be updated asynchronously, you
should not rely on their values for calculating the next state.
Your code should work fine since you're not relying on the previous state to calculate the next state and also you're accessing this.state in a different block. So your state updates should be reflected fine; however if you don't want to think about the state updates (or suspect that your code might pick up previous state), React docs also mention an alternative method (which is actually better) that accepts a function rather than an object:
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));
If you're still wondering when you can use the setState method safely, use it if other components don't rely on the state or when you don't need to persist the state (save in local storage or a server). When working with larger projects its always a good idea to make use of state containers to save yourself the hassle, like Redux.

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