Why is using 'push' inside createSlice add '3' to my state instead of appending an array? - redux

I am trying to append action.payload to my state. However, push methods adds action.payload.length to my state instead of appending the entire array! What am I doing wrong?
const initialState = { users: [] };
export const usersSlice = createSlice({
//other code.
,
extraReducers(builder) {
builder.addCase(fetchUsers.fulfilled, (state, action) => {
console.log(current(state));
state.users = state.users.push(...action.payload);
console.log(action.payload);
console.log(current(state));
// this one works.
// state.users = state.users.concat(action.payload);
});
},
});
// selector
export const selectUserById = (state, userId) =>
state.users.users.find((user) => user.id === userId);
This is the error I get (referring to selector):
TypeError: state.users.users.find is not a function
And this is my console:
// This is my state.
{
"users": []
}
// this is action.payload. Which is an array of 3 objects.
[
{
obj1
},
{
obj2
},
{
obj3
}
]
// This is the state after using push:
{
"users": 3
}

Well, such a silly mistake.
As y'all know, push method does not return anything. Thus, it made no sense for me to try to assign it to my state. concat method on the other hand, returns a new array. That's why it worked.
Here is what I changed:
state.users.push(...action.payload);
There is not state.users = anymore.

Related

What is the different for UseGetSomeQuery Arg Value

I'm new for the RTK Query for redux.
What's the different for auto generated hook in below two ways.
The first way look like correct from the docs but it return 304 network status.
Second way, return 200. working perfectly
1.
const ProjectsList = () => {
const {
data: projects,
isLoading,
isSuccess,
isError,
error,
} = useGetProjectsQuery("projectList") // -- return 304 network status
}
worked fine. but cannot retrieve the object from the store. return.
const {
data: projects,
isLoading,
isSuccess,
isError,
error,
} = useGetProjectsQuery() // -- return 200 network status
Third, the memoized return uninitialize. It seem didn't correct.
// ApiSlice status return uninitialize
import { createSelector, createEntityAdapter } from "#reduxjs/toolkit"
import { apiSlice } from "#/app/api/apiSlice"
const projectsAdapter = createEntityAdapter({})
export const projectsApiSlice = apiSlice.injectEndpoints({
endpoints: (builder) => ({
getProjects: builder.query({
query: () => "/api/projects",
validateStatus: (response, result) => {
return response.status === 200 && !result.isError
},
transformResponse: (responseData) => {
const loadedProjects = responseData.map((project) => {
project.id = project._id
return project
})
return projectsAdapter.setAll(initialState, loadedProjects)
},
providesTags: (result, error, arg) => {
if (result?.ids) {
return [
{ type: "Project", id: "LIST" },
...result.ids.map((id) => ({ type: "Project", id })),
]
} else return [{ type: "Project", id: "LIST" }]
},
}),
}),
})
export const {
useGetProjectsQuery,
} = projectsApiSlice
export const selectProjectsResult =
projectsApiSlice.endpoints.getProjects.select()
// creates memoized selector
const selectProjectsData = createSelector(
selectProjectsResult,
(projectsResult) => {
console.log("projectsResult: ", projectsResult) // -> { isUninitialized: true, status: "uninitialize" }
return projectsResult.data
}
)
export const {
selectAll: selectAllProjects,
selectById: selectProjectById,
selectIds: selectProjectIds,
} = projectsAdapter.getSelectors(
(state) => selectProjectsData(state) ?? initialState
)
Since your query function is just query: () => "/api/projects" (so, not using the argument in any way), both will make exactly the same request for the same resource.
There is no difference between them and every difference you see is probably something random happening on the server and not bound to either invocation.
As for retrieving from the store, there is a difference however.
Your code
export const selectProjectsResult =
projectsApiSlice.endpoints.getProjects.select()
creates a selector for the cache entry that is created calling useGetProjectsQuery() - if you wanted the cache entry for useGetProjectsQuery("foo"), that would need to be projectsApiSlice.endpoints.getProjects.select("foo").
Please note that there should almost never be any reason to use those selectors with React components - those are an escape hatch if you are not working with React. If you are working with React, use the useGetProjectsQuery hook with selectFromResult.
I am seeing people use select in this fashion quite often recently and I assume this traces back to a tutorial that misunderstood the feature - did you learn that in a tutorial and could you share that tutorial? Maybe I can convince the author to change that part.

How to access Apollo useMutaion response

