Hoe een component van zichzelf te ontkoppelen, te de-renderen of te verwijderen in een React/Redux/Typescript notificatiebericht

Ik weet dat deze vraag al een paar keer is gesteld, maar meestal is de oplossing om dit bij de ouder af te handelen, omdat de stroom van verantwoordelijkheid alleen maar afneemt. Soms moet u echter een component van een van zijn methoden doden.
Ik weet dat ik de rekwisieten niet kan wijzigen, en als ik booleans begin toe te voegen als de staat, wordt het erg rommelig voor een eenvoudig onderdeel. Dit is wat ik probeer te bereiken:
Een kleine foutboxcomponent, met een “x” om deze te negeren. Als ik een fout ontvang via de rekwisieten, wordt deze weergegeven, maar ik zou graag een manier willen om deze vanuit zijn eigen code te sluiten.

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;

En ik zou het als volgt gebruiken in de bovenliggende component:

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

In de sectie Wat moet ik hier plaatsen?heb ik het al geprobeerd:

ReactDOM.unmountComponentAtNode(ReactDOM.findDOMNode(this).parentNode);
Wat een mooie fout in de console gooit:

Waarschuwing: unmountComponentAtNode(): het knooppunt dat u probeert te ontkoppelen, is weergegeven door React en is geen container op het hoogste niveau. Laat in plaats daarvan de bovenliggende component zijn status bijwerken en opnieuw weergeven om deze component te verwijderen.

Moet ik de binnenkomende rekwisieten kopiëren in de ErrorBox-status en deze alleen intern manipuleren?


Antwoord 1, autoriteit 100%

Net als die aardige waarschuwing die je kreeg, probeer je iets te doen dat een antipatroon is in React. Dit is een nee-nee. React is bedoeld om een ​​ontkoppeling te laten plaatsvinden van een ouder-op-kind relatie. Als u nu wilt dat een kind zichzelf ontkoppelt, kunt u dit simuleren met een statusverandering in de ouder die door het kind wordt geactiveerd. laat het me je in code zien.

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

dit is een heel eenvoudig voorbeeld. maar je kunt een ruwe manier zien om een ​​actie door te geven aan de ouder

Dat gezegd hebbende, zou u waarschijnlijk door de winkel moeten gaan (verzendactie) zodat uw winkel de juiste gegevens kan bevatten wanneer deze wordt weergegeven

Ik heb fout-/statusberichten gedaan voor twee afzonderlijke applicaties, beide zijn door de winkel gegaan. Het is de voorkeursmethode… Als je wilt, kan ik wat code posten hoe je dat moet doen.

EDIT: Hier is hoe ik een meldingssysteem opzet met React/Redux/Typescript

Eerst een paar dingen om op te merken. dit is in typoscript, dus je zou de typedeclaraties moeten verwijderen 🙂

Ik gebruik de npm-pakketten lodash voor bewerkingen en klassenamen (cx-alias) voor inline toewijzing van klassennamen.

Het mooie van deze opstelling is dat ik een unieke identificatie gebruik voor elke melding wanneer de actie deze maakt. (bijv. melding_id). Deze unieke ID is een Symbol(). Op deze manier kan je, als je een melding op elk moment wilt verwijderen, omdat je weet welke je moet verwijderen. Met dit meldingssysteem kun je er zoveel stapelen als je wilt en ze verdwijnen als de animatie is voltooid. Ik haak aan bij de animatie-gebeurtenis en wanneer deze is voltooid, activeer ik een code om de melding te verwijderen. Ik heb ook een fallback-time-out ingesteld om de melding te verwijderen voor het geval de animatie-callback niet wordt geactiveerd.

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

in de basisweergave voor uw toepassing zou u de meldingen weergeven

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

klasse voor gebruikersmeldingen

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

Antwoord 2, autoriteit 24%

in plaats van

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

probeer

. te gebruiken

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

Antwoord 3, autoriteit 10%

In de meeste gevallen is het voldoende om het element te verbergen, bijvoorbeeld op deze manier:

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

Of u kunt op deze manier renderen/opnieuw weergeven/niet weergeven via bovenliggende component

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

Eindelijk is er een manier om html-knooppunten te verwijderen, maar ik weet echt niet of het een goed idee is. Misschien dat iemand die React van internal kent hier iets over kan zeggen.

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

Antwoord 4, autoriteit 3%

Ik ben nu ongeveer 10 keer naar dit bericht geweest en ik wilde hier even mijn twee cent achterlaten. Je kunt het gewoon voorwaardelijk ontkoppelen.

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

Het enige wat je hoeft te doen is het uit de DOM te verwijderen om het te ontkoppelen.

Zolang renderMyComponent = true, wordt de component weergegeven. Als u renderMyComponent = falseinstelt, wordt het ontkoppeld van de DOM.


Antwoord 5

Dit is niet in alle situaties geschikt, maar u kunt voorwaardelijk return falseretourneren binnen de component zelf als aan een bepaald criterium wel of niet wordt voldaan.

Het ontkoppelt de component niet, maar verwijdert alle weergegeven inhoud. Dit zou naar mijn mening alleen maar slecht zijn als je gebeurtenislisteners in de component hebt die moeten worden verwijderd wanneer de component niet langer nodig is.

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

Other episodes