Problem updating a list with redux toolkit - javascript

// './file_1'
import { createSlice } from '#reduxjs/toolkit';
export const array = createSlice({
name: 'array',
initialState: {
value: []
},
reducers:{
append(state, a) {
state.value.push(a)
}
}
});
export const { append } = array.actions;
export default array.reducer;
I've also updated the store, so no problem on that side
'/.file_2'
import { useSelector, useDispatch } from 'react-redux';
import { append } from './file_1';
const dispatch = useDispatch()
let x = 5
dispatch(append(x));
raises this error:
Objects are not valid as a React child (found: object with keys {type, payload}). If you meant to render a collection of children, use an array instead.

"a" is an object. The object looks like this -> {action: ..., payload: ...}
so you need to access it like this -> a.payload

Issue
The issue is that the code is attempting to render an object which isn't valid JSX.
Since you've no errors regarding arrays I'll assume you are correctly mapping it, something like this:
const array = useSelector(state => state.array);
...
return (
...
{array.map(el => (
<div>{el}</div> // <-- error, can't render object!!
))}
);
The error is error: Objects are not valid as a React child (found: object with keys {type, payload}) which is quite obvious the action object dispatched to the Redux store and saved into state.
export const array = createSlice({
name: 'array',
initialState: {
value: []
},
reducers:{
append(state, a) {
state.value.push(a) // <-- `a` is action object `{ type: 'append', payload: 5 }`
}
}
});
Solution
I'm sure you meant to save just the payload value and not the entire action object since the dispatched value x is a primitive number type.
const dispatch = useDispatch()
let x = 5
dispatch(append(x));
Update the reducer function to unpack the payload to save into state.
export const array = createSlice({
name: 'array',
initialState: {
value: []
},
reducers:{
append(state, action) {
state.value.push(action.payload); // 5
}
}
});

Related

How to fix state updating in react?

I am using little state machine for the state management. I have following state
export const todoState = { todoList: [] }
Now I am calling this using the actions where action is like
export const updateTodoList = (state, payload) => {
return {
...state,
toDoList: {
...state.toDoList,
...payload
}
}
}
calling this action
updateToDoList({ id: '1', text:'11', isComplete: 'false })
But still actions does not update the array of toDoList and also it does not take the previous values into consideration .
Can any one help me with the actions updation code ? Thanks.
1) TYPO: todoList instead of toDoList
2) todoList is an array not an object. So spread it into an array.
3) Since you are passing an object so no need to spread it
export const updateTodoList = (state, payload) => {
return {
...state,
todoList: [
...state.todoList,
payload, // It will add the payload object into todoList
],
};
};

How i access redux store outside a react component?

In my Store.js file i have that code:
export const INSERIR_USUARIO = 'app/login/INSERIR_USUARIO';
export const INITIAL_STATE = {
usuario: null,
};
export default function reducer(state = INITIAL_STATE, action = {}) {
if (action.type === INSERIR_USUARIO) {
return {
...state,
usuario: action.payload,
};
}
return state;
}
export const attUser = (usuario) => ({
type: INSERIR_USUARIO,
payload: usuario,
});
Now, in my login.js file, i need to access the function "attUser". I imported the function but I don't know if it is being called. But in redux is not saving
import { attUser } from './Store';
export const saveToken = () => {
attUser({ name: 'a', age: '13' });
};
How do I insert this item in redux outside of a react component?
export const attUser = (usuario) => ({
type: INSERIR_USUARIO,
payload: usuario,
});
attUser is an action creator. It creates a small object describing what to do, but otherwise doesn't cause any actual change. To have an effect, you then need to dispatch this action. You didn't show the line where you actually create the store, but assuming the store variable is named store, you'll do:
store.dispatch(attUser({ name: 'a', age: '13' }))
For more on dispatching actions, see this page.
P.S: you specifically asked about how to do this outside of react, so that's what i gave as an example. If a react component needs to do this dispatching, you should use the techniques in react-redux (useDispatch for function components, mapDispatchToProps for class components)

Why my products are empty with this function?

