How do I get the updated state from Redux using useEffect - reactjs

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

Related

Is there a solution for the map function not properly checking the elements from updated array (of cart) in my react-redux and redux toolkit store?

I add product to the cart from Products and product gets added to the cart (as each product increases length of array), but when rendering the updated cart from Cart component, it doesn't render properly and console keeps giving error "Warning: Each child in a list should have a unique "key" prop.
Check the render method of Cart. See https://reactjs.org/link/warning-keys for more information.
div".
I did various methods to go over the solution, but just figured out probably the product element's (from productsinCart array) payload is coming as undefined that is what giving problem, but I may be wrong here.
Any suggestions to solve the problem?
cartSlice.js
import { createSlice } from "#reduxjs/toolkit";
const cartSlice = createSlice({
name: "cart",
initialState: [],
reducers: {
add(state, action) {
state.push(action.payload);
},
remove(state, action) {
state.splice(action.payload, 1);
},
},
});
`
export const { add, remove } = cartSlice.actions;
export default cartSlice.reducer;
store.js
import { configureStore } from "#reduxjs/toolkit";
import cartReducer from "./Slices/cartSlice";
const store = configureStore({
reducer: {
cart: cartReducer,
},
});
export default store;
Products.js
import React, { useState, useEffect } from "react";
import { useDispatch } from "react-redux";
import { add } from "../store/Slices/cartSlice";
const Products = () => {
const [products, setProducts] = useState([]);
const dispatch = useDispatch();
useEffect(() => {
const fetchProducts = async () => {
const url = "https://fakestoreapi.com/products";
try {
const response = await fetch(url);
const result = await response.json();
setProducts(result);
} catch (error) {
console.log("Sorry, there is an error");
}
};
fetchProducts();
}, []);
const handleAdd = (product) => {
dispatch(add(product));
};
return (
<div>
{products?.map((product) => {
const { id, image, price, title } = product;
return (
<div key={id}>
<img src={image} alt="" />
<h4>{title}</h4>
<h5>{price}</h5>
<button
onClick={() => {
handleAdd(add());
}}
>
Add to Cart
</button>
</div>
);
})}
</div>
);
};
export default Products;
Cart.js
import React from "react";
import { useSelector } from "react-redux";
import Layout from "../components/Layout/Layout";
const Cart = () => {
const productsinCart = useSelector((state) => {
return state.cart;
});
return (
<Layout>
<h3>Cart </h3>
<section>
<div>
{productsinCart?.map((product) => {
const { id, image, price, title } = product;
return (
<div key={id}>
<img src={image} alt="" />
<p>{title}</p>
<p>{price}</p>
<button>Remove</button>
</div>
);
})}
</div>
</section>
</Layout>
);
};
export default Cart;
Note: I only tried adding the product and rendering the updated cart.

Redux Toolkit dispatches twice on page load and page change

I have several pages in my React app. Every thime when a user clicks on a page3 I want to send a fetch request to get the data and set it to the store. But for some reason Redux Toolkit dispatches twice on app load and page3 load as well. So everytime two fetch requests are made when a page loads or user switches between page3 and other pages.
How to fix that?
App.tsx
import { NavLink, Route, Routes } from "react-router-dom";
import "./styles.css";
import Todo from "./Todo";
const App = () => {
const routes = [
{
path: "/",
element: <div className={"page1"}>Page 1</div>
},
{
path: "/page2",
element: <div className={"page2"}>Page 2</div>
},
{
path: "/page3",
element: <Todo />
}
];
return (
<div className="App">
<div className="Navigation">
<NavLink to={"/"}>Page 1</NavLink>
<NavLink to={"/page2"}>Page 2</NavLink>
<NavLink to={"/page3"}>This sends a request</NavLink>
</div>
<div className="Content">
<h2>Content</h2>
<Routes>
{routes.map((route: any) => (
<Route path={route.path} element={route.element} key={route.path} />
))}
</Routes>
</div>
</div>
);
};
export default App;
Todo component
import { useEffect } from "react";
import { useDispatch } from "react-redux";
import { AppDispatch } from "./store/store";
import { fetchTodo } from "./store/TodoSlice";
const Todo: React.FC = () => {
const dispatch = useDispatch<AppDispatch>();
useEffect(() => {
dispatch(fetchTodo());
}, []);
return <div className={"page3"}>This dispatches twice</div>;
};
export default Todo;
Store slice
import { createAsyncThunk, createSlice } from "#reduxjs/toolkit";
export interface ICategoryState {
todo: any[];
isLoading: boolean;
}
const initialState: ICategoryState = {
todo: [],
isLoading: false
};
const BASE_URL = "https://jsonplaceholder.typicode.com/todos";
export const fetchTodo = createAsyncThunk("todos/fetchTodo", async () => {
const response = await fetch(BASE_URL);
const data = await response.json();
return data as any[];
});
export const todoSlice = createSlice({
name: "todos",
initialState,
reducers: {},
extraReducers: (builder) => {
builder.addCase(fetchTodo.pending, (state, action) => {
state.isLoading = true;
console.log("Start loading");
});
builder.addCase(fetchTodo.fulfilled, (state, { payload }) => {
state.todo = payload;
state.isLoading = false;
});
builder.addCase(fetchTodo.rejected, (state, { payload }) => {
console.log("Error happened");
state.isLoading = false;
});
}
});
export default todoSlice.reducer;
Here's a codesandbox as well: https://codesandbox.io/s/ecstatic-cookies-xicjxp?file=/src/App.tsx
By default CRA creates apps running in strict mode which causes useEffects with empty dependencies array to run twice on load.
It is supposed to help people writing better react code.
This only happens in development mode of course.
Just remove the tag
<StrictMode>
in index.tsx and it will work as desired.
I've corrected your sandbox.

Map fetched data in React with Redux Toolkit

i´m trying to fetch all the product i have in my DB (mongodb) using the fetch API. The result i get i store in a slice using Redux Toolkit. The problem is when i pass the data fetched and stored to a component as a props, the result is not beeing displayed.
slice
import { createSlice } from "#reduxjs/toolkit";
const initialState = {
products: [],
};
const productSlice = createSlice({
name: "product",
initialState,
reducers: {
setProducts(state, action) {
state.products.push(action.payload);
},
},
});
export const { setProducts } = productSlice.actions;
export default productSlice.reducer;
store
import { configureStore } from "#reduxjs/toolkit";
import uiSlice from "./ui-slice";
import userSlice from "./user-slice";
import productSlice from './product-slice';
const store = configureStore({
reducer: {
ui: uiSlice,
user: userSlice,
products: productSlice
},
});
export default store;
function i used to fetch the products
export const fetchProduct = () => {
return async (dispatch) => {
try {
const response = await fetch("http://localhost:xx/xxx");
const data = await response.json();
let loadedProducts = [];
for (const key in data) {
loadedProducts.push({
id: data[key]._id,
productName: data[key].productName,
price: data[key].price,
imageUrl: data[key].imageUrl,
});
}
dispatch(setProducts(loadedProducts));
} catch (error) {
console.log(error);
}
};
I get the value stored in my redux state with useSelector and use it to fetch the products
import { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { Container } from "react-bootstrap";
import { fetchProduct } from "../../actions/productActions";
import Hero from "../hero/Hero";
import Footer from "../footer/Footer";
import DisplayProductsList from "../displayProduct/DisplayProductsList";
export default function Home() {
const productsInfo = useSelector((state) => state.products.products);
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchProduct());
}, []);
return (
<>
<Hero />
<DisplayProductsList products={productsInfo} />
<Container fluid>
<Footer></Footer>
</Container>
</>
);
}
And then i map it
export default function DisplayProductsList(props) {
console.log(props)
return (
<ul>
{props.products.map((product) => (
<DisplayProducts
key={product.id}
imageUrl={product.imageUrl}
name={product.productName}
price={product.price}
/>
))}
</ul>
);
}
console log the props i sent
if i console log the state in the selector this is what i get
code in the backend
module.exports.fetchProduct = async (req, res) => {
try {
const products = await Product.find({});
console.log(products)
if (products) {
res.json(products);
}
} catch (error) {
console.log(error);
}
};
As I can see in your code, you are storing an array of objects i.e. 'loadedProducts' into 'products' which is also an array. Something like this:
nested array
So in order to get the productsInfo in DisplayProductsList component, instead of doing this:
{props.products.map((product) => (
<DisplayProducts
key={product.id}
imageUrl={product.imageUrl}
name={product.productName}
price={product.price}
/>
))}
you should do something like this:
{props.products[0].map((product) => (
//rest of your code
))}

Redux vs Local State vs React Query

I just can't decide the pattern I want to follow.
I'm implementing what I call a UserParent component. Basically a list of users and when you click on a user, it loads their resources.
Approach 1: Redux
import React, { useEffect, useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { NavList } from '../nav/NavList'
import { ResourceList } from '../resource/ResourceList'
import { getUserResources, clearResources } from './userSlice'
import CircularProgress from '#mui/material/CircularProgress';
import { getAllUsers } from './userSlice';
export const UserParent = () => {
const users = useSelector((state) => state.users.users )
const resources = useSelector((state) => state.users.user.resources )
const [highLightedUsers, setHighLightedItems] = useState([]);
const isLoading = useSelector((state) => state.users.isLoading)
let dispatch = useDispatch();
useEffect(() => {
dispatch(getAllUsers());
}, [])
const onUserClick = (user) => {
if (highLightedUsers.includes(user.label)) {
setHighLightedItems([])
dispatch(clearResources())
} else {
setHighLightedItems([...highLightedUsers, user.label])
dispatch(getUserResources(user.id))
}
}
return(
<>
{ isLoading === undefined || isLoading ? <CircularProgress className="search-loader" /> :
<div className="search-container">
<div className="search-nav">
<NavList
items={users}
onItemClick={onUserClick}
highLightedItems={highLightedUsers}
/>
</div>
<div className="search-results">
<ResourceList resources={resources} />
</div>
</div> }
</>
)
}
And then we have the reducer code:
import { createSlice } from '#reduxjs/toolkit';
import Api from '../../services/api';
const INITIAL_STATE = {
users: [],
isLoading: true,
user: { resources: [] }
};
export const userSlice = createSlice({
name: 'users',
initialState: INITIAL_STATE,
reducers: {
loadAllUsers: (state, action) => ({
...state,
users: action.payload,
isLoading: false
}),
toggleUserLoader: (state, action) => ({
...state,
isLoading: action.payload
}),
loadUserResources: (state, action) => ({
...state, user: { resources: action.payload }
}),
clearResources: (state) => ({
...state,
isLoading: false,
user: { resources: [] }
})
}
});
export const {
loadAllUsers,
toggleUserLoader,
loadUserResources,
clearResources
} = userSlice.actions;
export const getAllUsers = () => async (dispatch) => {
try {
const res = await Api.fetchAllUsers()
if (!res.errors) {
dispatch(loadAllUsers(res.map(user => ({id: user.id, label: user.full_name}))));
} else {
throw res.errors
}
} catch (err) {
alert(JSON.stringify(err))
}
}
export const getUserResources = (userId) => async (dispatch) => {
try {
const res = await Api.fetchUserResources(userId)
if (!res.errors) {
dispatch(loadUserResources(res));
} else {
throw res.errors
}
} catch (err) {
alert(JSON.stringify(err))
}
}
export default userSlice.reducer;
This is fine but I am following this pattern on every page in my app. While it is easy follow I don't believe I'm using global state properly. Every page makes and API call and loads the response into redux, not necessarily because it needs to be shared (although it may be at some point) but because it's the pattern I'm following.
Approach 2: Local State
import React, { useEffect, useState } from 'react'
import { NavList } from '../nav/NavList'
import { ResourceList } from '../resource/ResourceList'
import CircularProgress from '#mui/material/CircularProgress';
import Api from '../../services/api';
export const UserParent = () => {
const [users, setUsers] = useState([])
const [resources, setResources] = useState([])
const [highLightedUsers, setHighLightedItems] = useState([]);
const [isLoading, setIsLoading] = useState(true)
const getUsers = async () => {
try {
const res = await Api.fetchAllUsers()
setUsers(res.map(user => ({id: user.id, label: user.full_name})))
setIsLoading(false)
} catch (error) {
console.log(error)
}
}
const getUserResources = async (userId) => {
try {
setIsLoading(true)
const res = await Api.fetchUserResources(userId)
setResources(res)
setIsLoading(false)
} catch (error) {
console.log(error)
}
}
useEffect(() => {
getUsers()
}, [])
const onUserClick = (user) => {
if (highLightedUsers.includes(user.label)) {
setHighLightedItems([])
} else {
setHighLightedItems([...highLightedUsers, user.label])
getUserResources(user.id)
}
}
return(
<>
{ isLoading === undefined || isLoading ? <CircularProgress className="search-loader" /> :
<div className="search-container">
<div className="search-nav">
<NavList
items={users}
onItemClick={onUserClick}
highLightedItems={highLightedUsers}
/>
</div>
<div className="search-results">
<ResourceList resources={resources} />
</div>
</div>}
</>
)
}
What I like about this is that it uses local state and doesn't bloat global state however, I don't like that it still has business logic in the component, I could just move these to a different file but first I wanted to try React Query instead.
Approach 3: React Query
import React, { useState } from 'react'
import { NavList } from '../nav/NavList'
import { ResourceList } from '../resource/ResourceList'
import CircularProgress from '#mui/material/CircularProgress';
import Api from '../../services/api';
import { useQuery } from "react-query";
export const UserParent = () => {
const [resources, setResources] = useState([])
const [highLightedUsers, setHighLightedItems] = useState([]);
const getUsers = async () => {
try {
const res = await Api.fetchAllUsers()
return res
} catch (error) {
console.log(error)
}
}
const { data, status } = useQuery("users", getUsers);
const getUserResources = async (userId) => {
try {
const res = await Api.fetchUserResources(userId)
setResources(res)
} catch (error) {
console.log(error)
}
}
const onUserClick = (user) => {
if (highLightedUsers.includes(user.label)) {
setHighLightedItems([])
} else {
setHighLightedItems([...highLightedUsers, user.label])
getUserResources(user.id)
}
}
return(
<>
{ status === 'loading' && <CircularProgress className="search-loader" /> }
<div className="search-container">
<div className="search-nav">
<NavList
items={data.map(user => ({id: user.id, label: user.full_name}))}
onItemClick={onUserClick}
highLightedItems={highLightedUsers}
/>
</div>
<div className="search-results">
<ResourceList resources={resources} />
</div>
</div>
</>
)
}
This is great but there is still business logic in my component, so I can move those functions to a separate file and import them and then I end up with this:
import React, { useState } from 'react'
import { UserList } from '../users/UserList'
import { ResourceList } from '../resource/ResourceList'
import CircularProgress from '#mui/material/CircularProgress';
import { getUsers, getUserResources } from './users'
import { useQuery } from "react-query";
export const UserParent = () => {
const [resources, setResources] = useState([])
const [highLightedUsers, setHighLightedItems] = useState([]);
const { data, status } = useQuery("users", getUsers);
const onUserClick = async (user) => {
if (highLightedUsers.includes(user.full_name)) {
setHighLightedItems([])
} else {
setHighLightedItems([...highLightedUsers, user.full_name])
const res = await getUserResources(user.id)
setResources(res)
}
}
return(
<>
{ status === 'loading' && <CircularProgress className="search-loader" /> }
<div className="search-container">
<div className="search-nav">
<UserList
users={data}
onUserClick={onUserClick}
highLightedUsers={highLightedUsers}
/>
</div>
<div className="search-results">
<ResourceList resources={resources} />
</div>
</div>
</>
)
}
In my opinion this is so clean! However, is there anything wrong with the first approach using Redux? Which approach do you prefer?
The first approach you are using shows a very outdated style of Redux.
Modern Redux is written using the official Redux Toolkit (which is the recommendation for all your production code since 2019. It does not use switch..case reducers, ACTION_TYPES, immutable reducer logic, createStore or connect. Generally, it is about 1/4 of the code.
What RTK also does is ship with RTK-Query, which is similar to React Query, but even a bit more declarative than React Query. Which one of those two you like better is probably left to personal taste.
I'd suggest that if you have any use for Redux beyond "just api fetching" (which is a solved problem given either RTK Query or React Query), you can go with Redux/RTK-Query.
If you don't have any global state left after handling api caching, you should probably just go with React Query.
As for learning modern Redux including RTK Query, please follow the official Redux tutorial.
Personally I prefer React-Query for all API-calls, it is great it useMutate and how it manages re-fetching, invalidating queries and more.
I am using your third approach where I create the queries in separate files and then import them where needed.
So far it has been great, and I am using RecoilJS for managing global states. And with the right approach there is really not much that actually needs to be in a global state IMO. Some basic auth/user info and perhaps notification management. But other than that I have been putting less and less in global states keeping it much simpler and scoped.

Next.js + Redux: A Page is loaded before fetching data actions in getInitialProps is complete

I'm trying to fetch data from API by triggering a 'getItems' action in getInitialProps in a page.(Let's say it is page A)
Data can be successfully fetched if I go to page A from other page through Link(next/link).
However if I refresh in page A, data never get fetched and I have to manually fetch data by triggering the getItems action.
Could anyone let me know what I should do to fetch data even when refreshing the page?
_app.js
const MyApp = props => {
const { Component, pageProps, store } = props
return (
<>
<Head>
<link rel='stylesheet' href='/styles/global.css' />
</Head>
<Provider store={store}>
<MainLayout>
<Component {...pageProps} />
</MainLayout>
</Provider>
</>
)
}
MyApp.getInitialProps = async ({ Component, ctx }) => {
const pageProps = Component.getInitialProps
? await Component.getInitialProps(ctx)
: {}
return { pageProps }
}
export default withRedux(store)(MyApp)
pageA.js
import Head from 'next/head'
import Link from 'next/link'
import { connect } from 'react-redux'
import { getItems, addItem } from '../../store/actions/itemAction'
import { GET_ITEMS } from '../../store/types'
const Index = props => {
return (
<>
<Head>
<title>Item List</title>
<link rel='stylesheet' href='/styles/css_pages/shop.css' />
</Head>
<div><div>
</>
)
}
Index.getInitialProps = async ({ store }) => {
await store.dispatch(getItems())
return {}
}
const mapStateToProps = state => ({
goods: state.item.goods,
})
const mapDispatchToProps = dispatch => ({
getItems: () => dispatch(getItems()),
addItem: () => dispatch(addItem()),
})
export default connect(mapStateToProps, mapDispatchToProps)(Index)
itemAction.js
import fetch from 'isomorphic-unfetch'
import { GET_ITEMS } from '../types'
export const getItems = () => {
return dispatch => {
fetch('http://localhost:5000/item')
.then(res => res.json())
.then(data => dispatch({ type: GET_ITEMS, payload: data }))
.catch(err => console.error(err))
}
}
itemReducer.js
import { GET_ITEMS } from '../types'
const initialState = { goods: [], loading: false }
const itemReducer = (state = initialState, action) => {
switch (action.type) {
case GET_ITEMS:
return {
...state,
goods: action.payload,
loading: false,
}
default:
return state
}
}
export default itemReducer
store.js
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import rootReducer from './rootReducer'
const bindMiddleware = middleware => {
if (process.env.NODE_ENV !== 'production') {
const { composeWithDevTools } = require('redux-devtools-extension')
return composeWithDevTools(applyMiddleware(...middleware))
}
return applyMiddleware(...middleware)
}
export const store = () => createStore(rootReducer, bindMiddleware([thunk]))
rootReducer.js
import { combineReducers } from 'redux'
import itemReducer from './reducers/itemReducer'
const rootReducer = combineReducers({
item: itemReducer
})
export default rootReducer
You should wait for the getItems action response in your pageA's getInitialProps before returning and rendering.
I think if you try to use an await, it'll solve your problem:
Index.getInitialProps = async ({ store }) => {
await store.dispatch(getItems())
return {}
}
I would try to look into this example https://github.com/vercel/next.js/blob/canary/examples/with-redux-wrapper/store/store.js
It is recommended to use the next-redux-wrapper package to wrap your pages. Then you will have the store object available on your Index.getInitialProps

Resources