How do I use local state along with redux store state in the same react component?

stackjlei picture stackjlei · Oct 12, 2016 · Viewed 20k times · Source

I have a table that displays contacts and I want to sort the contacts by first name. The contacts array comes from the redux store, which will come then come through the props, but I want the local state to hold how those contacts are sorted, since it's local UI state. How do I achieve this? I so far have placed contacts into componentWillReceiveProps but for some reason it doesn't receive the props when it changes. How do I update the local state each time the redux store state changes?

const Table = React.createClass({
  getInitialState () {
    return {contacts: []}
  },
  componentWillReceiveProps () {
    this.setState({ contacts: this.props.data.contacts})
  },
  sortContacts (parameter, e){
    ...
  },
  render () {
    return (
      <table>
        <thead>
          <tr>
            <th onClick={this.sortContacts.bind(this, "firstName")}>First Name</th>
          </tr>
        </thead>
        <tbody>
          {contactRows}
        </tbody>
      </table>
    )
  }
})

update of current code that includes filtering

import React, {Component} from 'react'
import TableRow from './TableRow'

class Table extends Component {
  constructor (props) {
    super(props)
    this.state = { sortBy: "fistName" }
  }
  sortContacts (parameter) {
    console.log('in sortContacts')

    this.setState({ sortBy: parameter })
  }
  sortedContacts () {
    console.log('in sortedContacts')

    const param = this.state.sortBy
    return (
      this.props.data.contacts.sort(function (a, b){
        if (!a.hasOwnProperty(param)){
          a[param] = " ";
        }
        if (!b.hasOwnProperty(param)){
          b[param] = " ";
        }
        const nameA = a[param].toLowerCase(), nameB = b[param].toLowerCase();
        if (nameA > nameB) {
          return 1;
        } else {
          return -1;
        }
      })
    )
  }
  filteredSortedContacts () {
    console.log('in filteredSortedContacts')

    const filterText = this.props.data.filterText.toLowerCase()
    let filteredContacts = this.sortedContacts()
    if (filterText.length > 0) {
      filteredContacts = filteredContacts.filter(function (contact){
        return (
          contact.hasOwnProperty('lastName') &&
          contact.lastName.toLowerCase().includes(filterText)
        )
      })
    }
    return filteredContacts
  }
  contactRows () {
    console.log('in contactRows')
    return this.filteredSortedContacts().map((contact, idx) =>
      <TableRow contact={contact} key={idx}/>
    )
  }
  render () {
    return (
      <div className="table-container">
        <table className="table table-bordered">
          <thead>
            <tr>
              <th className="th-cell" onClick={this.sortContacts.bind(this, "firstName")}>First Name</th>
              <th onClick={this.sortContacts.bind(this, "lastName")}>Last Name</th>
              <th>Date of Birth</th>
              <th>Phone</th>
              <th>Email</th>
              <th>Notes</th>
            </tr>
          </thead>
          <tbody>
            {this.contactRows()}
          </tbody>
        </table>
      </div>
    )
  }
}

export default Table

The issue I'm seeing now is that contactRows, filteredSortedContacts, sortedContacts are being called multiple times, once for each TableRow. I don't see how this can be happening if I'm only calling contactRows once in the body.

Answer

Michał Szajbe picture Michał Szajbe · Oct 12, 2016

Your approach to use both redux store and local store is correct.

Just do not try to duplicate the state from redux store in your component. Keep referring to it via props.

Instead create a sortedContacts function that computes value on the fly by applying locally-stored sortBy param to redux-stored contacts.

const Table extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      sortBy: 'id' // default sort param
    }
  }

  sortContacts(param) {
    this.setState({ sortBy: param})
  }

  sortedContacts() {
    return this.props.contacts.sort(...); // return sorted collection
  }

  render() {
    return (
      <table>
        <thead>
          <tr>
            <th onClick={() => this.sortContacts("firstName")}>First Name</th>
          </tr>
        </thead>
        <tbody>
          {this.sortedContacts()}
        </tbody>
      </table>
    )
  }
}