Using the helper libraries is just more quick and avoid us all the boilerplate. They may be optimized, feature rich ...etc. As they make all different aspects more of a breeze. Testing them and making your arsenal as knowing what's useful and better for the different needs, is just the thing to do.
But if you already implemented everything yourself. Going the controlled way. And for a reason you need redux. In one of my projects. I needed to maintain the form states. So if i go to another page and come back it will stay in the same state. You only need redux, if it's a mean for communicating the change to multiple components. Or if it's a mean to store the state, that you need to restore.
You need redux, if the state need to be global. Otherwise you don't need it. For that matter you can check this great article here for a deep dive.
One of the problems that you may encounter! When using the controlled inputs. Is that you may just dispatch the changements at every keystroke. And your form will just start freezing. It became a snail.
You should never directly dispatch and use the redux flux, at every changement.
What you can do, is to have the inputs state stored on the component local state. And update using setState()
. Once the state change, you set a timer with a delay. It get canceled at every keystroke. the last keystroke will be followed by the dispatching action after the specified delay. (a good delay may be 500ms).
(Know that setState
, by default handle the multiple successive keystroke effectively. Otherwise we would have used the same technique as mentioned above (as we do in vanilla js) [but here we will rely on setState
])
Here an example bellow:
onInputsChange(change, evt) {
const { onInputsChange, roomId } = this.props;
this.setState({
...this.state,
inputs: {
...this.state.inputs,
...change
}
}, () => {
// here how you implement the delay timer
clearTimeout(this.onInputsChangeTimeoutHandler); // we clear at ever keystroke
// this handler is declared in the constructor
this.onInputsChangeTimeoutHandler = setTimeout(() => {
// this will be executed only after the last keystroke (following the delay)
if (typeof onInputsChange === "function")
onInputsChange(this.state.inputs, roomId, evt);
}, 500);
})
}
You can use the anti-pattern for initializing the component using the props as follow:
constructor(props) {
super(props);
const {
name,
description
} = this.props;
this.state = {
inputs: {
name,
description
}
}
In the constructor or in the componentDidMount
hook like bellow:
componentDidMount () {
const {
name,
description
} = this.props;
this.setState({
...this.state,
inputs: {
name,
description
}
});
}
The later allow us to restore the state from the store, at every component mounting.
Also if you need to change the form from a parent component, you can expose a function to that parent. By setting for setInputs()
method that is binded. And in the construction, you execute the props (that is a getter method) getSetInputs()
. (A useful case is when you want to reset the forms at some conditions or states).
constructor(props) {
super(props);
const {
getSetInputs
} = this.props;
// .....
if (typeof getSetInputs === 'function') getSetInputs(this.setInputs);
}
To understand better what i did above, here how i'm updating the inputs:
// inputs change handlers
onNameChange(evt) {
const { value } = evt.target;
this.onInputsChange(
{
name: value
},
evt
);
}
onDescriptionChange(evt) {
const { value } = evt.target;
this.onInputsChange(
{
description: value
},
evt
);
}
/**
* change = {
* name: value
* }
*/
onInputsChange(change, evt) {
const { onInputsChange, roomId } = this.props;
this.setState({
...this.state,
inputs: {
...this.state.inputs,
...change
}
}, () => {
clearTimeout(this.onInputsChangeTimeoutHandler);
this.onInputsChangeTimeoutHandler = setTimeout(() => {
if (typeof onInputsChange === "function")
onInputsChange(change, roomId, evt);
}, 500);
})
}
and here is my form:
const {
name='',
description=''
} = this.state.inputs;
// ....
<Form className="form">
<Row form>
<Col md={6}>
<FormGroup>
<Label>{t("Name")}</Label>
<Input
type="text"
value={name}
disabled={state === "view"}
onChange={this.onNameChange}
/>
{state !== "view" && (
<Fragment>
<FormFeedback
invalid={
errors.name
? "true"
: "false"
}
>
{errors.name !== true
? errors.name
: t(
"You need to enter a no existing name"
)}
</FormFeedback>
<FormText>
{t(
"Enter a unique name"
)}
</FormText>
</Fragment>
)}
</FormGroup>
</Col>
{/* <Col md={6}>
<div className="image">Image go here (one day)</div>
</Col> */}
</Row>
<FormGroup>
<Label>{t("Description")}</Label>
<Input
type="textarea"
value={description}
disabled={state === "view"}
onChange={this.onDescriptionChange}
/>
{state !== "view" && (
<FormFeedback
invalid={
errors.description
? "true"
: "false"
}
>
{errors.description}
</FormFeedback>
)}
</FormGroup>
</Form>