Home > Mobile >  How to force a route in React
How to force a route in React

Time:11-15

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

CodePudding user response:

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

CodePudding user response:

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.

CodePudding user response:

onRequestClose and handleClick are defined as function expressions, Function Expression is created when the execution reaches it and is usable only from that moment, but you want to call them on a exact moments so change it like this :

function onRequestClose() {
    setIsOpen(false);
    setCart(GetCart);
    history.push('/')
}

function handleClick() {
    setIsOpen(true)        
}
  • Related