简体   繁体   中英

React component won't re-render on props change

This is the BookList component to re-render on the filter.sortBy prop change.

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { getBooks } from '../actions/bookActions';
import Spinner from './Spinner';
import BookItem from './BookItem';
import sortBooks from '../selectors/books';

class BookList extends Component {
  componentDidMount() {
    this.props.getBooks();
  }

  render() {
    const { books, loading } = this.props;

    let booksContent;

    if (!books || loading) {
      booksContent = <Spinner />;
    } else {
      if (books.length > 0) {
        booksContent = books.map(book => <BookItem book={book} key={book._id} />);
      } else {
        booksContent = <h4>No books found</h4>;
      }
    }

    return (
      <div className='feed'>
                <div className='container'>
                    <div className='row'>
                        <div className='col-md-12'>
                            {booksContent}
                        </div>
                    </div>
                </div>
            </div>
    );
  }
}

BookList.propTypes = {
  books: PropTypes.oneOfType([PropTypes.array, PropTypes.object]).isRequired,
  getBooks: PropTypes.func.isRequired
};

const mapStateToProps = (state) => {
  const books = sortBooks(state.books.books, state.filter.sortBy);

  return {
    books: books
  }
};

export default connect(mapStateToProps, { getBooks })(BookList);

There is another component to change the state.filter.sortBy value. From what got logged inside mapStateToProps() above state.filter.sortBy changes correctly.

How do I get BookList to re-render when I change the sortBy value? The complete repo is on https://github.com/ElAnonimo/booklist So far I've tried a constructor() , a componentWillReceiveProps() in BookList .

UPDATE.

This is what my sortBooks selector looked like

export default (books, sortBy) => {
  return books
    .sort((a, b) => {
      if (sortBy === 'title') {
        return a.title < b.title ? -1 : 1;
      } else if (sortBy === 'releasedAt') {
        return a.releasedAt < b.releasedAt ? -1 : 1;
      }
    });
};

As was mentioned in the accepted answer that way the selector returned an internally modified but still the original books array to the BookList component. Which resulted in no props change for Redux.

This is how I got it to return a modified copy of the original array.

export default (books, sortBy) => {
  return [...books]
    .sort((a, b) => {
      if (sortBy === 'title') {
        return a.title < b.title ? -1 : 1;
      } else if (sortBy === 'releasedAt') {
        return a.releasedAt < b.releasedAt ? -1 : 1;
      }
    });
};

The credit goes to Ryan C.

UPDATE 2. Another credit to Ryan C. Thank you for sharing the wisdom.

Here are his suggested modifications applied.

booksReducer . Before it lacked the SORT_BY_TITLE and SORT_BY_RELEASED_AT cases.

import {
  GET_BOOKS,
  BOOKS_LOADING,
  DELETE_BOOK,
  SORT_BY_TITLE,
  SORT_BY_RELEASED_AT
} from '../actions/types';

const initialState = {
  books: []
};

export default function(state = initialState, action) {
  switch(action.type) {
    case BOOKS_LOADING:
      return {
        ...state,
        loading: true
      };
    case GET_BOOKS:
      return {
        ...state,
        books: action.payload,
        loading: false
      };
    case DELETE_BOOK:
      return {
        books: [...state.books.filter(book => book._id !== action.payload.id)]
      };
    case SORT_BY_TITLE:
      return {
        ...state,
        books: [...state.books.sort((a, b) => a.title < b.title ? -1 : 1 )]
      };            
    case SORT_BY_RELEASED_AT:
      return {
        ...state,
        books: [...state.books.sort((a, b) => a.releasedAt < b.releasedAt ? -1 : 1 )]
      };    
    default:
      return state;
  }
}

The modification to the BookList

const mapStateToProps = (state) => ({
    books: state.books.books
});

These changes effectively leave out the need for a selector in the project.

I suspect that your sortBooks method is sorting the array in place, such that the same books array reference is always returned (even though the order of the elements within it may have been changed). This causes react-redux to think that the properties returned by mapStateToProps have not changed (react-redux only does a shallow comparison). If you change sortBooks to first duplicate the array and then sort it and return the new array rather than mutating the existing array, it should work.

UPDATE After seeing your resolution, I think it would be better to handle this sorting in your reducer since the books array is in state. This way you can control only doing the sorting in response to the action that changes the sort. The way it is now, you will end up re-rendering the book list on every state change (even if the sort did not change) since mapStateToProps will ALWAYS return a different books array.

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