【问题标题】:React Route path not generating desired componentReact Route 路径未生成所需的组件
【发布时间】:2021-03-19 00:40:46
【问题描述】:

正在获取显示产品规格的产品详细信息视图页面,但该组件的视图未显示。它读取正确的路径和产品 ID,因为它显示在 URL 上,但页面本身是空白的。

我认为我的错误是在 PageLayout 部分,但我已经多次重做好几天了,但没有运气。

APP.js:

import React, { Fragment, useState, useEffect } from "react";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";

import "bootstrap/dist/css/bootstrap.min.css";

import NavbarMain from "./components/pagelayout/Navbar/Navbar";
import Footer from "./components/pagelayout/Footer/Footer";
import HomePage from "./components/landingpage/page/HomePage";
import setAuthToken from "./utilities/setAuthToken";
import Login from "./components/auth/Login";
import Register from "./components/auth/Register";
import AccountPage from "./components/pers_account/AccountPage";
import OurStory from "./components/story/OurStory";
import SustainableCode from "./components/sustcode/SustainableCode";
import AdminRoute from "./components/routing/AdminRoute";
import PrivateRoute from "./components/routing/PrivateRoute";
import ProductForm from "./components/forms/ProductForm/ProductForm";
import ProductDisplay from "./components/productdisplay/ProductDisplay";
import PageLayout from "./components/productdetails/layout/PageLayout";
import Alert from "./components/pagelayout/Alert/Alert";

//Redux
import { Provider } from "react-redux";
import store from "./store";
import { loadUser } from "./actions/auth";

if (localStorage.token) {
    setAuthToken(localStorage.token);
}


const App = () => {
    useEffect(() => {
        store.dispatch(loadUser());
    }, []);
    return ( < Provider store = { store } >
        <Router >
        <Fragment >
        <NavbarMain / >
        <Route exact path = "/" component = { HomePage }/> 
        <br/>
        <section className = "container">
        <Alert/>
        <Switch>
            <Route exact path = "/products/items" component = {ProductDisplay}/>
            <Route exact path = "/products/items/:id" component = {PageLayout} />
            <Route exact path = "/account/login"component = { Login }/> 
            <Route exact path = "/account/register"component = { Register }/> 
            <PrivateRoute exact path="/account" component = {AccountPage}/>
            <AdminRoute exact path = "/products/add/sustdrop" component = {ProductForm}/>
            <Route exact path = "/sustdrop/story" component = { OurStory }/>
            <Route exact path = "/sustdrop/sustainablecode" component = {SustainableCode}/>
        </Switch> 
        </section> 
        <Footer/>
        </Fragment>{" "} 
        </Router >
        </Provider>
    );
}

export default App;

Redux 操作:

import {
    GET_PRODUCTS,
    GET_PRODUCT,
    PRODUCT_ERROR,
    CLEAR_SELECTED_PRODUCT,
    ADD_PRODUCT_SUCCESS,
    DONE_LOADING_PRODUCTS,
    DONE_DELETING_PRODUCTS,
} from "./types";

import axios from "axios";
import { setAlert } from "./alert";

// Get all products
export const getProducts = () => async(dispatch) => {
    try {
        const res = await axios.get("/api/products");

        dispatch({
            type: GET_PRODUCTS,
            payload: res.data,
        });
    } catch (err) {
        dispatch({
            type: PRODUCT_ERROR,
            payload: { msg: err.response.statusText, status: err.response.status },
        });
    }
};

// change 'loading' to true
export const doneLoading = () => async(dispatch) => {
    dispatch({
        type: DONE_LOADING_PRODUCTS,
    });
};

// Get a specific product by ID
export const getProduct = (id) => async(dispatch) => {
    try {
        const res = await axios.get(`/api/products/${id}`);

        dispatch({
            type: GET_PRODUCT,
            payload: res.data,
        });
    } catch (err) {
        dispatch({
            type: PRODUCT_ERROR,
            payload: {
                status: err.response.status,
                statusText: err.response.statusText,
            },
        });
    }
};

