简体   繁体   中英

What's the redux-react way to manipulate props in react children component

I'm new to Redux and React. I'm trying to implement sorting for a react component, but as far as I know you're not allowed to manipulate the props in Redux. What's the correct way of doing that in redux-react

Here's my component

import React, { Component, PropTypes } from 'react';
import _ from 'lodash';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';

import * as ArticlesActions from '../../actions/ArticlesActions';
import { ArticleList } from '../../components';

class ArticleListApp extends Component {

  static propTypes = {
    articles: PropTypes.array.isRequired,
    next: PropTypes.string.isRequired,
    actions: PropTypes.object.isRequired
  };

  componentWillMount() {
    this.props.actions.fetchArticles('57bde6d6e4b0dc55a4f018e0');
  }

  renderLoadMoreButton() {
    if (!_.isEmpty(this.props.next)) {
      return (
        <button type="button" className="btn btn-link center-block" onClick={this.props.actions.fetchMoreArticles.bind(this, this.props.next)}>Load More</button>
      );
    }

    return '';
  }

  render () {
    return (
      <div>
        <ArticleList articles={this.props.articles}/>
        <div className="row">
          <div className="col-md-12 center-block">
            {this.renderLoadMoreButton()}
          </div>
        </div>
      </div>
    );
  }
}

function mapStateToProps(state) {
  return {
    articles: state.articleList.articleList,
    next: state.articleList.next
  };
}

function mapDispatchToProps(dispatch) {
  return {
    actions: bindActionCreators(ArticlesActions, dispatch)
  };
}

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

Once I get articles I pass that to ArticleList component which I think it's a dump component in redux-react

import React, { Component, PropTypes } from 'react';

export default class ArticleList extends Component {
  static propTypes = {
    articles: PropTypes.array.isRequired
  }
  constructor(props, context) {
    super(props, context);
  }

  renderList() {
    return this.props.articles.map((article) =>
       <tr>
        <td>{article.title}</td>
        <td>Author</td>
        <td>{article.words}</td>
        <td>ago</td>
       </tr>
    );
  }

  sortArticles() {
    this.props.articles = this.props.articles.sort(); // I'm not allowed to do this. 
  }

  render() {
    return (
        <table className="table table-hover">
          <thead>
            <tr>
              <th>UNPUBLISHED ARTICLES ({this.props.articles.length})</th>
              <th>AUTHOR</th>
              <th>WORDS</th>
              <th onClick={this.sortArticles.bind(this)}>SUBMITTED</th>
            </tr>
          </thead>
          <tbody>
            {this.renderList()}
          </tbody>
        </table>
    );
  }
}

In this component, I'd like to have a sort feature where a user can click on the column and sort the whole table by that column.

The example of articles object would be

[{title: 'title', word: 10}, {title: 'title', word: 5}]

so the Redux flow is component will invoke an action that the reducer will listen to and thereby updating the state. Any container (component that is wrapped in a connect function) listening to that state will re-render itself with the new state.

So what you want to do here is have a listener that listens to the onClick event of the column in your ArticleList component (dumb component), which fires a function to the parent component, the ArticleListApp (container, or smart component). That will result in the container firing off an action, say props.sortByColumn('AUTHOR', 'asc') for example. That action should be defined in your actions file and will basically be something like this

function sortByColumn(columnName, order) {
   return {
      type: 'SORT_BY_COLUMN',
      columnName: columnName,
      order: order
   }
}

The Reducer will be listening to that action and will basically update your store with the articles to be listed in the new order.

when that update occurs, the container listening to that state will re-render itself with the new state. You can put click handlers on every column you want to call the parent with the column it wants to sort by.

Your Components will look something like this:

import React, { Component, PropTypes } from 'react';
import _ from 'lodash';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';

import * as ArticlesActions from '../../actions/ArticlesActions';
import { ArticleList } from '../../components';

class ArticleListApp extends Component {

  static propTypes = {
    articles: PropTypes.array.isRequired,
    next: PropTypes.string.isRequired,
    actions: PropTypes.object.isRequired
  };

  componentWillMount() {
    this.props.actions.fetchArticles('57bde6d6e4b0dc55a4f018e0');
  }

  renderLoadMoreButton() {
    if (!_.isEmpty(this.props.next)) {
      return (
        <button type="button" className="btn btn-link center-block" onClick={this.props.actions.fetchMoreArticles.bind(this, this.props.next)}>Load More</button>
      );
    }

    return '';
  }

  render () {
    return (
      <div>
        <ArticleList articles={this.props.articles} 
                     handleSortByCol={this.props.sortByColumn(columnName, sortOrder)} />
        <div className="row">
          <div className="col-md-12 center-block">
            {this.renderLoadMoreButton()}
          </div>
        </div>
      </div>
    );
  }
}

function mapStateToProps(state) {
  return {
    articles: state.articleList.articleList,
    next: state.articleList.next
  };
}

function mapDispatchToProps(dispatch) {
  return {
    actions: bindActionCreators(ArticlesActions, dispatch)
  };
}

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

And your other dumb component will be:

import React, { Component, PropTypes } from 'react';

export default class ArticleList extends Component {
  static propTypes = {
    articles: PropTypes.array.isRequired
  }
  constructor(props, context) {
    super(props, context);
  }

  renderList() {
    return this.props.articles.map((article) =>
       <tr>
        <td>{article.title}</td>
        <td>Author</td>
        <td>{article.words}</td>
        <td>ago</td>
       </tr>
    );
  }

  sortArticles() {
    this.props.handleSortByCol('author', 'asc');
  }

  render() {
    return (
        <table className="table table-hover">
          <thead>
            <tr>
              <th>UNPUBLISHED ARTICLES ({this.props.articles.length})</th>
              <th>AUTHOR</th>
              <th>WORDS</th>
              <th onClick={this.sortArticles.bind(this)}>SUBMITTED</th>
            </tr>
          </thead>
          <tbody>
            {this.renderList()}
          </tbody>
        </table>
    );
  }

If I understand correctly, then there are two ways to go about this:

  1. Articles should be kept in the reducer, sorted or not sorted, depending on the user interaction. Technically this is done by having the "sort" action fired (by the container) and then a reducer handling that event by changing its state (returning a new state that is the previous array but sorted). Regardless of how this is done, this introduces a problem: any component subscribed to the articles reducer will always get a sorted list. Is this what you want? Maybe the "sorted" display is part of the state of the ui component and is specific to that component so....

  2. Have the reducer keep the unsorted array and have two different components (or a component with an additional prop stating if its "sorted" or not) and during the render functions of this component, either sort or don't sort, according to the prop given by the components container. Technically this is done by having the container component have a state (sorted or not sorted) that is passed as a prop to the view component.

If you are new to react/redux and what I said makes little sense to you, I don't mind adding code - but I think your question is about the principle and not about the actual code.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM