I Have a component (simple) and a reducer to modify value in my state.
I increment my value in my state, but when I want to display this value updated in my component, it's not updated with the new state value.
I don't know if my usedispatch is incorrect.
In my dev tools I see my state updated and incremented, but my page don't change...
Here is my reducer:
import { data } from "jquery";
export const INCREMENT_COUNTER = "INCREMENT_COUNTER";
export const DECREMENT_COUNTER = "DECREMENT_COUNTER";
export function increment(amount) {
console.log('-------------------amount', amount);
return {
type: INCREMENT_COUNTER,
payload: amount
}
}
export function decrement(amount) {
return {
type: DECREMENT_COUNTER,
payload: amount,
};
}
const initialState = {
data: 42,
}
export default function testReducer(state = initialState, {type, payload}) {
Object.assign(state.data, data);
switch (type) {
case INCREMENT_COUNTER:
console.log('increment reducer', {...state});
return {
...state,
...state.data,
data: state.data + payload,
};
case DECREMENT_COUNTER:
console.log('decrement reducer');
return {
//...state,
data: state.data - payload,
};
default:
return state;
}
}
and there is my component :
import React, {useCallback, useEffect} from "react"
import { useStore } from "react-redux";
import {useDispatch, useSelector} from "react-redux"
import {decrement, increment} from "./testReducer.js"
export default function Sandbox() {
const dispatch = useDispatch();
const data = useSelector((state) => state.test);
const store= useStore();
const toto = store.getState().test.data;
console.log('------------ttititi--------', store.getState().test.data);
const inc = useCallback(() => {
console.log('-----------inc', data);
console.log('-----------inc2', toto);
dispatch(increment(2))
}, [dispatch, data, toto]
);
const dec = useCallback(() => {
dispatch(decrement(2))
}, [dispatch, data]);
return (
<>
{toto.test?.data}
<h1>Testin2g</h1>
<h3>The data is: {data.data} -- {toto}</h3>
<button onClick={inc} content="increment" color="green">increment</button>
<button onClick={dec} content="decrement" color="red">decrement</button>
</>
)
}
This is how I do in my rootReducer:
import {combineReducers} from 'redux'
import testReducer from "../sandbox/testReducer.js";
import authReducer from "../modules/auth/authReducer";
import businessesReducer from "../modules/business/businessesReducer";
// import eventReducer from '../../features/events/eventReducer'
const rootReducer = combineReducers({
test: testReducer,
auth: authReducer,
businesses: businessesReducer,
// event: eventReducer
})
export default rootReducer;
configureStore.js file :
import {applyMiddleware, compose, createStore} from "redux"
import rootReducer from "./rootReducer"
import thunk from "redux-thunk";
export function configureStore() {
const composeEnchancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
return createStore(rootReducer, composeEnchancer(applyMiddleware(thunk)))
}
Related
authReducer.js
import {
LOGIN_SUCCESS,
LOGIN_FAIL,
REGISTER_SUCCESS,
REGISTER_FAIL
} from '../Actions/types';
export default (state = { isLoading: true, user: [] }, action) => {
switch(action.type){
case LOGIN_SUCCESS:
localStorage.setItem('token',action.payload.token);
return {
...state,
user: action.payload.data,
}
case LOGIN_FAIL:
return state;
case REGISTER_SUCCESS:
return {
...state,
user: action.payload.data,
}
case REGISTER_FAIL:
return state;
default:
return state;
}
}
profile.js
import React from 'react'
import { useSelector} from 'react-redux'
export const EditProfile = () => {
const data = useSelector((state) => state.user);
return (
<div>
{data}
</div>
)
}
index.js (rootReducer)
import { combineReducers } from 'redux';
import authReducer from './authReducer';
export default combineReducers({
auth: authReducer,
})
store.js
import {createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './Reducers';
const initialState = {};
const middleware = [thunk];
const store = createStore(rootReducer,initialState,compose(
applyMiddleware(...middleware),
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
));
export default store;
So Here when i logged in the details were on user object so i need to access then in profile.js so that i can display the details of the user
I don't how can i access i need to save the user data which are stored in user (redux store).
I don't know what i am missing any help will be grateful
I don't know how you setup your redux.
This is how I usually set them up:-
A. Reducer
/slices/auth.js
import { createSlice } from '#reduxjs/toolkit'
const initialState = {
user: [],
isLoading: false
}
export const authSlice = createSlice({
name: 'auth',
initialState,
// // The `reducers` field lets us define reducers and generate associated actions
reducers: {
setUser: (state, action) => {
// Redux Toolkit allows us to write "mutating" logic in reducers. It
// doesn't actually mutate the state because it uses the Immer library,
// which detects changes to a "draft state" and produces a brand new
// immutable state based off those changes
state.user = action.payload
},
setLoading: (state, action) => {
state.loading = action.payload
}
}
})
export const { setUser, setLoading } = authSlice.actions;
// The function below is called a selector and allows us to select a value from
// the state. Selectors can also be defined inline where they're used instead of
// in the slice file. For example: `useSelector((state: RootState) => state.counter.value)`
export const selectUser = (state) => state.auth.user
export const selectLoading = (state) => state.auth.isLoading
export default authSlice.reducer;
B. Store
store.js
import { configureStore, getDefaultMiddleware } from '#reduxjs/toolkit'
import authReducer from '../slices/auth'
export const store = configureStore({
reducer: {
auth: authReducer
},
middleware: getDefaultMiddleware({
serializableCheck: false
})
})
C. App Component
import { Provider } from 'react-redux'
import { store } from '../app/store'
export default function App() {
return {
<>
<Provider store={store}>
{/* your content... */}
</Provider>
</>
}
}
D. Component (where you use the redux)
import { useContext } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { selectUser } from '../slices/auth'
export default function EditProfile() {
const dispatch = useDispatch()
const user = useSelector(selectUser)
return {
<>
{user}
</>
}
}
I am clearly missing something about how this should work. I have a slice, it has reducers I can bring those in and I can see console.log firing as expected... buuut Redux Dev Tools says I have not changed my state - the default null still is still the listed value.
Slice
import { User } from "#firebase/auth";
import { createSlice, PayloadAction } from "#reduxjs/toolkit";
//Slice definition
// define state
interface UserState {
currentUser: User | null;
}
const initialState: UserState = {
currentUser: null,
};
//define action creators
const userSlice = createSlice({
name: "user",
initialState,
reducers: {
setUser(state, action: PayloadAction<User>) {
state.currentUser = action.payload;
},
setNoUser(state) {
state.currentUser = null;
},
},
});
//export all action creators
export const { setUser, setNoUser } = userSlice.actions;
//export reducer that handles all actions
export default userSlice.reducer;
Store
import { configureStore } from "#reduxjs/toolkit";
import userReducer from "../features/user/user-slice";
export const store = configureStore({
reducer: { user: userReducer },
});
export type AppDispatch = typeof store.dispatch;
export type RootState = ReturnType<typeof store.getState>;
App snippet
import React, { useEffect, useState } from "react";
...
import { setUser, setNoUser } from "./features/user/user-slice";
function App() {
const [loading, setLoading] = useState<boolean>(true);
useEffect(() => {
auth.onAuthStateChanged((user) => {
if (user) {
console.log("useEffect");
console.log(user);
setUser(user);
} else {
setNoUser();
console.log("useEffect, no user");
}
setLoading(false);
});
});
if (loading) return <Spinner animation="border" color="dark" />;
return <App/>;
}
export default App;
the reason for this is because setUser is an action and the action needs to be dispatched.
Per Mark Erikson's Video from may it is advisable to create a typed version of the useSelector and useDispatch hooks when working with typescript.
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
import { RootState, AppDispatch } from "./store";
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const userAppSelector: TypedUseSelectorHook<RootState> = useSelector;
you then import the hooks from the hooks file not react-redux and dispatch or select from there:
const dispatch = useAppDispatch();
dispatch(setUser(auth.currentUser));
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.
does anyone knows why nothing happens when the button is clicked?
im trying to fetch the movielist from the server when the button is clicked but it doesnt even shows that the action is working the way i expected to be.
my react index js
import React from 'react';
import { connect } from 'react-redux';
import { getMovies } from '../../actions/movieActions';
const Home = ({ movie }) => {
const handleClick = () => {
getMovies();
};
return (
<div>
<button onClick={() => handleClick()}>Get Movies</button>
</div>
);
};
const mapStateToProps = state => ({
movie: state.movie.movies
});
export default connect(mapStateToProps, { getMovies })(Home);
my rootReducer
import { combineReducers } from 'redux';
import movieReducer from './movieReducer';
export default combineReducers({
movie: movieReducer
// log is what we are calling our state
});
my movie reducer
import { GET_MOVIES } from '../actions/types';
const initialState = {
movies: null
};
export default (state = initialState, action) => {
switch (action.type) {
case GET_MOVIES:
return {
...state,
movies: action.payload
};
default:
return state;
}
};
my movie actions
import { GET_MOVIES } from './types';
// get movies from server
export const getMovies = () => async dispatch => {
try {
const res = await fetch('http://localhost:5000/api/movies');
const data = await res.json();
dispatch({
type: GET_MOVIES,
payload: data
});
} catch (error) {
console.log(error);
}
};
my types.js
export const GET_MOVIES = 'GET_MOVIES';
my store.js
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;
Your error is in calling the action directly, instead of the connected getMovies prop here:
const handleClick = () => {
getMovies();
};
Should be:
const handleClick = () => {
this.props.getMovies();
};
I have a simple react app that fetches data from a JSON in my local and updates the state. The problem I face is that my reducer is not being called when I dispatch an action. I can see dispatch getting the response from the JSON but state is not updated
I did a console.log on reducer but no luck. I checked the Redux devtools but I don't see the state updated. Here is my code,
store.js
import {createStore, compose, applyMiddleware} from 'redux'
import thunk from 'redux-thunk'
import {routerMiddleware} from 'react-router-redux'
import rootReducer from './combined.reducer'
import {createLogger} from 'redux-logger'
let middlewares = [
thunk
]
const logger = createLogger();
if (process.env.Node_ENV !== "production" && process.env.Node_ENV !== "test") {
middlewares = [
...middlewares,
logger,
require("redux-immutable-state-invariant").default()
]
}
export default function configureStore(history) {
const composeEnhancers =
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const enhancer = composeEnhancers(
applyMiddleware(...middlewares, routerMiddleware(history))
);
const store = createStore(
rootReducer(history),
enhancer
)
if (process.env.Node_ENV !== "production" && process.env.Node_ENV !== "test" && module.hot) {
module.hot.accept("./combined.reducer", () => {
const nextReducer = require("./combined.reducer").default
store.replaceReducer(nextReducer)
})
}
return store
combined.reducer.js
import { connectRouter } from 'connected-react-router';
import { combineReducers } from 'redux';
import * as workoutReducer from './features/WorkoutList/Containers/Workout.Reducer'
const createAppReducer = (history) => combineReducers({
router: connectRouter(history),
workoutReducer: workoutReducer
})
const rootReducer = (history) => (state, action) => {
return createAppReducer(history)(state,action)
};
export default rootReducer;
folder structure:
src
features
Workout
Components
Containers
Workout.Actions.js
Workout.Constants.js
Workout.Container.js
Workout.Reducer.js
Workout.js
Workout.Constants.js:
const constants = {
GET_WORKOUT_LIST_REQUEST: "GET_WORKOUT_LIST_REQUEST",
GET_WORKOUT_LIST_SUCCESS: "GET_WORKOUT_LIST_SUCCESS",
GET_WORKOUT_LIST_FAILURE: "GET_WORKOUT_LIST_FAILURE"
}
export default constants
Workout.Container.js:
import { connect } from 'react-redux'
import {withRouter} from 'react-router'
import {bindActionCreators} from 'redux'
import Workout from './Workout'
import * as workoutAction from './Workout.Actions'
export default withRouter(connect(
(state) => ({
workoutListData: state.workoutListData
}),
(dispatch) => ({
workoutActions:bindActionCreators(workoutAction, dispatch)
})
)(Workout))
Workout.Actions.js:
import constants from "./Workout.Constants"
import {getConfigProperty} from "../../../settings"
import {makeGetCall} from "../../../utils/Api"
const WORKOUT_LIST = getConfigProperty("workoutList")
export function getWorkoutList(url = WORKOUT_LIST) {
return (dispatch, getState) => {
dispatch(getWorkoutListRequest(true))
return makeGetCall(url, getState)
.then(res => res.json())
.then(json => {
dispatch(getWorkoutListSuccess(json))
dispatch(getWorkoutListRequest(false))
})
.catch(ex => {
dispatch(getWorkoutListFailure(ex))
dispatch(getWorkoutListRequest(false))
})
}
}
export function getWorkoutListRequest(req) {
return {
type: constants.GET_WORKOUT_LIST_REQUEST,
req
}
}
export function getWorkoutListSuccess(response) {
return {
type: constants.GET_WORKOUT_LIST_SUCCESS,
response
}
}
export function getWorkoutListFailure(exception) {
return {
type: constants.GET_WORKOUT_LIST_FAILURE,
exception
}
}
Workout.Reducer.js
import constants from "./Workout.Constants"
const initialState = {
workoutListData :{}
}
function reducer(state = initialState, action) {
console.log(".................I'm here................")
switch(action.type) {
case constants.GET_WORKOUT_LIST_SUCCESS: {
return Object.assign({}, state, {
workoutListData: action.response.GET_LIST_DATA
})
}
case constants.GET_WORKOUT_LIST_FAILURE: {
return Object.assign({}, state, initialState)
}
default:
return state
}
}
export {initialState}
export default reducer
workout.js:
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import {WorkoutListItem} from './../StyledComponents/WorkoutListItem'
import {isEmpty, isEqual} from 'lodash'
import {getConfigProperty} from "../../../settings"
const ROWS_PER_PAGE = getConfigProperty("rowsPerPage")
export default class Workout extends Component {
static get PropTypes() {
return {
workoutListData: PropTypes.array.isRequired
}
}
constructor(props){
super(props)
this.state = {
workoutListData: []
}
}
componentWillMount(){
if(isEmpty(this.props.workoutListData)){
this.props.workoutActions.getWorkoutList()
}
}
loadWorkoutListItem = () => {
return <WorkoutListItem / >
}
render() {
return (
<div>
{this.loadWorkoutListItem()}
</div>)
}
}
The reducer function should have been called. I don't even see the console log printed
I don't think you want
import * as workoutReducer from './features/WorkoutList/Containers/Workout.Reducer'
but just
import workoutReducer from './features/WorkoutList/Containers/Workout.Reducer'
The first is importing the initialState and the reducer as a combined object, as opposed to just importing the reducer.
EDIT: this is in combined.reducer.js