// clears selected products
export const clearSelectedProduct = () => (dispatch) => {
    dispatch({
        type: CLEAR_SELECTED_PRODUCT,
    });
};

// Add a product
export const addProduct = ({ formData }) => async(dispatch) => {
    const config = {
        headers: {
            "Content-Type": "application/json",
        },
    };

    try {
        const res = await axios.post("/api/products", formData, config);

        dispatch({
            type: ADD_PRODUCT_SUCCESS,
        });

        dispatch(setAlert(res.data.msg, "success"));
    } catch (err) {
        const errors = err.response.data.errors;

        if (errors) {
            errors.forEach((error) => dispatch(setAlert(error.msg, "danger")));
        }
    }
};

// Delete selected product
export const deleteProducts = (products_id) => async(dispatch) => {
    try {
        const res = await axios.delete(`/api/products/${products_id}`);

        dispatch({
            type: CLEAR_SELECTED_PRODUCT,
        });

        dispatch({
            type: DONE_DELETING_PRODUCTS,
        });

        dispatch(setAlert(res.data.msg, "success"));
    } catch (err) {
        dispatch({
            type: PRODUCT_ERROR,
            payload: { msg: err.response.statusText, status: err.response.status },
        });
    }
};

Redux 减速器:

import {
    GET_PRODUCTS,
    GET_PRODUCT,
    PRODUCT_ERROR,
    CLEAR_SELECTED_PRODUCT,
    ADD_PRODUCT_SUCCESS,
    DONE_LOADING_PRODUCTS,
    DONE_DELETING_PRODUCTS,
} from "../actions/types";

const initialState = {
    selectedProduct: null, // latest product you clicked
    products: [], // all the products
    loadingProducts: true,
    error: null,
    loadingSelectedProduct: true,
    isAddingProductsSuccessful: false,
    deletingProducts: false,
};

export default function(state = initialState, action) {
    const { type, payload } = action;

    switch (type) {
        case GET_PRODUCTS:
            return {
                ...state,
                products: payload,
                loadingProducts: false,
                isAddingProductsSuccessful: false,
                deletingProducts: false,
            };
        case DONE_LOADING_PRODUCTS:
            return {
                ...state,
                loadingProducts: true,
            };
        case GET_PRODUCT:
            return {
                ...state,
                selectedProduct: payload,
                loadingSelectedProduct: false,
            };
        case CLEAR_SELECTED_PRODUCT:
            return {
                ...state,
                selectedProduct: null,
                loadingSelectedProduct: false,
            };
        case ADD_PRODUCT_SUCCESS:
            return {
                ...state,
                isAddingProductsSuccessful: true,
            };
        case PRODUCT_ERROR:
            return {
                ...state,
                error: payload,
            };
        case DONE_DELETING_PRODUCTS:
            return {
                ...state,
                deletingProducts: true,
            };
        default:
            return state;
    }
}

产品详情文件夹:

Details.js

import React, { useState } from 'react';
import { withRouter } from "react-router-dom";

// CSS \\
import "./ProductDetails.css";

const ProductDetails = (props) => {
    const { _id, brand, name, description, retail_price} = props.selectedProduct;

    const { deleteProducts } = props;

    return (
        <div className = "details-wrapper">
            <div className = "product-info">
                <h1 className = "product-info-brand">{brand}</h1>
                <p className = "product-info-name">{name}</p>
                <p className = "product-info-desc">{description}</p>
                <p className = "product-info-price"> retail price: {retail_price}</p>
            </div>
            {
                props.isAdmin ? (
                    <>
                        <button className = "productinfo-button btn btn-danger btn-block btn-sm"
                        onClick = {() => deleteProducts(_id)}>Delete Product</button>
                    </>
                ) : null
            }
        </div>
    );
};

export default withRouter(ProductDetails);

产品图片显示规格:

import React, { useState } from "react";

// CSS \\
import "./Images.css";

