Micah Johnson
Micah Johnson

Reputation: 122

Getting an error message when I filter the redux state

I am getting an error when I try to filter the results of data I pulled from an API.

The error message comes when I use my searchBar component to filter the redux data immediately when I type anything.

Below is the error message:

"Error: [Immer] An immer producer returned a new value and modified its draft. Either return a new value or modify the draft."

What must I do to filter the data and return the new data?

Below are the components and the Redux TK slice I am using.

Home.js Component

import React, {useState, useEffect} from 'react';
import { Col, Container, Row } from 'react-bootstrap';
import { useDispatch, useSelector } from 'react-redux';
import { getCountries } from '../redux/search';
// import LazyLoading from 'react-list-lazy-load';
// import { updateText } from '../redux/searchTerm';


import SearchBar from '../components/SearchBar';
import CardComponent from '../components/Card';
import DropdownMenu from '../components/DropdownMenu';


const Home = () => {
    const dispatch = useDispatch();
    // const [ countries, setCountries ] = useState(null);
    const countries = useSelector((state) => state.search);
    

    // const filterCountry = (searchCountry) => {
    //     countries.list.payload.filter(country => country.name.toLowerCase() == searchCountry.toLowerCase())
    // }

    useEffect(() => {
        dispatch(getCountries());
        console.log(countries);
    }, [dispatch]);

    

    // console.log(countries.filter(country => country.region.toLowerCase() === 'africa'))

    return (
        <>
            <Container className="home-container">
                <Row>
                    <Col sm={12} md={6} className="left">
                        <SearchBar />
                    </Col>
                    <Col sm={12} md={6} className="right">
                        <DropdownMenu/>
                    </Col>
                </Row>
                <Row className="countryRow">
                    { countries.status == 'success' 
                    ?<>
                        {countries.list.map((country) => {
                            return <CardComponent key={country.name} 
                                        title={country.name} 
                                        image={country.flags[0]}
                                        population={country.population} 
                                        region={country.region} 
                                        capital={country.capital}/>
                        })}
                    </> 
                    :<div>Loading.....</div>
                    }
                </Row>
            </Container>
        </>
    )
}

export default Home;

SearchBar.js

import React from 'react';
import {useSelector, useDispatch} from 'react-redux';
// import { updateText } from '../redux/searchTerm';
import { searchTerm } from '../redux/search';


const SearchBar = () => {
    const query = useSelector((state) => state.searchDefault);
    const dispatch = useDispatch();

    return (
        <>
            <form>
                <input 
                    className="search" 
                    type="search" 
                    placeholder="Search for a country" 
                    value={query}
                    onChange={(e) => dispatch(searchTerm(e.target.value))}/>
            </form>
        </>
    )
}

export default SearchBar;

search.js slice

import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import axios from 'axios';


export const getCountries = createAsyncThunk(
    'searchDefault/getCountries', async () => {
        try {
            const resp = await axios.get('https://restcountries.com/v2/all');
            return await resp.data;
        } catch (error) {
            console.log(error.message);
        }
    }
)

const searchSlice = createSlice({
    name: 'searchDefault',
    initialState: {
        list: [],
        status: null,
        value: ''
    },
    reducers: {
        searchTerm: (state, action) => {
            // console.log(state.value);
            state.value = action.payload
            // console.log(state.value);
            state.list.filter( country => country.name == state.value);
            return state.list;
            // console.log(state.list);
      
        }
    },
    extraReducers: {
        [getCountries.pending]: (state, action) => {
            state.status = 'loading'
        },
        [getCountries.fulfilled]: (state, payload) => {
            console.log(payload)
            state.list = payload.payload
            state.status = 'success'
        },
        [getCountries.rejected]: (state, action) => {
            state.status = 'failed'
        }
    }
})

export const { searchTerm } = searchSlice.actions;
export default searchSlice.reducer;

Upvotes: 3

Views: 670

Answers (1)

NearHuscarl
NearHuscarl

Reputation: 81370

According to the docs from redux-toolkit:

Redux Toolkit's createReducer API uses Immer internally automatically. So, it's already safe to "mutate" state inside of any case reducer function that is passed to createReducer:

And Immer docs:

It is also allowed to return arbitrarily other data from the producer function. But only if you didn't modify the draft

In your reducer, you are using immer API to mutate the value (state.value = action.payload) and return the result. However Immer only allows you do one of the 2 things, not both. So either you mutate the state:

searchTerm: (state, action) => {  
  state.value = action.payload; // notify redux that only the value property is dirty
}

Or replace the new state completely:

searchTerm: (state, action) => {  
  return { ...state, value: action.payload }; // replace the whole state
  // useful when you need to reset all state to the default value
}

Most of the time, you only need to tell redux that a specific property of the slice is changed, and redux will then notify only the components subscribed to that property (via useSelector) to re-render. So remove the return statement in your reducer and your code should be working again:

searchTerm: (state, action) => {  
  state.value = action.payload;
  state.list = state.list.filter( country => country.name == state.value);
  // remove the return statement
}

Upvotes: 1

Related Questions