<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ redux-toolkit - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ redux-toolkit - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Mon, 27 Jul 2026 17:32:55 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/redux-toolkit/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Integrate RTK Query with Redux Toolkit: A Step-by-Step Guide for React Developers ]]>
                </title>
                <description>
                    <![CDATA[ Redux is a state management library for JavaScript applications. It lets you create applications that behave in a predictable manner and run on different environments, including server and native environments. Redux Toolkit is the recommended way to ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-integrate-rtk-query-with-redux-toolkit/</link>
                <guid isPermaLink="false">67a4fbc311e2c2609ca60771</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ redux-toolkit ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Chidera Humphrey ]]>
                </dc:creator>
                <pubDate>Thu, 06 Feb 2025 18:13:23 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1738854615563/3357bd11-3fcd-43b3-b459-b0e8b60e853d.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Redux is a state management library for JavaScript applications. It lets you create applications that behave in a predictable manner and run on different environments, including server and native environments. Redux Toolkit is the recommended way to write Redux logic, and was created to make working with Redux easier.</p>
<p>Traditionally, writing Redux logic required a lot of boilerplate code, configuration, and dependency installations. This made Redux difficult to work with. RTK was created to solve these issues. RTK contains utilities that simplify common Redux tasks such as store configuration, creation of reducers, and immutable state update logic.</p>
<p>Redux Toolkit Query (RTK Query) is an optional add-on included in the Redux ToolKit package. It was created to simplify data fetching and caching in web applications. RTK Query is built on top of Redux Toolkit and employs Redux for its internal architectural design.</p>
<p>In this article, you'll learn how to integrate RTK Query with Redux Toolkit in your React applications by building a simple CRUD Movie app.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ol>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-understanding-rtk-query-and-core-concepts">Understanding RTK Query and core concepts</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-integrating-rtk-query-with-redux-toolkit">Integrating RTK Query with Redux Toolkit</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-handling-data-caching-with-rtk-query">Handling Data Caching with RTK Query</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-error-handling-and-loading-states">Error Handling and Loading States</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-best-practices">Best Practices</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ol>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>For this article, I assume that you are familiar with React.</p>
<h2 id="heading-understanding-rtk-query-and-core-concepts">Understanding RTK Query and Core Concepts</h2>
<p>At the core of RTK Query is the <code>createApi</code> function. This function allows you to define an API slice, which includes the server's base URL and a set of endpoints that describe how to fetch and mutate data from the server.</p>
<p>RTK Query automatically generates a custom hook for each of the defined endpoints. These custom hooks can be used in your React component to conditionally render content based on the state of the API request.</p>
<p>The code below shows how to create an API slice using the <code>createApi</code> function:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { createApi, fetchBaseQuery } <span class="hljs-keyword">from</span> <span class="hljs-string">'@reduxjs/toolkit/query/react'</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> apiSlice = createApi({
    <span class="hljs-attr">reducerPath</span>: <span class="hljs-string">'api'</span>,
    <span class="hljs-attr">baseQuery</span>: fetchBaseQuery({ <span class="hljs-attr">baseUrl</span>: <span class="hljs-string">'https://server.co/api/v1/'</span>}),
    <span class="hljs-attr">endpoints</span>: <span class="hljs-function">(<span class="hljs-params">builder</span>) =&gt;</span> ({
        <span class="hljs-attr">getData</span>: builder.query({
            <span class="hljs-attr">query</span>: <span class="hljs-function">() =&gt;</span> <span class="hljs-string">'/data'</span>,
        })
    })
})

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> { useGetDataQuery } = apiSlice;
</code></pre>
<p><code>fetchBaseQuery</code> is a lightweight wrapper around the native JavaScript <code>fetch</code> function that simplifies API requests. The <code>reducerPath</code> property specifies the directory where your API slice is stored. A common convention is to name the directory <code>api</code>. The <code>baseQuery</code> property uses the <code>fetchBaseQuery</code> function to specify the base URL of your server. You can think of it as the root URL in which your endpoints are appended.</p>
<p><code>useGetDataQuery</code> is an auto-generated hook that you can use in your components.</p>
<h2 id="heading-how-to-integrate-rtk-query-with-redux-toolkit">How to Integrate RTK Query with Redux Toolkit</h2>
<p>In this section, you will learn how to integrate RTK Query with Redux Toolkit by building a simple Movie app. In this app, users will be able to view movies stored in your backend (though it's a mock backend), add movies, and update and delete any movie. In essence, you will build a CRUD app using RTK Query.</p>
<p>Also, I will be using TypeScript for this tutorial. If you're using JavaScript, skip the type annotations and/or <code>interface</code>s and replace <code>.tsx</code>/<code>.ts</code> with <code>.jsx</code>/<code>.js</code>.</p>
<h3 id="heading-setting-up-the-development-environment"><strong>Setting up the development environment</strong></h3>
<p>Create a new React project using the following command:</p>
<pre><code class="lang-sh">npm create vite@latest
</code></pre>
<p>Follow the prompts to create your React app.</p>
<p>Install the <code>react-redux</code> and <code>@reduxjs/toolkit</code> packages using the following command:</p>
<pre><code class="lang-sh"><span class="hljs-comment"># npm</span>
npm install @reduxjs/toolkit react-redux

<span class="hljs-comment"># yarn</span>
yarn add @reduxjs/toolkit react-redux
</code></pre>
<p>For the backend, you're going to use <code>json-server</code>. <code>json-server</code> is a light-weight Node.js tool that simulates a RESTful API using JSON files as the data source. It lets frontend developers create mock APIs without writing any server-side code.</p>
<p>You can read more about <code>json-server</code> <a target="_blank" href="https://github.com/typicode/json-server/tree/v0">here</a>.</p>
<p>Use the following command to install <code>json-server</code>:</p>
<pre><code class="lang-sh">npm install -g json-server
</code></pre>
<h3 id="heading-folder-structure"><strong>Folder structure</strong></h3>
<p>In the root directory of your application, create a <strong>data</strong> folder. Inside this folder, create a <code>db.json</code> file. This will be where your "backend" is stored.</p>
<p>In the <code>src</code> directory, create two folders: <strong>component</strong> and <strong>state</strong>.</p>
<p>Inside the <code>component</code> folder, create two folders: <strong>CardComponent</strong> and <strong>Modal,</strong> and a file:<code>Movies.tsx</code>.</p>
<p>Inside the state folder, create a <strong>movies</strong> folder and a file: <code>store.ts</code>.</p>
<p>After creating the folders and files, your app structure should look like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734786998116/7708adad-06b1-41bd-ab22-d6efb745246b.png" alt="7708adad-06b1-41bd-ab22-d6efb745246b" class="image--center mx-auto" width="312" height="576" loading="lazy"></p>
<h3 id="heading-building-the-app"><strong>Building the app</strong></h3>
<p>First, you're going to set up your <strong>JSON server</strong>.</p>
<p>Open the <code>db.json</code> file and paste in the following code:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"movies"</span>: [
    {
      <span class="hljs-attr">"title"</span>: <span class="hljs-string">"John Wick"</span>,
      <span class="hljs-attr">"description"</span>: <span class="hljs-string">"Retired assassin John Wick is pulled back into the criminal underworld when gangsters kill his beloved dog, a gift from his late wife. With his unmatched combat skills and a thirst for vengeance, Wick single-handedly takes on an entire criminal syndicate."</span>,
      <span class="hljs-attr">"year"</span>: <span class="hljs-number">2014</span>,
      <span class="hljs-attr">"thumbnail"</span>: <span class="hljs-string">"https://m.media-amazon.com/images/M/MV5BNTBmNWFjMWUtYWI5Ni00NGI2LWFjN2YtNDE2ODM1NTc5NGJlXkEyXkFqcGc@._V1_.jpg"</span>,
      <span class="hljs-attr">"id"</span>: <span class="hljs-string">"2"</span>
    },
    {
      <span class="hljs-attr">"id"</span>: <span class="hljs-string">"3"</span>,
      <span class="hljs-attr">"title"</span>: <span class="hljs-string">"The Dark Knight"</span>,
      <span class="hljs-attr">"year"</span>: <span class="hljs-number">2008</span>,
      <span class="hljs-attr">"description"</span>: <span class="hljs-string">"Batman faces off against his archenemy, the Joker, a criminal mastermind who plunges Gotham City into chaos. As the Joker tests Batman’s limits, the hero must confront his own ethical dilemmas to save the city from destruction."</span>,
      <span class="hljs-attr">"thumbnail"</span>: <span class="hljs-string">"https://m.media-amazon.com/images/M/MV5BMTMxNTMwODM0NF5BMl5BanBnXkFtZTcwODAyMTk2Mw@@._V1_FMjpg_UX1000_.jpg"</span>
    },
    {
      <span class="hljs-attr">"title"</span>: <span class="hljs-string">"Die Hard"</span>,
      <span class="hljs-attr">"description"</span>: <span class="hljs-string">"NYPD officer John McClane finds himself in a deadly hostage situation when a group of terrorists takes control of a Los Angeles skyscraper during a Christmas party. Armed only with his wit and a handgun, McClane must outsmart the heavily armed intruders to save his wife and others."</span>,
      <span class="hljs-attr">"year"</span>: <span class="hljs-number">1988</span>,
      <span class="hljs-attr">"thumbnail"</span>: <span class="hljs-string">"https://m.media-amazon.com/images/M/MV5BMGNlYmM1NmQtYWExMS00NmRjLTg5ZmEtMmYyYzJkMzljYWMxXkEyXkFqcGc@._V1_.jpg"</span>,
      <span class="hljs-attr">"id"</span>: <span class="hljs-string">"4"</span>
    },
    {
      <span class="hljs-attr">"title"</span>: <span class="hljs-string">"Mission: Impossible – Fallout"</span>,
      <span class="hljs-attr">"description"</span>: <span class="hljs-string">"Ethan Hunt and his IMF team must track down stolen plutonium while being hunted by assassins and former allies. With incredible stunts and non-stop action sequences, Hunt races against time to prevent a global catastrophe."</span>,
      <span class="hljs-attr">"year"</span>: <span class="hljs-number">2018</span>,
      <span class="hljs-attr">"thumbnail"</span>: <span class="hljs-string">"https://m.media-amazon.com/images/M/MV5BMTk3NDY5MTU0NV5BMl5BanBnXkFtZTgwNDI3MDE1NTM@._V1_.jpg"</span>,
      <span class="hljs-attr">"id"</span>: <span class="hljs-string">"5"</span>
    },
    {
      <span class="hljs-attr">"title"</span>: <span class="hljs-string">"Gladiator"</span>,
      <span class="hljs-attr">"description"</span>: <span class="hljs-string">"Betrayed by the Emperor’s son and left for dead, former Roman General Maximus rises as a gladiator to seek vengeance and restore honor to his family. His journey from slavery to becoming a champion captures the hearts of Rome’s citizens."</span>,
      <span class="hljs-attr">"year"</span>: <span class="hljs-number">2010</span>,
      <span class="hljs-attr">"thumbnail"</span>: <span class="hljs-string">"https://m.media-amazon.com/images/M/MV5BZmExODVmMjItNzFlZC00MDA0LWJkYjctMmQ0ZTNkYTcwYTMyXkEyXkFqcGc@._V1_.jpg"</span>,
      <span class="hljs-attr">"id"</span>: <span class="hljs-string">"6"</span>
    }
  ]
}
</code></pre>
<p>Start up your JSON server using the following command:</p>
<pre><code class="lang-sh">json-server --watch data\db.json --port 8080
</code></pre>
<p>This command will start up your JSON server and wrap the API endpoint running on port 8080. Your terminal should look like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734787039082/8331fca3-74ac-45aa-9fac-904af53cc961.png" alt="8331fca3-74ac-45aa-9fac-904af53cc961" class="image--center mx-auto" width="549" height="295" loading="lazy"></p>
<p>Next, you are going to create an API slice. This API slice will be used to configure your Redux store.</p>
<p>Navigate to the <strong>movies</strong> folder and create a <code>movieApiSlice.ts</code> file. Open the<code>movieApiSlice.ts</code> file and paste in the following code:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> { createApi, fetchBaseQuery } <span class="hljs-keyword">from</span> <span class="hljs-string">"@reduxjs/toolkit/query/react"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> moviesApiSlice = createApi({
  reducerPath: <span class="hljs-string">"movies"</span>,
  baseQuery: fetchBaseQuery({
    baseUrl: <span class="hljs-string">"http://localhost:8080"</span>,
  }),
  endpoints: <span class="hljs-function">(<span class="hljs-params">builder</span>) =&gt;</span> {
    <span class="hljs-keyword">return</span> {
      getMovies: builder.query({
        query: <span class="hljs-function">() =&gt;</span> <span class="hljs-string">`/movies`</span>,
      }),

      addMovie: builder.mutation({
        query: <span class="hljs-function">(<span class="hljs-params">movie</span>) =&gt;</span> ({
          url: <span class="hljs-string">"/movies"</span>,
          method: <span class="hljs-string">"POST"</span>,
          body: movie,
        }),
      }),

      updateMovie: builder.mutation({
        query: <span class="hljs-function">(<span class="hljs-params">movie</span>) =&gt;</span> {
          <span class="hljs-keyword">const</span> { id, ...body } = movie;
          <span class="hljs-keyword">return</span> {
            url: <span class="hljs-string">`movies/<span class="hljs-subst">${id}</span>`</span>,
            method: <span class="hljs-string">"PUT"</span>,
            body
          }
        },
      }),

      deleteMovie: builder.mutation({
        query: <span class="hljs-function">(<span class="hljs-params">{id}</span>) =&gt;</span> ({
          url: <span class="hljs-string">`/movies/<span class="hljs-subst">${id}</span>`</span>,
          method: <span class="hljs-string">"DELETE"</span>,
          body: id,
        }),
      }),
    };
  },
});

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> {
  useGetMoviesQuery,
  useAddMovieMutation,
  useDeleteMovieMutation,
  useUpdateMovieMutation,
} = moviesApiSlice;
</code></pre>
<p>In the code above, you created a <code>movieApiSlice</code> using the <code>createApi</code> function from RTK Query, which takes in an object as a parameter.</p>
<p>The <code>reducerPath</code> property specifies the path of the API slice.</p>
<p>The <code>baseQuery</code> uses the <code>fetchBaseQuery</code>. The <code>fetchBaseQuery</code> function takes in an object as a parameter, which has a <code>baseURL</code> property. The <code>baseURL</code> property specifies the root URL of our API.</p>
<p>In this case, you are using <a target="_blank" href="http://localhost:8080"><code>http://localhost:8080</code></a>, which is the URL of the JSON server.</p>
<p>The <code>endpoints</code> property is what your API interacts with. It’s a function that takes in a <code>builder</code> parameter and returns an object with methods (<code>getMovies</code>, <code>addMovie</code>, <code>updateMovie</code>, and <code>deleteMovie</code>) for interacting with your API.</p>
<p>Lastly, you are exporting custom hooks generated automatically by RTK Query. The custom hook starts with "use" and ends with "query" and is named based on the methods defined in the <code>endpoints</code> property.</p>
<p>These custom hooks let you interact with the API from your functional components.</p>
<p>Next, you are going to set up your Redux store. Navigate to the <code>store.ts</code> file located in the state folder and paste in the following code:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> { configureStore } <span class="hljs-keyword">from</span> <span class="hljs-string">"@reduxjs/toolkit"</span>;
<span class="hljs-keyword">import</span> { moviesApiSlice } <span class="hljs-keyword">from</span> <span class="hljs-string">"./movies/moviesApiSlice"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> store = configureStore({
    reducer: {
        [moviesApiSlice.reducerPath]: moviesApiSlice.reducer,
    },
    middleware: <span class="hljs-function">(<span class="hljs-params">getDefaultMiddleware</span>) =&gt;</span> {
        <span class="hljs-keyword">return</span> getDefaultMiddleware().concat(moviesApiSlice.middleware);
    }
})
</code></pre>
<p>In the code above, you are setting up a Redux store using the <code>configureStore</code> function from Redux Toolkit. The <code>reducer</code> property specifies a reducer for updating the state in the Redux store. The <code>moviesApiSlice.reducer</code> is the reducer for updating the state of your API.</p>
<p>For the <code>middleware</code> property, you are creating a middleware for handling asynchronous state updates. You don't have to worry too much about this part and what it does. This is required for all the caching functionality and all the other benefits that RTK Query provides.</p>
<p>Before we move further, you have to add your Redux store to your application. To do this, navigate to your <code>main.tsx</code> or <code>index.tsx</code> file (depending on what it is called in your application) and replace the code with the following code:</p>
<pre><code class="lang-tsx">// main.tsx

