Forms are not Simple

I miss simple_form already. Forms in React are such a pain and I hope to recreate the simple_form experience. We’ll take it a step at a time.

We’ll start by shoving a form onto the main page. We’ll tidy it up later.

// app/javascript/application/Application.jsx

export function Application(_props) {
  return <div id='application'>
    <h1 className='site-name'>Blogging</h1>
    <form>
      <input type='text' name='title' />
      <textarea name='body' />
      <input type='submit' />
    </form>
    <Post id={1} />
  </div>
}

Oh, dear. That’s ugly.

Come to think of it, the whole app is pretty ugly. Let’s tidy.

That’s better. It won’t win any prizes but it’s good enough for now.

I won’t show you the CSS (and you probably won’t like it anyway) but it’s all checked in if you want to peek.

I added a couple of divs and some CSS classes to the top-level elements too. I’ll show you in a bit, but first I am gonna move that form to its own component. I’m not TDDing this bit because I’m kind of stumbling around to find something that works. I’ll test it later.

Here’s the code after tidying the page structure and extracting an Editor.

export function Application() {
  return <div id='application'>
    <div id='header' className='header'>
      <h1 className='site-name'>Blogging</h1>
    </div>

    <div id='main'>
      <Editor />
      <Post />
    </div>
  </div>
}

Let’s extract a Header and Main section too before we get back to the form.

The Application now looks like this.

export function Application() {
  return <div id='application'>
    <Header />
    <Main />
  </div>
}

The header looks like this.

// javascript/application/Header.jsx
export function Header() {
  return <div id='header' className='header'>
      <h1 className='site-name'>Blogging</h1>
    </div>
}

The Main component looks like this.

export function Main() {
  return <div id='main'>
      <Editor />
      <Post id={1} />
    </div>
}

And the Editor looks like this.

export function Editor() {
  return <form>
      <input type='text' name='title'  className='title'/>
      <textarea name='body' className='body' />
      <div className='actions'>
        <input type='submit' className='button'/>
      </div>
    </form>
}

I wrote a test for the editor. It doesn’t do much yet.

// test/javascript/posts/Editor.test.jsx
describe('The post editor', () => {
  test('displays a form', () => {
    const component = display(<Editor />)
    assert_select(component, '.title')
    assert_select(component, '.body')
  })
}

Controlled Components

React has this concept of controlled components versus uncontrolled components. This concept comes to the fore when you use form elements.

Long story short, when we are writing plain HTML forms, we rely on the form elements to manage their own state that they magically send to the server when you hit SUBMIT. In React, we usually manage the state ourselves and do our own submitting.

A component is said to be controlled if its state changes are managed in a containing component. The basic idea is that you store the values from your form elements in React state, then pass them back in as properties when you render the form.

You can read more about controlled components here: https://reactjs.org/docs/forms.html

Here. Let me just show you.

export default class Editor extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      post: {}
    }
    this.handleChange = this.handleChange.bind(this)
    this.handleSubmit = this.handleSubmit.bind(this)
  }

  render() {
    const {post} = this.state

    return <form onChange={this.handleChange}
                 onSubmit={this.handleSubmit}>
      <input type='text' name='title' className='title' defaultValue={post.title} />
      <textarea name='body' className='body' defaultValue={post.body} />
      <div className='actions'>
        <input type='submit' className='button' />
      </div>
    </form>
  }

  handleChange(event) {
    const {name, value} = event.target
    const post = {
      ...this.state.post,
      [name]: value
    }
    this.setState({post})
  }

  handleSubmit(event) {
    event.preventDefault()

    const {post} = this.state
    const {onSubmit} = this.props
    onSubmit && onSubmit(post)
  }
}

I’ve converted our Editor function to a class so that we can manage its state. I’ve set up some state for the form elements and I’ve added an event handler for the onChange event.

When the user changes the value of the title element or the body element, the onChange event bubbles up to the form element where we handle it and store the value with this.setState(post).

The {…} in the handler is a spread operator. I’m using it to make a copy of post and overwriting one property. React really doesn’t like it when you mutate objects directly and the spread operator provides an easy way to make a copy without changing the original object.

When you submit the form, Editor calls an onSubmit function that was passed in as a property. It calls event.default() first to prevent the page refresh and then writes the current state of the post to the console.

That’s all quite straightforward, I think, except: let me draw your attention to the bind code in the constructor.

this.handleChange = this.handleChange.bind(this)

In Javascript, the value of this is elusive and fleeting and often not what you think it is. In this situation, when our event handler tries to handle the onChange event, the value of this will be null and weird errors result unless you call bind().

Mozilla has more details about binding here.

Now we have something we can test.

// test/javascript/posts/Editor.test.jsx

  test('updates the post details', () => {
    const submit = jest.fn()
    const component = display(<Editor onSubmit={submit} />)

    const title = component.find('.title')
    const body = component.find('.body')
    const form = component.find('form')

    title.simulate('change', {target: {name: 'title', value: 'The title'}})
    body.simulate('change',  {target: {name: 'body', value: 'The body'}})
    form.simulate('submit')

    const expected = {
      title: 'The title',
      body: 'The body',
    }
    expect(submit).toBeCalledWith(expected)
  })

I’ve mocked the onSubmit function and I check that, when I edit the fields and submit the form, the right thing comes out the other end.

In the next episode, we’ll send this new post to the server.

Forms are Simple

All the code is refactored and shiny. The tests look nice and we have the beginnings of a framework but we’ve only completed one story so far.

Let’s work on the next one.

  • An author can publish a blog post.

I’ve been using (and loving!) the simple_form gem for a while. It’s one of the things from Rails that I miss most after switching to React.

Let’s see if we can reproduce some of that functionality here. There are a lot of moving parts.

We’ll do the rails bit first because it is easy.

# posts_controller_test.rb

  test 'create a blog post' do
    post posts_url(format: :json), params: {
        post: {
            title: 'A new day',
            body: 'First post!'
        }
    }
    assert_response :success
    json = JSON.parse response.body, symbolize_names: true

    # The post was created
    @post = Post.last
    assert_equal 'A new day', @post.title

    # Return the post as JSON
    assert_equal @post.id, json[:id]
    assert_equal 'A new day', json[:title]
  end
class PostsController < ApplicationController
  def create
    @post = Post.new post_params
    if @post.save
      render :show, status: :created, location: @post
    else
      render json: @post.errors, status: :unprocessable_entity
    end
  end

  # ... existing stuff

  def post_params
    params.require(:post).permit(:title, :body)
  end
end
class PostTest < ActiveSupport::TestCase
  test 'post has a title and a body'
  test 'validate the title and body'
  test 'trim the title and body'
end

We’ll use the strip_attributes gem to trim the fields.

# Gemfile 
gem 'strip_attributes'
bundle install
class Post < ApplicationRecord
  strip_attributes
  validates :title, length: { minimum: 2, maximum: 255}
  validates :body,  length: { minimum: 2}
end

We’ll work on the client side tomorrow.