Search code examples
react-reduxredux-toolkitcreateentityadapter

EntityAdaper's method updateMany doesn't update state, even though addMany works fine, what is the reason?


I made an application in which the user passes coordinates. The function makes a request to the server according to the given coordinates and looks for the nearest available establishments. Further, the data is transferred to the formatter and finally to the state. This is what App.tsx looks like

//App.tsx

import React, { useEffect, useState } from "react";
import "./App.css";
import { useAppSelector } from "./hook";
import { useRequestPlaces } from "./hooks/index";
import { useAppDispatch } from "./hook";

const cities = [
  { name: "New York", latlong: "40.760898,-73.961219" },
  { name: "London", latlong: "51.522479,-0.104528" },
  { name: "London Suburb", latlong: "51.353340,-0.032366" },
  { name: "Desert", latlong: "22.941602,25.529665" },
];
const defaultLatlong = "40.760898,-73.961219";

function App() {
  const dispatch = useAppDispatch();
  const fetchPlaces = useRequestPlaces();
  const { ids, entities } = useAppSelector((state) => state.places);
  const [latlong, setLatlong] = useState(defaultLatlong);

  const minRadius = 50;

  useEffect(() => {
    fetchPlaces(minRadius, latlong, dispatch);
    console.log(entities);
  }, [fetchPlaces, latlong, entities, ids]);

  return (
    <div className="App">
      <header className="App-header">
        {cities.map((city) => {
          return (
            <button
              type="button"
              className="btn btn-outline-light"
              onClick={() => {
                setLatlong(city.latlong);
                console.log(latlong);
              }}
            >
              {city.name}
            </button>
          );
        })}
      </header>
      <main>
        {ids.map((id, index) => {
          const place = entities[id];
          return (
            <div
              className="card mx-auto mt-2"
              key={index}
              style={{ width: "18rem" }}
            >
              <div className="card-body">
                <h5 className="card-title">{place?.name}</h5>
                <h6 className="card-subtitle mb-2 text-muted">
                  <ul>
                    {place?.categories.map((category) => {
                      return <li key={category.id}>{category.name}</li>;
                    })}
                  </ul>
                </h6>
                <p className="card-text">
                  Distance: {place?.distance} meters
                  <br />
                  Adress: {place?.location}
                </p>
              </div>
            </div>
          );
        })}
      </main>
    </div>
  );
}

export default App;

At this stage, the user transmits the coordinates by clicking on the buttons with cities. Next, the coordinates are passed to the API handler functions.

//fetch.ts

import { Dispatch } from "react";
import { getClosestPlaces } from "./getClosestPlaces";
import { placesActions } from "../../slices";
import { Action } from "redux";
import client from "./client";

const fetch = async (
  radius: number,
  latlong: string,
  dispatch: Dispatch<Action>
) => {
  const { fetchPlaces } = client();
  const params = {
    client_id: `${process.env.REACT_APP_CLIENT_ID}`,
    client_secret: `${process.env.REACT_APP_CLIENT_SECRET}`,
    ll: latlong,
    radius: radius.toString(),
    limit: "50",
  };
  const response = await fetchPlaces(new URLSearchParams(params).toString());
  const { results } = response.data;
  if (results.length !== 0) {
    const closestPlaces = getClosestPlaces(results);

// AND HERE IS THE MAIN ISSUE! At this point all reqired data is ok it's an array of objects so I pass it to Action addPlaces which is addMany method.

    dispatch(placesActions.addPlaces(closestPlaces));
  } else if (results.length === 0 && radius < 1600) {
    fetch(radius + 50, latlong, dispatch);
  }
  return [];
};

export { fetch };

And finally I want to show you Slice, where the method is stored. All the payloads are OK, but it doesn't work with updateMany ???

import {
  createSlice,
  EntityState,
  createEntityAdapter,
} from "@reduxjs/toolkit";
import { FormattedPlace } from "./index";
import { RootState } from "./index";
import { Slice } from "@reduxjs/toolkit/src/createSlice";
import { SliceActions } from "@reduxjs/toolkit/dist/query/core/buildSlice";

const placesAdapter = createEntityAdapter<FormattedPlace>();
const initialState = placesAdapter.getInitialState();

type PlacesReducerActions = {
  addPlaces(state: any, { payload }: { payload: any }): void;
};

export type PlacesSliceType = Slice<
  EntityState<FormattedPlace>,
  PlacesReducerActions,
  "places"
>;

const placesSlice: PlacesSliceType = createSlice({
  name: "places",
  initialState,
  reducers: {
    addPlaces(state, { payload }) {

// HERE
      placesAdapter.updateMany(state, payload);
    },
  },
});
export const selectors = placesAdapter.getSelectors<RootState>(
  (state) => state.places
);
export const { actions } = placesSlice;
export default placesSlice.reducer;

Solution

  • Problem was solved with method setAll. I’m stupid, cause didn’t realise that method updateMany updates only those entities which had been added to state before. So if you want to rewrite your state totally use setAll()