I am trying to use redux for react.
i have action creator:
export const API_PATH_UPDATED = 'query/apiPathUpdated';
export const apiPathUpdated = (apiPath) => ({
type: API_PATH_UPDATED,
payload: {
apiPath,
},
});
This action creator is called in this function:
const getUpcoming = useCallback(
() => dispatch(apiPathUpdated('/movie/upcoming')),
[dispatch],
);
And should update my state with the following reducer:
const initialState = {
basePath: 'https://api.themoviedb.org/3',
apiPath: '/movie/popular',
lang: 'en-US',
adult: false,
genresPath: '/genre/movie/list',
};
const queryReducer = (state = initialState, action) => {
switch (action.type) {
case API_PATH_UPDATED: {
return {
...state,
apiPath: action.payload.apiPath,
};
}
default:
return state;
}
};
export default queryReducer;
And actually when i click on button that triggers getUpcoming function in Redux devtools i see that action is treggered with right argiments:
enter image description here
But it doesn't update state and i see the old apiPath value:
enter image description here
Here is the code for my store:
import {
createStore, applyMiddleware, compose, combineReducers,
} from 'redux';
import camelMiddleware from 'redux-camel';
import thunk from 'redux-thunk';
import moviesReducer from './movies/index';
import queryReducer from './query/index';
import genresReducer from './genres/index';
const rootReducer = combineReducers({
query: queryReducer,
movies: moviesReducer,
genres: genresReducer,
});
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
trace: true, traceLimit: 25,
}) || compose;
const store = createStore(rootReducer, composeEnhancers(
applyMiddleware(thunk, camelMiddleware()),
));
export default store;
Thanks everyone for help.
I found my issue. I forgot to put curly braces around named import in my queryReducer file.
so the line
import API_PATH_UPDATED from './queryActions';
should be
import { API_PATH_UPDATED } from './queryActions';
That what happens when coding at 5a.m. :/
Related
I have a react app that was working until I converted over to a nextjs app. The axios call is being made, the data is being returned but not being stored(the selectors are showing undefined). I suspect it is a setup issue in the conversion.
The call which works:
React.useEffect(() => {
dispatch(fetchCoinsByMarketCap()); // coin gecko
}, [dispatch]);
The slice. I am logging correct data and status in my fetchCoinsByMarketCap.fulfilled case:
import axios from "axios";
import { createAsyncThunk, createSlice } from "#reduxjs/toolkit";
import { coinGecko } from "../../url/urlList";
const initialState = {
coinsMCap: [],
status: "idle",
error: null,
};
export const fetchCoinsByMarketCap = createAsyncThunk(
"coinsByMarketCap/fetchCoinsByMarketCap",
async () => {
const res = await coinGecko.get(
`/coins/markets/?
vs_currency=usd&order=market_cap_desc&per_page=50&page=1&sparkline=false`
);
return res.data;
}
);
export const marketCapSlice = createSlice({
name: "coinsByMarketCap",
initialState,
reducers: {
updateCoinsMcapState: (state, action) => {
Object.assign(state, action.payload);
},
},
extraReducers(builder) {
builder
.addCase(fetchCoinsByMarketCap.pending, (state) => {
state.status = "loading";
})
.addCase(fetchCoinsByMarketCap.fulfilled, (state, action) => {
state.coinsMCap = action.payload;
state.status = "succeeded";
console.log("sc: ", state.coinsMCap);
console.log("ss: ", state.status);
})
.addCase(fetchCoinsByMarketCap.rejected, (state, action) => {
state.status = "failed";
state.error = action.error.message;
});
},
});
export const { updateCoinsMcapState } = marketCapSlice.actions;
export default marketCapSlice.reducer;
My store:
import { createStore, applyMiddleware, compose } from "redux";
import thunk from "redux-thunk";
import { createWrapper } from "next-redux-wrapper";
import reducer from "./rootReducer";
const middleware = [thunk];
const makeStore = () =>
createStore(reducer, compose(applyMiddleware(...middleware)));
export const wrapper = createWrapper(makeStore);
The next _app file which has the wrapper.withRedux export:
import React from "react";
import { wrapper } from "../redux/store";
const MyApp = ({ Component, pageProps }) => <Component {...pageProps} />;
export default wrapper.withRedux(MyApp);
The root reducer:
import { combineReducers } from "redux";
import marketCapReducer from "./slices/marketCapSlice";
const reducer = combineReducers({
coinsByMarketCap: marketCapReducer,
});
export default reducer;
The selector file:
// marketCapSlice selectors
export const selectCoinsMCap = (state) => state.coinsByMarketCap.coinsMCap;
export const selectCoinsMCapStatus = (state) => state.coinsByMarketCap.status;
export const selectCoinsMCapError = (state) => state.coinsByMarketCap.error;
These selectors should have data after the axios call, but they don't. Again bear in mind that this used to work before I tried to convert it to next and the only thing I changed was the store and the _app.js files. The old store without next was very simple:
import { configureStore } from "#reduxjs/toolkit";
import reducer from "./rootReducer";
const store = configureStore({ reducer });
export default store;
And the standard provider setup in the old index:
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById("root")
);
Maybe one of you nextjs experts can help me.
A reducer is supposed to return the updated state, as far as I know. It seems to me like you may have forgotten to return a value here:
reducers: {
updateCoinsMcapState: (state, action) => {
Object.assign(state, action.payload);
},
},
Should it be something like this?
reducers: {
updateCoinsMcapState: (state, action) => {
return Object.assign(state, action.payload);
},
},
Here is the store setup that fixed it:
import { configureStore } from "#reduxjs/toolkit";
import { applyMiddleware, compose } from "redux";
import thunk from "redux-thunk";
import { createWrapper } from "next-redux-wrapper";
import reducer from "./rootReducer";
const middleware = [thunk];
const makeStore = () =>
configureStore({ reducer }, compose(applyMiddleware(...middleware)));
export const wrapper = createWrapper(makeStore);
I used configureStore instead of createStore and enclosed reducer in curly braces.
I am following the Redux tutorials on the Redux site and I am having difficulty using combine reducers. If I run my code without combine reducers, it works fine. As soon as I put the single reducer into the combined reducers function. I get this: Error: "reducer" is a required argument, and must be a function or an object of functions that can be passed to combineReducers. What am I doing wrong?
Here is the store before using combine reducers:
import { configureStore } from "#reduxjs/toolkit";
import CoinsAllReducer from "./slices/CoinsAll";
export const store = configureStore({
reducer: {
coinsAll: CoinsAllReducer,
},
});
and here is the slice:
import axios from "axios";
import { createAsyncThunk, createSlice } from "#reduxjs/toolkit";
const initialState = {
coinsAll: [],
status: "idle",
error: null,
};
export const fetchAllCoins = createAsyncThunk(
"coinsAll/fetchAllCoins",
async () => {
const res = await axios.get(
`https://api.coingecko.com/api/v3/coins/markets/?vs_currency=usd&order=market_cap_desc&per_page=100&page=1&sparkline=false`
);
return res.data;
}
);
export const coinsAll = createSlice({
name: "coinsAll",
initialState,
reducers: {},
extraReducers(builder) {
builder
.addCase(fetchAllCoins.pending, (state) => {
state.status = "loading";
})
.addCase(fetchAllCoins.fulfilled, (state, action) => {
state.status = "succeeded";
state.coinsAll = action.payload;
})
.addCase(fetchAllCoins.rejected, (state, action) => {
state.status = "failed";
state.error = action.error.message;
});
},
});
export const selectCoinsAll = (state) => state.coinsAll.coinsAll;
export const selectStatus = (state) => state.coinsAll.status;
export default coinsAll.reducer;
The above code works and gets the data from the endpoint.
Here is the store modified to use the combined reducer:
import { configureStore } from "#reduxjs/toolkit";
import reducer from "./rootReducer";
export const store = configureStore(reducer);
Here is the rootReducer:
import { combineReducers } from "redux";
import coinsAllReducer from "./slices/coinsAll";
const reducer = combineReducers({
coinsAll: coinsAllReducer,
});
export default reducer;
I changed nothing in the slice.
Any help would be appreciated, thanks.
configureStore takes an object with a reducer key.
It should be
export const store = configureStore({
reducer, // <-- object shorthand for the imported root reducer
// other configurations, etc...
});
You have to pass the combined reducer inside the configuration object to the configureStore method
export const store = configureStore({
reducer:combinedReducer
});
Hence, your updated code should be
import { configureStore } from "#reduxjs/toolkit";
import reducer from "./rootReducer";
export const store = configureStore({reducer:reducer});
And since key and value is same, you can simply go with
import { configureStore } from "#reduxjs/toolkit";
import reducer from "./rootReducer";
export const store = configureStore({reducer});
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}
</>
}
}
Given:
//Reducers/ScriptReaderReducers.js
let initialState = {};
const sceneReaderReducers = (state = initialState, action => { //eslint-disable-line
switch (action.type) {
case ScriptActions.MOVE_NEXT: {
return {
...state, //eslint-disable-line
currentIndex: action.currentIndex
};
}
I've mapped this into an app that appears to be otherwise working:
import { createHashHistory } from 'history';
import { applyMiddleware, combineReducers, createStore} from 'redux';
import { connectRouter, routerMiddleware } from 'connected-react-router';
import createSagaMiddleware from 'redux-saga';
import SceneReducer from './Logic/Reducers/ScriptReaderReducers.js';
import ScriptSagas from './Logic/Sagas/ScriptSagas';
const sagaMiddleware = createSagaMiddleware();
// Start history
const history = createHashHistory({});
//Merge Reducers
let rootReducer = combineReducers({
SceneReducer
});
// Merge middlewares
let middlewares = [
routerMiddleware(history),
sagaMiddleware
];
// Development adds logging, must be last
if ( process.env.NODE_ENV !== 'production') {
middlewares.push( require('redux-logger')({
// Change this configuration to your liking
duration: true, collapsed: true
}) );
}
// Generate store
const store = createStore(connectRouter(history)(rootReducer),
{},
applyMiddleware(...middlewares)
);
console.error(store); //eslint-disable-line
sagaMiddleware.run(ScriptSagas);
// Export all the separate modules
export {
history,
store
};
On starting up this app in dev, I receive an error message: ScriptReaderReducers.js:9 Uncaught TypeError: Cannot read property 'type' of undefined. This appears to be the first test of the switch on action.
I've proven to my satisfaction that actions are being dispatched but I can't seem to get a call stack for the exact timing on this error.
Why would action be null within the Reducers?
As jmargolisvt said, you missed () in your code.
Please change line const sceneReaderReducers = (state = initialState, action => { to const sceneReaderReducers = ((state = initialState, action) => { and try again.
I got an error when using redux-persist. I could find few documents about redux-persist v5. And I just follow the official usage example. But I am confused about this. Before I use redux-persist, I can get state from store correctly. But I want to keep login state in local. So I try to use redux-persist. Then I got some problems. Here is my code:
reducer.js
const initialState = {
isLogin: false,
uname: "",
}
const userReducer = (state = initialState, action) => {
switch(action.type) {
case 'DO_LOGIN':
return Object.assign({}, state, {
isLogin: true,
uname: action.payload.username
})
default:
return state
}
}
const reducers = combineReducers({
userInfo: userReducer
})
export default reducers
store.js
import thunk from 'redux-thunk'
import { createLogger } from 'redux-logger'
import { createStore, applyMiddleware, compose } from 'redux'
import { persistStore, persistCombineReducers } from 'redux-persist'
import storage from 'redux-persist/es/storage'
import reducers from '../reducers'
const loggerMiddleware = createLogger()
const middleware = [thunk, loggerMiddleware]
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
const configureStore = composeEnhancers(
applyMiddleware(...middleware),
)(createStore)
const config = {
key: 'root',
version: 1,
storage,
}
const combinedReducer = persistCombineReducers(config, reducers)
const createAppStore = () => {
let store = configureStore(combinedReducer)
let persistor = persistStore(store)
return { persistor, store }
}
export default createAppStore
App.js
const mapStateToProps = (state) => ({
logged: state.userInfo.isLogin
})
When I run this code I got this error message TypeError: Cannot read property 'isLogin' of undefined
And this error message in console Store does not have a valid reducer. Make sure the argument passed to combineReducers is an object whose values are reducers.
I guess something is not correct when combine reducers. But I have no idea where is wrong?
In the redux-persist docs:
import reducers from './reducers' // where reducers is a object of reducers
the 2nd argument to persistCombineReducers must be an object of reducers.
The export in yout reducer.js should be:
export default {
reducer: reducer
};
Make the changes and let me know if it solved.