DEV Community

Cover image for Client side update with RTK Query
Yuko
Yuko

Posted on • Edited on

Client side update with RTK Query

This is my memo from when I delved into Redux Toolkit Query (RTK query) and discovered how to update the Redux state created by RTK query solely on the client side.

This article is not intended to advocate for always using RTK query for managing state solely on the client side. In my opinion, the most common use of RTK query is to communicate frequently with the server side, taking advantage of features such as default cache storage behavior and optimistic updates.

Anyway, I was interested in understanding how I could update the state only on the client side, just like with normal Redux, which is why I decided to write this article.

Basic usage

Please read the official document here.

Assuming we have the following files:

folder structure

starWars-api.ts

    import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
    import {
      RowCharacter,
      CategorizedCharacters,
      ModifiedCharacter
    } from "./starWars-types";
    import { modifyRowCharacters, categorizeCharacters } from "./starWars-utils";

    export const starWarsApi = createApi({
      reducerPath: "starWarsApi",
      baseQuery: fetchBaseQuery({
        baseUrl: "https://akabab.github.io/starwars-api/api/"
      }),
      endpoints: (builder) => ({
        getCharacters: builder.query<CategorizedCharacters, undefined>({
          query: () => "all.json",
          transformResponse: (response: RowCharacter[]): CategorizedCharacters => {
            if (response.length === 0) return {};
            const modifiedCharacters = modifyRowCharacters(response);
            const categorizedCharacters = categorizeCharacters(modifiedCharacters);
            return { all: modifiedCharacters, ...categorizedCharacters };
          }
        })
      })
    });
Enter fullscreen mode Exit fullscreen mode

I hope to write another article about transformResponselater soon.

store.js

    import { useDispatch } from "react-redux";
    import { configureStore } from "@reduxjs/toolkit";
    import { setupListeners } from "@reduxjs/toolkit/query";
    import { starWarsApi } from "./api/starWars-api";

    export const store = configureStore({
      reducer: {
        [starWarsApi.reducerPath]: starWarsApi.reducer
      },
      middleware: (getDefaultMiddleware) =>
        getDefaultMiddleware().concat(starWarsApi.middleware)
    });

    setupListeners(store.dispatch);
Enter fullscreen mode Exit fullscreen mode

💡You can use ApiProvider if you haven’t set up a Redux store in your project. In that case, you don’t need to setup Redux store. I haven’t yet determined if I can use the ApiProvider instead of the Provider in this project.

App.js

    import { isEmpty } from "lodash";
    import { useAppDispatch } from "./store";
    import { useGetCharactersQuery, updateCharacter } from "./api/starWars-api";

    import CharacterList from "./components/CharacterList";

    import "./styles.css";

    export default function App() {
      const dispatch = useAppDispatch();
      const { data, error, isLoading } = useGetCharactersQuery(undefined);
      if (isLoading) return <div>Loading</div>;
      if (error) return <div>Error</div>;
      if (!data || isEmpty(data)) return <div>No data found</div>;

      const { all, ...charactersBySpecies } = data;

      return (
        <div className="App">
          {Object.keys(charactersBySpecies).map((key) => (
            <CharacterList
              key={key}
              name={key}
              characters={charactersBySpecies[key]}
            />
          ))}
        </div>
      );
    }
Enter fullscreen mode Exit fullscreen mode

Recipe

I implemented this updating logic using updateQueryData .

store.js

Set up custom useDispatch in store.js.
💡This is TypeScript specific process.

    export type AppDispatch = typeof store.dispatch;
    export const useAppDispatch: () => AppDispatch = useDispatch;
Enter fullscreen mode Exit fullscreen mode

starWars-api.ts

I added an action-like function, following the example in the official documentation.

    export const updateCharacter = (characters: ModifiedCharacter[]) =>
      starWarsApi.util.updateQueryData(
        "getCharacters",
        undefined,
        (draftPosts) => ({
          new: characters,
          ...draftPosts
        })
      );
    export const { useGetCharactersQuery } = starWarsApi;
Enter fullscreen mode Exit fullscreen mode

App.js

Use updateCharacter in App.js.

    import { isEmpty } from "lodash";
    import { useAppDispatch } from "./store";
    import { useGetCharactersQuery, updateCharacter } from "./api/starWars-api";

    import CharacterList from "./components/CharacterList";

    import "./styles.css";

    export default function App() {
      const dispatch = useAppDispatch();
      const { data, error, isLoading } = useGetCharactersQuery(undefined);
      if (isLoading) return <div>Loading</div>;
      if (error) return <div>Error</div>;
      if (!data || isEmpty(data)) return <div>No data found</div>;

      const { all, ...charactersBySpecies } = data;
      const handleClick = () =>
        dispatch(
          updateCharacter([
            {
              id: 0,
              name: "name",
              image: "image",
              gender: "gender",
              species: "speceis"
            }
          ])
        );

      return (
        <div className="App">
          <button onClick={handleClick}>click</button>
          {Object.keys(charactersBySpecies).map((key) => (
            <CharacterList
              key={key}
              name={key}
              characters={charactersBySpecies[key]}
            />
          ))}
        </div>
      );
Enter fullscreen mode Exit fullscreen mode

That’s it. Thank you for reading :)

The entire code is available here.
The original article is here

Top comments (0)