Йо-йо! Пару месяцев назад (сентябрь 2019) я переехал в МСК и было не до блога. Но теперь я вроде разобрался с самым важным и готов вновь быть проводником знаний для своих читателей. И так…
На новой работе я недавно писал react-компонент для подписки и добавления автомобиля в избранное.
По факту, когда пользователь нажимает на кнопку, идёт запрос на сервер. Сервер отвечает и тогда пользователь подписывается или добавляет авто в избранное.
В прошлой реализации списка автомобилей иконки подсвечивались только после ответа сервера и пока ожидался ответ создавалось впечатление, что пользователь промахнулся или каталог тормозит. Это мне не понравилось и я решил отображать сразу же новое состояние и просто отправлять данные на сервер.
1. Проблема заключалась в том, что данные я получал из redux довольно специфическим методом, т.к. структура данных списка довольно непростая (ввиду разных причин) и из-за этого я не хотел изменять состояние в store сразу.
2. Обновление store могло случиться не только в каталоге, но и в отдельном компоненте со списком избранного.
Т.к. одновременно не видно избранное и каталог, данные в избранном можно обновить после ответа сервера, т.е. после изменения props.
Схема работы у меня получилась такая: При клике на подписку (например) я сразу меняю цвет, отправляю запрос на сервер и вызываю dispatch в redux после ответа. Если ответ будет отрицательным, то я получу другие данные и отменю действие по добавлению.
Как же это в коде…. я умышленно пропущу часть из redux. В данном случае он не важен. А ещё вы можете по-разному «отыграть» ситуацию после ответа (показать сообщение об удачной или неудачной подписке например).
class AddToFavorites extends Component { constructor(props) { super(props); const { subscribe, data } = this.props; this.state = { data: data, subscribe: subscribe } } handelSubscribe = () => { const { subscribeAction, data, subscribe } = this.props; this.setState({ subscribe: !subscribe }); /* Изменяю state */ subscribeAction(data); /* Отправляю данные в Redux */ } /* Прочий код */ render() { const { subscribe } = this.state; {/* Прочий код */} return ( <Fragment> <span className={`pr10 listItem__addButtons ${this.props.className}`}> {/* Прочий код */} <svg onClick={handelSubscribe} className={`favorite_iconbox__icon mr-right10 ${ (subscribe) ? 'active' : '' }`} > <use xlinkHref={`#notification`}> <title>Подписаться на изменение цены</title> </use> </svg> {/* Прочий код */} </span> </Fragment> ) } }
Почему я вообще сохраняю состояние подписан/не подписан в state?! Т.к. пользователь в любой момент может, например, отфильтровать список или сделать другое действие, которое может вызвать перерендер, компонент бы обязательно обратился к store, в котором ещё нет нового значения.
Далее, мы допишем код так, чтобы знать предыдущее состояние, пришедшее к нам из пропс.
constructor(props) { super(props); const { subscribe, data } = this.props; this.state = { memoryProps: this.props, // Вот тут data: data, subscribe: subscribe } }
Так мы сможем сравнивать изменилось ли значение props, пришедшее ранее (как в старом componentWillUpdate). Собственно давайте сравним. Дополним наш компонент методом
static getDerivedStateFromProps(props, state){ if(props.subscribe !== state.memoryProps.subscribe){ return { ...state, ...{subscribe: props.subscribe, memoryProps: props} } } return null; }
Мы сравниваем новое и старое значение состояния подписки, и если оно изменится, то и поменяем его в state. Так мы не даём изменить состояние при других действиях пользователя.
Такого же эффекта можно было бы добиться и с помощью PureComponent. Но он бы был уместен, если у вас была только одна функция (подписка, например), а не как у меня несколько.
Его использование помогло повысить usability. Для лучшего UX вы бы могли выводить ненавязчивое окно с результатом (положительная подписка или отрицательная). Таким образом, мы улучшаем UX и поддерживаем асинхронность. Надеюсь вам будет полезен приведённый пример.
Такое вы можете провернуть не только с redux, но и при использовании данных из hight order component.