React useEffect Hook не вызывается, действие Redux не запускается - PullRequest
0 голосов
/ 30 января 2020

Всякий раз, когда я go на своей странице EditProduct, я вызываю useEffect, чтобы получить продукт с идентификатором match.params.id. Проблема в том, что хук не вызывается. Честно говоря, я не знаю, почему это так или вообще так будет, потому что я называю это так, как это следует называть. Я также получаю сообщение об ошибке типа, в котором говорится, что он не может прочитать имя свойства undefined. Ясно, что если продукт имеет нулевое значение, он не может прочитать его имя. Я также вижу в моих инструментах redux devtools, что никакие действия не запускаются, хотя loadUser - это действие, которое всегда должно запускаться при повторном рендеринге / монтировании сайта, который я настроил в приложении. js.

Я вставлю некоторые из моих кодов ниже и репозиторий Github.

https://github.com/tigerabrodi/eBuy

editProduct компонент

import React, {Fragment, useState, useEffect} from 'react';
import {withRouter} from "react-router-dom";
import {connect} from "react-redux";
import {editProduct, getProduct} from '../../redux/product/product.actions';
import Spinner from '../layout/Spinner';

const EditProduct = ({history, editProduct, match, product: {loading, product}}) => {
    useEffect(() => {
        getProduct(match.params.id);
    }, [getProduct, match.params.id]);

    const [formData,
        setFormData] = useState({name: product.name, description: product.description, price: product.price, image: ""});
    const [showImage, setShowImage] = useState(false);
    const [imageName, setImageName] = useState("");

    const onChangeImage = e => {
        setFormData({...formData, image: e.target.files[0]});
        setShowImage(true);
        setImageName(e.target.files[0].name);
    }

    const onChange = e => setFormData({
        ...formData,
        [e.target.name]: e.target.value
    });

        const onSubmit = e => {
        e.preventDefault();
        editProduct(formData, history, match.params.id);
        }

    const {name, description, price} = formData;

    return (
        <Fragment>
            <div className="container">
                <div className="row">
                {loading && (
                    <Spinner />
                )}
                    <div className="col text-info font-weight-bold m-2">
                    *- All Fields Requried!
                        <form onSubmit={e => onSubmit(e)}>
                        <div className="form-group m-2">
                        <label htmlFor="name">Name</label>
                        <input type="text" placeholder="Enter Products Name" name="name" value={name} onChange={e => onChange(e)} className="form-control" required/>
                        </div>
                        <div className="form-group m-2">
                        <label htmlFor="price">Price</label>
                        <input type="number" name="price" placeholder="Enter Products Price" value={price} onChange={e => onChange(e)}  className="form-control" required/>
                        </div>
                        <div class="custom-file m-2">
                        <input type="file"  onChange={e => onChangeImage(e)}  class="custom-file-input bg-info" required/>
                        <label class="custom-file-label">{showImage ? imageName : "Upload Image"}</label>
                      </div>
                        <div className="form-group m-2">
                        <label htmlFor="title">Description</label>
                        <textarea name="description" onChange={e => onChange(e)} placeholder="Enter Products description" value={description} className="form-control" required/>
                        </div>
                        <input type="submit" value="Add Product" className="btn btn-block btn-info"/>
                        </form>
                    </div>
                </div>
            </div>

        </Fragment>
    );
}

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

export default connect(mapStateToProps, {editProduct})(withRouter(EditProduct));

приложение. js

import './App.css';
import React, {Fragment, useEffect} from 'react';
import {Provider} from "react-redux";
import {BrowserRouter as Router, Route, Switch} from "react-router-dom";
import store from "./redux/store";
import setAuthToken from './utils/setAuthToken';
import { loadUser } from './redux/auth/auth.actions';
import Navbar from './components/layout/Navbar';
import Landing from './components/layout/Landing';
import Alert from './components/layout/Alert';
import Register from './components/auth/Register';
import Login from './components/auth/Login';
import PrivateRoute from './components/routing/PrivateRoute';
import Dashboard from './components/dashboard/Dashboard';
import CreateProduct from './components/product-forms/CreateProduct';
import Products from './components/products/Products';
import EditProduct from './components/product-forms/EditProduct';


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


const App = () => {
  useEffect(() => {
    store.dispatch(loadUser());
  }, []);

  return (  
    <Provider store={store}>
    <Router>
    <Fragment>
    <Navbar />
    <Alert />
    <Route exact path="/" component={Landing} />
    <Switch>
    <Route exact path="/register" component={Register} />
    <Route exact path="/login" component={Login} />
    <PrivateRoute exact path="/dashboard" component={Dashboard} />
    <PrivateRoute exact path="/add-product" component={CreateProduct} />
    <PrivateRoute exact path="/products" component={Products} />
    <PrivateRoute exact path="/products/edit/:id" component={EditProduct} />
    </Switch>
    </Fragment>
    </Router>
    </Provider>
  );
}

export default App;