When I do a mutation with useMutation hook, I can console.log my data but I cannot save the data object in a localState. I don't know what am I missing. Any help is appreciated.
My code is as follows:
//Schema file
startTest(identifier: String): TestResponse
the mutation payload is as follows:
type TestResponse {
errorCode: Int
errorMessage: String
success: Boolean
transactionId: ID
version: Int
}
In the resolver I hardcoded the following data return:
startTest(_, { identifier }) {
console.log("hello from server");
return {
errorCode: 0,
errorMessage: null,
success: true,
transactionId: "d2984911-bbc4-4e6a-9103-96ca934f6ed3",
version: 0,
};
},
In component folder I've triggered the mutation with a button click.
const [startTestt, { loading, error, data }] = useMutation(
START_FIRE_DAMPER_TEST,
{
onCompleted: ({ startFireDampersTest }) =>
console.log(startFireDampersTest.success),
useState(startFireDampersTest.success)
},
{
onError: () => console.log("error!"), // never gets called
}
);
console.log(result); // undefined
if (loading) return <p>Loading...</p>;
if (error) return <p>Error!</p>;
if (data && data.startFireDampersTest)
return (
<div>
<p>Success!</p>
<pre>{JSON.stringify(data, null, 2)}</pre> // I can see string object but how to render the element of the object inside the component??
</div>
);
const triggerTest = () => {
startTest({ variables: { identifier: "0000000" } });
};
return (
<>
<Banner />
<button onClick={triggerTest}>Click to Delete Buck</button>
</>
);
I need to access returned data returned from mutation for later use. the test response/payload is not related to any other queries or types to update. It just contains status of the test and other key values that I need to save in a local state or maybe render it inside a component.
You already have a useState inside your onCompleted, you'd just need to write properly. Assuming the useState is a call to the a React's useState hook, then you're probably just need to move the initialization of your state to your component.
const [isSuccess, setIsSuccess] = useState(false);
const [startTestt, { loading, error, data }] = useMutation(
START_FIRE_DAMPER_TEST,
{
onCompleted: ({ startFireDampersTest }) =>
console.log(startFireDampersTest.success),
setIsSuccess(startFireDampersTest.success)
},
{
onError: () => console.log("error!"), // never gets called
}
);

Cannot read property 'ids' of undefined when use reduxjs/toolkit

I am trying to pass values from API to state but always give this error.
TypeError: Cannot read property 'ids' of undefined
selectIds
I am using the 'reduxjs/toolkit' I try everything but still continue that error could you please help me
this is a code from the Slic file
export const getListNamesDictionary = createAsyncThunk('dictionary/names/getNames', async () => {
try {
const response = await axios.get('http://localhost:6005/api/lookup/list-name');
const data = await response.data;
// dispatch(getNames(data));
debugger;
console.log(data);
return data;
} catch (error) {
return console.error(error.message);
}
});
const namesAdapter = createEntityAdapter({});
and the Slic :
const namesDictionarySlice = createSlice({
name: 'names',
initialState: {
names: []
},
reducers: {
},
extractors: {
[getListNamesDictionary.fulfilled]: (state, action) => {
state.entities.push(action.payload);
}
}
});
export const { selectAll: selectNamesDictionary } = namesAdapter.getSelectors(state => state.data);
and this code from component where I need to dispatch the action
const names = useSelector(selectNamesDictionary);
useEffect(() => {
// dispatch(getListNamesDictionary()).then(() => setLoading(false));
dispatch(getListNamesDictionary()).then(() => setLoading(false));
}, [dispatch]);
any suggesting why that error? and thanks
You are not using the entity adapter properly. It expects to manage a state in the form:
{
ids: [1, 2],
entities: {
1: {/*...*/},
2: {/*...*/}
}
}
Your names slice doesn't match that shape. But that's an easy fix as the namesAdapter provides all of the needed tools. Quick rundown of errors to fix:
property name extractors should be extraReducers
state.entities.push needs to be replaced with an adapter function
initialState needs to have properties ids and entities
selectors need to target the correct location
const namesAdapter = createEntityAdapter({});
const namesDictionarySlice = createSlice({
name: "names",
initialState: namesAdapter.getInitialState(),
reducers: {},
extraReducers: {
[getListNamesDictionary.fulfilled]: namesAdapter.upsertMany
}
});
This fixes the first three bullets. Regarding the reducer, it might make more sense if you write it out like this, but it does the same thing.
[getListNamesDictionary.fulfilled]: (state, action) => {
namesAdapter.upsertMany(state, action)
}
The last bullet point is the cause of the specific error message the you posted:
TypeError: Cannot read property 'ids' of undefined
It actually seems like state.data is undefined. Is this namesDictionarySlice being used to control the data property of your root state? If it is something else, like state.names, then you need to change your selectors to namesAdapter.getSelectors(state => state.names).
If your store looks like this:
const store = configureStore({
reducer: {
names: namesReducer
}
});
You would want:
export const { selectAll: selectNamesDictionary } = namesAdapter.getSelectors(
(state) => state.names // select the entity adapter data from the root state
);
in Slic function, I make a mistake in writing, I most write 'extraReducer' but I wrote "extractors" :D

