React getDerivedStateFromProps. Создание удобных интерфейсов

Йо-йо! Пару месяцев назад (сентябрь 2019) я переехал в МСК и было не до блога. Но теперь я вроде разобрался с самым важным и готов вновь быть проводником знаний для своих читателей. И так…

На новой работе я недавно писал react-компонент для подписки и добавления автомобиля в избранное.

Кнопки действий я подчеркнул

По факту, когда пользователь нажимает на кнопку, идёт запрос на сервер. Сервер отвечает и тогда пользователь подписывается или добавляет авто в избранное.

В прошлой реализации списка автомобилей иконки подсвечивались только после ответа сервера и пока ожидался ответ создавалось впечатление, что пользователь промахнулся или каталог тормозит. Это мне не понравилось и я решил отображать сразу же новое состояние и просто отправлять данные на сервер.

Проблемы

1. Проблема заключалась в том, что данные я получал из redux довольно специфическим методом, т.к. структура данных списка довольно непростая (ввиду разных причин) и из-за этого я не хотел изменять состояние в store сразу.

2. Обновление store могло случиться не только в каталоге, но и в отдельном компоненте со списком избранного.

Открывается в боковой колонке

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

Решение и getDerivedStateFromProps

Схема работы у меня получилась такая: При клике на подписку (например) я сразу меняю цвет, отправляю запрос на сервер и вызываю 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. Но он бы был уместен, если у вас была только одна функция (подписка, например), а не как у меня несколько.

Итог getDerivedStateFromProps

Его использование помогло повысить usability. Для лучшего UX вы бы могли выводить ненавязчивое окно с результатом (положительная подписка или отрицательная). Таким образом, мы улучшаем UX и поддерживаем асинхронность. Надеюсь вам будет полезен приведённый пример.

P.S.

Такое вы можете провернуть не только с redux, но и при использовании данных из hight order component.