web-dev-qa-db-fra.com

Comment utiliser l'état local avec l'état du magasin redux dans le même composant react?

J'ai une table qui affiche les contacts et je veux trier les contacts par prénom. Le tableau de contacts provient du magasin redux, qui passera ensuite par les accessoires, mais je veux que l'état local détienne la manière dont ces contacts sont triés, car il s'agit de l'état de l'interface utilisateur locale. Comment puis-je y arriver? Jusqu'ici, j'ai placé des contacts dans componentWillReceiveProps, mais pour une raison quelconque, il ne reçoit pas les accessoires lorsqu'il change. Comment mettre à jour l'état local chaque fois que l'état du magasin redux change?

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>
    )
  }
})

mise à jour du code actuel incluant le filtrage

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

Le problème que je vois maintenant, c'est que contactRows, filteredSortedContacts, sortedContacts sont appelés plusieurs fois, une fois pour chaque table. Je ne vois pas comment cela peut se produire si j'appelle seulement contactRows une fois dans le corps.

10
stackjlei

Votre approche consistant à utiliser à la fois le magasin redux et le magasin local est correcte.

Il suffit de ne pas essayer de dupliquer l’état du magasin Redux dans votre composant. Continuez à vous y référer via des accessoires.

Créez plutôt une fonction sortedContacts qui calcule une valeur à la volée en appliquant le paramètre sortBy stocké localement aux contacts stockés en redux.

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>
    )
  }
}
8
Michał Szajbe

La méthode componentWillReceiveProps() n'est pas appelée pour le rendu initial. Que pouvez-vous faire si vous souhaitez uniquement utiliser les données des accessoires comme données initiales, par exemple:

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

Dans les documents Réagir , ils suggèrent de nommer les accessoires initialContacts, juste pour préciser que le seul but des accessoires est d'initialiser quelque chose en interne.

Maintenant, si vous voulez qu'il se mette à jour lorsque this.props.contacts change, vous pouvez utiliser componentWillReceiveProps() comme vous l'avez fait. Mais je ne suis pas sûr que ce soit la meilleure idée. De la docs:

L'utilisation d'accessoires pour générer un état dans getInitialState conduit souvent à duplication de "source de vérité", c’est-à-dire où se trouvent les données réelles. C'est parce que getInitialState n'est appelé que lorsque le composant est premier créé.

Autant que possible, calculez les valeurs à la volée pour vous assurer qu'elles ne le font pas désynchroniser plus tard et causer des problèmes de maintenance.

3
tobiasandersen

React 16.3 inclut une fonction statique getDerivedStateFromProps(nextProps, prevState), qui sera invoquée après le montage initial ainsi que lors de la réception de nouveaux accessoires. Voir https://reactjs.org/docs/react-component.html#static-getderivedstatefromprops

1
Jesper Lehtinen