Cannot access variable before initialization - Redux Reducer - javascript

I have a variable stored in a constants file called constants.tsx as such:
export const ITEM_UNITS = ["ml", "g", "kg"];
I then access this ITEM_UNITS in my reducer so that I can use it as in my initialState as such:
export const initialState = {
...
itemUnits: ITEM_UNITS,
...
};
export default function reducer(
state = initialState,
action: BaseAction
) {
...
}
But for some reason, I'm getting an error saying:
ReferenceError: Cannot access 'ITEM_UNITS' before initialization
And this happens in my reducer file.

Related

Redux preloadedState causing override (and subsequent undefined error) of default reducer state?

I currently am creating a website with react-redux and was getting an error with a typo in the preloadedState variable within createStore. My relevant store code is as follows (Note the cartitems spelling in initialState instead of cartItems):
const reducer = combineReducers({
productList: productListReducer,
productDetails: productDetailsReducer,
cart: cartReducer,
})
const cartItemsFromStorage = localStorage.getItem('cartItems') ?
JSON.parse(localStorage.getItem('cartItems')) : []
//BELOW GETS PASSED INTO createStore
const initialState = {
cart: {cartitems: cartItemsFromStorage}
}
Within my cartReducer the code being affected is as follows:
export const cartReducer = (state = {cartItems: []}, action) => {
switch(action.type){
case CART_ADD_ITEM:
const item = action.payload
const existItem = state.cartItems.find(x => x.product === item.product)
***other code below...***
default:
return state
}
I noticed that this throws an Unhandled Rejection (TypeError): state.cartItems is undefined error on the existItem line. Why does the addition of the preloadedState cause this issue? From my understanding the reducer's default state (in this case given by state = {cartItems: []}) should still be accessible from within the reducer? Is this not the case?
This is by design. From the doc Initializing State:
Without combineReducers() or similar manual code, preloadedState always wins over state = ... in the reducer because the state passed to the reducer is preloadedState and is not undefined, so the ES6 argument syntax doesn't apply.
With combineReducers() the behavior is more nuanced. Those reducers whose state is specified in preloadedState will receive that state. Other reducers will receive undefined and because of that will fall back to the state = ... default argument they specify.
For your case, the preloadedState is {cart: { cartitems: cartItemsFromStorage }}, the { cartitems: cartItemsFromStorage } object will be passed in cartReducer as it's default state rather than the ES6 default argument syntax.
That's why your cart state shape is {cartitems: cartItemsFromStorage}
If the preloadedState is undefined, then your cart default state is {cartItems: []}.

How does Redux Toolkit determine property names on the state object?

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]

slice reducer for key "pageName" returned undefined during initialization

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 ^^)

Javascript imports (redux toolkit)

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.

How to access one Vuex state from another in Nuxt?

I have two Vuex stores inside the Nuxt store directory. I'm not able to access one store's states from another, even through routeState.
Store 1: index.js
export const state = () => ({
token: false
})
export const getters = {}
export const mutations = {}
export const actions = {}
Store 2: api.js
export const state = () => ({
apiBase: 'https://myurl.com/'
})
export const getters = {
getAPI: (state, rootState) => {
// Need the state token from index.js here
},
}
export const mutations = {}
export const actions = {}
Here,
state returns the state variables in api.js, that is apiBase
routeState returns the getters in api.js
this is undefined inside Vuex getters, it doesn't work
How do I access the state or getters from index.js?
The rootState is exposed ad the third argument into a getter.
const getters = {
getApi: (state, getters, rootState) => {
// Access the state token from index.js
rootState.token
}}
Check this page for more details Vuex Docs

Categories