136 votes

Comment démonter, détricoter 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 plusieurs fois, mais la plupart du temps, la solution est de gérer cela dans le parent, car le flux de responsabilité est uniquement descendant. Cependant, il arrive que l'on ait besoin de tuer un composant à partir de l'une de ses méthodes. Je sais que je ne peux pas modifier ses props, et si je commence à ajouter des booléens comme état, cela va commencer à être vraiment désordonné pour un simple composant. Voici ce que j'essaie de réaliser : Un petit composant de boîte d'erreur, avec un "x" pour l'écarter. La réception d'une erreur à travers ses props l'affichera mais j'aimerais avoir 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); Ce qui génère une belle erreur dans la console :

Attention : unmountComponentAtNode() : Le nœud que vous tentez de démonter a été rendu par React et n'est pas un conteneur de niveau supérieur. Faites plutôt en sorte que le composant parent mette à jour son état et effectue un nouveau rendu afin de supprimer ce composant.

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

0 votes

Utilisez-vous Redux ?

1 votes

Pourquoi est-ce une exigence "Recevoir une erreur à travers ses props l'affichera mais j'aimerais un moyen de la fermer à partir de son propre code." ? L'approche normale consisterait à envoyer une action qui effacerait l'état d'erreur et serait ensuite fermée dans un cycle de rendu du parent, comme vous y avez fait allusion.

0 votes

J'aimerais offrir la possibilité de faire les deux en fait. En effet, il sera possible de le fermer comme vous l'avez expliqué, mais mon cas est "que faire si je veux aussi pouvoir le fermer de l'intérieur".

108voto

John Ruddell Points 6445

Tout comme le bel avertissement que vous avez reçu, vous essayez de faire quelque chose qui est un anti-modèle dans React. C'est un non-non. React est conçu pour qu'un démontage se produise à partir d'une relation parent-enfant. Maintenant, si vous voulez qu'un enfant se démonte lui-même, vous pouvez simuler cela avec un changement d'état dans le parent qui est déclenché par l'enfant. Laissez-moi vous montrer en 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}
    }

}

Il s'agit d'un exemple très simple, mais vous pouvez voir une manière approximative de transmettre une action au parent.

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

J'ai créé des messages d'erreur ou d'état pour deux applications distinctes, qui sont toutes deux passées par le magasin. C'est la méthode préférée... Si vous le souhaitez, je peux poster du code sur la façon de le faire.

EDIT : Voici comment j'ai mis en place un système de notification en utilisant React/Redux/Typescript

Quelques points à noter tout d'abord. Il s'agit d'un script de type, vous devez donc supprimer les déclarations de type :)

J'utilise les paquets npm lodash pour les opérations, et classnames (cx alias) pour l'attribution des noms 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() . Ainsi, si vous souhaitez supprimer une notification à tout moment, vous pouvez le faire car vous savez laquelle supprimer. Ce système de notification vous permettra d'en empiler autant que vous le souhaitez et elles disparaîtront lorsque l'animation sera terminée. Je me connecte à l'événement d'animation et lorsqu'il se termine, je déclenche un code pour supprimer la notification. J'ai également mis en place 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 le rendu de base de votre application, vous rendriez 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>
    );
}

utilisateur-notification.tsx

classe de notification des utilisateurs

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

2 votes

"à travers le magasin" ? Je pense que j'ai manqué quelques leçons cruciales à ce sujet :D Merci pour la réponse et le code mais ne pensez-vous pas que c'est sérieusement exagéré pour un simple composant d'affichage de message d'erreur ? Il ne devrait pas être de la responsabilité du parent de gérer une action définie sur l'enfant...

0 votes

Ce devrait être le parent en fait, puisque le parent est responsable de la mise en place de l'enfant dans le DOM en premier lieu. Comme je le disais, même si c'est un moyen de le faire, je ne le recommande pas. Vous devriez utiliser une action qui met à jour votre magasin. Les deux modèles Flux et Redux devraient être utilisés de cette façon.

0 votes

Ok alors, je serais heureux d'avoir quelques fragments de code si vous le voulez bien. Je reviendrai sur ce morceau de code quand j'aurai lu un peu sur Flux et Reduc !

27voto

M Rezvani Points 131

Au lieu d'utiliser

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

essayez d'utiliser

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

0 votes

Quelqu'un a-t-il essayé cela avec React 15 ? Cela semble à la fois potentiellement utile et peut-être un anti-modèle.

4 votes

@theUtherSide c'est un anti pattern en react. La documentation de React recommande de démonter un enfant du parent via state / props.

1 votes

Que se passe-t-il si le composant qui est démonté est la racine de votre application React mais pas l'élément racine qui est remplacé ? F <div id="c1"><div id="c2"><div id="react-root" /></div></div> . Et si le texte intérieur de c1 est remplacé ?

11voto

Sasha Kos Points 581

Dans la plupart des cas, il suffit de masquer l'élément, par exemple de cette manière :

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 effectuer un rendu/rendu/non rendu 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 nœud html, mais je ne sais vraiment pas si c'est une bonne idée. Peut-être que quelqu'un qui connaît React de l'intérieur pourra dire 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>
        );
    }
}

0 votes

Mais, dans le cas où je veux démonter un enfant qui se trouve dans une liste d'enfants... Que puis-je faire si je veux remplacer un composant cloné avec la même clé dans cette liste ?

1 votes

Si je comprends bien, vous voulez faire quelque chose comme ceci : document.getElementById( CHILD_NODE_ID ) -> .remove() ; -> document.getElementById( PARENT_NODE_ID ) -> .appendChild(NEW_NODE) ? Ai-je raison ? Oubliez cela. Ce n'est PAS une approche react. Utilisez l'état du composant pour le rendu des conditions

2voto

ihodonald Points 328

J'ai lu ce message environ 10 fois et je voulais juste laisser mes deux cents ici. Vous pouvez simplement le démonter de manière conditionnelle.

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

Il suffit de le retirer du DOM pour le démonter.

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

-1voto

nebulousecho Points 286

Ce n'est pas approprié dans toutes les situations, mais vous pouvez conditionnellement return false à l'intérieur du composant lui-même si un certain critère est ou n'est pas rempli.

Il ne démonte pas le composant, mais il supprime tout le contenu rendu. À mon avis, ce serait seulement mauvais si vous avez des écouteurs d'événements dans le composant qui devraient être supprimés lorsque le composant n'est plus nécessaire.

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X