Randy
Randy

Reputation: 1287

How to force a route in React

I am trying to follow this SO Answer to "go home" after I close my Modal...

import React, { Suspense, useState } from 'react';
import { BrowserRouter, Route, Switch, useHistory } from "react-router-dom";
import './App.css';
import { Button } from 'reactstrap';
import Slideshow from './components/Slideshow.js';
import LogoHeader from './components/LogoHeader';
import Login from "./components/Login/Login.js";
import ChangePassword from "./components/Login/ChangePassword.js";
import useToken from "./components/useToken";
import NewFooter from "./components/NewFooter";
import GetCategories from "./components/GetCategories";
import { formatter } from './components/common.js'
import { GetCart, Increment, Decrement } from './components/ShoppingCart/CartHandler.js'
import CheckoutModal from './components/ShoppingCart/CheckoutModal.js'

const FeaturedCards = React.lazy(() => import('./components/featuredCards'));
const ProdCard = React.lazy(() => import('./components/productCards'));
const Portal = React.lazy(() => import('./components/Portal/portal'));

function App() {    
    const { token, setToken } = useToken();
    const [cart, setCart] = useState(GetCart());
    const [isOpen, setIsOpen] = useState(false);
    const history = useHistory();

    if (!token) {
        return <Login setToken={setToken} />
    }
    
    if (token.forcePasswordChange === true) {
        return <ChangePassword setToken={setToken} />
    }

    function handleIncrementClick(item) {
        Increment(item.ItemNumber, cart);
        setCart(GetCart);
    }

    function handleDecrementClick(item) {
        Decrement(item.ItemNumber, cart);
        setCart(GetCart);
    }

    let onRequestClose = () => {
        setIsOpen(false);
        setCart(GetCart);
        history.push('/')
    }

    let handleClick = () => {
        setIsOpen(true)        
    }

    return (
        <div className="App">
            <BrowserRouter>
                <p className="greeting">Hello, {token.firstName}</p>
                <LogoHeader />
                <GetCategories />                
                <Switch>
                    <Route path="/cart">
                        <Suspense fallback={<div>Loading...</div>}>
                            <div>
                                <table className="table table-striped table-bordered">
                                    <thead>
                                        <tr>
                                            <th className="number">Item Number</th>
                                            <th className="price">Price</th>
                                            <th className="quantity">Quantity</th>
                                        </tr>
                                    </thead>
                                    <tbody>
                                        {cart.map(item =>
                                            <tr key={item.ItemNumber}>
                                                <td>{item.ItemNumber}</td>
                                                <td>{formatter.format(item.Price)}</td>
                                                <td>{item.Quantity}</td>
                                                <td><Button className="BtnIncrement" onClick={() => handleIncrementClick(item)}>{'+'}</Button></td>
                                                <td><Button className="BtnDecrement" onClick={() => handleDecrementClick(item)}>{'-'}</Button></td>
                                            </tr>
                                        )}
                                    </tbody>
                                </table>
                                <Button onClick={() => handleClick()}>Checkout</Button>
                                {isOpen ? <CheckoutModal onRequestClose={onRequestClose} /> : null}
                            </div>
                        </Suspense>
                    </Route>
                    <Route path="/portal">
                        <Suspense fallback={<div>Loading...</div>}>
                            <Portal />
                        </Suspense>
                    </Route>
                    <Route path="/">
                        <Slideshow id="slideshow" />
                        <div id="productContainer">
                            <br />
                            <h3>Featured Products</h3>
                            <br />
                            <Suspense fallback={<div>Loading...</div>}>
                                
                                <FeaturedCards setCartFunc={setCart} />
                                
                            </Suspense>
                            <br />
                            <h3>Most Popular</h3>
                            <br />
                            <Suspense fallback={<div>Loading...</div>}>
                                <ProdCard />
                            </Suspense>
                            <br />
                            <h3>New Products</h3>
                            <br />
                            <Suspense fallback={<div>Loading...</div>}>
                                <ProdCard />
                            </Suspense>
                        </div>
                    </Route>                               
                </Switch>
                <NewFooter />
            </BrowserRouter>
        </div>
    );
}
export default App;

UPDATE

As requested, my CheckoutModal:

import React from 'react';
import "./Checkout.scss"
import AddressBlock from './AddressBlock';
import PaymentBlock from './PaymentBlock';
import ShippingBlock from './ShippingBlock';
import TotalsBlock from './TotalsBlock';
import { Modal, ModalHeader, ModalBody } from 'reactstrap';

class CheckoutModal extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            name: 'React',
            shippingMethod: null
        };
    }    

    setShippingMethod(method) {
        this.setState(state => ({ ...state, shippingMethod: method }));
    }

    render() {
        const buttonStyles = {
            position: "absolute",
            top: "-35px",
            right: "10px",
            border: "0",
            background: "none"
        };        

        return (
            <Modal id="checkout"
                isOpen
                size='xl'>
                <button onClick={this.props.onRequestClose} style={buttonStyles}>x</button>
                <ModalHeader className="header">
                    Checkout                    
                </ModalHeader>
                <ModalBody>                    
                    <div className="row">
                        <AddressBlock />
                        <ShippingBlock setShippingMethod={this.setShippingMethod.bind(this)} shippingMethod={this.state.shippingMethod}/>
                    </div>
                    <div className="row">
                        <PaymentBlock onRequestClose={this.props.onRequestClose} />
                        <TotalsBlock />
                    </div>
                </ModalBody>
            </Modal>
        );
    }
}
export default CheckoutModal;

I am getting history is undefined from my function onRequestClose. I tried prepending this but that didn't work. Is this because App.js is not a class component or rather just a function? Any tips or suggestions would be greatly appreciated.

nodejs 16.13.0

react-router-dom 8.1.2

Upvotes: 2

Views: 784

Answers (2)

Drew Reese
Drew Reese

Reputation: 202608

Issue

You are using/calling the useHistory hook from App but App is the very component rendering the BrowserRouter that provides the routing context to the app, including a valid history object reference.

Solution

Move the BrowserRouter out of App, likely into the index.js where you are rendering App. This is to provide a valid routing context for App and all other components in the App's subtree to access.

Example index.js

...
import { BrowserRouter } from "react-router-dom";
...

const rootElement = document.getElementById("root");
ReactDOM.render(
  <StrictMode>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </StrictMode>,
  rootElement
);

App

function App() {    
  const { token, setToken } = useToken();
  const [cart, setCart] = useState(GetCart());
  const [isOpen, setIsOpen] = useState(false);
  const history = useHistory(); // <-- will now be defined

  ...

  const onRequestClose = () => {
    setIsOpen(false);
    setCart(GetCart);
    history.push('/'); // <-- should navigate now
  }

  ...

  return (
    <div className="App">
      <p className="greeting">Hello, {token.firstName}</p>
      <LogoHeader />
      <GetCategories />                
      <Switch>
        <Route path="/cart">
          <Suspense fallback={<div>Loading...</div>}>
            <>
              <table className="table table-striped table-bordered">
                ...
              </table>
              <Button onClick={handleClick}>Checkout</Button>
              {isOpen && <CheckoutModal onRequestClose={onRequestClose} />}
            </>
          </Suspense>
        </Route>
        ...                               
      </Switch>
      <NewFooter />
    </div>
  );
}

Upvotes: 2

Santheepkumar
Santheepkumar

Reputation: 179

Using useRouter in App.js will not work. Becourse its outside the browser router. BrowserRouter wrapped components can only able to use useRouter. Hope this is helpfull.

Upvotes: 0

Related Questions