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});
Related
In my slice.js file, I have my slices:
import {createSlice} from "#reduxjs/toolkit";
import { combineReducers } from "#reduxjs/toolkit";
export const counterSlice = createSlice({
name:"trigger",
initialState:{
trigger:false
},
reducers:{
flip: (state) =>{
state.trigger = !state.trigger;
},
}
})
export const projectNameSlice = createSlice({
name:"setName",
initialState:{
name: "project"
},
reducers:{
setName: (state, action) =>{
state.name = action.payload;
}
}
})
export const {flip} = counterSlice.actions;
export const {setName} = projectNameSlice.actions;
export default combineReducers({
counterSlice:counterSlice.reducer,
projectNameSlice: projectNameSlice.reducer
})
At the bottom, I used the combineReducer to export multiple slices, and in my store.js I have the following code:
import { configureStore } from "#reduxjs/toolkit";
import {counterSlice, projectNameSlice} from "./trigger";
export default configureStore({
reducer:{
trigger: counterSlice,
projectName:projectNameSlice
},
})
When I try to deconstruct the values of either reducers like this:
const {trigger} = useSelector((state) => state.trigger);
const {myProj} = useSelector((state) => state.projectName);
I get the following error:
Store does not have a valid reducer. Make sure the argument passed to combineReducers is an object whose values are reducers.
When I export only one reducer, it works fine, but when I add the second I get this error, I looked at threads of other people getting the same error, but the cause/solution in their case did not work for me.
You don't need that combineReducers call, but you cannot put your slices into configureStore, but you have to put the slice reducers in:
import { configureStore } from "#reduxjs/toolkit";
import {counterSlice, projectNameSlice} from "./trigger";
export default configureStore({
reducer:{
trigger: counterSlice.reducer,
projectName:projectNameSlice.reducer,
},
})
I think this is what you want
export default configureStore({
reducer: combineReducers({
trigger: counterSlice,
projectName: projectNameSlice,
})
})
https://redux.js.org/api/combinereducers
how to export todos reducer from todosSlice by using createSlice #reduxjs/toolkit and how to import it to store by using configureStore
here is my todosSlice file :
//todosSlice file
import {createSlice} from '#reduxjs/toolkit';
const todosSlice = createSlice({
name: 'todos',
initialState: [],
reducers: {
addTodos: (state, action) => {
state.push(action.payload)
},
removeTodos: (state, action) => {
state.filter(todo => todo.id !== action.payload.id)
}
}
});
export const { addTodos, removeTodos } = todosSlice.actions;
export default todosSlice.reducer; //this reducer that i want to import to store file
and here is my store file :
import {configureStore} from '#reduxjs/toolkit';
//and here is supposed where the reducer imported
const store = configureStore({
reducer: {
todos: //here todos reducer should be.
anyReducer: anyReducer,
}
});
export default store;
The export default statement in your TodosSlice allows you to import the function with the name you want. So here what you could do.
import {configureStore} from '#reduxjs/toolkit';
import todosReducer from 'path/to/your/TodosSlice';
const store = configureStore({
reducer: {
todos: todosReducer,
anyReducer: anyReducer,
}
});
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.
Pls tell me what I did wrong?
configureStore.js:
import {configureStore, combineReducers} from "#reduxjs/toolkit";
import pokemonSearchSlice from "./slices/pokemonSearch";
const reducer = combineReducers({
pokemonSearch: pokemonSearchSlice
});
const store = configureStore({
reducer
});
export default store;
pokemonSearch.js
import { createSlice } from "#reduxjs/toolkit";
const pokemonSearchSlice = createSlice({
name: "pokemonSearch",
initialState: {
searchInputValue: ""
},
reducers: {
setValue:(state, action)=>({...state, searchInputalue: action.payload})
}
}) ;
export const {setValue} = pokemonSearchSlice.actions;
export default pokemonSearchSlice;
Full text of error: redux.js:394 Store does not have a valid reducer. Make sure the argument passed to combineReducers is an object whose values are reducers.
Also I'm not able to get that "searchInputValue" from the store, console says :Cannot destructure property 'searchInputValue' of '(0 , react_redux__WEBPACK_IMPORTED_MODULE_2__.useSelector)(...)' as it is undefined.
But I think it's because of combineReducers error. Any suggestions?
There's a few issues here:
configureStore automatically calls combineReducers for you, so you don't want to do that directly.
configureStore's reducer object needs reducers, but you're passing it a slice. (The slice contains a reducer, but the slice itself is not a reducer.)
Your setValue is returning a new object, but RTK uses Immer, so you should mutate the state passed into the function and not return anything.
In the end your code should look like this:
// configureStore.js
import { configureStore } from "#reduxjs/toolkit";
import pokemonSearchSlice from "./slices/pokemonSearch";
const store = configureStore({
reducer: {
pokemon: pokemonSearchSlice,
},
});
export default store;
// slices/pokemonSearch.js"
import { createSlice } from "#reduxjs/toolkit";
const pokemonSearchSlice = createSlice({
name: "pokemonSearch",
initialState: {
searchInputValue: "",
},
reducers: {
setValue: (state, action) => {
state.searchInputValue = action.payload;
},
},
});
export const { setValue } = pokemonSearchSlice.actions;
export default pokemonSearchSlice.reducer;
You need to pass the reducer from the slice, like this
const pokemonSearchSlice = createSlice({
name: "pokemonSearch",
initialState: {
searchInputValue: ""
},
reducers: {
setValue: (state, action) => ({ ...state, searchInputalue: action.payload })
}
});
const { actions, reducer } = pokemonSearchSlice;
export const {setValue} = actions;
export default reducer
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. :/