简体   繁体   中英

How do I get the updated state from Redux using useEffect

I'm making a MERN stack online store website and I'm fetching my products from a useEffect hook in my Shoes.js component. But I'm only getting the initial state from redux instead of the updated state.

The data is being fetched just fine but I can only access the initial state. So the values being passed to the ProductsArea component are false and null How do I get the updated state?

Here's my Shoes.js file:

import React, { useEffect } from 'react';
import './Shoes.css';
import { Link } from 'react-router-dom';
import { connect } from 'react-redux';


import { getProducts } from '../../../actions/productsActions';
import ProductsArea from './ProductsArea';
import Navbar from '../landing/Navbar';
import Search from './Search';
export const Shoes = (props) => {

  useEffect(() => {
    props.getProducts();

    console.log(props.products);
    console.log(props.loading);
  }, []); 
  if(props.loading) {
    return (
      <h1>loading</h1>
    )
  }

  else {
    return (
      <div>
        <Navbar />
        <div className="shoes">
          <Search />
          <h1 className="productsTitle">Our Selection</h1>
            <ProductsArea loading={props.loading} products={props.products} /> 
            {/* {
              props.products.map(product => (
                <ProductCard key={product._id} product={product} />
              ))
            } */}
        </div>
      </div>
    )
  }
}

const mapStateToProps = state => ({
  products: state.products.products,
  loading: state.products.loading
})

export default connect(mapStateToProps, { getProducts })(Shoes);

Here's my productsActions file

import {GET_PRODUCTS, SET_LOADING, SET_ERROR} from './types';

export const getProducts = () => async (dispatch) => {
  try{
    setLoading();
    const res = await fetch('http://localhost:5000/products');
    const data = await res.json();
    console.log(data);
    dispatch({
      type: GET_PRODUCTS,
      payload: data
    });
  }
  catch(err) {
    dispatch({
      type: SET_ERROR,
      payload: err
    })
  }
}

export const setLoading = () => {
  console.log('Loading true');
  return {
    type: SET_LOADING
  }
}

This is the getProductsReducer file:

import {GET_PRODUCTS, SET_LOADING, SET_ERROR} from '../actions/types';
const initialState = {
  products: [],
  loading: false,
  error: null
}

export default (state = initialState, action) => {
  switch (action.type) {
    case GET_PRODUCTS: 
    console.log(action.payload);
      return {
        ...state,
        products: action.payload,
        loading: false
      }

    case SET_LOADING: 
      return {
        ...state,
        loading: true
      };

    case SET_ERROR: 
      console.log(action.payload);
      return {
        ...state,
        error: action.payload
      };
    default: return state;
  }
}

Here's my index.js file for redux:

import {combineReducers} from 'redux';
import getProductReducer from './getProductReducer';

export default combineReducers({
  products: getProductReducer
});

And the Store.js file:

import { createStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import thunk from 'redux-thunk';
import rootReducer from './reducers';

const initialState = {};

const middleware = [thunk];

const store = createStore(rootReducer, initialState, composeWithDevTools(applyMiddleware(...middleware)));

export default store;

So I checked the redux extension and the state is showing up on my Home.js page but not on the Shoes.js file

Here's the Home.js file:

import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import { getProducts, setLoading } from '../../../actions/productsActions';
import { connect } from 'react-redux';
import {Link} from 'react-router-dom';
import './Home.css';
import Navbar from './Navbar';
export const Home = (props) => {

  useEffect(() => {
    props.setLoading();
    props.getProducts();
    //eslint-disable-next-line
    console.log(props.products);
    console.log(props.loading);
  }, []); 
  if(props.loading) {
    return <div>loading</div>
  }
  else {
  return (
      <div>
        <Navbar />
        <div className="home">
          <div className="group-1">
            <div className="branding">
              <div className="brandName">
                The
                <br/>
                Sole
                <br/>
                Store
              </div>
              <div>
                  <p>The finest designs and fits.</p>
              </div>
            </div>
            <div className="viewProducts">
              <div>
                <p>
                  Check out our latest and greatest models
                </p>
                <Link className="productsBtn" to="/shoes">GO <i className="fas fa-arrow-right"/></Link>
              </div>
            </div>
          </div>
          <div className="group-2">
            <div className="products">
              <div className="product"></div>
              <div className="product"></div>
              <div className="product"></div>
              <div className="product"></div>
            </div>
            <div className="something"></div>
          </div>
        </div>
      </div>
    )
  }
}

Home.propTypes = {
  products: PropTypes.object.isRequired,
  loading: PropTypes.bool.isRequired
}

const mapStateToProps = state => ({
  products: state.products.products,
  loading: state.products.loading
});

export default connect(mapStateToProps, {getProducts, setLoading})(Home);

Although, I'm still only getting the initial state and not the updated state in the console from Home.js too.

I've made the changes that @Kalhan.Toress suggested and this is the updated Shoes.js file

import React, { useEffect } from 'react';
import './Shoes.css';
// import { Link } from 'react-router-dom';
import { connect } from 'react-redux';

import { getProducts } from '../../../actions/productsActions';
import ProductsArea from './ProductsArea';
import Navbar from '../landing/Navbar';
import Search from './Search';
export const Shoes = (props) => {

  useEffect(() => {  
    props.fetchData();
    console.log(JSON.parse(props.products.products));
  }, []); 

  if(props.loading) {
    return (
      <h1>loading</h1>
    )
  }

  else {
    return (
      <div>
        <Navbar />
        <div className="shoes">
          <Search />
          <h1 className="productsTitle">Our Selection</h1>
            <ProductsArea loading={props.loading} products={JSON.parse(props.products.products)} /> 
            {/* {
              props.products.map(product => (
                <ProductCard key={product._id} product={product} />
              ))
            } */}
        </div>
      </div>
    )
  }
}

const mapDispatchToProps = dispatch => {
  return {
    fetchData: () => dispatch(getProducts())
  };
};

const mapStateToProps = state => ({
  products: state.products.products,
  loading: state.products.loading
})

export default connect(mapStateToProps, mapDispatchToProps)(Shoes);

I can click on the link to the Shoes page from Home and everything works perfectly, but as soon as I reload the Shoes.js page or go to it directly, this is the error I get: Error: A cross-origin error was thrown. React doesn't have access to the actual error object in development.

This is my App.js file for the server side where I do have CORS enabled:

const express = require('express');
const app = express();
const bodyParser = require('body-parser')
const productRoute = require('./products/productRoute');
const orderRoute = require('./orders/orderRoute');
const userRoute = require('./users/userRoute');
const adminRoute = require('./admins/adminRoute');
app.use((req, res, next) => {
    res.header('Access-Control-Allow-Origin','*');
    res.header('Access-Control-Allow-Headers','Origin, X-Requested-With, Content-Type, Authorization, Accept');
    if(res.method === 'OPTIONS') {
        res.header('Access-Control-Allow-Methods', 'GET, PUT, POST, PATCH, DELETE');
    }
    next();
});
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));

app.use('/products', productRoute);
app.use('/orders', orderRoute);
app.use('/users', userRoute);
app.use('/admin', adminRoute);


app.use((req, res, next) => {
    const error = new Error();
    error.status = 404;
    next(error);
});

app.use((error, req, res, next) => {
    res.status(error.status || 500 ).json({
        error: error
    })
});

module.exports = app;

I'd really appreciate any help! Thank you!

I think the way you dispatch the sync action is incorrect

by invoking props.getProducts(); it will return a sync function, that's will not trigger any dispatch action as i see

const getProducts = () => async (dispatch) => {
  try{
  ....

to make sure it put a console.log as below and check it

useEffect(() => {
    const returnedFromAction = props.getProducts();
    console.log(returnedFromAction); // this should prints the returned function and it will not get dispatched
    ....
}

Here you need to dispatch a sync action by by executing returning function as below

You have to add a mapDispatchToProps as below

....
const mapDispatchToProps = dispatch => {
  return {
    fetchData: () => dispatch(getProducts())
  };
};    

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(App);

and then inside the useEffect use this fetchData function to dispatch the fetch action so now in useEffect

useEffect(() => {
  props.fetchData();
}, []);

That will do the job for you, i have created a sample demo for you, check it out here

This will align with your approach by not using redux hooks , but theres another way that you can easily do as below.


import { useDispatch } from 'react-redux'; // import the dispatcher

const App = props => {
  const dispatch = useDispatch(); // get a reference to dispatch
  useEffect(() => {
    dispatch(getProducts()); // dispatch the action
  }, []);

see it in here

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM