Why my Redux App return that [ Immer ] error? - redux

I don't know. Why even I added my push function on my object to return my new result, The app is printing error on my console.log.
slice.js
import { createSlice } from '#reduxjs/toolkit';
import { pushProduct } from '../commons/push';
export const slice = createSlice({
name: 'initial',
initialState : {
product: [],
},
reducers: {
ADDS(state, actions) {
return {
...state,
product: pushProduct(state.product, actions.payload),
console1: console.log('State: ', state.product),
console2: console.log('Actions: ', actions.payload),
}
}
}
});
export const { ADDS } = slice.actions;
export default slice.reducer;
push.js
// Push new prpduct to the cart
export const pushProduct = (initial, productSelect) => { return initial.push(productSelect) };
console.log error
errors.ts:49 Uncaught Error: [Immer] An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft.
Thank You

Per the error message: Immer lets you update the state in two ways. One is "mutating" the existing state, and the other is returning a new value. But, you can only do one of those at a time.
You're trying to do both. You have return {...state}, but you also have pushProduct() which sounds like it's mutating.
The best answer here is to not try to do return {...state} at all, and just "mutate" the existing state.
See https://redux-toolkit.js.org/usage/immer-reducers#mutating-and-returning-state for more details.

Related

Getting this error "Invariant failed: A state mutation was detected inside a dispatch, in the path: todoReducer.1."

I tried everything like spread operator but nothing works.
Here is my reducer
//state is an array of objects.
const initialState = [
{taskName: "kkkkk", isEdit: false},
]
export const todoReducer = (state=initialState, action) =>{
switch(action.type){
case 'add' :
const temp=
{
taskName: action.payload.taskName,
isEdit: action.payload.isEdit
}
state.push(temp);
return {state}
default: return state
}
}
The error message indicates that you are using Redux Toolkit - that is very good. The problem is that you are not using createSlice or createReducer and outside of those, in Redux you are never allowed to assign something to old state properties with = or call something like .push as it would modify the existing state.
Use createSlice instead:
const initialState = [
{taskName: "kkkkk", isEdit: false},
]
const slice = createSlice({
name: 'todos',
reducers: {
add(state, action) {
state.push(action.payload)
}
}
})
export const todoReducer = slice.reducer;
// this exports the auto-generated `add` action creator.
export const { add } = slice.actions;
Since the tutorial you are currently following seems to be incorporating both modern and completely outdated practices, I would highly recommend you to read the official Redux Tutorial instead, which shows modern concepts.

how do I migrate from redux to redux toolkit

I managed to write reducer using createSlice but the action seems to be confusing.
My old reducer :
function listPeopleReducer(state = {
getPeople:{}
}, action){
switch (action.type) {
case D.LIST_PEOPLE: {
return {
...state
, getPeople:action.payload
}
}
default:{}
}
return state
}
By using createSlice from the redux toolkit, I migrated the reducer to this,
const listPeopleReducer = createSlice({
initialState:{getPeople:{}},
name:"listPeople",
reducers:{
listPeople(state,action){
return {
...state,
getPeople : action.payload
}
}
}
})
My old action, makes an api call inside it, with the help of a helper function makeApiRequest (which takes in parameters and returns the response of the api),
export function listPeople(config: any) {
return function (dispatch: any) {
makeApiRequest(config)
.then((resp) => {
dispatch({
type : D.LIST_PEOPLE,
payload : resp.data
})
})
.catch((error) => {
dispatch({
type : D.LIST_PEOPLE,
payload : error
})
})
}
}
With reduxtool kit, we could do something like,
const listPeople = listPeopleReducer.actions.listPeople;
But, how will I write my custom action that contains the helper function makeApiRequest ?
i.e The old Action should be migrated to reduxtoolkit type.
It's definitely tricky when migrating, since there are some major conceptual changes that you must eventually wrap your head around. I had to do it a couple of times before it clicked.
First, when you are creating const listPeopleReducer with createSlice(), that is not actually what you are creating. A slice is a higher level object that can generate action creators and action types for you, and allows you to export reducers and actions FROM it.
Here are the changes I would make to your code:
const peopleSlice = createSlice({
initialState:{getPeople:{}},
name:"people",
reducers:{
listPeople(state,action){
// uses immer under the hood so you can
// safely mutate state here
state.getPeople = action.payload
}
},
extraReducers:
// each thunk you create with `createAsyncThunk()` will
// automatically have: pending/fulfilled/rejected action types
// and you can listen for them here
builder =>
builder.addCase(listPeople.pending, (state,action) => {
// e.g. state.isFetching = true
})
builder.addCase(listPeople.fulfilled, (state,action) => {
// e.g. state.isFetching = false
// result will be in action.payload
})
builder.addCase(listPeople.rejected, (state,action) => {
// e.g. state.isFetching = false
// error will be in action.payload
})
}
})
Then, outside of your slice definition, you can create actions by using createAsyncThunk(), and do like:
export const listPeople = createAsyncThunk(
`people/list`,
async (config, thunkAPI) => {
try {
return makeApiRequest(config)
} catch(error) {
return thunkAPI.rejectWithError(error)
// thunkAPI has access to state and includes
// helper functions like this one
}
}
}
The "Modern Redux with Redux Toolkit" page in the Redux Fundamentals docs tutorial shows how to migrate from hand-written Redux logic to Redux Toolkit.
Your makeApiRequest function would likely be used with Redux Toolkit's createAsyncThunk, except that you should return the result and let createAsyncThunk dispatch the right actions instead of dispatching actions yourself.