const Images = (props) => {
    const { images } = props;

    const [mainImage, setMainImage] = useState(images[0]);

    const changeMainImage = (image) => {
        setMainImage(image);
    };

    return ( 
        <div className = "image-wrapper" >
            <div className = "mainrow" >
                <img src = { mainImage } alt = "mainproduct" />
            </div> 
        <div className = "alt-images" >
        <div className = "secondrow" > 
            {images.map((productImage, index) => ( 
                <div key = { index } className = "alternativepics" onClick = {() => changeMainImage(productImage) }>
                    <img src = { productImage } alt = "small_alts" / >
                </div>
            ))
        }
        </div> 
        </div>
        </div>
    );
};

export default Images;

最重要的,详细的页面布局:

layout.js

import React, { useEffect, Fragment } from "react";
import { connect } from "react-redux";
import PropTypes from "prop-types";
import { Redirect } from "react-router-dom";
import { getProducts, getProduct, clearSelectedProduct, deleteProducts } from "../../../actions/product";
import Loading from "../../pagelayout/Loading/Loading";

import ProductDetails from "../details/ProductDetails";
import Images from "../images/Images";

const PageLayout = ({
    getProduct, getProducts, clearSelectedProduct, deleteProducts, match : {
        params: { id },
    },
    product: { selectedProduct, products, deletingProducts, error },
    auth: { isAdmin },
}) => {
    useEffect(() => {
        selectProduct(id);
        getProduct();
    }, []);

    const selectProduct = (id) => {
        clearSelectedProduct();
        getProduct(id);
    };

    if(deletingProducts){
        return <Redirect to = { "/"}/>;
    };

    return selectedProduct === null ? (
        error ? (
            <div>
                <p>{error.msg}</p>
            </div>
        ) : (<Loading/> )
    ) : (
        <Fragment>
            <Images images = { selectedProduct.images}/>
            <ProductDetails selectedProduct = {selectedProduct} isAdmin = {isAdmin} deleteProducts = {deleteProducts}/>
        </Fragment>
    );
};

const mapStateToProps = (state) => ({
    product: state.product,
    auth: state.auth,
});

PageLayout.propTypes= {
    getProduct: PropTypes.func,
    getProducts: PropTypes.func,
    clearSelectedProduct: PropTypes.func,
    deleteProducts: PropTypes.func,
    product: PropTypes.object.isRequired,
    auth: PropTypes.object.isRequired
};

export default connect(mapStateToProps, {
    getProduct, getProducts, clearSelectedProduct, deleteProducts
})(PageLayout);

【问题讨论】:

  • 您好,您将最后一个文件称为layout.js,我们可以假设它的全名确实是./components/productdetails/layout/PageLayout.js,并且您没有引用另一个文件吗?
  • 我很抱歉文件名实际上是 PageLayout.js 而不仅仅是 layout.js。文件路径正确

标签: reactjs redirect redux routes


【解决方案1】:

您应该尝试在PageLayout.js 中添加id 作为效果的输入:

    useEffect(() => {
        selectProduct(id);
        getProduct();
    }, [id]); // <--- instead of just []

如果您只指定一个空输入,则一旦选择了新元素,它就不会改变,而在组件生命周期内只会改变一次。

这个调用也很奇怪:

// in selectProduct:
        clearSelectedProduct();
        getProduct(id);


// in your useEffect:
        selectProduct(id);
        getProduct(); // <- won't that empty getProduct cause an issue ?

我会避免创建selectProduct 函数,而只是在挂钩中展开调用:

    useEffect(() => {
        clearSelectedProduct();
        getProduct(id);
        getProduct(); // propably remove this ?
    }, [id]);

我假设路由工作正常并且您至少触发一次useEffect

【讨论】:

  • 感谢您的快速响应,发出 selectProduct 并传入 id 似乎已经解决了这个问题。非常感谢!
  • 如果答案对您有用,您应该接受它,以便问题显示为已回答@AJ_Omer
猜你喜欢
  • 2019-08-05
  • 1970-01-01
  • 2014-08-17
  • 1970-01-01
  • 2018-02-27
  • 2015-11-24
  • 2016-11-09
  • 1970-01-01
  • 2022-01-21
相关资源
最近更新 更多