действия продукта

import {ProductActionTypes} from "./product.types"
import {setAlert} from "../alert/alert.actions"
import axios from "axios"


// Add A Product
export const addProduct = (productData, history) => async dispatch => {
    const formData = new FormData();
    formData.append("name", productData.name);
    formData.append("description", productData.description);
    formData.append("price", productData.price);
    formData.append("image", productData.image);
    try {
        const res = await axios.post("/products", formData);
        dispatch({
            type: ProductActionTypes.ADD_PRODUCT,
            payload: res.data
        });
        history.push("/dashboard");
        dispatch(setAlert("Product created successfully", "success"))
    } catch (err) {
        const errors = err.response.data.errors;
        if (errors) {
          errors.forEach(error => dispatch(setAlert(error.msg, 'danger')));
        }
        dispatch({
        type: ProductActionTypes.PRODUCT_ERROR,
        payload: {msg: err.response.statusText, status: err.response.status}
        })
    }
}

// Get all products
export const getAllProducts = page => async dispatch => {
    try {
        const res = await axios.get(`/products?page=${page}`);
        dispatch({
            type: ProductActionTypes.GET_PRODUCTS,
            payload: {products: res.data.products, totalItems: res.data.totalItems}
        })
    } catch (err) {
        dispatch({
            type: ProductActionTypes.PRODUCT_ERROR,
            payload: err
            })
    }
}

// Delete a product
export const deleteSingleProduct = id => async dispatch => {
    try {
        await axios.delete(`/products/${id}`);
        dispatch({
            type: ProductActionTypes.DELETE_PRODUCT,
            payload: id
        });
        dispatch(setAlert("Product deleted successfully", "success"))
    } catch (err) {
        dispatch({
            type: ProductActionTypes.PRODUCT_ERROR,
            payload: {msg: err.response.statusText, status: err.response.status}
            })
    }
}

// Get A Single users products
export const getUserProducts = (id, page) => async dispatch => {
    try {
        const res = await axios.get(`/products/${id}?page=${page}`);
        dispatch({
            type: ProductActionTypes.GET_PRODUCTS,
            payload: res.data
        })
    } catch (err) {
        dispatch({
            type: ProductActionTypes.PRODUCT_ERROR,
            payload: {msg: err.response.statusText, status: err.response.status}
            })
    }
}

// Edit a Product
export const editProduct = (productData, history, id) => async dispatch => {
    const formData = new FormData();
    formData.append("name", productData.name);
    formData.append("description", productData.description);
    formData.append("price", productData.price);
    formData.append("image", productData.image);
    try {
        const res = await axios.put(`/products/edit/${id}`, formData);
        dispatch({
            type: ProductActionTypes.UPDATE_PRODUCT,
            payload: res.data
        });
        dispatch(setAlert("Product updated successfully", "success"))
        history.push("/dashboard")
    } catch (err) {
        dispatch({
            type: ProductActionTypes.PRODUCT_ERROR,
            payload: {msg: err.response.statusText, status: err.response.status}
            })
    }
}

// Get a single product by ID
export const getProduct = id => async dispatch => {
    try {
        const res = await axios.get(`/products/product/${id}`);
        dispatch({
            type: ProductActionTypes.GET_PRODUCT,
            payload: res.data
        });
    } catch (err) {
        dispatch({
            type: ProductActionTypes.PRODUCT_ERROR,
            payload: {msg: err.response.statusText, status: err.response.status}
            });
    }
}

редуктор продукта

import {ProductActionTypes} from "./product.types";

const initialState = {
    products: [],
    totalProducts: null,
    product: null,
    loading: true,
    error: {}
}

const productReducer = (state = initialState, action) => {
    const {payload, type} = action;
    switch(type) {
        case ProductActionTypes.GET_PRODUCTS:
            return {
                ...state,
                products: payload.products,
                totalProducts: payload.totalItems,
                loading: false
            }
            case ProductActionTypes.GET_PRODUCT:
                return {
                    ...state,
                    product: payload,
                    loading: false
                }
        case ProductActionTypes.ADD_PRODUCT:
            return {
                ...state,
                products: [payload, ...state.products],
                loading: false
            }
            case ProductActionTypes.UPDATE_PRODUCT:
                return {
                    ...state,
                    products: state.products.map(product => product._id === payload.id ? {product: payload.product} : product),
                    loading: false
                }
            case ProductActionTypes.DELETE_PRODUCT:
                return {
                    ...state,
                    products: state.products.filter(product => product._id !== payload),
                    loading: false
                }
            case ProductActionTypes.PRODUCT_ERROR: 
            return {
                ...state,
                error: payload,
                loading: false
            }
        default:
            return state;
    }
}

export default productReducer

действия авторизации

import axios from "axios";
import {setAlert} from "../alert/alert.actions"
import {AuthActionTypes} from "./auth.types"
import setAuthToken from "../../utils/setAuthToken"