import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App.tsx";
import { Provider } from "react-redux";
import { store } from "./state/store.ts";

createRoot(document.getElementById("root")!).render(
  &lt;StrictMode&gt;
    &lt;Provider store={store}&gt;
      &lt;App /&gt;
    &lt;/Provider&gt;
  &lt;/StrictMode&gt;
);
</code></pre>
<p>In the code above, you are importing the <code>Provider</code> component from <code>react-redux</code> and the <code>store</code> you created earlier. Also, you are wrapping the <code>Provider</code> component around your <code>App</code> component. The <code>store</code> prop is used to pass your Redux store to your application.</p>
<h3 id="heading-building-the-movie-component"><strong>Building the Movie component</strong></h3>
<p>In this section, you're going to build out the <code>Movies.tsx</code> component, which is where all of your application logic lives.</p>
<p>Navigate to your <code>Movies.tsx</code> file and paste in the following code:</p>
<pre><code class="lang-tsx">import "../movie.css";
import { ChangeEvent, FormEvent, useState } from "react";

import {
  useGetMoviesQuery,
  useAddMovieMutation,
  useDeleteMovieMutation,
} from "../state/movies/moviesApiSlice";
import MovieCard from "./CardComponent/MovieCard";

export interface Movie {
  title: string;
  description: string;
  year: number;
  thumbnail: string;
  id: string;
}


