Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 300
stackjlei Avatar asked Oct 12 '16 06:10

stackjlei


People also ask

Can you use local state with Redux?

Using local component state is fine. Some common rules of thumb for determining what kind of data should be put into Redux: Do other parts of the application care about this data? Do you need to be able to create further derived data based on this original data?

Do you need to keep all component states in Redux store?

Any time you do not store a component state change in Redux, that change is completely lost from the stack of Redux changes and your application UI will be out of sync with the store.

Can a component have multiple states?

You can create a component, add multiple variations (states) to it, and wire it to mimic real-world user behavior (without having to copy your components multiple times).

How do I run a redux store in a React project?

The package react-redux contains the bindings to run a Redux store in a React project. You’ll use code from react-redux to send actions from your components and to pull data from the store into your components. Use npm to install the two packages with the following command: When the component is finished installing, you’ll receive output like this.

What is state in react?

In other words, state is responsible for the behavior of a component behind the scenes and can be considered the source of truth for it. There are multiple ways to manage the state for a component like local state, Redux store, and even the use of this.

Can I use Redux store and local store in a component?

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.

What is state in Redux?

In redux literature this data is also referred as ‘state’. The whole state of the application is stored in the store. How do we create a store ? There is a function called ‘ createStore’ provided by redux library. We pass a ‘Reducer’ as an argument to the store.


2 Answers

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>
    )
  }
}
like image 177
Michał Szajbe Avatar answered Oct 18 '22 23:10

Michał Szajbe


The componentWillReceiveProps() method is not called for the initial render. What could do, if you only intend to use the data from props as the initial data, is something like:

getInitialState () {
  return {
    contacts: this.props.data.contacts
  }
}

In the React docs they suggest you name the props initialContacts, just to make it really clear that the props' only purpose is to initialize something internally.

Now if you want it to update when this.props.contacts change, you could use componentWillReceiveProps() like you did. But I'm not sure it's the best idea. From the docs:

Using props to generate state in getInitialState often leads to duplication of "source of truth", i.e. where the real data is. This is because getInitialState is only invoked when the component is first created.

Whenever possible, compute values on-the-fly to ensure that they don't get out of sync later on and cause maintenance trouble.

like image 3
tobiasandersen Avatar answered Oct 18 '22 23:10

tobiasandersen