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.
Related
I am using redux-saga as a middleware and i want to dispatch multiple action items to the store. As of now i am able to dispatch only one action (i.e., within the fetchData() function which i am calling in Home.js component).
I've tried adding multiple actions but its not working, Only the first action type is getting dispatched
action.js
import { FETCH_ABOUT, FETCH_CTA, FETCH_PRODUCTS} from './actionType'
//import axios from 'axios';
export const fetchData = () => (
{ type:FETCH_PRODUCTS}
)
export const fetchProducts = (products) => ({
type: FETCH_PRODUCTS,
payload: products,
})
export const fetchCta = (cta) => ({
type: FETCH_CTA,
payload: cta,
})
export const fetchAbout = (about) => ({
type: FETCH_ABOUT,
payload: about,
})
reducer.js
import { FETCH_ABOUT, FETCH_CTA, FETCH_PRODUCTS } from "./actionType"
const initialState = {
products: [],
cta:'',
about:'',
}
const productsReducer = (state=initialState,action) => {
switch(action.type){
case FETCH_PRODUCTS:
return{
...state,
products: action.payload
}
case FETCH_CTA:
return{
...state,
cta: action.payload
}
case FETCH_ABOUT:
return{
...state,
about: action.payload
}
default:
return state;
}
}
export default productsReducer;
ProductsSaga.js
import {call,fork,put,takeLatest} from 'redux-saga/effects'
import { fetchCta, fetchProducts } from '../redux/action';
import { FETCH_CTA, FETCH_PRODUCTS } from '../redux/actionType';
import { fetchAPIcall } from '../redux/api'
function* fetchData() {
try{
const { data } = yield call(fetchAPIcall);
console.log(data.data.productCopy);
yield put(fetchProducts(data.data.productCopy));
}catch(e){
console.log(e);
}
}
//watcher saga
export function* watcherSaga() {
yield takeLatest(FETCH_PRODUCTS,fetchData)
}
export const productsSaga = [fork(watcherSaga)]
ctaSaga.js
import { call,put,takeLatest,fork } from "redux-saga/effects";
import { fetchCta } from "../redux/action";
import { FETCH_CTA } from "../redux/actionType";
import { fetchAPIcall } from "../redux/api";
function* onFetchCta() {
try{
const { data } = yield call(fetchAPIcall);
console.log(data.data.cta);
yield put(fetchCta(data.data.cta));
}catch(e){
console.log(e);
}
}
//watcher saga
export function* watcherSaga() {
yield takeLatest(FETCH_CTA,onFetchCta)
}
export const ctaSaga = [fork(watcherSaga)]
Home.js
import React, { useEffect, useState } from 'react'
import './Home.css'
import {useHistory} from 'react-router-dom';
import { useSelector,useDispatch } from 'react-redux';
import {fetchData} from './redux/action'
const Home = () => {
const history = useHistory();
const {products,cta} = useSelector((state)=>(state.productsReducer));
console.log(products,cta);
const dispatch = useDispatch();
useEffect(()=>{
dispatch(fetchData());
},[])
const productDetail = (item,i) => {
history.push({
pathname:`product-detail/${i}`,
state:item
})
}
return (
<div className='container'>
<div className='product'>
{products.map((item,i) =>{
return(
<div key={item.id}>
<img src={item.Image.path} alt = {item.Image.alt}/>
<p>{item.title}</p>
<button onClick={()=>productDetail(item,i)}type='button'>{cta}</button>
</div>
)
})}
</div>
</div>
)
}
export default Home
Action creators such as fetchData will always create only a single action object. Also the dispatch function (or put effect) can always dispatch only a single action, however nothing is preventing you from dispatching multiple actions one after the other:
function* mySaga() {
yield put(firstAction())
yield put(secondAction())
yield put(thirdAction())
}
// or
function MyComponent() {
const dispatch = useDispatch()
return <div onClick={() => {
dispatch(firstAction())
dispatch(secondAction())
dispatch(thirdAction())
}}>Hello</div>
}
If you are worried about rerenders, react-redux has the batch function that allows you to wrap your dispatches making sure that react batches all the updates and rerenders only a single time. Note that this is not necessary starting from React 18 as it batches things automatically.
It is more difficult to use the batched updates with redux saga due to its internal scheduler that doesn't guarantee that everything will happen in single tick, but again starting from React 18 you don't need to worry about this. In case you really need it, there are libraries out there that allows you to do it though, check out Mark's post about it which includes links to some of these libraries: https://blog.isquaredsoftware.com/2020/01/blogged-answers-redux-batching-techniques/
One more thing, if you find yourself dispatching the same list of actions again and again, maybe it make sense to merge these together to a single action and avoid the issue entirely.
rootReducer
import { combineReducers } from "redux";
import mods from "./mods.js";
export default combineReducers({
mods
})
reducers/mods.js
import { GET_MODS, GET_SPECIFC_MOD } from "../actions/types"
const initialState = {
mods: [],
currMod: []
}
export default function(state = initialState, action) {
switch(action.type) {
case GET_MODS:
return {
...state,
mods: action.payload
}
case GET_SPECIFC_MOD:
return {
...state,
currMod: action.payload
}
default:
return state
}
}
actions/mods.js
import axios from 'axios'
import { GET_MODS, GET_SPECIFC_MOD } from './types'
// get the mods
export const getMods = () => dispatch => {
axios.get('http://localhost:8000/api/mods')
.then(res => {
dispatch({
type: GET_MODS,
payload: res.data
})
}).catch(err => console.log(err))
}
// get single mod
export const getSpecificMod = (title) => dispatch => {
axios.get(`http://localhost:8000/api/mods/${title}`)
.then(res => {
dispatch({
type: GET_SPECIFC_MOD,
payload: res.data
})
}).catch(err => console.log(err))
}
components/download.js
import React from 'react'
import { useState, useEffect } from 'react'
import { connect } from 'react-redux'
import { getSpecificMod } from '../actions/mods'
const Download = () => {
useEffect(() => {
const title = window.location.pathname.split('/')[3]
getSpecificMod(title)
})
return (
<></>
)
}
const mapStateToProp = state => ({
currMod: state.mods.currMod
})
export default connect(mapStateToProp, getSpecificMod)(Download)
Response from backend
GET http://localhost:8000/api/mods/function(){return!window.__REDUX_DEVTOOLS_EXTENSION_LOCKED__&&a.dispatch.apply(a,arguments)}
Basically the user clicks on a mod and gets sent to the download section that is handled by 'download.js' the component ('download.js') renders it and reads the window.location to retrieve the title, with redux I want to get the mod so i made a function that takes the title and sends the request 'getMod(title)' but for some reason it is throwing horrible errors that I dont understand, any help is appreciated!
You are not dispatching the action properly in your component. Right now you are actually just calling the getSpecificMod action creator function from your imports. Your Download component doesn't read anything from props so it is ignoring everything that gets created by the connect HOC.
If you want to keep using connect, you can fix it like this:
import React, { useEffect } from 'react'
import { connect } from 'react-redux'
import { getSpecificMod } from '../actions/mods'
const Download = ({currMod, getSpecificMod}) => {
const title = window.location.pathname.split('/')[3]
useEffect(() => {
getSpecificMod(title)
}, [title])
return (
<></>
)
}
const mapStateToProps = state => ({
currMod: state.mods.currMod
})
export default connect(mapStateToProps, {getSpecificMod})(Download)
We are now accessing the bound action creator as a prop of the component. mapDispatchToProps is an object which maps the property key to the action.
But it's better to use the useDispatch hook:
import React, { useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { getSpecificMod } from '../actions/mods'
const Download = () => {
const currentMod = useSelector(state => state.mods.currMod);
const dispatch = useDispatch();
const title = window.location.pathname.split('/')[3]
useEffect(() => {
dispatch(getSpecificMod(title));
}, [title, dispatch]);
return (
<></>
)
}
export default Download;
There might be some confusion on terminology here. Your getSpecificMod function is a function which takes dispatch as an argument but it is not a mapDispatchToProps. It is a thunk action creator.
Make sure that you have redux-thunk middleware installed in order to handle this type of action. Or better yet, use redux-toolkit.
Your useEffect hook needs some sort of dependency so that it knows when to run. If you only want it to run once you can use an empty array [] as your dependencies. If you don't specify the dependencies at all then it will re-run on every render.
Does the pathname change? If so, how do you know when? You might want to add an event listener on the window object. Or consider using something like react-router. But that is a separate question.
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
I have a small app that displays a component that is a list (JobsList) and another component that that contains a text field and submit button (CreateJob). While I am able to populate JobsList with API data (passing through Redux), I am not sure how I should update JobsList with a new API call, once I have successfully posted a new job in CreateJob. This is the code I have so far:
JobsList.js
import React, { Fragment, useEffect } from 'react';
import { connect } from 'react-redux';
import JobCard from './JobCard';
import CreateJob from './CreateJob';
import api from './Api';
import { JOBS_LOADED } from './ActionTypes';
const JobsList = ({ jobs, onLoad }) => {
useEffect(() => {
const fetchJobs = async () => {
try {
const data = await api.Jobs.getAll();
onLoad({ data });
} catch (err) {
console.error(err);
}
};
fetchJobs();
}, [onLoad]);
return (
<Fragment>
<CreateJob />
{teams.map(job => (
<JobCard job={job} key={team.jobId} />
))}
</Fragment>
);
}
const mapStateToProps = state => ({
jobs: state.jobsReducer.teams
});
const mapDispatchToProps = dispatch => ({
onLoad: payload =>
dispatch({ type: JOBS_LOADED, payload }),
});
export default connect(mapStateToProps, mapDispatchToProps)(JobsViewer);
CreateJob.js
import React, { useState } from 'react';
import Button from '#material-ui/core/Button';
import TextField from '#material-ui/core/TextField';
import api from './Api';
const CreateJob = () => {
const [state, setState] = React.useState({
jobName: '',
creator: ''
});
const handleInputChange = event => {
setState({
...state,
[event.target.name]: event.target.value
});
// validation stuff
}
const handleSubmit = async e => {
api.Jobs.create({state})
try {
await request;
// Reload the Jobs list so it does an another API request to get all new data
// DO I CALL A DISPATCH HERE?????
} catch (err) {
console.error(err);
}
}
return (
<div>
<TextField
name="jobName"
value={state.jobName || ''}
onChange={handleInputChange}
/>
<Button onClick={handleSubmit}>Create job</Button>
</div>
);
}
export default CreateJob;
JobsReducer.js
import { TEAMS_LOADED } from './ActionTypes';
export default (state = {teams: []}, action) => {
switch (action.type) {
case TEAMS_LOADED:
return {
...state,
teams: action.payload.data,
};
default:
return state;
}
};
In the success result in handleSubmit in CreateJob.js, how do I trigger/dispatch a new API call to update JobsList from CreateJob.js? I'm new to react/redux so apologies for any poor code. Any advice for a learner is greatly appreciated.
The simplified solution to take is wrapper the function for fetching jobs as a variable in the JobsList, and assign it to CreateJob as a prop. Then from the CreateJob, it's up to you to update the job list.
The shortage of this solution is it doesn't leverage redux as more as we can. It's better to create action creator for shared actions(fetch_jobs) in the JobsReducer.js and map these actions as props to the component which need it exactly.
JobsReducer.js
export const fetchJobsAsync = {
return dispatch => {
try {
const data = await api.Jobs.getAll();
dispatch({type: TEAMS_LOADED, payload: {data}})
} catch (err) {
console.error(err);
}
}
}
tips: You must install redux-thunk to enable the async action.
After, you will be able to fire the API to update the jobs(or teams anyway) from any component by dispatching the action instead of calling the API directly.
JobsList.jsx or CreateJob.js
const mapDispatchToProps = dispatch => ({
fetchAll: () => dispatch(fetchJobsAsync())
})
At the end of CreateJob.js, it's totally the same as calling the fetchAll to reload the jobs list like calling other regular functions.
And, if you are ok to go further, move the API call which creates new job to the reducer and wrapper it as an action. Inside it , dispatching the fetchJobsAsync if the expected conditions meet(If create new job finished successfully). Then you will end up with a more clearly component tree with only sync props without the data logic regarding to when/how to reload the jobs list.
Yes, your approach is absolutely right.
Once you have posted a new job, based on it's response you can trigger fetchJobs which you can pass as prop to <CreateJob fetchJobs={fetchJobs}/>.
For that you will have to declare it outside useEffect() like this:
import React, { Fragment, useEffect } from 'react';
import { connect } from 'react-redux';
import JobCard from './JobCard';
import CreateJob from './CreateJob';
import api from './Api';
import { JOBS_LOADED } from './ActionTypes';
const JobsList = ({ jobs, onLoad }) => {
const fetchJobs = async () => {
try {
const data = await api.Jobs.getAll();
onLoad({ data });
} catch (err) {
console.error(err);
}
};
useEffect(() => {
fetchJobs();
}, [onLoad]);
return (
<Fragment>
<CreateJob fetchJobs={fetchJobs}/>
{teams.map(job => (
<JobCard job={job} key={team.jobId} />
))}
</Fragment>
);
}
const mapStateToProps = state => ({
jobs: state.jobsReducer.teams
});
const mapDispatchToProps = dispatch => ({
onLoad: payload =>
dispatch({ type: JOBS_LOADED, payload }),
});
export default connect(mapStateToProps, mapDispatchToProps)(JobsViewer);
Once you trigger the api call new data will be loaded in redux state:
import React, { useState } from 'react';
import Button from '#material-ui/core/Button';
import TextField from '#material-ui/core/TextField';
import api from './Api';
const CreateJob = props => {
const [state, setState] = React.useState({
jobName: '',
creator: ''
});
const handleInputChange = event => {
setState({
...state,
[event.target.name]: event.target.value
});
// validation stuff
}
const handleSubmit = async e => {
api.Jobs.create({state})
try {
await request;
props.fetchJobs()
} catch (err) {
console.error(err);
}
}
return (
<div>
<TextField
name="jobName"
value={state.jobName || ''}
onChange={handleInputChange}
/>
<Button onClick={handleSubmit}>Create job</Button>
</div>
);
}
export default CreateJob;
As JobsList component is subscribed to the state and accepts state.jobsReducer.teams as props here:
const mapStateToProps = state => ({
jobs: state.jobsReducer.teams
});
The props will change on loading new jobs from <CreateJobs />and this change in props will cause <JobsLists /> to be re-rendered with new props.
I get my action called in Redux Dev Tools and even the new state, but in the actual Component props is undefined.
The component:
import React, { useEffect } from 'react';
import { Link } from 'react-router-dom';
import { connect } from 'react-redux';
import { getPromos } from '../../actions/promo';
import PropTypes from 'prop-types';
const Landing = ({ getPromos, data }) => {
useEffect(() => {
getPromos();
console.log(data) // ==>> "UNDEFINED"
}, []);
return (
<div>
<section className='landing'>
<div className='dark-overlay'>
<div className='landing-inner'>
<h1 className='x-large'> Developer Connector </h1>
<p className='lead'>
Create a developer profile/portfolio, share posts and get help
from other developers
</p>
<div className='buttons'>
<Link to='/register' className='btn btn-primary'>
Sign Up
</Link>
<Link to='/login' className='btn btn-light'>
Login
</Link>
</div>
</div>
</div>
</section>
</div>
);
};
Landing.propTypes = {
getPromos: PropTypes.func.isRequired
};
const mapStateToProps = state => ({
data: state.data
});
export default connect(
mapStateToProps,
{ getPromos }
)(Landing);
Actions:
import axios from 'axios';
import { setAlert } from './alert';
import { GET_PROMOS, REGISTER_FAIL } from './types';
export const getPromos = () => async dispatch => {
try {
const res = await axios.get('/api/promo');
dispatch({
type: GET_PROMOS,
payload: res.data
});
} catch (err) {
const errors = err.response.data.errors;
if (errors) {
errors.forEach(error => dispatch(setAlert(error.msg, 'danger')));
}
dispatch({ type: REGISTER_FAIL });
}
};
And reducer:
import { GET_PROMOS } from '../actions/types';
const initialState = {
data: null,
title: ''
};
export default function(state = initialState, action) {
const { type, payload } = action;
switch (type) {
case GET_PROMOS:
return { ...state, data: payload };
default:
return state;
}
}
Like I said, in Redux Dev Tools I get my desired output. But for some reason I cant get to echo this state in the component. What im getting wrong? Can it be something about the hooks?
Thanks !
First thing that jumps at me is that you have a naming conflict with the getPromos in your component, it's defined in the imports as getPromos then it's destructured in the component as { getPromos } as well. I'm surprised you didn't get an error there for naming conflicts.
You will want to NOT destructure getPromos in the component and instead call it as (props) => { props.getPromos } to actually call the connected action creator instead of the unconnected one.
Second, Is that reducer the main root reducer? or is it nested in the root reducer? if the latter is true then in your mapStateToProps the data prop should be one level deeper, as in state: state.rootLevelState.data
(sorry can't ask questions in the comments due to reputation < 50)
enter image description here
Here's a screenshot of the redux dev tools