export default function Movies() {
  // Form input states
  const [title, setTitle] = useState&lt;string&gt;("");
  const [year, setYear] = useState&lt;string&gt;("");
  const [thumbnail, setThumbnail] = useState&lt;string&gt;("");
  const [description, setDescription] = useState&lt;string&gt;("");

  const { data: movies = [], isLoading, isError } = useGetMoviesQuery({});

  const [ addMovie ] = useAddMovieMutation();
  const [ deleteMovie ] = useDeleteMovieMutation();

  // Handle form submission to add a new movie
  const handleSubmit = (e: FormEvent&lt;HTMLFormElement&gt;): void =&gt; {
    e.preventDefault();
    console.log("New movie submitted:", { title, thumbnail, description, year });
    addMovie({ title, description, year: Number(year), thumbnail, id: String(movies.length + 1) })
    // Reset form inputs after submission
    setTitle("");
    setThumbnail("");
    setDescription("");
    setYear("");
  };

  if (isError) {
    return &lt;div&gt;Error&lt;/div&gt;;
  }

  if (isLoading) {
    return &lt;div&gt;Loading...&lt;/div&gt;;
  }

  return (
    &lt;div className="movie-container"&gt;
      &lt;h2&gt;Movies to Watch&lt;/h2&gt;

      {/* Form to add a new movie */}
      &lt;div className="new-movie-form"&gt;
        &lt;form onSubmit={handleSubmit}&gt;
          &lt;div className="form-group"&gt;
            &lt;label htmlFor="title"&gt;Title&lt;/label&gt;
            &lt;input
              type="text"
              name="title"
              id="title"
              placeholder="Enter movie title"
              value={title}
              onChange={(e: ChangeEvent&lt;HTMLInputElement&gt;) =&gt; setTitle(e.target.value)}
              required
            /&gt;
          &lt;/div&gt;

          &lt;div className="form-group"&gt;
            &lt;label htmlFor="imageAddress"&gt;Image Link:&lt;/label&gt;
            &lt;input
              type="text"
              name="imageAddress"
              id="imageAddress"
              placeholder="Enter image link address"
              value={thumbnail}
              onChange={(e: ChangeEvent&lt;HTMLInputElement&gt;) =&gt; setThumbnail(e.target.value)}
              required
            /&gt;
          &lt;/div&gt;

          &lt;div className="form-group"&gt;
            &lt;label htmlFor="year"&gt;Year of release:&lt;/label&gt;
            &lt;input
              type="text"
              name="year"
              id="year"
              placeholder="Enter year of release"
              value={year}
              onChange={(e: ChangeEvent&lt;HTMLInputElement&gt;) =&gt; setYear(e.target.value)}
            /&gt;
          &lt;/div&gt;

          &lt;div className="form-group"&gt;
            &lt;label htmlFor="description"&gt;Description&lt;/label&gt;
            &lt;textarea
              name="description"
              id="description"
              placeholder="Enter movie description"
              value={description}
              onChange={(e: ChangeEvent&lt;HTMLTextAreaElement&gt;) =&gt; setDescription(e.target.value)}
              required
            &gt;&lt;/textarea&gt;
          &lt;/div&gt;

          &lt;button type="submit"&gt;Add Movie&lt;/button&gt;
        &lt;/form&gt;
      &lt;/div&gt;

      {/* Render list of movies */}
      &lt;div className="movie-list"&gt;
        {movies.length === 0 ? (
          &lt;p&gt;No movies added yet.&lt;/p&gt;
        ) : (
          movies.map((movie: Movie) =&gt; (
            &lt;div key={movie.id}&gt;
              &lt;MovieCard movie={movie} deleteMovie={deleteMovie} /&gt;
            &lt;/div&gt;
          ))
        )}
      &lt;/div&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<p>In the code above, you're creating a <code>Movies</code> component and using RTK Query to handle CRUD operations.</p>
<p>Let's go step-by-step through what each part of the code does.</p>
<p>In the top part, you imported the <code>useGetMoviesQuery</code>, <code>useAddMovieMutation</code>, and <code>useDeleteMovie</code> functions from the <code>moviesApiSlice</code> you created earlier. The functions will be used for fetching, adding, and deleting movies, respectively.</p>
<p>You also imported a <code>MovieCard</code> component, which you'll use to display each movie. You'll create the <code>MovieCard</code> component in a second.</p>
<p>The <code>Movie</code> interface defines the shape of each movie object. It ensures consistency in the structure of movie data across the component. Again, ignore if you're using JavaScript.</p>
<p>You defined some state variables: <code>title</code>, <code>year</code>, <code>thumbnail</code>, and <code>description</code> to store form input values.</p>
<p>The <code>useGetMoviesQuery</code> hook fetches the movie data when the component mounts. The hook returns an object with several properties, but we're focusing on three properties: <code>data</code> aliased as <code>movies</code>, <code>isLoading</code>, and <code>isError</code>.</p>
<p>The <code>useAddMovieMutation</code> and <code>useDeleteMovieMutation</code> hooks return two functions: <code>addMovie</code> and <code>deleteMovie</code>, respectively.</p>
<p>The <code>handleSubmit</code> function handles the submission of the form. When the form is submitted, the <code>addMovie</code> function is called with the new movie details. The <code>year</code> is converted to a number, and the <code>id</code> is generated based on the current length of the movie array.</p>
<p>If an error occurs while fetching the movies (<code>isError</code>), a simple error message is displayed.</p>
<p>If the API request is still loading (<code>isLoading</code>), a loading message is shown.</p>
<p>If everything goes well, the main JSX structure of the component is returned, which includes:</p>
<ul>
<li><p>a form for adding new movies.</p>
</li>
<li><p>a list of movies rendered using the <code>MovieCard</code> component. Each<code>MovieCard</code> is passed the individual <code>movie</code> data along with the <code>deleteMovie</code> function to handle deletions.</p>
</li>
</ul>
<p>Now, let's create our <code>MovieCard</code> component.</p>
<p>Inside the <strong>CardComponent</strong> folder, create a <code>MovieCard.tsx</code> file. Open the <code>MovieCard.tsx</code> and paste in the following code:</p>
<pre><code class="lang-tsx">import { useRef, useState } from "react";
import EditModal from "../Modal/EditModal";
import { Movie } from "../Movies";

type DeleteMovie = (movie:{id:string}) =&gt; void;

interface MovieCardProps {
  movie: Movie;
  deleteMovie: DeleteMovie;
}

function MovieCard({ movie, deleteMovie }: MovieCardProps) {

  const dialogRef = useRef&lt;HTMLDialogElement | null&gt;(null);
  const [selectedMovie, setSelectedMovie] = useState&lt;Movie&gt;(movie);

  const handleSelectedMovie = () =&gt; {
    setSelectedMovie(movie);
    dialogRef.current?.showModal();
    document.body.style.overflow = 'hidden';
  }

  const closeDialog = (): void =&gt; {
    dialogRef.current?.close();
    document.body.style.overflow = 'visible';
  }

  return (
    &lt;div className="movie-wrapper" key={movie.id}&gt;
      &lt;div className="img-wrapper"&gt;
        &lt;img src={movie.thumbnail} alt={`${movie.title} poster`} /&gt;
      &lt;/div&gt;
      &lt;h3&gt;
        {movie.title} ({movie.year})
      &lt;/h3&gt;
      &lt;p&gt;{movie.description}&lt;/p&gt;
      &lt;div className="button-wrapper"&gt;
        &lt;button onClick={handleSelectedMovie}&gt;Edit&lt;/button&gt;
        &lt;button onClick={() =&gt; deleteMovie({ id: movie.id })}&gt;Delete&lt;/button&gt;
      &lt;/div&gt;

      &lt;EditModal dialogRef={dialogRef} selectedMovie={selectedMovie} closeDialog={closeDialog} /&gt;

    &lt;/div&gt;
  );
}

export default MovieCard;
</code></pre>
<p>In the code above, you're creating a <code>MovieCard</code> component for displaying the movies on the screen.</p>
<p>You're importing the <code>useRef</code> and <code>useState</code> hooks from React to manage the component’s state and references. You also import the <code>EditModal</code> component, which will handle editing the movie details, and the<code>Movie</code> type to enforce the shape of the movie object (this is for TypeScript).</p>
<p>The <code>MovieCard</code> component accepts two props: <code>movie</code> and <code>deleteMovie</code>.</p>
<p>The <code>dialogRef</code> variable is used to manage the reference to the modal dialog element.</p>
<p>The <code>selectedMovie</code> state is initialized with the <code>movie</code> prop. This will be used to track the currently selected movie for editing purposes.</p>
<p>The <code>handleSelectedMovie</code> function is called when the <strong>Edit</strong> button is clicked. It does the following:</p>
<ul>
<li><p>Sets <code>selectedMovie</code> to the current movie object.</p>
</li>
<li><p>Opens the <code>EditModal</code> dialog using <code>dialogRef.current?.showModal()</code>.</p>
</li>
<li><p>Prevents the page from scrolling while the modal is open by setting <code>document.body.style.overflow</code> to <code>'hidden'</code>.</p>
</li>
</ul>
<p>The <code>closeDialog</code> function closes the modal dialog using <code>dialogRef.current?.close()</code> and resets the page’s scroll behavior by setting <code>document.body.style.overflow</code> back to <code>'visible'</code>.</p>
<p>In the <code>return</code> statement, a JSX structure is returned that displays:</p>
<ul>
<li><p>an image for the movie's thumbnail,</p>
</li>
<li><p>the movie's title and year of release in an <code>h3</code> element,</p>
</li>
<li><p>a short description of the movie,</p>
</li>
<li><p>two buttons:</p>
<ul>
<li><p>The "Edit" button triggers the <code>handleSelectedMovie</code> function to open the <code>EditModal</code>.</p>
</li>
<li><p>The "Delete" button calls the <code>deleteMovie</code> function, passing the movie’s queryID to delete the specified movie from your API.</p>
</li>
</ul>
</li>
</ul>
<p>The <code>EditModal</code> component is also rendered, passing <code>dialogRef</code>, <code>closeDialog</code>, and <code>selectedMovie</code> as props. This ensures that the <code>EditModal</code> has access to the selected movie's details and a function to close itself.</p>
<p>Next up, you're going to create the <code>EditModal</code> component.</p>
<p>Inside the <strong>Modal</strong> folder, create a file: <code>EditModal.tsx</code>, that will house the modal component.</p>
<p>Open the <code>EditModal.tsx</code> file and paste in the following code:</p>
<pre><code class="lang-tsx">import { useUpdateMovieMutation } from "../../state/movies/moviesApiSlice";
import { Movie } from "../Movies";
import "./modal.css";
import { useState, RefObject, FormEvent } from "react";

interface EditModalProps {
  dialogRef: RefObject&lt;HTMLDialogElement&gt;;
  selectedMovie: Movie;
  closeDialog: () =&gt; void;
}

function EditModal({ dialogRef, selectedMovie, closeDialog }: EditModalProps) {
  const [title, setTitle] = useState&lt;string&gt;(selectedMovie.title);
  const [year, setYear] = useState&lt;string | number&gt;(selectedMovie.year);
  const [description, setDescription] = useState&lt;string&gt;(selectedMovie.description);
  const [thumbnail, setThumbnail] = useState&lt;string&gt;(selectedMovie.thumbnail);

  const [updateMovie] = useUpdateMovieMutation();

  async function handleUpdateMovie(e: FormEvent&lt;HTMLFormElement&gt;){
    e.preventDefault();
    try {
      await updateMovie({title, description, year: Number(year), thumbnail, id: selectedMovie.id});
      closeDialog();
    } catch (error) {
      alert(`${error} occurred`);
    }
  }

  return (
    &lt;dialog ref={dialogRef} className="modal-dialog"&gt;
      &lt;form onSubmit={handleUpdateMovie}&gt;
        &lt;div className="form-group"&gt;
          &lt;label htmlFor="title"&gt;Title:&lt;/label&gt;
          &lt;input
            type="text"
            id="title"
            value={title}
            onChange={(e) =&gt; setTitle(e.target.value)}
          /&gt;
        &lt;/div&gt;

        &lt;div className="form-group"&gt;
          &lt;label htmlFor="year"&gt;Year of release:&lt;/label&gt;
          &lt;input
            type="text"
            id="year"
            value={year}
            onChange={(e) =&gt; setYear(e.target.value)}
          /&gt;
        &lt;/div&gt;

        &lt;div className="form-group"&gt;
          &lt;label htmlFor="thumbnail"&gt;Image URL:&lt;/label&gt;
          &lt;input
            type="text"
            id="thumbnail"
            value={thumbnail}
            onChange={(e) =&gt; setThumbnail(e.target.value)}
          /&gt;
        &lt;/div&gt;

        &lt;div className="form-group"&gt;
          &lt;label htmlFor="description"&gt;Description:&lt;/label&gt;
          &lt;textarea
            id="description"
            value={description}
            onChange={(e) =&gt; setDescription(e.target.value)}
          &gt;&lt;/textarea&gt;
        &lt;/div&gt;
        &lt;button type="submit"&gt;Save&lt;/button&gt;
      &lt;/form&gt;
      &lt;button className="close-btn" onClick={closeDialog}&gt;
        Close
      &lt;/button&gt;
    &lt;/dialog&gt;
  );
}

export default EditModal;
</code></pre>
<p>In the code above, you're simply creating a modal dialog using the native HTML <code>&lt;dialog&gt;</code> element. Inside the <code>dialog</code> element is a <code>form</code> field populated with the details of the selected movie, obtained from the state variables: <code>title</code>, <code>year</code>, <code>description</code>, and <code>thumbnail</code>.</p>
<p>You imported the <code>useUpdateMovieMutation</code> hook from your <code>moviesApiSlice</code>. The <code>useUpdateMovieMutation</code> hook returns an <code>updateMovie</code> function you can use to update movie details.</p>
<p>The <code>handleUpdateMovie</code> is called when the <strong>Save</strong> button is clicked. It does the following:</p>
<ul>
<li><p>updates the movie details by calling the <code>updateMovie</code> function</p>
</li>
<li><p>closes the dialog using the <code>closeDialog</code> function</p>
</li>
</ul>
<h3 id="heading-mounting-our-component"><strong>Mounting our component</strong></h3>
<p>Navigate to your <code>App.tsx</code> file and add in your <code>Movies</code> component the following code:</p>
<pre><code class="lang-tsx">import "./App.css";
import Movies from "./components/Movies";

function App() {
  return (
    &lt;div&gt;
      &lt;Movies /&gt;
    &lt;/div&gt;
  );
}

export default App;
</code></pre>
<p>In your browser, open your <a target="_blank" href="http://localhost">localhost</a> and you should see something like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734787096281/f4f87b33-d5ba-4537-acd1-39dfa740410a.gif" alt="f4f87b33-d5ba-4537-acd1-39dfa740410a" class="image--center mx-auto" width="640" height="339" loading="lazy"></p>
<p>Congratulations! You've successfully integrated RTK Query with the Redux Toolkit.</p>
<p>In the next section, you'll learn how caching in RTK Query works and how to invalidate caches.</p>
<h2 id="heading-how-to-handle-data-caching-with-rtk-query">How to Handle Data Caching with RTK Query</h2>
<p>In this section, you'll learn how caching works in RTK Query and how to invalidate caches.</p>
<p>In programming, caching is one of the hardest things to do. But RTK Query makes handling caching easier for us.</p>
<p>When you call your API, RTK Query automatically caches the result of successfully calling your API. This means that subsequent calls to the API return the cached result.</p>
<p>For example, if you try editing any movie in your app, you'll notice that nothing changes. This doesn't mean that it's not working – in fact, it is working. And the results returned are the cached version (the results when you first called the API, that is on component mount).</p>
<p>To stop this behaviour, you need to invalidate the cache each time you make changes to your backend. This will cause RTK Query to automatically refetch the data to reflect your changes.</p>
<p>Navigate to your <code>moviesApiSlice.ts</code> file and replace that code with the following code:</p>
<pre><code class="lang-tsx">
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";

export const moviesApiSlice = createApi({
  reducerPath: "movies",
  baseQuery: fetchBaseQuery({
    baseUrl: "http://localhost:8080",
  }),
  tagTypes: ['Movies'],
  endpoints: (builder) =&gt; {
    return {
      getMovies: builder.query({
        query: () =&gt; `/movies`,
        providesTags: ['Movies']
      }),

      addMovie: builder.mutation({
        query: (movie) =&gt; ({
          url: "/movies",
          method: "POST",
          body: movie,
        }),
        invalidatesTags: ['Movies']
      }),

      updateMovie: builder.mutation({
        query: (movie) =&gt; {
          const { id, ...body } = movie;
          return {
            url: `movies/${id}`,
            method: "PUT",
            body
          }
        },
        invalidatesTags: ['Movies']
      }),

      deleteMovie: builder.mutation({
        query: ({id}) =&gt; ({
          url: `/movies/${id}`,
          method: "DELETE",
          body: id,
        }),
        invalidatesTags: ['Movies']
      }),
    };
  },
});

export const {
  useGetMoviesQuery,
  useAddMovieMutation,
  useDeleteMovieMutation,
  useUpdateMovieMutation,
} = moviesApiSlice;
</code></pre>
<p>In the code above, you added the <code>tagTypes</code> property to your <code>moviesApiSlice</code> and set it to<code>[Movies]</code>. This will be used to invalidate the cached results when you make changes to your backend.</p>
<p>In the <code>getMovies</code> function, you added the <code>providesTags</code> property. This means that you're providing a tag to your API call, which you can invalidate with the mutation functions.</p>
<p>In the mutation functions (<code>addMovie</code>, <code>updateMovie</code>, and <code>deleteMovie</code>), you added the <code>invalidatesTags</code> property set to the value of the <code>tagTypes</code> property. This invalidates the cache whenever each of these mutation functions are called, which causes RTK Query to automatically refetch the data.</p>
<p>With these changes, you can update and delete movies and see the result of your changes.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734787129856/983cd55d-9714-4c0e-a038-2b7c9f60f881.gif" alt="983cd55d-9714-4c0e-a038-2b7c9f60f881" class="image--center mx-auto" width="640" height="339" loading="lazy"></p>
<h2 id="heading-error-handling-and-loading-states">Error Handling and Loading States</h2>
<p>When you were building your app, you handled any errors that might arise from calling your API by simply displaying a "Error..." text.</p>
<p>In real-world applications, you want to display something meaningful, such as a UI that tells your users what went wrong exactly.</p>
<p>Similarly, when your API request is loading, you want to display a loading spinner or a loading skeleton UI so that your users know that your app data is loading.</p>
<p>For the purposes of this article, we are not going to dive into advanced error handling or managing loading states – but these would be things you’d want to look into.</p>
<h2 id="heading-best-practices">Best Practices</h2>
<p>Below are some of the best practices to consider when working with RTK Query:</p>
<ol>
<li><p><strong>Separate multiple API slices</strong>: if you have multiple API slices for different APIs, consider separating them into different API slices. This keeps your API slices modular, making it easier to maintain and debug.</p>
</li>
<li><p><strong>Use the Redux Devtools</strong>: the Redux Devtools let you get an inside look at what is going on in your Redux store as well as your queries and mutations. This makes debugging much easier. The Redux Devtools are available as a Chrome extension.</p>
</li>
<li><p><strong>Prefetch data</strong>: use the <code>usePrefetch</code> hook to make a data fetch before a user navigates to a page on your website or loads some known content. This reduces load time and makes the UI feel faster.</p>
</li>
<li><p><strong>Use middleware for complex logic</strong>: implement middleware when you need to intercept and modify actions or responses, such as adding authentication tokens to headers or logging specific errors.</p>
</li>
<li><p><strong>Use optimistic updates</strong>: when using <code>useMutation</code> to update or change existing data, you can implement an optimistic update to the UI. This helps to give the impression of immediate changes. If the request fails, you can roll back the update.</p>
</li>
</ol>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this article, you learned what RTK Query is and how to integrate RTK Query with Redux Toolkit by building a CRUD React Movie app. You also learned about the caching strategies of RTK Query and how to invalidate the caches.</p>
<p>Thanks for reading!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Learn Redux and Redux Toolkit for State Management ]]>
                </title>
                <description>
                    <![CDATA[ State management is one of the most important aspects of building scalable and efficient React applications. Whether you're managing user interactions, API data, or application-wide settings, having a robust tool for handling state can save time and ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/learn-redux-and-redux-toolkit-for-state-management/</link>
                <guid isPermaLink="false">673e03874ee4d6be53b9fe65</guid>
                
                    <category>
                        <![CDATA[ Redux ]]>
                    </category>
                
                    <category>
                        <![CDATA[ redux-toolkit ]]>
                    </category>
                
                    <category>
                        <![CDATA[ youtube ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Beau Carnes ]]>
                </dc:creator>
                <pubDate>Wed, 20 Nov 2024 15:43:03 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1732117361940/ab139527-5428-4787-bd2c-c2f1336ece8a.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>State management is one of the most important aspects of building scalable and efficient React applications. Whether you're managing user interactions, API data, or application-wide settings, having a robust tool for handling state can save time and prevent bugs. That's where Redux, a popular state management library, and its modern sibling, Redux Toolkit, shine. These tools provide a structured and predictable way to manage state in your applications, making them an essential skill for React developers.</p>
<p>We just published a course on the freeCodeCamp.org YouTube channel that will teach you all about Redux and Redux Toolkit. Created by Khaiser Khanam, this comprehensive course takes you from foundational concepts to advanced state management patterns. Whether you're a beginner eager to understand how Redux works or an experienced developer looking to master Redux Toolkit and industry best practices, this course has something for everyone. You'll build real-world applications and learn to avoid common pitfalls while integrating Redux into React projects.</p>
<h3 id="heading-what-youll-learn-in-this-course">What You’ll Learn in This Course</h3>
<h4 id="heading-part-1-redux-fundamentals"><strong>Part 1: Redux Fundamentals</strong></h4>
<p>The course begins with an in-depth exploration of Redux's core concepts, including actions, reducers, and the store. Through clear analogies and visualizations, you’ll learn:</p>
<ul>
<li><p>Why Redux is useful and when it might not be the right choice.</p>
</li>
<li><p>The three principles of Redux and how they guide state management.</p>
</li>
<li><p>Middleware, including how to use Thunk for handling async logic.</p>
</li>
<li><p>Setting up React-Redux and leveraging hooks like <code>useSelector</code> and <code>useDispatch</code> to connect your React components to Redux.</p>
</li>
<li><p>Building a professional folder structure for your Redux projects.</p>
</li>
</ul>
<p>The first half of the course also dives into practical examples, such as creating a "burger application," combining reducers, implementing logger middleware, and integrating the Redux DevTools extension. By the end of this section, you’ll have a solid foundation to use Redux confidently in your projects.</p>
<h4 id="heading-part-2-redux-toolkit-essentials"><strong>Part 2: Redux Toolkit Essentials</strong></h4>
<p>In the second part, you’ll dive into Redux Toolkit, a modern library that simplifies working with Redux by reducing boilerplate code and improving developer experience. Topics include:</p>
<ul>
<li><p>The <code>createSlice</code> method and its role in simplifying reducer and action creation.</p>
</li>
<li><p>Advanced patterns like extra reducers and the <code>createAsyncThunk</code> function for handling async actions.</p>
</li>
<li><p>Setting up projects with Vite, and building features like a pizza-and-burger ordering application.</p>
</li>
<li><p>Utilizing the Immer library for immutable state updates, and exploring how Redux Toolkit integrates seamlessly with React DevTools.</p>
</li>
</ul>
<p>This section also covers best practices, common beginner mistakes, and Redux Toolkit interview questions to help you prepare for real-world use cases and technical evaluations.</p>
<h3 id="heading-conclusion">Conclusion</h3>
<p>Redux and Redux Toolkit are must-know tools for React developers aiming to build professional-grade applications. This course provides a step-by-step approach, ensuring that even complex topics like middleware, async actions, and advanced state patterns are accessible. Plus, you’ll work on real-world projects, reinforcing the concepts through practical application.</p>
<p>Watch the full course on <a target="_blank" href="https://youtu.be/SlC8941Wwrk">the freeCodeCamp.org YouTube channel</a> (8-hour watch).</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/SlC8941Wwrk" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
