Как отключить, удалить или удалить компонент из себя в уведомлении React/Redux/Typescript

Я знаю, что этот вопрос уже задавался пару раз, но в большинстве случаев решение заключается в том, чтобы справиться с этим в родительском элементе, поскольку поток ответственности только нисходящий. Однако иногда вам нужно убить компонент одним из его методов. Я знаю, что не могу изменить его реквизиты, и если я начну добавлять логические значения как состояния, для простого компонента это станет очень грязным. Вот что я пытаюсь достичь: Небольшой компонент с ошибкой, отмеченный знаком "x". При получении ошибки через ее реквизиты будет отображаться ее, но я бы хотел, чтобы закрыть ее из своего собственного кода.

class ErrorBoxComponent extends React.Component {

  dismiss() {
    // What should I put here ?
  }

  render() {
    if (!this.props.error) {
      return null;
    }

    return (
      <div data-alert className="alert-box error-box">
        {this.props.error}
        <a href="#" className="close" onClick={this.dismiss.bind(this)}>&times;</a>
      </div>
    );
  }
}


export default ErrorBoxComponent;

И я бы использовал это так в родительском компоненте:

<ErrorBox error={this.state.error}/>

В разделе Что я должен поставить здесь? Я уже пробовал:

ReactDOM.unmountComponentAtNode(ReactDOM.findDOMNode(this).parentNode);Который выдает приятную ошибку в консоли:

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

Должен ли я копировать входящий реквизит в состоянии ErrorBox и манипулировать им только внутри?

3 ответа

Решение

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

class Child extends React.Component {
    constructor(){}
    dismiss() {
        this.props.unmountMe();
    } 
    render(){
        // code
    }
}

class Parent ...
    constructor(){
        super(props)
        this.state = {renderChild: true};
        this.handleChildUnmount = this.handleChildUnmount.bind(this);
    }
    handleChildUnmount(){
        this.setState({renderChild: false});
    }
    render(){
        // code
        {this.state.renderChild ? <Child unmountMe={this.handleChildUnmount} /> : null}
    }

}

это очень простой пример. но вы можете увидеть грубый способ передать родителю действие

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

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

РЕДАКТИРОВАТЬ: Вот как я настраиваю систему уведомлений, используя React/Redux/Typescript

Несколько вещей, на которые стоит обратить внимание в первую очередь... это в машинописи, поэтому вам нужно будет удалить объявления типов:)

Я использую пакеты npm lodash для операций и имена классов (псевдоним cx) для встроенного назначения имен классов.

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

уведомление-actions.ts

import { USER_SYSTEM_NOTIFICATION } from '../constants/action-types';

interface IDispatchType {
    type: string;
    payload?: any;
    remove?: Symbol;
}

export const notifySuccess = (message: any, duration?: number) => {
    return (dispatch: Function) => {
        dispatch({ type: USER_SYSTEM_NOTIFICATION, payload: { isSuccess: true, message, notify_id: Symbol(), duration } } as IDispatchType);
    };
};

export const notifyFailure = (message: any, duration?: number) => {
    return (dispatch: Function) => {
        dispatch({ type: USER_SYSTEM_NOTIFICATION, payload: { isSuccess: false, message, notify_id: Symbol(), duration } } as IDispatchType);
    };
};

export const clearNotification = (notifyId: Symbol) => {
    return (dispatch: Function) => {
        dispatch({ type: USER_SYSTEM_NOTIFICATION, remove: notifyId } as IDispatchType);
    };
};

уведомление-reducer.ts

const defaultState = {
    userNotifications: []
};

export default (state: ISystemNotificationReducer = defaultState, action: IDispatchType) => {
    switch (action.type) {
        case USER_SYSTEM_NOTIFICATION:
            const list: ISystemNotification[] = _.clone(state.userNotifications) || [];
            if (_.has(action, 'remove')) {
                const key = parseInt(_.findKey(list, (n: ISystemNotification) => n.notify_id === action.remove));
                if (key) {
                    // mutate list and remove the specified item
                    list.splice(key, 1);
                }
            } else {
                list.push(action.payload);
            }
            return _.assign({}, state, { userNotifications: list });
    }
    return state;
};

app.tsx

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

render() {
    const { systemNotifications } = this.props;
    return (
        <div>
            <AppHeader />
            <div className="user-notify-wrap">
                { _.get(systemNotifications, 'userNotifications') && Boolean(_.get(systemNotifications, 'userNotifications.length'))
                    ? _.reverse(_.map(_.get(systemNotifications, 'userNotifications', []), (n, i) => <UserNotification key={i} data={n} clearNotification={this.props.actions.clearNotification} />))
                    : null
                }
            </div>
            <div className="content">
                {this.props.children}
            </div>
        </div>
    );
}

пользователя notification.tsx

класс уведомлений пользователя

/*
    Simple notification class.

    Usage:
        <SomeComponent notifySuccess={this.props.notifySuccess} notifyFailure={this.props.notifyFailure} />
        these two functions are actions and should be props when the component is connect()ed

    call it with either a string or components. optional param of how long to display it (defaults to 5 seconds)
        this.props.notifySuccess('it Works!!!', 2);
        this.props.notifySuccess(<SomeComponentHere />, 15);
        this.props.notifyFailure(<div>You dun goofed</div>);

*/

