Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Read <input> in a functional (stateless) component

I have two components: one container component and one presentational component.

The container fetches all the information and actions needed to display a Post using the presentational component. On the presentational component I have a way where I can toggle between presenting the information and editing it. When I edit and submit the data about the post, I want to read the values from all the different inputs I have so I can dispatch an action.

However, the inputs are not inside a <form id='theForm' onSubmit={onHandleSubmit}> tag. Instead, all the <input> and <button type='submit'> that are outside the <form> have a form='theForm' attribute.

I thought that I could have many <input> outside the form, but as long as the form attribute pointed towards the corresponding <form>, I could read it values using the event that handleOnSubmit (e) {...} recieves. However, I haven't figured out how to do that.

How can I read the values of the inputs on my handleOnSubmit function? Or this a completely wrong idea I should abandon?

// PostSummaryContainer.js
import React, { PropTypes, Component } from 'react'
import { connect } from 'react-redux'

import { loadPost, editPost, editPostCancel, editPostSubmit } from '../../actions/posts'

import PostSummaryView from '../../views/details/summary'

class PostSummaryContainer extends Component {
  constructor (props) {
    super(props)
    this.handleOnSubmit = this.handleOnSubmit.bind(this)
    this.handleOnCancel = this.handleOnCancel.bind(this)
    this.handleOnSubmit = this.handleOnSubmit.bind(this)
  }

  handleOnEdit (e) {
    e.preventDefault()
    this.props.editPost()
  }

  handleOnCancel (e) {
    e.preventDefault()
    this.props.editPostCancel()
  }

  handleOnSubmit (e) {
    e.preventDefault()

    // How do I read the input values? <--------------------
  }

  componentWillMount () {
    const {
      id,
      loadPost
    } = this.props

    loadPost(id)
  }

  render () {
    const {
      post,
      isLoading,
      isEditing
    } = this.props

    const viewProps = {
      bacon,
      isLoading,
      isEditing,
      handleOnEdit: this.handleOnEdit,
      handleOnCancel: this.handleOnCancel,
      handleOnSubmit: this.handleOnSubmit
    }

    return (
      <PostSummaryView {...viewProps} />
    )
  }
}

const mapStateToProps = (state, ownProps) => {
  const {
    params: {
      id
    }
  } = ownProps

  const post = state.entities.post[id]

  const {
    isLoading,
    isEditing
  } = state.posts

  return {
    id,
    post,
    isLoading,
    isEditing
  }
}

export default connect(
  mapStateToProps,
  { loadPost, editPost, editPostCancel, editPostSubmit }
)(PostSummaryContainer)

On my presentation component:

// PostSummmaryView.js
import React from 'react'
import moment from 'moment'

function PostSummaryView (props) {
  const {
    post,
    isLoading,
    isEditing,
    handleOnEdit,
    handleOnCancel,
    handleOnSubmit
  } = props

  const formId = 'editPostForm'

  return (
    isLoading
      ? <div>Loading...</div>
      : <div className='row'>
          {isEditing && <form id={formId} onSubmit={handleOnSubmit}><input type='text' name='name' /></form>}

          <div className='col-md-6'>
            <img src={post.media.url} className='img-responsive' />
            {isEditing && <input type='file' name='media' form={formId}/>}
          </div>
          <div className='col-md-6'>

            <h1>{post.name}</h1>
            <p>
              {moment(post.publicationDate).format('dddd, MMMM Do')}
            </p>

            <hr />

            <p className='text-left'>
              {post.description || 'Lorem ipsum dolor sit amet, consectetur adipisici elit...'}
            </p>

            {isEditing
              ? <div>
                  <button className='btn btn-lg btn-default' onClick={handleOnCancel}>Cancel</button>
                  <button type='submit' className='btn btn-lg btn-default' form={formId}>Submit</button>
                </div>
              : <button className='btn btn-lg btn-default' onClick={handleOnEdit}>Edit</button>
            }

          </div>
        </div>
  )
}

export default PostSummaryView
like image 960
Sparragus Avatar asked Feb 18 '16 03:02

Sparragus


People also ask

Are functional components stateless?

A functional component is always a stateless component, but the class component can be stateless or stateful. There are many distinct names to stateful and stateless components.

How do you find the ref of the functional component?

To create a ref in a functional component we use the useRef() hook which returns a mutable object with a . current property set to the initialValue we passed to the hook. This returned object will persist for the full lifetime of the component. Thus, throughout all of its re-rendering and until it unmounts.


1 Answers

Disclaimer: I'm still new to React/Redux, so take this answer with a big grain of salt.

I think your approach is slightly off, in that you shouldn't need to go around and gather any data from inputs when submitting (whether inside or outside the <form>). Your state should always be in one central, consolidated place.

Given you provide a Cancel option, keeping the Post data updated during an Edit in a separate part of the state is more elegant (IMHO) than directly modifying the "source" Post data.

You could create a reducer for the Edit Post form that keeps key/value pairs for the input fields.

When a user starts editing, you could clone the original Post data into this new part of the state specific to the Edit form. As the user changes the inputs, you could dispatch actions saying "hey, form field X was changed to value Y", which could be reduced onto the state without modifying the original Post data. Pseudo-code example of the state object in this flow:

{
    // The actual Post data
    post: {
        media: {...},
        title: "My First Post",
        publicationDate: 1455768160589,
        description: "Lorem ipsum dolor sit amet"                  
    },

    // The temporary Post data in the Edit form
    postForm: {
        media: {...},
        title: "My Turbo-Charged First Post",
        publicationDate: 1455769951276,
        description: "Updated description yadda blah"                  
    }
}

Then, in your presentation component, you could drive the input values off postForm instead of post.

Each input would be given a change handler function so updates are immediately reflected in the state (or not reflected, depending on your validation logic/reducers). i.e.:

// In your actions associated with `Post`
// ------------------------------------------
function updateForm(field, value) {
    return {
        type: UPDATE_FORM,
        field,
        value
    }
}

// In your container
// ------------------------------------------
handleOnEdit(event) {
    postActions.updateForm(event.target.name, event.target.value)
}

// In your reducer
// ------------------------------------------
switch (action.type) {
    case UPDATE_FORM:
        return {
            ...state,
            [action.field]: action.value
        }
}

// In your presentational component's render() method
// ------------------------------------------
const {postForm, handleOnEdit} = this.props
const descriptionMarkup = (
    isEditing
    ? <input type='text' name='description' value={postForm.description} onChange={handleOnEdit} />
    : (post.description || 'Lorem ipsum dolor sit amet, consectetur adipisici elit...')
)
// ...
<p className='text-left'>
    {descriptionMarkup}
</p>

If you follow this pattern (and again, not sure it's "right"!), submitting the form becomes as simple as doing something with your state's postForm object. That object should always reflect the latest-and-greatest form input values.

Canceling the form becomes as simple as setting the postForm part of the state tree to {}. The original Post data remains the same.

Hope this helps jog some ideas...

Some other examples/approaches you could try:

  • redux-form by erikras
  • stateless-form-examples by nackjicholson
  • redux-form-example
like image 179
rkd Avatar answered Oct 12 '22 19:10

rkd