Search code examples
javascriptreactjsreduxreact-reduxredux-toolkit

useSelector is not updating the state in reduxjs/toolkit


I am trying to update the state.user.price state to some other value using reduxjs/toolkit but it is not working properly. I know this has to do with immutablity then I tried returning new objects without changing them but it didn't work.

const initialUserState = {
  isLoggedIn: !!localStorage.getItem("token"),
  carName: "",
  price: "any",
  avaliablity: "any",
  type: "any"
}

const userSlice = createSlice({
  name: "user",
  initialState: initialUserState,
  reducers: {
    handleAuthState: state => {
      state.isLoggedIn = !!localStorage.getItem("token");
    },
    changeCarName: (state, action) => {
      state.carName = action.payload;
    },
    changeAvailablity: (state, action) => {
      state.avaliablity = action.payload;
    },
    changeType: (state, action) => {
      state.type = action.payload;
    },
    changePrice: (state, action) => {
      state.price = action.payload;
    }
  }
});
const store = configureStore({
  reducer: {
    UI: UISlice.reducer,
    user: userSlice.reducer,
    car: carSlice.reducer
  }
});

export default store;
export const userActions = userSlice.actions;
const Filters = () => {
  const type = useSelector(state => state.user.type);
  const price = useSelector(state => state.user.price);
  const carName = useSelector(state => state.user.carName);
  const availablity = useSelector(state => state.user.availablity);

  const sendRequest = async () => {
    console.log("priceInSendRequestFunction = ", price);
  }

  const priceHandler = event => {
    if (event.target.value === "") {
      dispatch(userActions.changePrice("any"))
    } else {
      console.log("event.target.value = ", event.target.value);
      dispatch(userActions.changePrice(event.target.value));
    }
    sendRequest();
  }
  return (
    <Select onChange={priceHandler} placeholder="Select Price">
      <option value="2000">Below 2000</option>
      <option value="4000">2000 - 4000</option>
      <option value="6000">4000 - 6000</option>
      <option value="infinity">Above 6000</option>
      <option value="any">Any</option>
    </Select>
  )
}

The value of price in sendRequest function should get the updated value but it is getting another value.


Solution

  • The current selected price state is closed over in callback scope when priceHandler is called. priceHandler dispatches an action to the store and immediately calls sendRequest. The component has yet to rerender and access any updated Redux state values.

    You can forward the value to sendRequest that you are updating the state to:

    const sendRequest = async (price) => {
      console.log("priceInSendRequestFunction = ", price);
    };
    
    const priceHandler = event => {
      const { value } = event.target;
      const newPrice = value || "any";
    
      console.log({ newPrice });
      dispatch(userActions.changePrice(newPrice));
    
      sendRequest(newPrice);
    };
    

    Or you can import the store and access the current state directly:

    import store from '../path/to/store';
    
    ...
    
    const sendRequest = async () => {
      const state = store.getState();
      const { price } = state.user;
    
      console.log("priceInSendRequestFunction = ", price);
    };
    
    const priceHandler = event => {
      const { value } = event.target;
      const newPrice = value || "any";
    
      console.log({ newPrice });
      dispatch(userActions.changePrice(newPrice));
    
      sendRequest();
    };
    

    If the sendRequest function/logic is effectively a "side-effect" then an alternative might be to convert the changePrice action into an asynchronous action, e.g. a Thunk.

    import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
    
    const initialUserState = {
      isLoggedIn: !!localStorage.getItem("token"),
      carName: "",
      price: "any",
      avaliablity: "any",
      type: "any"
    };
    
    export const changePrice = createAsyncThunk(
      "user/changePrice",
      async (price, thunkAPI) => {
        console.log("price in changePrice action = ", price);
        try {
          // side-effect to send price ...somewhere
          ...
          return price;
        } catch(error) {
          return thunkAPI.rejectWithValue(error);
        }
      }
    );
    
    const userSlice = createSlice({
      name: "user",
      initialState: initialUserState,
      reducers: {
        handleAuthState: state => {
          state.isLoggedIn = !!localStorage.getItem("token");
        },
        changeCarName: (state, action) => {
          state.carName = action.payload;
        },
        changeAvailablity: (state, action) => {
          state.avaliablity = action.payload;
        },
        changeType: (state, action) => {
          state.type = action.payload;
        },
      },
      extraReducers: builder => {
        builder
          // Update local price state if side-effect successful
          .addCase(changePrice.fulfilled, (state, action) => {
            state.price = action.payload;
          });
      },
    });
    
    const Filters = () => {
      const dispatch = useDispatch();
    
      const {
        availablity,
        carName,
        price,
        type,
      } = useSelector(state => state.user);
    
      const priceHandler = event => {
        const { value } = event.target;
        const newPrice = value || "any";
    
        console.log({ newPrice });
        dispatch(userActions.changePrice(newPrice));
      }
    
      return (
        <Select onChange={priceHandler} placeholder="Select Price">
          <option value="2000">Below 2000</option>
          <option value="4000">2000 - 4000</option>
          <option value="6000">4000 - 6000</option>
          <option value="infinity">Above 6000</option>
          <option value="any">Any</option>
        </Select>
      )
    }