I'm using react-redux hooks to write a login/logout system.
Currently i'm facing a problem where inside the logout() & login1() will only run the first dispatch only. Did some research found that redux-thunks might can solve this, but get confused cause redux-thunks are more likely to load data. Anyone have any ideas on it??
import React from "react"
import {chgStatus,chgName,chgPw} from "../Actions"
import {useSelector,useDispatch,shallowEqual} from "react-redux";
import {useHistory} from "react-router-dom";
import '../App.css'
const LoginOutBtn =()=>{
const {name,password,status} = useSelector(state=>({
name: state.name,
password: state.password,
status: state.status
}),shallowEqual);
const dispatch = useDispatch()
const history = useHistory()
const loginStatus = status?
<span>登出</span> :
<span>登入</span>
const logout=()=>dispatch(
chgStatus(!status),
// chgName("logout"),
//chgPw("123"),
console.log(status,name,password,456),
history.push("/subPage1")
)
const login=()=>dispatch(
chgStatus(!status),
chgName("Qweq"),
chgPw("pw"),
console.log(status,name,password,123),
history.push("/subPage2")
)
const login1 =()=>{
dispatch(
dispatch(chgStatus(!status)),
dispatch(chgName("Qweq")),
dispatch(chgPw("pw")))
console.log(status,name,password,123)
history.push("/subPage2")
}
const handleClick=()=>{
if(status){
logout()
}else if(status === false){
login1()
}
console.log(status,name,password,789)
// logout()
// console.log(status,name,password,"logout")
// login1()
// console.log(status,name,password,"login")
}
return(
<>
<button
className="btn"
onClick={handleClick}
>
{loginStatus}
</button>
</>
)
}
export default LoginOutBtn
You aren't using redux correctly.
// Actions
function Login(name, password) => ({ type: 'LOGIN', payload: { name, password }});
function Logout() => ({ type: 'LOGOUT' });
// Reducer
function Reducer(state, action) {
...
case 'LOGIN': {
return { ...state, ...action.payload, status: true };
}
case 'LOGOUT': {
return { ...state, status: false };
}
...
}
However, I still recommend to not use redux at all. Just use react context with hooks.
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.
This is the first time I am working with redux saga. I have a backend route called https://localhost:5000/developers/signup. I have a signup form:
import { FormEvent, useState } from 'react';
import HttpService from 'services/Http';
export default function Signup() {
const [formData, setFormData] = useState({ firstName: '', lastName: '', email: '', password: '' });
const handleSubmit = async (e: FormEvent) => {
e.preventDefault();
try {
const httpService = new HttpService('api/developers/signup');
const res = await httpService.create(formData);
// I receive the user data + JWT token
console.log(res);
} catch (err) {
console.log(err);
}
};
return (
<main>
<h1>Signup</h1>
<form onSubmit={handleSubmit}>
// Some JSX to show the form
</form>
</main>
);
}
Store:
import { applyMiddleware, combineReducers, compose, createStore } from 'redux';
import createSagaMiddleware from 'redux-saga';
import authReducer from './ducks/auth';
import rootSaga from './sagas/root';
const reducers = combineReducers({
auth: authReducer,
});
const sagas = createSagaMiddleware();
const composeSetup =
/*#ts-ignore eslint-disable */
process.env.NODE_ENV !== 'production' && typeof window === 'object' && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
? /*#ts-ignore eslint-disable */
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
: compose;
/*eslint-enable */
const middleWare = [sagas];
const store = createStore(reducers, composeSetup(applyMiddleware(...middleWare)));
sagas.run(rootSaga);
export default store;
I am unable to understand what redux saga does, It would be great if someone could explain this. I've seen a lot of posts and youtube video. I looked at the docs but then it did not have a basic AJAX example. I would like to have a redux state structure like this:
{
auth: {
// Some auth data like the token & user details
}
}
Also, I am using functional components, so It would be great if your solution is compatible with that. Looking forward to talking, thanks in advance!
you should create action to send data to the server
and use the action in your function
const handleSubmit = async (e: FormEvent) => {
e.preventDefault();
dispatch(sendDataForSignup(e))
};
On submit fire an action with form data
import { signUp } from "actions";
const handleSubmit = async (e) => {
e.preventDefault();
signUp(formData);
};
which will forward the type and payload to watcher
export const signUp = (payload) => ({
type: "SIGNUP_REQUEST",
payload
});
the watcher will take the latest action and pass it to worker function that will make the api call and dispatch an action with response type and data
import { put, takeLatest, all } from "redux-saga/effects";
function* signupWorker() {
const json = yield fetch("URL/signup").then((response) => response.json());
yield put({ type: "SIGNUP_SUCCESS", json: json.data });
}
function* signupWatcher() {
yield takeLatest("SIGNUP_REQUEST", signupWorker);
}
export default function* rootSaga() {
yield all([signupWatcher()]);
}
and the reducer will handle the data as you like based on the types
const reducer = (state = {}, action) => {
switch (action.type) {
case "SIGNUP_REQUEST":
return { ...state, loading: true };
case "SIGNUP_SUCCESS":
return { ...state, ...action.payload, loading: false };
default:
return state;
}
};
I'm just starting in redux and I want to include it on my existing app. What I want to do is to store my login response for me to use the user details on other page.
LandingPage.js
import { useDispatch } from 'react-redux'
function LandingPage(){
const dispatch = useDispatch();
const authLogin = async()=>{
const response = await axios.get('/api',)
let responseValue = response.data.success
if(responseValue === true) {
const parsedData = JSON.parse(response.data.resp)
dispatch({
type: 'SAVE_AUTH',
payload: {
isLoggedIn: responseValue,
username: parsedData.data.user.userName,
token: parsedData.data.token
}
})
}
useEffect(() => {
authLogin();
}, [])
return (
<div>
<label>Authenticating....</label>
<Login1 /> //updated based on #LindaPaiste's answer
</div>
export default LandingPage;
MainLanding.js
import React from 'react'
import Login1 from './Login1'
function MainLanding(){
return(
<div>
<h1>User Login Details</h1>
<Login1 /> //Nothing hapens here
</div>
)
}
export default MainLanding;
Login1.js
import React from 'react'
import LoginDetailsx from './LoginDetailsx'
import { useSelector } from 'react-redux'
function Login1(){
const userLoginDetails = useSelector((state) => state.loginDetails)
console.log('userLoginDetails',userLoginDetails)
return(
<div>
<h2>Login Details</h2>
<LoginDetailsx isLogin={userLoginDetails.isLoggedIn} username={userLoginDetails.username} token={userLoginDetails.token}/>
})}
</div>
)}
export default Login1;
loginDetailsReducer.js
const initialState = [
{
isLoggedIn: false,
}];
const loginDetailsReducer = (state = initialState, action) => {
const { type, payload } = action;
console.log('typex',type)
console.log('payloadx',payload)
switch(type){
case "SAVE_AUTH":
alert('dasdasd')
return payload;
case "LOGOUT_AUTH":
return initialState
default:
return state;
}
}
export default loginDetailsReducer;
rootReducer.js
import { combineReducers } from 'redux'
import loginDetailsReducer from '../reduxReducers/loginDetailsReducer'
const rootReducer = combineReducers({
loginDetails: loginDetailsReducer
});
export default rootReducer;
store.js
import { createStore } from 'redux'
import rootReducer from '../reduxReducers/rootReducer'
const store = createStore(rootReducer);
export default store;
LoginDetailsx.js
import React from 'react'
function LoginDetailsx(props){
return(
<div>
<p>Details: isloggedin: {props.isloggedin}, username: {props.username}, token: {props.token}</p>
</div>
)
}
export default LoginDetailsx;
This is what I'm getting on MainLanding.js after successful login.
and this is what i'm getting on LandingPage.js console.log
State Shape
While not necessarily a problem, it really doesn't make sense that the loginDetails state should be an array. Only one user should be logged in at a time, so it should just be an object with the user details. That makes your reducer extremely simple (as always Redux Toolkit can make it even simpler).
You'll want to add a logout case too. isLoggedIn should be a boolean instead of a string. I personally think that undefined makes more sense than '' for username and token when there is no logged in user but that's up to you.
const initialState = {
isLoggedIn: false,
// no username or token when logged out
};
const loginDetailsReducer = (state = initialState, action) => {
const { type, payload } = action;
switch(type) {
case "SAVE_AUTH":
// replace the state with the action payload
return payload;
case "LOGOUT_AUTH":
// revert to initial state
return initialState;
default:
return state;
}
}
export default loginDetailsReducer;
Logging In
I was going to say that asynchronous actions like API calls need to be done inside a useEffect hook in the component. You can use an empty dependency array to run the effect once when the component is mounted.
useEffect(() => {
authLogin();
}, []);
But now I'm looking at your image and it seems like you are executing the action in response to a button click, so that's fine too.
axios handles JSON parsing so you should not need to use JSON.parse() (unless your API is returning strange data).
function MainLanding() {
const isLoggedIn = useSelector((state) => state.loginDetails.isLoggedIn);
// access dispatch function
const dispatch = useDispatch();
// define the function to log in
const authLogin = async () => {
const response = await axios.get("/api");
const data = response.data;
if (data.success === true) {
dispatch({
type: "SAVE_AUTH",
payload: {
isLoggedIn: true,
username: data.resp.user.userName,
token: data.resp.data.token
}
});
}
};
return (
<div>
{isLoggedIn ? (
<>
<h1>User Login Details</h1>
<Login1 />
</>
) : (
<button onClick={authLogin}>Log In</button>
)}
</div>
);
}
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 am fairly new to React and Redux and I have an issue with my component not updating on the final dispatch that updates a redux store. I am using a thunk to preload some data to drive various pieces of my site. I can see the thunk working and the state updating seemingly correctly but when the data fetch success dispatch happens, the component is not seeing a change in state and subsequently not re rendering. the interesting part is that the first dispatch which sets a loading flag is being seen by the component and it is reacting correctly. Here is my code:
actions
import { programsConstants } from '../constants';
import axios from 'axios'
export const programsActions = {
begin,
success,
error,
};
export const loadPrograms = () => dispatch => {
dispatch(programsActions.begin());
axios
.get('/programs/data')
.then((res) => {
dispatch(programsActions.success(res.data.results));
})
.catch((err) => {
dispatch(programsActions.error(err.message));
});
};
function begin() {
return {type:programsConstants.BEGIN};
}
function success(data) {
return {type:programsConstants.SUCCESS, payload: data};
}
function error(message) {
return {type:programsConstants.ERROR, payload:message};
}
reducers
import {programsConstants} from '../constants';
import React from "react";
const initialState = {
data: [],
loading: false,
error: null
};
export function programs(state = initialState, action) {
switch (action.type) {
case programsConstants.BEGIN:
return fetchPrograms(state);
case programsConstants.SUCCESS:
return populatePrograms(state, action);
case programsConstants.ERROR:
return fetchError(state, action);
case programsConstants.EXPANDED:
return programsExpanded(state, action);
default:
return state
}
}
function fetchPrograms(state = {}) {
return { ...state, data: [], loading: true, error: null };
}
function populatePrograms(state = {}, action) {
return { ...state, data: action.payload, loading: false, error: null };
}
function fetchError(state = {}, action) {
return { ...state, data: [], loading: false, error: action.payload };
}
component
import React from "react";
import { connect } from 'react-redux';
import { Route, Switch, Redirect } from "react-router-dom";
import { Header, Footer, Sidebar } from "../../components";
import dashboardRoutes from "../../routes/dashboard.jsx";
import Loading from "../../components/Loading/Loading";
import {loadPrograms} from "../../actions/programs.actions";
class Dashboard extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
this.props.dispatch(loadPrograms());
}
render() {
const { error, loading } = this.props;
if (loading) {
return <div><Loading loading={true} /></div>
}
if (error) {
return <div style={{ color: 'red' }}>ERROR: {error}</div>
}
return (
<div className="wrapper">
<Sidebar {...this.props} routes={dashboardRoutes} />
<div className="main-panel" ref="mainPanel">
<Header {...this.props} />
<Switch>
{dashboardRoutes.map((prop, key) => {
let Component = prop.component;
return (
<Route path={prop.path} component={props => <Component {...props} />} key={key} />
);
})}
</Switch>
<Footer fluid />
</div>
</div>
);
}
}
const mapStateToProps = state => ({
loading: state.programs.loading,
error: state.programs.error
});
export default connect(mapStateToProps)(Dashboard);
The component should receive updated props from the success dispatch and re render with the updated data. Currently the component only re renders on the begin dispatch and shows the loading component correctly but doesn't re render with the data is retrieved and updated to the state by the thunk.
I've researched this for a couple days and the generally accepted cause for the component not getting a state refresh is inadvertent state mutation rather than returning a new state. I don't think I'm mutating the state but perhaps I am.
Any help would much appreciated!
Update 1
As requested here's the code for creating the store and combining the reducers
store:
const loggerMiddleware = createLogger();
const composeEnhancers =
typeof window === 'object' &&
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
}) : compose;
const enhancer = composeEnhancers(
applyMiddleware(
thunk,
loggerMiddleware)
);
export const store = createStore(rootReducer, enhancer);
reducer combine:
import { combineReducers } from 'redux';
import { alert } from './alert.reducer';
import { programs } from './programs.reducer';
import { sidenav } from './sidenav.reducer';
const rootReducer = combineReducers({
programs,
sidenav,
alert
});
export default rootReducer;
The 2nd param is expected to be [preloadedState]:
export const store = createStore(rootReducer, {} , enhancer);
axios.get return a promise that you need to await for to get your data:
Try this:
export const loadPrograms = () => async (dispatch) => {
dispatch(programsActions.begin());
try {
const res = await axios.get('/programs/data');
const data = await res.data;
console.log('data recieved', data)
dispatch(programsActions.success(data.results));
} catch (error) {
dispatch(programsActions.error(error));
}
};
const mapStateToProps = state => ({
loading: state.programs.loading,
error: state.programs.error,
data: state.programs.data,
});
Action Call
import React from 'react';
import { connect } from 'react-redux';
import { loadPrograms } from '../../actions/programs.actions';
class Dashboard extends React.Component {
componentDidMount() {
// Try to call you action this way:
this.props.loadProgramsAction(); // <== Look at this
}
}
const mapStateToProps = state => ({
loading: state.programs.loading,
error: state.programs.error,
});
export default connect(
mapStateToProps,
{
loadProgramsAction: loadPrograms,
},
)(Dashboard);
After three days of research and refactoring, I finally figured out the problem and got it working. Turns out that the version of react-redux is was using (6.0.1) was the issue. Rolled back to 5.1.1 and everything worked flawlessly. Not sure if something is broken in 6.0.1 or if I was just using wrong.