Selector returns empty array from the redux state, even though the array has values

I have the following normalized redux state:
rootReducer: {
blocks: {
"key1": {
id: "key1",
beverages: [], // Array of objects
}
}
}
and I'm trying to select the value of beverages for beverage with the id of "key1" using this selector:
export const getBlockBeverages = (state, blockId) => {
console.log("selector", state.blocks[blockId].beverages);
return state.blocks[blockId].beverages;
};
Whenever I add a new beverage into the beverages array, the selector gets called twice, first time with an empty array, second time with proper values:
Initial state
selector []
selector []
Adding new beverage:
selector []
selector [{/*beverage1*/}]
// Adding another beverage
selector []
selector [{/*beverage1*/}, {/*beverage2*/}]
I'd really appreciate any help/explanation why does the selector get called and beverages value for the block instance is an empty array.
Below is the code for reducers I'm using - I don't see where I could be mutating the original state, I used Immer's produce from the beginning and the problem is still present. Then I tried to use lodash.clonedeep to make sure that I return a new state, but the selector still logs that empty array.
const blockReducer = (state = { id: "", beverages: [] }, action) => {
if (action.type === ADD_BEVERAGE_TO_BLOCK) {
const { beverageId } = action.payload;
const newBeverage = { id: uuid4(), beverageId };
return produce(state, (draft) => {
draft.beverages.push(newBeverage);
});
}
return state;
};
const blocks = (state = {}, action) => {
const key = action.payload.key;
if (key && (state[key] || action.type === CREATE_BLOCK)) {
const instanceState = blockReducer(state[key], action);
return produce(state, (draft: any) => {
draft[key] = instanceState;
});
}
return state;
};
Any ideas why the selector returns empty array instead of array of length 0, 1, 2 etc. as I'm adding new beverages? I'm clueless and will appreciate any help.
The problem was in a different selector that I had been using in a wrong way.
export const makeGetBlockBeveragesLength = () => createSelector(
(state, blockId) => getBlockBeverages(state, blockId),
(blockBeverages) => blockBeverages.length,
);
and instead of mapStateToProps I used createMapStateToProps:
const createMapStateToProps = (state, { blockId }) => () => {
const getBlockBeveragesLength = makeGetBlockBeveragesLength();
return {
length: getBlockBeveragesLength(state, blockId),
};
};
export const Component = connect(createMapStateToProps)(MyComponent);
The empty array logged in one of the logs refers to an older state (the initial state in this case).
I fixed the code to this and it works:
export const getBlockBeveragesLength = createSelector(
(state, blockId) => getBlockBeverages(state, blockId),
(blockBeverages) => blockBeverages.length,
);
const mapStateToProps = (state, { blockId }) => ({
length: getBlockBeveragesLength(state, blockId),
});
export const Component = connect(mapStateToProps)(MyComponent);

Redux action reuse

