I'm working on a personal React project with Redux and I've an issue. All my redux functions are working well except one. I want to load all the requests into the redux. In requestAction.js, I have the first console.log but not the second from the return function(dispatch). Have you any idea why ?
Thank you in advance =)
Here is my code :
import React, {useState, useEffect} from "react"
import {connect} from "react-redux"
import {getAllRequests} from "../../api/requests"
import {loadAllRequests} from "../../actions/request/requestAction"
import {convertDate} from "../../utils/utils"
import Header from "../headers/header"
import HeaderPages from "../headers/headerPages"
import Footer from "../footer"
import AdminMenu from "../../components/adminMenu"
const AdminRequests = (props) => {
const headerTitle ="Administration"
const headerBreadcrumbs = [{value: "Accueil", link:"/"},{value: "Administration", link:"/admin"},{value: "Commandes", link:null}]
const [displayedRequests, setDisplayedRequests] = useState([])
useEffect(() => {
loadDatas()
}, [])
useEffect(() => {
loadDisplayedRequests(props.requests.list)
}, [props.requests])
const loadDatas = () => {
getAllRequests()
.then(requestsDB => {
loadAllRequests(requestsDB) //My redux function
})
}
//Construction de la liste des commandes à afficher
const loadDisplayedRequests = (requests) => {
requests.map((requestItem) => {
setDisplayedRequests(displayedRequests => [...displayedRequests,
<article key={requestItem.id} className="profile-user-request-item">
<section className="request-item-header">
<p>N°{requestItem.request_number}</p>
<p>du {convertDate(requestItem.request_date)}</p>
<p>Statut : {requestItem.preparation_status}</p>
</section>
<section className="request-item-resume">
<p>Total</p>
<p>{requestItem.total_amount} € TCC</p>
</section>
</article>])
})
}
const showDisplayedRequests = () => {
return(
<section>
{displayedRequests}
</section>
)
}
return (
<div className="root">
<Header />
<HeaderPages headerTitle={headerTitle} headerBreadcrumbs={headerBreadcrumbs}/>
<section className="admin-container">
<AdminMenu />
<section className="admin-content">
<h4>Gestion des commandes</h4>
{showDisplayedRequests()}
</section>
</section>
<Footer />
</div>
)
}
const mapStateToProps = (store) => {
return {
requests: store.requests
}
}
const mapDispatchToProps = {
loadAllRequests
}
export default connect(mapStateToProps, mapDispatchToProps)(AdminRequests)
requestAction.js
import {LOAD_ALL_REQUESTS} from "./action-type"
export const loadAllRequests = (requests) => {
console.log("requests action = ", requests) //Displayed
return function(dispatch){
console.log("dispatch") //Not displayed
dispatch({
type: LOAD_ALL_REQUESTS,
payload: requests
})
}
requestReducer.js
import { LOAD_ALL_REQUESTS } from "../actions/request/action-type"
const initialState = {
list: []
}
export default function RequestReducer(state = initialState, action) {
switch(action.type){
case LOAD_ALL_REQUESTS :
return {list: action.payload}
break
default :
return state
break
}
}
index.js
import {combineReducers } from "redux"
import UserReducer from "./userReducer"
import ProductsReducer from "./productsReducer"
import RequestReducer from "./requestReducer"
const rootReducer = combineReducers({
user: UserReducer,
products: ProductsReducer,
requests: RequestReducer
})
export default rootReducer
The problem at heart is that you don't dispatch. You need to
const dispatch = useDispatch()
useEffect(() => {
dispatch(loadDisplayedRequests(props.requests.list))
// instead of
// loadDisplayedRequests(props.requests.list)
}, [props.requests])
Adding to that: What you have written there is not an action creator, but a thunk. It will only work if you have the thunk middleware enabled - and even then, for this simple use case it just does nothing extra that you need.
As a normal action creator, it would look like this:
import {LOAD_ALL_REQUESTS} from "./action-type"
export const loadAllRequests = (requests) => {
return {
type: LOAD_ALL_REQUESTS,
payload: requests
}
}
Generally, I want to make you aware that you are writing a very old style of Redux here and might have been following an outdated tutorial. Modern Redux does not have string action type constants, switch case reducers, action creators or connect any more.
For a quick look at modern Redux, take a look at https://redux.js.org/tutorials/fundamentals/part-8-modern-redux and for a longer tutorial, read https://redux.js.org/tutorials/essentials/part-1-overview-concepts
Related
I need a help to solve this error:
"useDispatch is called in function that is neither a React function component nor a custom React Hook function".
Explanation:
store.js and userSlice.js hold the definition of my Redux related things (rtk).
Auth.js is meant to hold functions to authenticate/logout and keep redux "user" storage updated. By now I have just the google auth, that is authenticated when I call redirectToGoogleSSO.
The authentication part is working flawless and i'm retrieving the user info correctly, but I'm having a hard time making it update the user store.
The dispatch(fetchAuthUser()) is where I get the error.
Sidebar.js is a navigation sidebar that will hold a menu to sign in/sign out and to access the profile.js (not implemented yet).
If I bring all the code from Auth to inside my Sidebar component, the authentication work and the redux store is filled, but I would like to keep things in the Auth.js so I can use that in other components and not just in the Sidebar.
//store.js:
import { configureStore } from '#reduxjs/toolkit';
import userReducer from './userSlice';
export default configureStore({
reducer: {
user: userReducer
}
});
//userSlice.js
import { createSlice } from '#reduxjs/toolkit';
import axios from "axios";
export const userSlice = createSlice({
name: 'user',
initialState: {
email: 'teste#123',
name: 'teste name',
picture: 'teste pic',
isAuthenticated: false
},
reducers: {
setUser (state, actions) {
return {...state,
email: actions.payload.email,
name: actions.payload.name,
picture: actions.payload.picture,
isAuthenticated: true
}
},
removeUser (state) {
return {...state, email: '', name: '', picture: '', isAuthenticated: false}
}
}
});
export function fetchAuthUser() {
return async dispatch => {
const response = await axios.get("/api/auth/user", {withCredentials: true}).catch((err) => {
console.log("Not properly authenticated");
dispatch(removeUser());
});
if (response && response.data) {
console.log("User: ", response.data);
dispatch(setUser(response.data));
}
}
};
export const { setUser, removeUser } = userSlice.actions;
export const selectUser = state => state.user;
export default userSlice.reducer;
//Auth.js
import React, { useEffect } from 'react';
import { useDispatch } from 'react-redux';
import { fetchAuthUser } from '../../redux/userSlice';
export const AuthSuccess = () => {
useEffect(() => {
setTimeout(() => {
window.close();
},1000);
});
return <div>Thanks for loggin in!</div>
}
export const AuthFailure = () => {
useEffect(() => {
setTimeout(() => {
window.close();
},1000);
});
return <div>Failed to log in. Try again later.</div>
}
export const redirectToGoogleSSO = async() => {
const dispatch = useDispatch();
let timer = null;
const googleAuthURL = "http://localhost:5000/api/auth/google";
const newWindow = window.open(
googleAuthURL,
"_blank",
"toolbar=yes,scrollbars=yes,resizable=yes,top=200,left=500,width=400,height=600"
);
if (newWindow) {
timer = setInterval(() => {
if(newWindow.closed) {
console.log("You're authenticated");
dispatch(fetchAuthUser()); //<----- ERROR HERE ---->
if (timer) clearInterval(timer);
}
}, 500);
}
}
//Sidebar.js
import React from 'react';
import { Link } from 'react-router-dom';
import { redirectToGoogleSSO } from '../auth/Auth';
import { useSelector } from 'react-redux';
export const Sidebar = () => {
const handleSignIn = async() => {
redirectToGoogleSSO();
};
const {name,picture, isAuthenticated} = useSelector(state => state.user);
return (
<div id="sidenav" className="sidenav">
<div className="nav-menu">
<ul>
{
isAuthenticated
? <li>
<img className="avatar" alt="" src={picture} height="40" width="40"></img>
<Link to="/" className="user">{name}</Link>
<ul>
<li><Link to="/"><i className="pw-icon-export"/> logout</Link></li>
</ul>
</li>
: <li>
<Link to="/" className="login" onClick={handleSignIn}>
<i className="pw-icon-gplus"/>
Sign In / Sign Up
</Link>
</li>
}
</ul>
</div>
</div>
)
}
You only can use the useDispatch hook from a react component or from a custom hook, in your case, you should use store.dispatch(), try to do the following:
import { configureStore } from '#reduxjs/toolkit';
import userReducer from './userSlice';
// following the docs, they assign configureStore to a const
const store = configureStore({
reducer: {
user: userReducer
}
});
export default store;
Edit: i also noticed that you are trying to dispatch a function that is not an action, redux doesn't work like that, you should only dispatch the actions that you have defined in your reducer, otherwise your state will be inconsistent.
So first of all, move the fetchAuthUser to another file, like apiCalls.ts or anything else, it's just to avoid circular import from the store.js.
after this, call the store.dispatch on the fetchAuthUser:
// File with the fetch function
// Don't forget to change the path
import store from 'path/to/store.js'
export function fetchAuthUser() {
const response = await axios.get("/api/auth/user", {withCredentials: true}).catch((err) => {
console.log("Not properly authenticated");
store.dispatch(removeUser());
});
if (response && response.data) {
console.log("User: ", response.data);
store.dispatch(setUser(response.data));
}
};
In the Auth.js you don't have to call the dispatch, because you have already called it within your function.
export const redirectToGoogleSSO = async() => {
let timer = null;
const googleAuthURL = "http://localhost:5000/api/auth/google";
const newWindow = window.open(
googleAuthURL,
"_blank",
"toolbar=yes,scrollbars=yes,resizable=yes,top=200,left=500,width=400,height=600"
);
if (newWindow) {
timer = setInterval(() => {
if(newWindow.closed) {
console.log("You're authenticated");
// Just call the fetchAuthUser, you are already dispatching the state inside this function
await fetchAuthUser();
if (timer) clearInterval(timer);
}
}, 500);
}
}
So keep in mind that ever you need to use dispatch outside a react component or a custom hook, you must use the store.dispatch, otherwise it will not work, and don't forget to only dispatch actions to keep the state consistent. I suggest you to read the core concepts about redux, and also see this video to understand better how it works under the hoods. Hope i helped a bit!
Just as the error states, you are calling useDispatch in Auth.js-> redirectToGoogleSSO. This is neither a React Component nor a React Hook function. You need to call useDispatch in either of those. So you can:
Handle the redux part of the user information and the Google SSO part in a component by calling both useDispatch and redirectToGoogleSSO in handleSignIn itself (this is probably easier to implement right now, you just need to move the dispatch code from redirectToGoogleSSO to handleSignIn), or
turn redirectToGoogleSSO into a Hook you can call from within components.
I am supposed to fetch data from an endpoint and display the results using Redux-Sauce.
All is fine except I can't seem to update the state after I fetch data. Read the docs so this is what I could come up with. Please tell me what I am doing wrong!?
How do I update the state calling the action creators inside HomeContainer.js?
Link to codeSandbox
https://codesandbox.io/s/fragrant-sky-56yhi?file=/src/index.js
HomeContainer.js
import React, { useEffect, useState } from "react";
import axios from "axios";
import { connect } from "react-redux";
import Creators from "../redux/Reducers/reducers";
const HomeContainer = ({ iTunesData, actions }) => {
const { loading, data, error } = iTunesData;
const [searchTerm, setSearchTerm] = useState("");
const submitHandler = (e) => {
e.preventDefault();
const getData = async () => {
actions.fetchDataRequest();
try {
const { data } = await axios.get(
`https://itunes.apple.com/search?term=${searchTerm}`
);
// console.log(data);
actions.fetchDataSuccess(data);
} catch (error) {
actions.fetchDataFail(error);
}
};
getData();
// console.log("On submit handler clicked!");
};
// console.log(iTunesData, actions);
// console.log(searchTerm);
// console.log(iTunesData);
console.log(loading, data, error);
return (
<form onSubmit={submitHandler}>
<h1> Home Container</h1>
<input
placeholder="Search..."
type="text"
onChange={(e) => setSearchTerm(e.target.value)}
/>
<button>Go</button>
</form>
);
};
const mapStateToProps = (state, ownProps) => {
return {
iTunesData: state
};
};
const mapDispatchToProps = (state, ownProps) => {
return {
actions: Creators
};
};
export default connect(mapStateToProps, mapDispatchToProps)(HomeContainer);
reducer.js
import { createReducer, createActions } from "reduxsauce";
const { Types, Creators } = createActions({
fetchDataRequest: null,
fetchDataSuccess: ["payload"],
fetchDataFail: ["error"]
});
export default Creators;
const initialState = {
loading: false,
data: [],
error: false
};
export const fetchDataRequest = (state = initialState, action) => {
return { ...state, loading: true, data: [], error: false };
};
export const fetchDataSuccess = (state = initialState, action) => {
return { ...state, data: action.payload, error: false };
};
export const fetchDataFail = (state = initialState, action) => {
return { ...state, data: null, error: action.error };
};
// map our action types to our reducer functions
export const HANDLERS = {
[Types.FETCH_DATA_REQUEST]: fetchDataRequest,
[Types.FETCH_DATA_SUCCESS]: fetchDataSuccess,
[Types.FETCH_DATA_FAIL]: fetchDataFail
};
export const reducer = createReducer(initialState, HANDLERS);
store.js
import { applyMiddleware, combineReducers, compose, createStore } from "redux";
import { reducer } from "./Reducers/reducers";
import thunk from "redux-thunk";
const store = createStore(reducer, applyMiddleware(thunk));
export default store;
index.js
import { StrictMode } from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import store from "./redux/store";
import App from "./App";
const rootElement = document.getElementById("root");
ReactDOM.render(
<StrictMode>
<Provider store={store}>
<App />
</Provider>
</StrictMode>,
rootElement
);
Your mapDispatchToProps is wrong. Written like you want to use it, it would need to acutally bind dispatch to the actions, which you don't.
If you want to use that nested, you will have to call bindActionCreators manually.
const mapDispatchToProps = (dispatch) => {
return {
actions: bindActionCreators(Creators, dispatch)
};
};
Otherwise you could also use the "object notation"
const mapDispatchToProps = Creators
in which case the bound action creators will be available as props.fetchDataSuccess, not props.actions.fetchDataSuccess.
Generally, it is also recommended to not use connect at all with function components, but the React-Redux hooks useSelector and useDispatch.
See https://react-redux.js.org/api/hooks
Also, as for your internship, please forward the official Redux Style Guide to your team, with best regards from a Redux Maintainer ;)
https://redux.js.org/style-guide/style-guide/
We really want them to use the official Redux Toolkit, as it will simplify their code a lot more than Redux-Sauce already does - including allowing for immutable logic in reducers thanks to immer integration and containing a full blown api cache abstraction.
Maybe trying that out and prototyping using it might make for a nice internship project for you in the end ;)
Hi im new to redux and im trying to create a movie app using the API from www.themoviedb.org. I am trying to display the popular movies and im sure the API link works since ive tested it in postman but i cant seem to figure out why redux doesnt pick up the data.
//action
import { FETCH_POPULAR } from "./types";
import axios from "axios";
export const fetchPopularMovies = () => (dispatch) => {
axios
.get(
`https://api.themoviedb.org/3/movie/popular?api_key=${API}&language=en-US`
)
.then((response) =>
dispatch({
type: FETCH_POPULAR,
payload: response.data
})
)
.catch((err) => console.log(err));
};
//reducer
import { FETCH_POPULAR } from "../actions/types";
const initialState = {
popular: [],
};
export default function (state = initialState, action) {
switch (action.type) {
case FETCH_POPULAR:
return {
...state,
popular: action.payload,
};
default:
return state;
}
}
import React from "react";
import { connect } from "react-redux";
import Popular from "./Popular";
const FetchedPopular = (props) => {
const { popular } = props;
let content = "";
content =
popular.length > 0
? popular.map((item, index) => (
<Popular key={index} popular={item} />
))
: null;
return <div className="fetched-movies">{content}</div>;
};
const mapStateToProps = (state) => ({
popular: state.popular.popular,
});
export default connect(mapStateToProps)(FetchedPopular);
import React from "react";
import "../Styles.css";
const Popular = (props) => {
return (
<div className="movie-container">
<img
className="poster"
src={`https://image.tmdb.org/t/p/w400/${props.poster_path}`}
/>
</div>
);
};
export default Popular;
I cant really tell what I'm missing can someone help?
Next to mapStateToProps you need to create mapDispatchToProps. After that, you will be able to call your Redux action from your React component.
I suggest you the mapDispatchToProps as an Object form. Then you need to use this mapDispatchToProps as the second parameter of your connect method.
When you will have your action mapped to your component, you need to call it somewhere. It is recommended to do it for example on a component mount. As your React components are Functional components, you need to do it in React useEffect hook.
import React, { useEffect } from "react";
import { connect } from "react-redux";
import Popular from "./Popular";
import { fetchPopularMovies } from 'path_to_your_actions_file'
const FetchedPopular = (props) => {
const { popular } = props;
let content = "";
useEffect(()=> {
// call your mapped action (here it is called once on component mount due the empty dependency array of useEffect hook)
props.fetchPopularMovies();
}, [])
content =
popular.length > 0
? popular.map((item, index) => (
<Popular key={index} popular={item} />
))
: null;
return <div className="fetched-movies">{content}</div>;
};
const mapStateToProps = (state) => ({
popular: state.popular.popular,
});
// create mapDispatchToProps
const mapDispatchToProps = {
fetchPopularMovies
}
// use mapDispatchToProps as the second parameter of your `connect` method.
export default connect(mapStateToProps, mapDispatchToProps)(FetchedPopular);
Moreover, as I wrote above in my comment, your Popular does not have the prop poster_path but it has the prop popular which probably has the property poster_path.
import React from "react";
import "../Styles.css";
const Popular = (props) => {
return (
<div className="movie-container">
<img
className="poster"
src={`https://image.tmdb.org/t/p/w400/${props.popular.poster_path}`}
/>
</div>
);
};
export default Popular;
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
I have an app with 2 reducers (city and doctor)
I use sagas.
I have a problem that with weird state overrides.
For example here is the start of fetch doctors action. As you see state was changed for doctor isLoading: false to isLoading: true
After that fetch cities action was started right before. And isLoading for doctors was changed back to false.
On every action dispatch, another reducer state reset.
I have doubt that it's a NextJS specific problem so root store is creating couple of times and cause a race condition.
Technologies: react, redux, react-redux, next-redux-wrapper, next-redux-saga, redux-saga
app.js
....
export default withRedux(createStore)(withReduxSaga(MyApp))
store.js
import { applyMiddleware, createStore } from 'redux'
import createSagaMiddleware from 'redux-saga'
import rootReducer from './rootReducer'
import rootSaga from './rootSaga'
const bindMiddleware = middleware => {
if (process.env.NODE_ENV !== 'production') {
const { composeWithDevTools } = require('redux-devtools-extension')
return composeWithDevTools(applyMiddleware(...middleware))
}
return applyMiddleware(...middleware)
}
function configureStore(initial={}) {
const sagaMiddleware = createSagaMiddleware()
const store = createStore(
rootReducer,
initial,
bindMiddleware([sagaMiddleware])
)
store.sagaTask = sagaMiddleware.run(rootSaga)
return store
}
export default configureStore
rootReducer.js
import { combineReducers } from 'redux'
import { cityReducer } from "./reducers/city"
import { doctorReducer } from "./reducers/doctor"
export default combineReducers({
city: cityReducer,
doctor: doctorReducer
})
city/reducer.js
import {actionTypes} from "../../actions/city"
const initialState = {
cities: []
}
export const cityReducer = (state = initialState, action) => {
switch (action.type) {
case actionTypes.FETCH_CITIES:
return initialState
case actionTypes.FETCH_CITIES_SUCCESS:
return {
cities: action.data
}
case actionTypes.FETCH_CITIES_FAILURE:
return initialState
default:
return initialState
}
}
city/actions.js
export const actionTypes = {
FETCH_CITIES: 'FETCH_CITIES',
FETCH_CITIES_SUCCESS: 'FETCH_CITIES_SUCCESS',
FETCH_CITIES_FAILURE: 'FETCH_CITIES_FAILURE',
}
export const fetchCities = () => {
return {
type: actionTypes.FETCH_CITIES
}
}
export const fetchCitiesSuccess = (data) => {
return {
type: actionTypes.FETCH_CITIES_SUCCESS,
data
}
}
export const fetchCitiesFailure = () => {
return {
type: actionTypes.FETCH_CITIES_FAILURE
}
}
city/saga.js
import {call, put, takeLatest} from "redux-saga/effects"
import {actionTypes, fetchCitiesFailure, fetchCitiesSuccess} from "../../actions/city"
import {getCities} from "../../api"
export function* watchFetchCitiesRequest() {
yield takeLatest(actionTypes.FETCH_CITIES, workerFetchCitiesRequest)
}
function* workerFetchCitiesRequest() {
try {
const {data} = yield call(getCities)
yield put(fetchCitiesSuccess(data.results))
} catch (e) {
yield put(fetchCitiesFailure())
}
}
(!) For the doctors reducer/saga/actions the structure is the same except names.
Page.js is the main layout for every page. Basically wraps every page content in _document.js
const Page = ({dispatch}) => {
useEffect(() => {
dispatch(fetchCities())
}, [])
}
const mapStateToProps = ({city}) => ({cities: city.cities})
export default connect(mapStateToProps)(Page)
DoctorList.js Component that /doctor page consumes
import {useEffect} from "react"
import {fetchDoctors} from "../../actions/doctor"
import {getCity} from "../../utils/functions"
import {DoctorItemPreview} from "./DoctorItem"
const DoctorList = ({dispatch, isLoading, isError, response}) => {
useEffect(() => {
getCity() ? dispatch(fetchDoctors()) : null
},[getCity()])
return <>
{!isLoading ? <>
</> : <>
{[...Array(10)].map((e, i) => <span key={i}>
<DoctorItemPreview/>
</span>)}
</>}
</>
}
const mapStateToProps = ({doctor, city}) => ({
isLoading: doctor.isLoading,
isError: doctor.isError,
response: doctor.response,
})
export default connect(mapStateToProps)(DoctorList)
What can be the place where the problem appears? What parts of code do you need for the answer? Thanks
I am pretty sure your reducers will overwrite your current state when returning initialState. See this answer on GitHub.
There are no known race conditions or other problems related to multiple root stores nor reducers in next-redux-saga.