redux state not changing

I'm using a Net Core, React-Redux boiler-plate, and when I run the fetch api action, the reducer state does not change at all.
Here is my action
import axios from "axios";
import config from '../config';
const ROOT_URL = config[process.env.NODE_ENV].api;
export const FETCH_EVENTS = "FETCH_EVENTS";
export function fetchEvents() {
const url = ROOT_URL +"/Event/GetAllEvents";
const request = axios.get(url);
return {
type: FETCH_EVENTS,
payload: request
};
}
my index reducer:
import { combineReducers} from 'redux';
import { routerReducer } from 'react-router-redux';
import dataReducer from './dataReducer'
const reducers = {
events: dataReducer
};
const rootReducer = combineReducers({
...reducers,
routing: routerReducer
});
export default rootReducer;
and my reducer:
import { FETCH_EVENTS } from "../actions/ExtractActions";
export default function (state = [], action) {
switch (action.type) {
case FETCH_EVENTS:
console.log("inside reducer")
return [action.payload, ...state];
}
return state;
}
So I add this code in the Home component:
function mapDispatchToProps(dispatch) {
return bindActionCreators({ fetchEvents }, dispatch);
}
function mapStateToProps(state) {
return {
events: state.events
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Home);
but when I try to run the action and try to see if the reducer state has changed, I get on console log an empty array for "this.props.events". Even though if I am trying to store api data to the state, I even tried modifying the reducer method and simply returning a string, but this.props.events returns an empty array [] again. I am guessing my redux is not working but I don't know why. I've been debugging all night long
componentWillMount() {
this.props.fetchEvents()
console.log(this.props.events)
}
I found the error. For some reason I had to call this.props.events in the render() method and not componentwillmount.
axios.get() is an async function. That's why you couldn't see the updated state when you logged it right after fetching the events. I would recommend you to use the redux-devtools-extension for debugging. Hope this helps. Cheers!

Fix Spread syntax error in redux

I am a novice in Redux and I am creating a react book app with redux for state management. As far as I read about redux, it is recommended to use spread syntax to prevent state mutation. In my case, I have 2 default books and I want to output them when the program gets starting. Something goes wrong with the return in case 'GET_BOOK' because I got a syntax error. Any recommendation to reduce my code in bookReducers, I am highly appreciated
bookReducers.js:
let defaultBooks=[{
id:1,
title:'First Book',
description:'This is first book description',
price:33,
currency:'Euro'
},
{
id:2,
title:'Second Book',
description:'This is second book description',
price:24.50,
currency:'Euro'
}];
// Book reducer
export function bookReducer(state={books:defaultBooks} , action){
switch (action.type) {
case 'GET_BOOK':
return {...state, books:[...state.books]};
break;
case 'POST_BOOK':
// return state= action.load;
return {books:[...state.books,...action.load]};
break;
case 'DELETE_BOOK':
const indexToDelete=[...state.books].findIndex((book)=>{return book.id===action.load.id;})
return {books:[...state.books.slice(0,indexToDelete),...state.books.slice(indexToDelete+1)]};
break;
case 'UPDATE_BOOK':
const indexToUpdate=[...state.books].findIndex((book)=>{return book.id===action.load.id});
const newBook={
id:state.books[indexToUpdate],
title:action.load.title,
description:action.load.description,
price:action.load.price,
currency:action.load.currency
};
console.log(newBook);
return {books:[...state.books.slice(0,indexToUpdate),newBook,...state.books.slice(indexToUpdate+1)]};
break
}
return state
}
action.js
export function getBooks(){
return{
type:'GET_BOOK'
}
}
// add books
export function postBooks(book){
return{
type:'POST_BOOK',
load:book
}
}
// delete a book
export function deleteBook(id){
return{
type:'DELETE_BOOK',
load:id
}
}
// Update a book
export function updateBook(book){
return{
type:'UPDATE_BOOK',
load:book
}
}
The error occurs in return {...state, books:[...state.books]}; in case GET_BOOK in bookReducers. And here is my error:
You don't need a Redux action to get books, just have your component in react that connects to redux get the books from the state.
For example:
const library = (props) => {
// You can access your books here with this.props.books
}
const mapStateToProps = (state) => {
return {books: this.state.books}
}
const mapDispatchToProps = () => {}
export default connect(mapStateToProps, mapDispatchToProps)(library)
Edit:
Your interaction with Redux seems like you are trying to mock a RESTful api, you really don't need to do this, you can just have update, add, and delete book actions. If you want initial books in the state then add them when you create the store with the preloaded state parameter.

How to render properties of objects in React?

Where exactly should I deal with the problem of this component not loading with the desired state?
My render method causes the following error...
Uncaught TypeError: Cannot read property 'email' of undefined
...even though the JSON.stringify line shows me that the email property does (eventually) exist.
The console.log down in mapStateToProps confirms that state loads first without the any user property (thus causing the error).
Behold my naive attempt to resolve this in my constructor method. It's not working.
What is the right way to deal with this situation? Some conditional inside the render method? Tried that too but still no luck.
import React, {Component, PropTypes} from 'react';
import {connect} from 'react-redux';
import * as actions from '../actions';
class Feature extends Component {
constructor(props){
super(props);
this.state = {
'auth': {
'user':{
email:'',
id:''
}
}
}
}
componentWillMount() {
this.props.fetchMessage(); // puts the user object into state
}
render() {
return (
<div className="feature">
Here is your feature
{JSON.stringify(this.props.user , null, 2)}
{this.props.user.email}
</div>
);
}
}
function mapStateToProps(state) {
console.log('state',state);
return { user: state.auth.user }
}
export default connect(mapStateToProps, actions)(Feature);
/////////// action /////////
export function fetchMessage(){
return function(dispatch){
axios
.get(ROOT_URL, {
headers: {
authorization: localStorage.getItem('token')
}
})
.then((response) => {
dispatch({
type: FETCH_MESSAGE,
payload: response.data.user
})
})
}
}
///////////////// reducer /////////////
var authReducer = (state={}, action) => {
console.log('action.payload',action.payload);
switch(action.type){
case AUTH_USER: return {...state, error: '', authenticated: true};
case UNAUTH_USER: return {...state, error: '', authenticated: false};
case AUTH_ERROR: return {...state, error: action.payload};
case FETCH_MESSAGE: return {...state, user: {
email: action.payload.email,
id: action.payload._id
}};
default: return state;
};
};
So here is what is happening.. You are making a server request for a user object and on success the received object is stored in your redux store. While performing such an action your component is rendered as follows:
You have initiated the request to the server, which means currently there is no user in your store and so the component is rendered with this.props.user undefined.
Your request is successful and the user object is stored in your store. When this happens react-redux will re-render your component with the user object and this.props.user is available in your component.
During the first step since this.props.user is unavailable you are getting an error while accessing this.props.user.email. Although #jpdelatorre 's answer is pretty good, another thing you can do is simply add a check in your render method.
render() {
let user = this.props.user || {};
....
{user.email}
....
}
Redux uses a global state called store that lives outside your component.
Inside your constructor you have a this.state = {...} statement. This is a local state that is only available to the Feature component.
connect from react-redux basically connects the info inside the store to your component props hence called mapStateToProps.
Edit your mapStateToProps to something like...
function mapStateToProps(state) {
const { auth: { user = {} } = {}} = state;
return {
user
}
}
What it does is try to extract the user property from the store and if it doesn't exist yet, set it to empty object. Your error is due to your mapStateToProps accessing the user property that doesn't exist yet. You might want to set a default value as your initialState to avoid this issue.

Resources