Перерисовать компонент React при смене реквизита

Я пытаюсь отделить презентационный компонент от контейнерного компонента. У меня есть SitesTable и SitesTableContainer. Контейнер отвечает за запуск избыточных действий для извлечения соответствующих сайтов на основе текущего пользователя.

Проблема заключается в том, что текущий пользователь выбирается асинхронно после того, как компонент контейнера был визуализирован изначально. Это означает, что компонент контейнера не знает, что ему нужно повторно выполнить код в своей функции componentDidMount которая обновит данные для отправки в SitesTable. Я думаю, что мне нужно повторно визуализировать компонент контейнера, когда один из его реквизит (пользователь) меняется. Как мне сделать это правильно?

class SitesTableContainer extends React.Component {
    static get propTypes() {
      return {
        sites: React.PropTypes.object,
        user: React.PropTypes.object,
        isManager: React.PropTypes.boolean
      }
     }

    componentDidMount() {
      if (this.props.isManager) {
        this.props.dispatch(actions.fetchAllSites())
      } else {
        const currentUserId = this.props.user.get('id')
        this.props.dispatch(actions.fetchUsersSites(currentUserId))
      }  
    }

    render() {
      return <SitesTable sites={this.props.sites}/>
    }
}

function mapStateToProps(state) {
  const user = userUtils.getCurrentUser(state)

  return {
    sites: state.get('sites'),
    user,
    isManager: userUtils.isManager(user)
  }
}

export default connect(mapStateToProps)(SitesTableContainer);

Ответы

Ответ 1

Вы должны добавить условие в ваш метод componentDidUpdate.

В примере используется fast-deep-equal для сравнения объектов.

import equal from 'fast-deep-equal'

...

constructor(){
  this.updateUser = this.updateUser.bind(this);
}  

componentDidMount() {
  this.updateUser();
}

componentDidUpdate(prevProps) {
  if(!equal(this.props.user, prevProps.user)) // Check if it a new user, you can also use some unique property, like the ID  (this.props.user.id !== prevProps.user.id)
  {
    this.updateUser();
  }
} 

updateUser() {
  if (this.props.isManager) {
    this.props.dispatch(actions.fetchAllSites())
  } else {
    const currentUserId = this.props.user.get('id')
    this.props.dispatch(actions.fetchUsersSites(currentUserId))
  }  
}

Использование крючков (Реакция 16.8. 0+)

import React, { useEffect } from 'react';

const SitesTableContainer = ({
  user,
  isManager,
  dispatch,
  sites,
}) => {
  useEffect(() => {
    if(isManager) {
      dispatch(actions.fetchAllSites())
    } else {
      const currentUserId = user.get('id')
      dispatch(actions.fetchUsersSites(currentUserId))
    }
  }, [user]); 

  return (
    return <SitesTable sites={sites}/>
  )

}

Если упор, который вы сравниваете, является объектом или массивом, вы должны использовать useDeepCompareEffect вместо useEffect.

Ответ 2

ComponentWillReceiveProps() в будущем будет устаревшим из-за ошибок и несоответствий. Альтернативное решение для повторного рендеринга компонента при смене реквизита заключается в использовании ComponentDidUpdate() и ShouldComponentUpdate().

ComponentDidUpdate() вызывается всякий раз, когда компонент обновляется И, если ShouldComponentUpdate() возвращает истину (если не определено ShouldComponentUpdate(), он возвращает true по умолчанию).

shouldComponentUpdate(nextProps){
    return nextProps.changedProp !== this.state.changedProp;
}

componentDidUpdate(props){
    // Desired operations: ex setting state
}

Такое же поведение может быть достигнуто с использованием только метода ComponentDidUpdate() путем включения в него условного оператора.

componentDidUpdate(prevProps){
    if(prevProps.changedProp !== this.props.changedProp){
        this.setState({          
            changedProp: this.props.changedProp
        });
    }
}

Если ShouldComponentUpdate() установить состояние без условия или без определения ShouldComponentUpdate() компонент будет бесконечно повторно отображать

Ответ 3

componentWillReceiveProps(nextProps) { // your code here}

Я думаю, что это то, что вам нужно. componentWillReceiveProps срабатывает каждый раз, когда ваш компонент получает что-то через реквизит. Оттуда вы можете сделать свою проверку, а затем делать все, что хотите.

Ответ 4

Я бы порекомендовал взглянуть на этот ответ и посмотреть, соответствует ли оно тому, что вы делаете. Если я понимаю вашу реальную проблему, это означает, что вы просто не используете свое асинхронное действие правильно и обновляете "хранилище" redux, которое автоматически обновит ваш компонент с помощью новых реквизитов.

Этот раздел вашего кода:

componentDidMount() {
      if (this.props.isManager) {
        this.props.dispatch(actions.fetchAllSites())
      } else {
        const currentUserId = this.props.user.get('id')
        this.props.dispatch(actions.fetchUsersSites(currentUserId))
      }  
    }

Не следует запускать в компоненте, он должен обрабатываться после выполнения вашего первого запроса.

Взгляните на этот пример из redux-thunk:

function makeASandwichWithSecretSauce(forPerson) {

  // Invert control!
  // Return a function that accepts `dispatch` so we can dispatch later.
  // Thunk middleware knows how to turn thunk async actions into actions.

  return function (dispatch) {
    return fetchSecretSauce().then(
      sauce => dispatch(makeASandwich(forPerson, sauce)),
      error => dispatch(apologize('The Sandwich Shop', forPerson, error))
    );
  };
}

Вы не обязательно должны использовать redux-thunk, но это поможет вам рассуждать о подобных сценариях и писать код для соответствия.

Ответ 5

Вы можете использовать KEY уникальный ключ (комбинацию данных), который изменяется с помощью реквизита, и этот компонент будет перерисован с обновленными реквизитами.

Ответ 6

Дружественный метод, который можно использовать, заключается в следующем: после обновления prop он автоматически переопределяет компонент:

render {

let textWhenComponentUpdate = this.props.text 

return (
<View>
  <Text>{textWhenComponentUpdate}</Text>
</View>
)

}