I'm a beginner in react / redux.
I've finished a basic component <HeatMap /> in my app, with its actions / reducer / store and it works well.
And I'll render another <HeatMap /> with different settings (props).
What I'm trying to do is to separate this 2 component, because when i dispatch an update action in one, the other one performed it simultaneously.
Question 1
I tried this to separate the states in store
import heatMap from './heat-map1'
import {combineReducers} from 'redux';
export let reducers = combineReducers({
heatMap1: heatMap,
heatMap2: heatMap
});
combineReducers and connectthe 2 heatmap in different object in store
export default connect((state)=> {
return {
onState: state.heatMap1.onState,
config: state.heatMap1.config
}
})(CHMSHeatMap1)
and
export default connect((state)=> {
return {
onState: state.heatMap2.onState,
config: state.heatMap2.config
}
})(CHMSHeatMap2)
is this correct?
Question 2
Because 2 component both react when action is dispatched
I'm thinking about separating the shared actions, but I don't think it's a good idea. Or maybe the issue is not here.
So can you tell me what cause this problem and how to solve it?
Here are my reducer
import * as actionTypes from '../actions/heat-map';
import Immutable from 'immutable';
const onState = {
fetching: 'FETCHING',
error: 'ERROR',
drawn: 'DRAWN'
};
const initialState = {
onState: onState.fetching,
config: {}
};
export default function heatMapReducer(state = initialState, action) {
let immutableState = Immutable.fromJS(state);
switch (action.type) {
case actionTypes.INITIALIZING:
return immutableState.set('onState', onState.drawn).set('config', action.payload.initConfig).toJS();
case actionTypes.FETCH_DATA_REQUEST:
return immutableState.set('onState', onState.fetching).toJS();
case actionTypes.FETCH_DATA_SUCCESS:
return immutableState.set('onState', onState.drawn).setIn(['config','series',0,'data'],Immutable.fromJS(action.payload.mapData.data)).toJS();
case actionTypes.FETCH_DATA_FAILURE:
return immutableState.set('onState', onState.error).set('config', action.payload.mapData).toJS();
default:
return state;
}
}
Action is simple
export function initializeConfig(initConfig) {
return {
type: INITIALIZING,
payload: {
text: 'Initializing',
initConfig
}
}
}
export function requireMapData() {
return {
type: FETCH_DATA_REQUEST,
payload: {
text: 'Loading'
}
};
}
..........
//Async Action for fetching map data and redraw the map
export function fetchMapData(address) {
return function (dispatch) {
//dispatch requireMapData action to set the map in loading state
dispatch(requireMapData());
return fetch(address)
.then(fetchUtil.checkHttpStatus) //check if 404
.then(fetchUtil.parseJSON)
.then(mapData => dispatch(fetchDataSucceed(mapData)))
.catch(error => dispatch(fetchDataFailed(error)));
}
}
Thank you my friend.
You cannot duplicate your reducers in the manner you've depicted. Both are going to respond in the exact same way to the exact same actions.
The solution is to have all of your heat map data in the same reducer state. e.g.
const initialState = {
heatMap1: {},
heatMap2: {}
};
export default heatmap(state = initialState, action) {
// etc
Now if you want to use the same actions for both heat maps, you'll need to have an action property specifying which heap map you're targeting. If you have several heat maps, I'd recommend an array of heat maps with each action containing an index or id to target a particular heat map. e.g.
function updateHeatMap(index, value) {
return {
type: UPDATE_HEATMAP,
index: index,
value: value
}
}
You can also take a look at the multireducer module (https://github.com/erikras/multireducer). It was designed to solve exactly the scenario you propose.
So you would be able to configure your store as such:
import multireducer from 'multireducer';
import heatMap from './heat-map1'
import {combineReducers} from 'redux';
export let reducers = combineReducers({
multireducer: multireducer({
heatMap1: heatMap,
heatMap2: heatMap
})
});
After that, you would then need to use connectMultireducer() instead of redux's standard connect() in order to connect the specific slice of the store to particular components like so:
export default connectMultireducer((state)=> {
return {
onState: state.heatMap.onState,
config: state.heatMap.config
}
})(CHMSHeatMap)
And finally in order to get the correct part of the state to each of those components you would pass in the key when rendering them as such:
<CHMSHeatMap multireducerKey="heatMap1"/>
<CHMSHeatMap multireducerKey="heatMap2"/>
Obviously it's better to read the actual docs at the multireducer repo, but that should give a brief overview. Basically the module is just abstracting the process of adding a key-based lookup to each reducer that is created through the multireducer function.
I suggest original concept of multireducer working without any libraries.
The base idea is unique Symbol action types and self-contained Redux-module like this:
import * as services from './../../../api/services';
const initialState = {
list: [],
};
function getListReducer(state, action) {
return {
...state,
list: action.payload.list,
};
}
function removeItemReducer(state, action) {
const { payload } = action;
const list = state.list.filter((item, i) => i !== payload.index);
return {
...state,
list,
};
}
export default class List {
constructor() {
// action types constants
this.GET_LIST = Symbol('GET_LIST');
this.REMOVE_ITEM = Symbol('REMOVE_ITEM');
}
getList = (serviceName) => {
return async (dispatch) => {
const list = await services[serviceName].get();
dispatch({
type: this.GET_LIST,
payload: {
list,
serviceName,
},
});
};
}
removeItem = (index) => {
return (dispatch) => {
dispatch({
type: this.REMOVE_ITEM,
payload: {
index,
},
});
};
}
reducer = (state = initialState, action) => {
switch (action.type) {
case this.GET_LIST:
return getListReducer(state, action);
case this.REMOVE_ITEM:
return removeItemReducer(state, action);
default:
return state;
}
}
}
More information read there.

Resources