In the console it shows me a warning:
The entity passed to the selectId implementation returned undefined. You should probably provide your own selectId implementation. The entity that was passed: (2) [{…}, {…}] The selectId implementation: item => item._id.
What am I missing?
I try to call the productos with:
const products = useSelector(productSelectors.selectIds)
import { createSlice, createEntityAdapter, createAsyncThunk } from "#reduxjs/toolkit";
import axios from "axios";
//Fetch the businesses to show in the home page
export const fetchProducts = createAsyncThunk(
'products/fetchProducts',
async (id, { dispatch }) => {
return axios.get(`https://api-test-carrinho.herokuapp.com/product/business/${id}`
).then(resp => {
dispatch(addProducts(resp.data))
})
}
)
const productsAdapter = createEntityAdapter({
selectId: (item) => item._id
});
const productsSlice = createSlice({
name: "products",
initialState: productsAdapter.getInitialState({ loading: false }),
reducers: {
/* addProduct: productsAdapter.addOne, */
addProducts: productsAdapter.setAll
},
extraReducers: {
[fetchProducts.pending](state){
state.loading = true
},
[fetchProducts.fulfilled](state, { payload }){
state.loading = false
productsAdapter.setAll(state, payload)
}
}
});
export default productsSlice.reducer;
export const { /* addProduct, */addProducts } = productsSlice.actions;
export const productSelectors = productsAdapter.getSelectors(
(state) => state.products
);
export const {
selectById: selectProductById,
selectAll: selectAllProducts
} = productSelectors;
Undefined id
I was able to replicate your error message
The entity passed to the selectId implementation returned undefined.
On the first instance of the error what I'm seeing in the console is an array of three products. It is trying to find the ._id property on that array. It thinks the array is a single entity rather than an array of entities. Then it is trying to find the ._id property on numbers like 3 and 0.
What's happening here is that it is treating your response as dictionary of product objects which it isn't. We need to be looking at just the .data property and that's where we'll find the products.
Now this is where it gets confusing, but you actually need resp.data.data instead of resp.data. An axios response has a .data property which contains the JSON. Then inside your JSON you have your own .data property.
Thunk Issues
After I fix that, the products get successfully added to the state by addProducts. But there is still a problem further down in fetchProducts.fulfilled. You will get an error
TypeError: Cannot convert undefined or null to object
Because the payload of your fulfilled action is undefined.
With createAsyncThunk you actually don't need to dispatch anything from the thunk. What you need to do is return the data that you want to have as the payload of your fetchProducts.fulfilled action.
export const fetchProducts = createAsyncThunk(
"products/fetchProducts",
async (id) => {
return axios
.get(`https://api-test-carrinho.herokuapp.com/product/business/${id}`)
.then((resp) => resp.data.data);
}
);
According to Redux Documentation, useSelector should be a function:
import React from 'react'
import { useSelector } from 'react-redux'
export const CounterComponent = () => {
const counter = useSelector((state) => state.counter)
return <div>{counter}</div>
}
So, maybe this will help you:
const products = useSelector(state => state./* however your store is implemented */productSelectors.selectIds)

useSelector not updating when store has changed in Reducer. ReactJS Redux

