React Redux - How to store form state so that it c

2019-08-27 14:30发布

问题:

I have a search form that loads results. Everything gets updated in the Redux state except for the values selected in the form fields.

Lets say the search below shows 3 results. User clicks on result #2 and does not like it. The click the back button but the form state is cleared. How can I avoid this and make sure the form state stays ?

I am storing the results in Redux and able to load that but not sure how to handle state. I dont want to use packages such as redux-form if possible because this is the only place I need to store form state.

The state does not update when I change the selections. This is probably a slight change in code for an expert but I am beginner and this is my first React-Redux app.

Also, when I navigate away from the page, the state that was last stored is not retained. This is probably because of the local state formValues: {} that I am initializing but without this, there are errors. I tried to delete local state and just use props but it is giving me errors on the select box at this line - value={this.state.formValues['gender']}

Here is the overall code of the component:

import React, { Component } from 'react'
import { get } from 'axios'
import { connect } from 'react-redux'
import { FadeIn } from 'animate-components'
import {
  getSearchResults,
  setSearchedOnce,
  setPageNum,
  setFormValues,
} from '../../actions/searchresults'
import SearchResult from './Result/searchResult'
import SearchNoResult from './Result/searchNoResult'

class SearchAdvanced extends Component {
  constructor(props) {
    super(props)
    this.state = { searchedOnce: false, users: [] }
  }

  handleChange(event) {
    event.preventDefault()
    this.props.setFormValues(this.props.formValues)
  }

  handleSubmit(event) {
    event.preventDefault()
    this.props.setSearchedOnce(true)
    const apiSearchURL = `/api/search/religion/${this.props.formValues.religion}/gender/${this.props.formValues.gender}`
    get(apiSearchURL, { maxContentLength: 400 })
    .then((searchResults) => {
      this.props.getSearchResults(searchResults)
    })
  }

  loadMoreClick() {
    this.props.setPageNum(this.props.pageNo + 1)
  }

  render() {
    const { users } = this.props
    let mapPageNo = this.props.pageNo
    let map_usersList = users.data && users.data.slice(0, mapPageNo * 2).map((userlist => (
      <SearchResult key={userlist.id} {...userlist} />
    )))
    let mapSearchedOnce = this.props.searchedOnce
    return (
      <div>
      <FadeIn duration="300ms">
        <div className="mid_rig_inner">
          <div className="mid_inner">
            <ul>
            { mapSearchedOnce
              ?  map_usersList
              :  <SearchNoResult/>
            }
            {
              mapSearchedOnce ?
              (mapPageNo * 2 >= 3)
              ?
              <div className="text-center my3 text-danger">
                No more profiles. Try to modify search criteria.
              </div> :
              <div className="text-center my3">
              <button type="button" className="btn btn-primary" onClick={this.loadMoreClick.bind(this)}>
                   Load More
                </button>
              </div>
              : ''
            }
            </ul>
          </div>
          <div className="rig_inner">
          <div className="my-4">
            <div className="recomm">
              <div className="recomm_top">
                <span>Search</span>
              </div>
            </div>
            <div className="search_advan_box">
              <form onSubmit={this.handleSubmit.bind(this)}>
                <select
                  name="religion"
                  className="mb-2"
                  value={this.props.formValues.religion}
                  onChange={this.handleChange.bind(this)}
                >
                  <option value="" disabled="">
                    Select Religion
                  </option>
                  <option value="Any">Any Religion</option>
                  <option>Christian</option>
                  <option>Hindu</option>
                  <option>Muslim</option>
                  <option>Jain</option>
                  <option>Buddhist</option>
                  <option>Sikh</option>
                  <option>Parsi</option>
                  <option>Jewish</option>
                  <option>Spiritual</option>
                  <option>No Religion</option>
                  <option>Other</option>
                </select>

                <select
                  name="gender"
                  className="mb-2"
                  value={this.props.formValues.gender}
                  onChange={this.handleChange.bind(this)}
                >
                  <option value="" disabled="">
                    Select Gender
                  </option>
                  <option>Male</option>
                  <option>Female</option>
                  <option>Other</option>
                </select>
                <input
                  type="submit"
                  className="my-4 btn btn-primary p2"
                  value={mapSearchedOnce ? "Refine Results":"Search Profiles"}
                />
              </form>
            </div>
          </div>

          </div>
        </div>
        </FadeIn>
      </div>
    )
  }
}

const mapStateToProps = state => ({
  users: state.Result.users,
  searchedOnce: state.Result.searchedOnce,
  pageNo: state.Result.pageNo,
  formValues: state.Result.formValues
})
const mapDispatchToProps = {
    getSearchResults,
    setSearchedOnce,
    setPageNum,
    setFormValues
}

export default connect(mapStateToProps, mapDispatchToProps)(SearchAdvanced)

回答1:

The state does not update when I change the selections

This is because you are not dispatching a Redux action in your handleChange method, only in your handleSubmit method. Simply dispatching the appropriate action in handleChange will resolve this issue.

when I navigate away from the page, the state that was last stored is not retained

This is because the values from before (before you navigate away) will only be kept in the Redux store, while the form fields are populated from the local state of the SearchAdvanced component.

To solve this one well, you should get rid of your local state entirely. Instead only use the Redux store. Keeping both intact is unnecessary and breaks the 'Single Source of Truth' that Redux is meant for. I recommend you do away with the local state and only update the Redux state and then pass values to the form inputs from the Redux store (props for the component).

Regarding your note that you tried this, but get errors: you need to change anything like:

<input value={ this.state.formValues.someValue }/>

to

<input value={ this.props.formValues.someValue }/>

where formValues comes from the Redux store.

Update

Problem now is the line

this.props.setFormValues(this.props.formValues)

in handleChange. You're using the old formValues to update, so the store never actually updates. I think you want something like:

handleChange(event) {
    event.preventDefault();
    const { name, value, type, checked } = event.target;
    this.props.setFormValues({
        ...this.props.formValues,
        [name]: type === 'checkbox' ? checked : value
    });
}

So that you are updating the store's formValues with the input from the user. The ternary operator is necessary for checkbox inputs since the value of a checked checkbox is 'on' rather than true, but the checked attribute is true if checked.

Extra

It seems that you pass the dispatch method to the SearchAdvanced via props from the parent. You can (and should) do this more cleanly by using the second argument of connect, which is mapDispatchToProps. Here's an example:

const mapDispatchToProps = {
    getSearchResults,
    setSearchedOnce,
    setPageNum,
    setFormValues
}

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

Then you can just call any of these as methods that already have a dispatch bound to them. So

this.props.dispatch(setSearchedOnce(true))

becomes

this.props.setSearchedOnce(true)

and you can remove the passing of the dispatch from the parent component.

Docs on connect: https://github.com/reduxjs/react-redux/blob/master/docs/api.md#connectmapstatetoprops-mapdispatchtoprops-mergeprops-options