interface IUserNotifyProps {
    data: any;
    clearNotification(notifyID: symbol): any;
}

export default class UserNotify extends React.Component<IUserNotifyProps, {}> {
    public notifyRef = null;
    private timeout = null;

    componentDidMount() {
        const duration: number = _.get(this.props, 'data.duration', '');

        this.notifyRef.style.animationDuration = duration ? `${duration}s` : '5s';


        // fallback incase the animation event doesn't fire
        const timeoutDuration = (duration * 1000) + 500;
        this.timeout = setTimeout(() => {
            this.notifyRef.classList.add('hidden');
            this.props.clearNotification(_.get(this.props, 'data.notify_id') as symbol);
        }, timeoutDuration);

        TransitionEvents.addEndEventListener(
            this.notifyRef,
            this.onAmimationComplete
        );
    }
    componentWillUnmount() {
        clearTimeout(this.timeout);

        TransitionEvents.removeEndEventListener(
            this.notifyRef,
            this.onAmimationComplete
        );
    }
    onAmimationComplete = (e) => {
        if (_.get(e, 'animationName') === 'fadeInAndOut') {
            this.props.clearNotification(_.get(this.props, 'data.notify_id') as symbol);
        }
    }
    handleCloseClick = (e) => {
        e.preventDefault();
        this.props.clearNotification(_.get(this.props, 'data.notify_id') as symbol);
    }
    assignNotifyRef = target => this.notifyRef = target;
    render() {
        const {data, clearNotification} = this.props;
        return (
            <div ref={this.assignNotifyRef} className={cx('user-notification fade-in-out', {success: data.isSuccess, failure: !data.isSuccess})}>
                {!_.isString(data.message) ? data.message : <h3>{data.message}</h3>}
                <div className="close-message" onClick={this.handleCloseClick}>+</div>
            </div>
        );
    }
}

Вместо того, чтобы использовать

ReactDOM.unmountComponentAtNode(ReactDOM.findDOMNode(this).parentNode);

попробуйте использовать

ReactDOM.unmountComponentAtNode(document.getElementById('root'));

В большинстве случаев достаточно просто скрыть элемент, например, так:

export default class ErrorBoxComponent extends React.Component {
    constructor(props) {
        super(props);

        this.state = {
            isHidden: false
        }
    }

    dismiss() {
        this.setState({
            isHidden: true
        })
    }

    render() {
        if (!this.props.error) {
            return null;
        }

        return (
            <div data-alert className={ "alert-box error-box " + (this.state.isHidden ? 'DISPLAY-NONE-CLASS' : '') }>
                { this.props.error }
                <a href="#" className="close" onClick={ this.dismiss.bind(this) }>&times;</a>
            </div>
        );
    }
}

Или вы можете визуализировать / перерисовать / не визуализировать через родительский компонент, как это

export default class ParentComponent extends React.Component {
    constructor(props) {
        super(props);

        this.state = {
            isErrorShown: true
        }
    }

    dismiss() {
        this.setState({
            isErrorShown: false
        })
    }

    showError() {
        if (this.state.isErrorShown) {
            return <ErrorBox 
                error={ this.state.error }
                dismiss={ this.dismiss.bind(this) }
            />
        }

        return null;
    }

    render() {

        return (
            <div>
                { this.showError() }
            </div>
        );
    }
}

export default class ErrorBoxComponent extends React.Component {
    dismiss() {
        this.props.dismiss();
    }

    render() {
        if (!this.props.error) {
            return null;
        }

        return (
            <div data-alert className="alert-box error-box">
                { this.props.error }
                <a href="#" className="close" onClick={ this.dismiss.bind(this) }>&times;</a>
            </div>
        );
    }
}

Наконец, есть способ удалить узел html, но я действительно не знаю, хорошая ли это идея. Может быть, кто-то, кто знает React изнутри, скажет что-нибудь об этом.

export default class ErrorBoxComponent extends React.Component {
    dismiss() {
        this.el.remove();
    }

    render() {
        if (!this.props.error) {
            return null;
        }

        return (
            <div data-alert className="alert-box error-box" ref={ (el) => { this.el = el} }>
                { this.props.error }
                <a href="#" className="close" onClick={ this.dismiss.bind(this) }>&times;</a>
            </div>
        );
    }
}

Я был на этом посте около 10 раз и просто хотел оставить здесь свои два цента. Можно просто размонтировать условно.

if (renderMyComponent) {
  <MyComponent props={...} />
}

Все, что вам нужно сделать, это удалить его из DOM, чтобы размонтировать.

Так долго как renderMyComponent = true, компонент отобразит. Если вы установитеrenderMyComponent = false, он будет отключен от DOM.

Это подходит не во всех ситуациях, но вы можете условно return false внутри самого компонента, если определенные критерии выполняются или не выполняются.

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

import React, { Component } from 'react';

export default class MyComponent extends Component {
    constructor(props) {
        super(props);

        this.state = {
            hideComponent: false
        }
    }

    closeThis = () => {
        this.setState(prevState => ({
            hideComponent: !prevState.hideComponent
        })
    });

    render() {
        if (this.state.hideComponent === true) {return false;}

        return (
            <div className={`content`} onClick={() => this.closeThis}>
                YOUR CODE HERE
            </div>
        );
    }
}
Другие вопросы по тегам