React-Router Redirect from a portal not redirecting

1.1k Views Asked by At

I'm working on a Twitch clone application for React practice, and I'm in the process of creating a modal popup for when users want to delete a video stream. Basically they have a list of streams, they click a delete button on one of the streams, and that opens a modal using ReactDOM.createPortal

Here is the delete button

import React from "react";

import Modal from "../Modal";

const StreamDelete = () => {

  return (
    <>
      <Modal />
    </>
  );
};

export default StreamDelete;

Here is the code for the Modal

import React from "react";
import ReactDOM from "react-dom";
import { Redirect } from "react-router-dom";

const Modal = () => {

  
  return ReactDOM.createPortal(
    <div
      className="ui dimmer modals visible active"
      onClick={ () => {
        return <Redirect to="/" />;
      } }
    >
      <div className="ui standard modal visible active">
        <div className="header">Delete Stream</div>
        <div className="content">
          Are you sure you want to delete this stream?
        </div>
        <div className="actions">
          <button className="ui primary button">Delete</button>
          <button className="ui button">Cancel</button>
        </div>
      </div>
    </div>,
    document.querySelector("#modal")
  );
};

export default Modal;

how the modal looks

This modal can only be viewed on the React-Router Route /streams/:id/delete

My expected behavior is that when I click on the dark background my onClick function there should return the <Redirect to="/"> which should therefore close the Modal window, because it is not being rendered in that route.

The behaviour that i'm getting is that clicking on the dark background is not redirecting, although i'm not receiving any errors either.

For additional context the GIT repo for this project is here Glitch Client

And I have an extensive set of notes on all parts of this project so far here Ncoughlin: Tag Twitch Clone

2

There are 2 best solutions below

0
On

Since StreamDelete is directly rendered by a Route component

<Route path="/streams/:id/delete" exact component={StreamDelete} />

Solution

It is passed route props, and thusly has access to the history prop. You can consume this in StreamDelete and pass a callback to the modal to take the action you want.

const StreamDelete = ({ history }) => {
  const doRedirect = () => history.replace("/");
  return (
    <>
      <Modal onDelete={doRedirect} />
    </>
  );
};

Modal

const Modal = ({ onDelete }) => {
  return ReactDOM.createPortal(
    <div
      className="ui dimmer modals visible active"
      onClick={onDelete}
    >
      <div className="ui standard modal visible active">
        <div className="header">Delete Stream</div>
        <div className="content">
          Are you sure you want to delete this stream?
        </div>
        <div className="actions">
          <button className="ui primary button">Delete</button>
          <button className="ui button">Cancel</button>
        </div>
      </div>
    </div>,
    document.querySelector("#modal")
  );
};

Alternative

Declarative Implementation if you want to avoid using the history prop for some reason.

Use some "redirect" state in the modal conditionally render a Redirect.

const Modal = () => {
  const [redirect, setRedirect] = useState(false);

  if (redirect) {
    return <Redirect to="/" />;
  }
  
  return ReactDOM.createPortal(
    <div
      className="ui dimmer modals visible active"
      onClick={() => setRedirect(true)}
    >
      <div className="ui standard modal visible active">
        <div className="header">Delete Stream</div>
        <div className="content">
          Are you sure you want to delete this stream?
        </div>
        <div className="actions">
          <button className="ui primary button">Delete</button>
          <button className="ui button">Cancel</button>
        </div>
      </div>
    </div>,
    document.querySelector("#modal")
  );
};

Suggested Alternative

Don't couple app behavior with presentational (i.e. Modal) components. Conditionally render a Redirect in StreamDelete upon some "confirmation" from the modal.

const StreamDelete = () => {
  const [confirm, setConfirm] = useState(false);
  
  return confirm ? (
    <Redirect to="/" />
  ) : (
    <Modal onConfirm={() => setConfirm(true)} />
  );
};
0
On

I was also able to get this to function by following the example in the article linked above. Convert to component, create a state, use the click handler to trigger a helper function that set's state to conditionally render the redirect.

import React from "react";
import ReactDOM from "react-dom";
import { Redirect } from "react-router-dom";

class Modal extends React.Component {
  state = {
    toDashboard: false,
  }

  handleClick = () => {
    this.setState(() => ({
      toDashboard: true
    }))
  }


  render() {
    if (this.state.toDashboard === true) {
      return <Redirect to='/' />
    }

    return ReactDOM.createPortal(
      
      <div
        className="ui dimmer modals visible active"
        onClick={ this.handleClick }
      >
        <div className="ui standard modal visible active">
          <div className="header">Delete Stream</div>
          <div className="content">
            Are you sure you want to delete this stream?
          </div>
          <div className="actions">
            <button className="ui primary button">Delete</button>
            <button className="ui button">Cancel</button>
          </div>
        </div>
      </div>,
      document.querySelector("#modal")
    );
  }
  
};

export default Modal;

This technique was explained in this article ui.dev : programmatic navigation