You can just unmount it conditionally. All you have to do is remove it from the DOM in order to unmount it. As long as renderMyComponent = true , the component will render. If you set renderMyComponent = false , it will unmount from the DOM.
The main job of React is to figure out how to modify the DOM to match what the components want to be rendered on the screen. React does so by "mounting" (adding nodes to the DOM), "unmounting" (removing them from the DOM), and "updating" (making changes to nodes already in the DOM).
React has a top-level API called unmountComponentAtNode() that removes a component from a specific container. The function unmountComponentAtNode() takes an argument as a container from which the specific component should be removed. Below is the basic syntax of the function unmountComponentAtNode() .
The componentWillUnmount() method allows us to execute the React code when the component gets destroyed or unmounted from the DOM (Document Object Model). This method is called during the Unmounting phase of the React Life-cycle i.e before the component gets unmounted.
Just like that nice warning you got, you are trying to do something that is an Anti-Pattern in React. This is a no-no. React is intended to have an unmount happen from a parent to child relationship. Now if you want a child to unmount itself, you can simulate this with a state change in the parent that is triggered by the child. let me show you in 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}
}
}
this is a very simple example. but you can see a rough way to pass through to the parent an action
That being said you should probably be going through the store (dispatch action) to allow your store to contain the correct data when it goes to render
I've done error/status messages for two separate applications, both went through the store. It's the preferred method... If you'd like I can post some code as to how to do that.
Few things to note first. this is in typescript so you would need to remove the type declarations :)
I am using the npm packages lodash for operations, and classnames (cx alias) for inline classname assignment.
The beauty of this setup is I use a unique identifier for each notification when the action creates it. (e.g. notify_id). This unique ID is a Symbol()
. This way if you want to remove any notification at any point in time you can because you know which one to remove. This notification system will let you stack as many as you want and they will go away when the animation is completed. I am hooking into the animation event and when it finishes I trigger some code to remove the notification. I also set up a fallback timeout to remove the notification just in case the animation callback doesn't fire.
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);
};
};
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;
};
in the base render for your application you would render the 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 class
/*
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>
);
}
}
instead of using
ReactDOM.unmountComponentAtNode(ReactDOM.findDOMNode(this).parentNode);
try using
ReactDOM.unmountComponentAtNode(document.getElementById('root'));
In most cases, it is enough just to hide the element, for example in this way:
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) }>×</a>
</div>
);
}
}
Or you may render/rerender/not render via parent component like this
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) }>×</a>
</div>
);
}
}
Finally, there is a way to remove html node, but i really dont know is it a good idea. Maybe someone who knows React from internal will say something about this.
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) }>×</a>
</div>
);
}
}
I've been to this post about 10 times now and I just wanted to leave my two cents here. You can just unmount it conditionally.
if (renderMyComponent) {
<MyComponent props={...} />
}
All you have to do is remove it from the DOM in order to unmount it.
As long as renderMyComponent = true
, the component will render. If you set renderMyComponent = false
, it will unmount from the DOM.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With