Remove state from a Form component and allow Redux-Form to handle it

Brett Cole
ITNEXT
Published in
5 min readJul 10, 2018

--

I could feel the pitchforks being sharpened as I used my React form component to control it’s own local state inside of my Redux app. To purist’s isn’t that the convention? You either allow Redux’s store to manage your apps entire state or you don’t at all! There is no middle ground. Right?

This was the exact dilemma I was in as I was deciding how to handle my component’s state. I wasn’t a 100% sure yet what I needed to include inside my app’s state. So I started with keeping it self contained. This helped prevent me from having to write needless actions, reducers and then worry about going back and changing or deleted something in the future.

Let me show you what I came up with first and then we can address our Redux problem.

import React, { Component } from 'react';
import { Form, Message, Modal } from 'semantic-ui-react';
import ModalPlayerButtons from './ModalPlayerButtons';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { currentPlayer } from '../actions';
class QuestionModalClue extends Component {
constructor(props) {
super(props);
this.state = {
disabled: true,
hidden: true,
correct: false,
value: ''
}
this.playersAnswer = this.playersAnswer.bind(this);
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}

handleChange(e) {
this.setState({
value: e.target.value
});
}
handleSubmit(e) {
e.preventDefault();
this.setState({
hidden: false,
value: ''
})
if (this.state.value.toLowerCase() === this.props.clue.answer.toLowerCase()) {
this.setState({
correct: true
})
}
this.setState({
disabled: true
})
}
playersAnswer = (e) => {
let player = e.currentTarget.textContent;
this.props.currentPlayer(player);
this.setState({
disabled: false
})
}
render() {
return (
<Modal
open={this.props.modalOpen}
centered={true}
size='fullscreen'
dimmer='blurring'
style={{ 'textAlign': 'center', 'marginTop': '20px', 'backgroundColor': '#2185d0' }}
>
<Modal.Content
style={{ 'backgroundColor': '#2185d0', 'color': '#fff' }}
>
{this.props.clue !== undefined &&
<Modal.Header
as='h1'
onClick={this.props.test}
>
{this.props.clue.description}
</Modal.Header>
}
</Modal.Content>
<ModalPlayerButtons playersAnswer={this.playersAnswer} />

<Form
as='form'
size='massive'
onSubmit={this.handleSubmit}
>
<Form.Field width={6} style={{ 'marginLeft': 'auto', 'marginRight': 'auto', 'marginTop': '20px', 'marginBottom': '10px' }}
disabled={this.state.disabled}
>
<label style={{'color': 'white'}}>Player's Answer</label>
<Form.Input
type="text"
placeholder="What is..."
value={this.state.value}
onChange={this.handleChange}
/>
</Form.Field>
<Form.Field
width={6}
style={{ 'marginLeft': 'auto', 'marginRight': 'auto', 'marginTop': '20px', 'marginBottom': '10px' }}
>
{
this.state.correct ? (
<Message
hidden={this.state.hidden}
size="massive"
color="green"
header="Correct Answer"
content="Current score plus 200"
/>
) : (
<Message
hidden={this.state.hidden}
size="massive"
color="red"
header="Incorrect Answer"
content="Current score minus 200"
/>
)
}
</Form.Field>
</Form>
</Modal>
)
}
}
const mapDispatchToProps = dispatch => {
return bindActionCreators({
currentPlayer
}, dispatch);
}
export default connect(null, mapDispatchToProps)(QuestionModalClue);

What makes it better, probably worse, is I am also connected to Redux’s store inside my component at the same time. We have some functions just controlling state, others controlling state and props from Redux. It’s all over the place. I have two different things controlling my state inside this one component. Talk about a nightmare to debug and efficiency. I could feel the lynch mob coming after me.

So what should I do? Looking at the different items in state I didn’t want to have to worry about controlling them in Redux and hand code each individual one. I subscribe to coders are lazy, so let’s find something to control these things for us. But I must make this point, I also am a firm believer in knowing what is going on behind the scenes. So know what your tools are doing for you.

This brings me to the title of this blog. Lets talk about ‘Redux-Form’. Redux-Form makes state simple. Redux-Form describes itself as the best way to manage your form state in Redux.

This blog isn’t a complete tutorial to setup Redux-Form. But they give a good tutorial on their site to include Redux-Form in a project.

Quickly, if you already have a made project just include Redux-Form with yarn add redux-form. If you haven’t noticed I will be using yarn not npm. Follow the instructions for connecting redux-form to your redux store. Then just follow their api to include your necessary items.

So let’s take another look at that same component but this time allowing redux-form to control my form state.