// Load User
export const loadUser = () => async dispatch => {
    if (localStorage.token) {
      setAuthToken(localStorage.token);
    }

    try {
      const res = await axios.get('/auth');

      dispatch({
        type: AuthActionTypes.USER_LOADED,
        payload: res.data
      });
    } catch (err) {
      dispatch({
        type: AuthActionTypes.AUTH_ERROR
      });
    }
  };

// Register User
export const register = ({ name, email, password }) => async dispatch => {
    const config = {
      headers: {
        'Content-Type': 'application/json'
      }
    };

    const body = JSON.stringify({ name, email, password });

    try {
      const res = await axios.post('/auth/signup', body, config);

      dispatch({
        type: AuthActionTypes.REGISTER_SUCCESS,
        payload: res.data
      });

      dispatch(loadUser());
    } catch (err) {
      const errors = err.response.data.errors;

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

      dispatch({
        type: AuthActionTypes.REGISTER_FAIL
      });
    }
  };

    // Login User
export const login = (email, password) => async dispatch => {
    const config = {
      headers: {
        'Content-Type': 'application/json'
      }
    };

    const body = JSON.stringify({ email, password });

    try {
      const res = await axios.post('/auth/signin', body, config);

      dispatch({
        type: AuthActionTypes.LOGIN_SUCCESS,
        payload: res.data
      });

      dispatch(loadUser());
    } catch (err) {
      const errors = err.response.data.errors;
      if (errors) {
        errors.forEach(error => dispatch(setAlert(error.msg, 'danger')));
      }

      dispatch({
        type: AuthActionTypes.LOGIN_FAIL
      });
    }
  };

    // Logout / Clear Profile
export const logout = () => dispatch => {
    dispatch({ type: AuthActionTypes.LOGOUT });
  };

редуктор авторизации

import {AuthActionTypes} from "./auth.types";


const initialState = {
    token: localStorage.getItem("token"),
    isAuthenticated: null,
    loading: true,
    user: null
}


const authReducer = (state = initialState, action) => {
    const {type, payload} = action;
    switch (type) {
        case AuthActionTypes.USER_LOADED:
          return {
            ...state,
            isAuthenticated: true,
            loading: false,
            user: payload
          };
        case AuthActionTypes.REGISTER_SUCCESS:
        case AuthActionTypes.LOGIN_SUCCESS:
          localStorage.setItem('token', payload.token);
          return {
            ...state,
            ...payload,
            isAuthenticated: true,
            loading: false
          };
        case AuthActionTypes.REGISTER_FAIL:
        case AuthActionTypes.AUTH_ERROR:
        case AuthActionTypes.LOGIN_FAIL:
        case AuthActionTypes.LOGOUT:
        case AuthActionTypes.ACCOUNT_DELETED:
          case AuthActionTypes.USER_ERROR:
          localStorage.removeItem('token');
          return {
            ...state,
            token: null,
            isAuthenticated: false,
            loading: false
          };
        default:
          return state;
    }
}

export default authReducer

Ответы [ 2 ]

0 голосов
/ 30 января 2020

Мне кажется, проблема в том, что withRouter HO C "заблокирован" connect HO C и не передает данные маршрутизатора вашему компоненту.

Вы должны изменить порядок HOCS (withRouter сначала):

withRouter(connect(mapStateToProps)(EditProduct))
0 голосов
/ 30 января 2020

Я почти уверен, что useEffect срабатывает, вы должны поместить туда лог, и я гарантирую, что вы его увидите.

Что мне кажется, с кодом, который вы разместили, так это вы берете создатель действия , getProduct и вызываете его в крючке - и вы не видите, как он отправляет в магазин, и вы не видите работу, которую он делает огонь. Исходя из этих проблем, это потому, что вы не отправляете действие .

. Вам нужно использовать react-redux s connect, чтобы подключить компонент либо получить dispatch, или предварительно свяжите создателя действия с отправкой, или используйте useDispatch, чтобы получить ссылку на отправку, а затем вам нужно обернуть это dispatch(getProduct(...)).

Не связано, если getDispatch - это состояние c функция (то есть определение никогда не изменится) вам не понадобится, и вы не должны передавать ее как зависимость useEffect. Этот массив зависимостей для ловушек предназначен не для всех вещей, на которые вы ссылаетесь в ловушке, а для всех значений, которые при их изменении должны вызывать повторный запуск ловушки. Другими словами, если вы просто сводите его к идентификатору продукта, то каждый раз, когда компонент отображается с новым идентификатором продукта, этот обработчик эффекта перезапустит функцию. А поскольку getProduct импортируется и никогда не переназначается или не изменяется, то useEffect никогда не будет повторно запускать это значение, поэтому вы можете удалить его из массива зависимостей.

Следуя этому, я рекомендую поставить dispatch в массиве зависимостей, потому что react-redux не отмечает в своей документации, что ссылка на dispatch является stati c, поэтому не следует предполагать, что она не изменится.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...