web-dev-qa-db-fra.com

Comment démonter, réémettre ou supprimer un composant de lui-même dans un message de notification React / Redux / TypeScript

Je sais que cette question a déjà été posée à quelques reprises, mais la plupart du temps, la solution consiste à gérer cela dans le parent, car le flux de responsabilité est seulement descendant. Cependant, il est parfois nécessaire de tuer un composant à l’aide d’une de ses méthodes. Je sais que je ne peux pas modifier ses accessoires, et si je commence à ajouter des booléens en tant qu'état, cela va devenir vraiment compliqué pour un composant simple. Voici ce que je cherche à réaliser: Un petit composant de boîte d’erreur, avec un "x" pour le rejeter. Recevoir une erreur via ses accessoires l'affichera, mais j'aimerais un moyen de la fermer à partir de son propre code.

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;

Et je l'utiliserais comme ceci dans le composant parent:

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

Dans la section Que dois-je mettre ici?, j'ai déjà essayé:

ReactDOM.unmountComponentAtNode(ReactDOM.findDOMNode(this).parentNode); qui génère une erreur Nice dans la console:

Avertissement: unmountComponentAtNode (): le noeud que vous tentez de démonter a été rendu par React et n'est pas un conteneur de niveau supérieur. Au lieu de cela, demandez au composant parent de mettre à jour son état et son rendu afin de le supprimer.

Devrais-je copier les accessoires entrants dans l'état ErrorBox et les manipuler uniquement en interne?

91
Sephy

Juste comme ça, bon avertissement, vous essayez de faire quelque chose qui est un anti-modèle dans React. C'est un non non. React est destiné à ce qu'une relation de démontage se produise d'une relation parent à enfant. Maintenant, si vous voulez qu'un enfant se démonte, vous pouvez simuler ceci avec un changement d'état dans le parent qui est déclenché par l'enfant. laissez-moi vous montrer dans le code.

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}
    }

}

c'est un exemple très simple. mais vous pouvez voir un moyen approximatif de transmettre au parent une action

Cela étant dit, vous devriez probablement passer par le magasin (action de répartition) pour permettre à votre magasin de contenir les données correctes lors du rendu.

J'ai envoyé des messages d'erreur/d'état pour deux applications distinctes, les deux étant passés par le magasin. C'est la méthode préférée ... Si vous le souhaitez, je peux poster du code pour savoir comment faire cela.

EDIT: Voici comment configurer un système de notification avec React/Redux/TypeScript

Peu de choses à noter en premier .. ceci est dans TypeScript, vous devrez donc supprimer les déclarations de type :)

J'utilise les paquets npm lodash pour les opérations et les noms de classe (alias cx) pour l'affectation de nom de classe en ligne.

La beauté de cette configuration est que j'utilise un identifiant unique pour chaque notification lorsque l'action la crée. (par exemple notify_id). Cet identifiant unique est un Symbol(). De cette façon, si vous souhaitez supprimer une notification à tout moment, vous le pouvez car vous savez laquelle supprimer. Ce système de notification vous permettra d’empiler autant que vous le souhaitez et qu’ils disparaîtront à la fin de l’animation. Je suis connecté à l'événement d'animation et lorsqu'il est terminé, je déclenche un code pour supprimer la notification. J'ai également configuré un délai de repli pour supprimer la notification au cas où le rappel de l'animation ne se déclenche pas.

notification-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);
    };
};

notification-réducteur.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

dans la base rendre pour votre application vous rendrez les notifications

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>
    );
}

user-notification.tsx

classe de notification utilisateur

/*
    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>
        );
    }
}
85
John Ruddell

à la place d'utiliser

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

essayez d'utiliser

ReactDOM.unmountComponentAtNode(document.getElementById('root'));
21
M Rezvani

Dans la plupart des cas, il suffit simplement de masquer l'élément, par exemple:

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>
        );
    }
}

Ou vous pouvez rendre/rendre/ne pas rendre via le composant parent comme ceci

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>
        );
    }
}

Enfin, il existe un moyen de supprimer le noeud HTML, mais je ne sais vraiment pas si c’est une bonne idée. Peut-être que quelqu'un qui connaît React of internal dira quelque chose à ce sujet.

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
Sasha Kos

Je suis allé à ce message environ 10 fois maintenant et je voulais juste laisser mes deux cents ici. Vous pouvez simplement le démonter de manière conditionnelle.

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

Tout ce que vous avez à faire est de le supprimer du DOM afin de le démonter.

Tant que renderMyComponent = true, le composant sera rendu. Si vous définissez renderMyComponent = false, il sera démonté du DOM.

0
ihodonald