import React, { Component } from 'react';
import { Form, Message, Modal } from 'semantic-ui-react';
import ModalPlayerButtons from './ModalPlayerButtons';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { currentPlayer } from '../actions';
import { Field, reduxForm, formValueSelector } from 'redux-form';
class QuestionModalClue extends Component {
constructor(props) {
super(props);
// variable names to be assigned depending on result of answer
this.correctAnswer;
this.incorrectAnswer;
this.playersAnswer = this.playersAnswer.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(e) {
e.preventDefault();
if (this.props.answerValue.toLowerCase() === this.props.clue.answer.toLowerCase()) {
this.correctAnswer = <Message
size="massive"
color="green"
header="Correct Answer"
content="Current score plus 200"
/>
this.forceUpdate();
} else {
this.incorrectAnswer = <Message
size="massive"
color="red"
header="Incorrect Answer"
content="Current score minus 200"
/>
this.forceUpdate();
}
}
playersAnswer = (e) => {
let player = e.currentTarget.textContent;
this.props.currentPlayer(player);
}
render() { const { answerValue } = this.props; return (
<Modal
open={this.props.modalOpen}
centered={true}
size='fullscreen'
dimmer='blurring'
style={{ 'textAlign': 'center', 'marginTop': '20px', 'backgroundColor': '#2185d0' }}
>
<Modal.Content
style={{ 'backgroundColor': '#2185d0', 'color': '#fff' }}
>
{
this.props.clue !== undefined &&
<Modal.Header
as='h1'
onClick={this.props.modalClose}
>
{this.props.clue.description}
</Modal.Header>
}
</Modal.Content>
<ModalPlayerButtons playersAnswer={this.playersAnswer} />
<Form
as='form'
size='massive'
onSubmit={this.handleSubmit}
>
<Form.Field
width={6}
style={{ 'marginLeft': 'auto', 'marginRight': 'auto', 'marginTop': '20px', 'marginBottom': '10px' }}
disabled={this.props.playerGuessing == undefined}
>
<label style={{'color': 'white'}}>Player's Answer</label>
<Field
name='playerAnswer'
component='input'
type='text'
placeholder='What is... ?'
/>
</Form.Field>
<Form.Field
width={6}
style={{ 'marginLeft': 'auto', 'marginRight': 'auto', 'marginTop': '20px', 'marginBottom': '10px' }}
>
{this.correctAnswer}
{this.incorrectAnswer}
</Form.Field>
</Form>
</Modal>
)
}
}
const mapStateToProps = state => {
return {
playerGuessing: state.currentPlayer.currentPlayer
}
}
const mapDispatchToProps = dispatch => {
return bindActionCreators({
currentPlayer
}, dispatch);
}
QuestionModalClue = reduxForm({
form: 'answer'
})(QuestionModalClue);
const selector = formValueSelector('answer');QuestionModalClue = connect(state => {
const answerValue = selector(state, 'playerAnswer')
return {
answerValue,
}
})(QuestionModalClue);
export default connect(mapStateToProps, mapDispatchToProps)(QuestionModalClue);

First thing you might notice is state has been removed from our constructor. Our functions are no longer used to set our components state. Instead they are used to assign a value to either of our variables. I created a new mapStateToProps function to access our redux store. This is so that when a player is clicked on the form input will show. It gets a little confusing at the bottom of the code. So lets go over that a little.

QuestionModalClue = reduxForm({
form: 'answer'
})(QuestionModalClue);

This is used to connect our form directly to redux. We use form: 'answer' to name our form for reference.

Then const selector = formValueSelector('answer');. We can use formValueSelector to connect to form values. It creates a selector function that accepts field names and returns corresponding values from the named form. Reference Redux-Form site because there is a couple of different ways to implement this. You may not need a separate mapStateToProps function or you may have multiple fields in a field group you want access to.

QuestionModalClue = connect(state => {
const answerValue = selector(state, 'playerAnswer')
return {
answerValue
}
})(QuestionModalClue);

Here is where we have the option to assign a variable name with the value from the name of the input in our form. Then we return that value so we have access to it.

export default connect(mapStateToProps, mapDispatchToProps)(QuestionModalClue);

Finally we have our usual connect to redux which gives us the ability to use mapStateToProps and mapDispatchToProps. You do have the option to combine some of this together but for me it was easier to break them all down individually for readability.

Well now everyone can relax. I no longer have local state contained in my component but it’s all back in redux’s store. I won’t say what is correct and incorrect. But I totally understand controlling local state for a simple form inside that component instead of involving redux. I’d love to hear some opinions in the comments below or any other advice about controlling state. This wraps this blog up for me. I guess it’s on to my next project or idea. Until next time.

--

--