I am changing the state in reducer. On debug I checked that the state was really changed. But the component is not updating.
Component:
function Cliente(props) {
const dispatch = useDispatch()
const cliente = useSelector(({ erpCliente }) => erpCliente.cliente)
const { form, handleChange, setForm } = useForm(null)
...
function searchCepChangeFields() {
//This call the action and change the store on reducer
dispatch(Actions.searchCep(form.Cep))
.then(() => {
// This function is only taking values ​​from the old state.
// The useSelector hook is not updating with store
setForm(form => _.setIn({...form}, 'Endereco', cliente.data.Endereco))
setForm(form => _.setIn({...form}, 'Uf', cliente.data.Uf))
setForm(form => _.setIn({...form}, 'Cidade', cliente.data.Cidade))
setForm(form => _.setIn({...form}, 'Bairro', cliente.data.Bairro))
})
}
Reducer:
case Actions.SEARCH_CEP:
{
return {
...state,
data: {
...state.data,
Endereco: action.payload.logradouro,
Bairro: action.payload.bairro,
UF: action.payload.uf,
Cidade: action.payload.cidade
}
};
}
NOTE: you better start using redux-toolkit to prevent references
in you code its a better and almost a must way for using redux
the problem your facing is very common when handling with objects,
the props do not change because you're changing an object property but the object itself does not change from the react side.
even when you're giving it a whole new object
react doesn't see the property object change because the reference stays the same.
you need to create a new reference like this:
Object.assign(state.data,data);
return {
...state,
data: {
...state.data,
Endereco: action.payload.logradouro,
Bairro: action.payload.bairro,
UF: action.payload.uf,
Cidade: action.payload.cidade
}
}
to add more you can learn about the Immer library that solves this
problem.
It's not necessary to
Object.assign(state.data, data);
always when changing data of arrays or objects
return(
object: {...state.object, a: 1, b: 2},
array: [...state.array, 1, 2, 3]
)
this 3 dots (...) ensure that you create a new object. On redux you have to always create a new object, not just update the state. This way redux won't verify that your data has changed.
When having nesting objects or arrays, is the same thing
Just have attention to:
initialState = {
object: {
...object,
anotherObject:{
...anotherObject,
a: 1,
b: 2
}
}
}
Somehow, the Object.assgin is not recognize
Update with ES6 syntax.
updatedConnectors = state.connectors
This will create a reference to the current state. In ES6, that introduce the ... to make new reference.
updatedConnectors = { ...state.connectors }
.....
return {
...state,
connectors: updatedConnectors
};
use this to extract and copy new reference. That will trigger state change too
Update Sep/27/20
I've wrote some utils function to handle this, Let try this
//Utils
export const getStateSection = ({ state, sectionName }) => {
const updatedState = { ...state }
const updatedSection = updatedState[sectionName]
return updatedSection
}
export const mergeUpdatedSection = ({ state, sectionName, updatedValue }) => {
const updatedState = { ...state }
updatedState[sectionName] = updatedValue
return updatedState
}
Then In any reducer, It should use like this:
//reducer
const initState = {
scheduleDetail: null,
timeSlots: null,
planDetail: {
timeSlots: [],
modifedTimeSlots: [],
id: 0
}
}
.....
const handlePickTimeSlot = (state, action) => {
let planDetail = getStateSection({ state, sectionName: 'planDetail' })
// do stuff update section
return mergeUpdatedSection({ state, sectionName: 'planDetail', updatedValue: planDetail })
}
Since the edit queue for elab BA is full.
The accepted answer here is what he meant by data being there
case MYCASE:
let newDataObject = Object.assign(state.data, {...action.payload});
// or
// let newDataObject = Object.assign(state.data, {key: 'value', 'key2': 'value2' ...otherPropertiesObject);
return {
...state,
...newDataObject
}
There is an interesting edge case that can happen when modifying the file where you create your Store.
If the file where you have your redux store Provider component (usually App.tsx) does not get reloaded by React's hot module reloader (HMR) but the redux store file gets modified and therefore reloaded by HMR, a new store is created and the store Provider in your App.tsx can actually end up passing an old instance of your redux store to useSelector.
I have left the following comment in my setup-store.ts file:
/**
* Note! When making changes to this file in development, perform a hard refresh. This is because
* when changes to this file are made, the react hot module reloading will re-run this file, as
* expected. But, this causes the store object to be re-initialized. HMR only reloads files that
* have changed, so the Store Provider in App.tsx *will not* be reloaded. That means useSelector
* values will be querying THE WRONG STORE.
*/
It is not a problem, you should understand how the React is working. It is expected behavior
In your case you are invoking
dispatch(Actions.searchCep(form.Cep))
.then(() => {...some logic...}
But all of this work in ONE render, and changed store you will get only in the NEXT render. And in then you are getting props from the first render, not from the next with updated values. Look for example below:
import React from 'react';
import { useSelector, useDispatch } from "react-redux";
import { selectCount, incrementAsync } from "./redux";
import "./styles.css";
export default function App() {
const value = useSelector(selectCount);
const dispatch = useDispatch();
const incrementThen = () => {
console.log("value before dispatch", value);
dispatch(incrementAsync(1)).then(() =>
console.log("value inside then", value)
);
};
console.log("render: value", value);
return (
<div className="App">
<p>{value}</p>
<button onClick={incrementThen}>incrementThen</button>
</div>
);
}
And output
value before dispatch 9
render: value 10
value inside then 9

How to I convert my props data into an array in my React with Redux project?

In my solution which is an ASP.NET Core project with React, Redux, and Kendo React Components I need to return my props as an array. I'm using the Kendo Dropdown widget as below.
<DropDownList data={this.props.vesseltypes} />
However I receive the error of :
Failed prop type: Invalid prop data of type object supplied to
DropDownList, expected array.
So, I checked my returned data from the props.vesseltypes which is an array of as opposed to a flat array.
Here is my code for how this data is returned:
components/vessels/WidgetData.js
import React, { Component } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { actionCreators } from '../../store/Types';
import { DropDownList } from '#progress/kendo-react-dropdowns';
class WidgetData extends Component {
componentWillMount() {
this.props.requestTypes();
}
render() {
console.log(this.props.vesseltypes)
return (
<div>
<DropDownList data={this.props.vesseltypes} />
</div>
);
}
}
export default connect(
vesseltypes => vesseltypes,
dispatch => bindActionCreators(actionCreators, dispatch)
)(WidgetData);
components/store/Types.js
const requestVesselTypes = 'REQUEST_TYPES';
const receiveVesselTypes = 'RECEIVE_TYPES';
const initialState = {
vesseltypes: [],
isLoading: false
};
export const actionCreators = {
requestTypes: () => async (dispatch) => {
dispatch({ type: requestVesselTypes });
const url = 'api/KendoData/GetVesselTypes';
const response = await fetch(url);
const alltypes = await response.json();
dispatch({ type: receiveVesselTypes, alltypes });
}
}
export const reducer = (state, action) => {
state = state || initialState;
if (action.type === requestVesselTypes) {
return {
...state,
isLoading: true
};
}
if (action.type === receiveVesselTypes) {
alltypes = action.alltypes;
return {
...state,
vesseltypes: action.alltypes,
isLoading: false
}
}
return state;
};
And finally, the reducer is defined in the store
components/store/configureStore.js
const reducers = {
vesseltypes: Types.reducer
};
Controllers/KendoDataController.cs
[HttpGet]
public JsonResult GetVesselTypes()
{
var types = _vesselTypeService.GetVesselTypes();
return Json(types);
}
So, the dropdown widget expects an array, what I return via the store is an array of objects. As such, this can't be used by the dropdown because it's not what it is expecting. My question is, how do I return this as a single array or flat array?
First deconstruct the part that you want to map to a property from your state:
export default connect(
({vesseltypes}) => ({vesseltypes}),
dispatch => bindActionCreators(actionCreators, dispatch)
)(WidgetData);
Then you could just map vesselTypes to an array of strings, since that's what Kendo DropdownList seems to expect:
<div>
<DropDownList data={this.props.vesseltypes.map((vessel) => vessel.TypeName)} />
</div>
Which should result in what you wanted to achieve.
Alternatively you could look into how to implement a HOC to map your objects to values, it's specified in the Kendo docs, or you can checkout the Stackblitz project they've prepared.
It looks like you forgot to extract vesselTypes from the response here
const alltypes = await response.json();
and your console.log shows that, it contains whole response not just vesselTypes array.
EDIT: On top of that your connect seems wrong, you just pass whole state as a prop not extracting the part you need.
I assume you need an array of strings where the value is in key TypeName.
First of all, I would suggest renaming your variables, if there isn't any back-end restriction like how it's returned via fetch.
For example, these:
alltypes => allTypes
vesseltypes => vesselTypes
Regarding the issue, you just need to do a quick transform before passing data into component. Not sure how the drop down component uses the original input data but I would reduce the array into separate variable to create it only once.
Then pass the variable vesselTypeList into component DropDownList.
Last thing is where to do this transform, when result has been retrieved and Redux updates your props via mapStateToProps first argument of connect function.
const getTypeList = (vesseltypes) => {
return vesseltypes.reduce((result, item) => {
result.push(item.TypeName);
return result;
}, []);
}
const mapStateToProps = ({ vesseltypes }) => { vesseltypes: getTypeList(vesseltypes) };
export default connect(
mapStateToProps,
dispatch => bindActionCreators(actionCreators, dispatch)
)(WidgetData);

Categories