import { createSlice } from '#reduxjs/toolkit'
export const countersSlice = createSlice({
name: 'based? based on what',
initialState: [0, 0, 0, 0],
reducers: {
updateCounter: (state, action) => {
var id = action.payload.id
var value = action.payload.value
state[id] += value
}
}
})
export const { updateCounter } = countersSlice.actions
export const selectCount = id => state => state.counter[id]
export default countersSlice.reducer
Why is it that, in the selectCount line, I have to use state.counter when .counter isn't referenced anywhere else in the slice? I do like that it's .counter but I just want to understand how it's coming up with that property.
The name property in createSlice is used internally by redux-toolkit to create the names for your actions. If the name is 'counter' then the updateCounter action will have { type: 'counter/updateCounter' }. If it's 'abc' then your action will have { type: 'abc/updateCounter' }. This name doesn't matter. As long as it's different from any other reducers in your app then you're fine.
If I change .counter to something else, it breaks the entire project
Now you are talking about something else, which is how you select your data from the root state of your app.
export const selectCount = id => state => state.counter[id]
This selector function assumes that the reducer from this slice will be on the counter property of your root reducer. The location of this reducer relative to the root state is determined by the property key when you combine your reducers with configureStore or combineReducers.
Your current selector assumes that your store looks like this:
import {configureStore} from '#reduxjs/toolkit';
import counterReducer from './yourReducerFile.js'
export default configureStore({
reducer: {
counter: counterReducer
}
});
This property key often matches the name of your slice, but it doesn't have to match.
You can use any property key as long as the selector function uses the same one. For example:
export default configureStore({
reducer: {
someOtherProperty: counterReducer
}
});
export const selectCount = id => state => state.someOtherProperty[id]
Related
I have an input field where I am trying to pass some information before moving onto a separate page. My problem is the Redux state is not changing, but the console is showing the value is being passed correctly. I'm assuming something is wrong with my Slice but I believe I am passing the payload correctly. My Redux slice looks like:
import { createSlice } from "#reduxjs/toolkit";
export const walletSlice = createSlice({
name: "wallet",
initialState: {
wallet: "xxx-xxxx-xxx-xxxx",
},
reducers: {
setWalletAddress: (state, action) => {
state.value = action.payload;
},
},
});
export const { setWalletAddress } = walletSlice.actions;
export default walletSlice.reducer;
While my from component looks like:
import { setWalletAddress } from "../../redux/wallet";
import { useDispatch } from "react-redux";
export default function AddressForm() {
return (
const dispatch = useDispatch();
const handleChangeWallet = (event) => {
dispatch(setWalletAddress (event.target.value));
console.log(event.target.value);
};
<React.Fragment>
<TextField
onChange={handleChangeWallet}
label="Wallet address"
/>
</React.Fragment>
);
}
My store looks pretty standard:
export default configureStore({
reducer: {
wallet: walletReducer,
},
});
I assume that you need to use correct state field name in the reducer function. I guess following code line makes an issue,
state.value = action.payload;
Instead of this, you need to write correct field name wallet not value
state.wallet = action.payload;
You've mistyped value instead of wallet
setWalletAddress: (state, action) => {
state.wallet = action.payload;
},
Basically I got a state called optimizer in which I store a field named optimizer_course_entries , this field has 2 reducers on it:
import { createSlice } from "#reduxjs/toolkit";
const initialState = {
optimizer_course_entries : [],
}
export const optimizerSlice = createSlice(
{
name: 'optimizer',
initialState,
reducers: {
edit_entry: (state, action) => {
console.log('CALLED EDIT REDUCERS');
state.optimizer_course_entries[action.payload.index] = action.payload.value;
},
reset: (state) => {
console.log('CALLED RESET REDUCER');
state.optimizer_course_entries = [];
}
}
}
)
export const {edit_entry, reset} = optimizerSlice.actions;
export default optimizerSlice.reducer;
In my react app, I have a call to edit_entry everything a textbox is edited, and it sends the index and value in a payload to Redux.
const receiveChange = (Value, Index) => {
dispatch(edit_entry({
index : Index,
value : Value,
}));
}
I have the reset reducer set on component mount like this:
React.useEffect(
() => {
dispatch(reset());
} , []
)
The issue i'm having is that on component mount, instead of redux only doing a reset, it also restores previous reducer actions..
And in my redux store, the optimizer_course_entries entry is identical to before the reset...
I'm still pretty new to redux, is there a way I can specify it so that upon re-mount it doesn't do this repopulation?
export const itemReducer = (state, action) => {
switch (action.type) {
default:
return state
}
}
import React, { useState, useReducer, createContext, useContext } from 'react'
import { useQuery } from '#apollo/client'
import { CURRENT_MONTH_BY_USER } from '../graphql/queries'
import { itemReducer } from '../reducers/ItemReducer'
const Items = createContext()
export const ItemProvider = ({ children }) => {
let items = []
const [state, dispatch] = useReducer(itemReducer, { items: items })
const result = useQuery(CURRENT_MONTH_BY_USER)
if (result.data && result.data.getCurrentMonthByUser) {
items = [...result.data.getCurrentMonthByUser]
}
return <Items.Provider value={{ state, dispatch }}>{children}</Items.Provider>
}
export const ItemsState = () => {
return useContext(Items)
}
export default ItemProvider
let items gets correct data from the useQuery, however nothing is passed into the state, therefore I am unable to transfer data into another components from the context. What am I doing wrong here?
When debugging both items and state they're initially empty because of the loading however then only the items receives correct data and state remains as empty array.
If i put static data into let items it works just fine, so maybe there can be something wrong with my useQuery as well?
It's easy to see your problem if you look at where items is used. That's only as the initial state to your useReducer call - but items is only set to a non-empty value after this. That has absolutely no effect on the component, because items is not used later in your component function, and the initial state is only ever set once, on the first render.
To solve this you need to embrace your use of a reducer, adding a new action type to set this initial data, and then dispatching that when you have the data. So add something like this to your reducer:
export const itemReducer = (state, action) => {
switch (action.type) {
case SET_INITIAL_DATA: // only a suggestion for the name, and obviously you need to define this as a constant
return { ...state, items: action.items };
/* other actions here */
default:
return state
}
}
and then rewrite your component like this:
export const ItemProvider = ({ children }) => {
const [state, dispatch] = useReducer(itemReducer, { items: [] })
const result = useQuery(CURRENT_MONTH_BY_USER)
if (result.data && result.data.getCurrentMonthByUser) {
dispatch({ type: SET_INITIAL_DATA, items: result.data.getCurrentMonthByUser });
}
return <Items.Provider value={{ state, dispatch }}>{children}</Items.Provider>
}
Also, while this is unrelated to your question, I will note that your ItemsState export appears to be a custom hook (it can't be anything else since it isn't a component but uses a hook) - that is perfectly fine but there is a very strong convention in React that all custom hooks have names of the form useXXX, which I strongly suggest you should follow. So you could rename this something like useItemsState (I would prefer useItemsContext to make clear it's just a useContext hook specialised to your specific context).
I am creating redux app using createSlice(), but get the error:
Error: The slice reducer for key "pageName" returned undefined during
initialization. If the state passed to the reducer is undefined, you
must explicitly return the initial state. The initial state may not be
undefined. If you don't want to set a value for this reducer, you can
use null instead of undefined.
Here is the minimal example for this error.
pageNameSlice.js:
import { createSlice } from "#reduxjs/toolkit";
const pageNameSlice = createSlice({
name: 'pageName',
initalState: "",
// The `reducers` field lets us define reducers and generate associated actions
reducers: {
setPageName: (state, newName) => {
state.pageName = newName
}
}
});
export default pageNameSlice.reducer;
store.js:
import { configureStore } from '#reduxjs/toolkit';
import pageNameReducer from '../features/pageNameSlice';
export const store = configureStore({
reducer: {
pageName: pageNameReducer
},
});
You have a typo there - initalState should be initialState. (This is more common than you might think ^^)
In the official redux toolkit documentation/tutorial, there's this file (counterSlice.js)
import { createSlice } from '#reduxjs/toolkit'
export const counterSlice = createSlice({
name: 'counter',
initialState: {
value: 0
},
reducers: {
increment: state => {
// 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.value += 1
},
decrement: state => {
state.value -= 1
},
incrementByAmount: (state, action) => {
state.value += action.payload
}
}
})
// Action creators are generated for each case reducer function
export const { increment, decrement, incrementByAmount } = counterSlice.actions
export default counterSlice.reducer
The reducer is then imported in the store:
import { configureStore } from '#reduxjs/toolkit'
import counterReducer from '../features/counter/counterSlice'
export default configureStore({
reducer: {
counter: counterReducer
}
})
As I cannot see any reference to 'counterReducer' in the counterSlice.js file, I assume the 'counterReducer' in the import:
import counterReducer from '../features/counter/counterSlice'
is an arbitratry name and could be any other name of our choice, for example:
import counterSliceReducer from '../features/counter/counterSlice'
Is that correct?
Also, where is this 'reducer' in the default export coming from?
export default counterSlice.reducer
The counterSlice element contains 'reducers' object, not 'reducer'. Is that pulled from under the hood?
Thanks
You can use any name if the imported module uses the export default xxx method to export the module.
createSlice will return an object that looks like:
{
name : string,
reducer : ReducerFunction,
actions : Record<string, ActionCreator>,
caseReducers: Record<string, CaseReducer>
}
Take a look at this docs
I assume the 'counterReducer' in the import:
import counterReducer from '../features/counter/counterSlice'
is an arbitratry name and could be any other name of our choice
You are correct, it is an ES6 feature, a default export can be imported with any name. see: MDN Page
Also, where is this 'reducer' in the default export coming from?
export default counterSlice.reducer
The counterSlice element contains 'reducers' object, not 'reducer'. Is that pulled from under the hood?
createSlice API returns an object in a form:
{
name : string,
reducer : ReducerFunction,
actions : Record<string, ActionCreator>,
caseReducers: Record<string, CaseReducer>
}
counterSlice.reducer is the reducer function, it needs to be exported and then passed to the store.