Sajjat Hossain - Prayers Connect https://www.prayersconnect.org From 2016 Wed, 31 May 2023 02:04:58 +0000 en-US hourly 1 https://wordpress.org/?v=6.7.2 https://www.prayersconnect.org/wp-content/uploads/2023/03/cropped-main-logo-png-1-32x32.png Sajjat Hossain - Prayers Connect https://www.prayersconnect.org 32 32 RTK Query: Let’s go beyond minimal setup https://www.prayersconnect.org/137-rtk-query-lets-go-beyond-minimal-setup/ https://www.prayersconnect.org/137-rtk-query-lets-go-beyond-minimal-setup/#respond Sun, 23 Oct 2022 14:20:42 +0000 https://hq.prayersconnect.com/?p=137 Previously we saw how we can get started with RTK Query with a minimal setup. If you haven’t read that already, I would suggest you take a look at it. It would make it easy for you to understand this one. In this setup, we will go beyond our minimal setup and would learn some...

The post RTK Query: Let’s go beyond minimal setup first appeared on Prayers Connect.

]]>
Previously we saw how we can get started with RTK Query with a minimal setup. If you haven’t read that already, I would suggest you take a look at it. It would make it easy for you to understand this one. In this setup, we will go beyond our minimal setup and would learn some simple tricks. Click here to see the previous blog

I assume that you’re using Redux Toolkit to manage your global states, and not just RTK Query to manage your APIs. Here’s how you have to configure your store to use Redux Toolkit Query in conjunction with your global states.

Configure the store :

import { configureStore } from '@reduxjs/toolkit';
import { demo } from 'store/api/demoSlice';
import { setupListeners } from '@reduxjs/toolkit/dist/query';

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

setupListeners(store.dispatch);

Here, as you can see, it looks different than the usual setup. We’re adding the demo api slice as the rest but there are two extra thing (middleware and setupListeners). Let’s learn why we need these two.

The API slices are like other state slices. They hold their own state. But why do they need their own state? For the caching purpose that we talked about. Now, If they are similar, then why do we need these extra configurations? Because, they might be similar, but as you may know, when you fetch data from an API, you need to handle it asynchronously. You have to wait for the response. That’s why you can not use the API slices as regular slices. You need to add them to the middleware list so that they don’t interrupt your regular state management and also handle the responses asynchronously. Also, you need to add the setupListeners to listen for the actions being dispatched.

Multiple API slices:

Another part of this setup is to show you, how you can configure your store to have multiple API slices. It’s pretty simple. You just have to create another slice and add it to the reducer and middleware list as shown below,

import { configureStore } from '@reduxjs/toolkit';
import { demo } from 'store/api/demoSlice';
import { demo2 } from 'store/api/demo2Slice';
import { setupListeners } from '@reduxjs/toolkit/dist/query';

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

setupListeners(store.dispatch);

See! really easy right? So, far we’ve configured the store but there’s one thing still remaining. That is to wrap up the root app with the provider. But wait, didn’t we do that previously? we don’t need to do that again. You’re right and also wrong at the same time. You’re right that we’ve already wrapped the root with the provider. But you’re also wrong cause we wrapped the root with the ApiProvider. You can only use API slices with that provider, not the state slices.

Yeah. Right. Then we’ll just wrap the root with the provider that we used to use for the state slices right?

No, we can not just use that. First, we have to remove the API provider and wrap the root only with the regular state provider because we’re already passing the API slices with the rest of the state slices. So we don’t need that anymore. The updated code should look like this,

import React from 'react';
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux';
import { store } from 'store/';
import Index from 'pages/'

function App() {
  return (
    <Provider store={store}>
      <Index />
    </Provider>
  );
 }

Just pass the store and you’re all done.

Now, as you have multiple API slices, please be sure to differentiate the API tags from each API slice. Because they might end up creating some unexpected behavior.

Here comes a trick :

How can you request multiple API calls that may require a response from another API?

Ans: You have to skip the next request conditionally. This might sound tricky but it’s actually easy. Here’s how to do it.

const {data, ...} = demo.useAnImportantQuery();
const {data: data2, ...} = demo.useAnothereImportantQuery({data.id},{skip: !data})

This way the second call will be skipped until you get a successful response from the first API or the data has no value.

The post RTK Query: Let’s go beyond minimal setup first appeared on Prayers Connect.

]]>
https://www.prayersconnect.org/137-rtk-query-lets-go-beyond-minimal-setup/feed/ 0
RTK Query: Make fetching and caching data effortless https://www.prayersconnect.org/86-rtk-query-make-fetching-and-caching-data-effortless/ https://www.prayersconnect.org/86-rtk-query-make-fetching-and-caching-data-effortless/#comments Tue, 04 Oct 2022 18:41:24 +0000 https://hq.prayersconnect.com/?p=86 RTK query which is the short form of Redux Toolkit Query is a data fetching and caching library that comes bundled with Redux Toolkit. If you’re a person using Redux Toolkit to manage your application global store then this is going to be the best go-to for you. There are a few alternatives to Redux...

The post RTK Query: Make fetching and caching data effortless first appeared on Prayers Connect.

]]>
RTK query which is the short form of Redux Toolkit Query is a data fetching and caching library that comes bundled with Redux Toolkit. If you’re a person using Redux Toolkit to manage your application global store then this is going to be the best go-to for you.

There are a few alternatives to Redux Toolkit. React-Query and Redux-Saga are amongst them. These libraries need a bit more configuration to work. Like, React Query wants you to use your choice of data fetching library (e.g Axios or fetch API). But RTK Query handles all of it for you. You just need to create an API module and add the required configurations. After you add the base configurations you’re ready to roll. Also, it makes handling loading states, and errors a lot easier. Now let’s see how we can implement RTK Query with minimal configuration.

First, we’ll see how you can just use the RTK Query without using Redux Toolkit in your project.

1. Create a store :

Create a folder named store at the root of your project.

Now, create a folder called reducers inside of it. To separate our API slices from global state slices let’s create a folder called API and we’ll create all the API slices inside of it. Now the slice config,

  • Slice with a single endpoint. Let’s name it to demo.
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';

interface IReturn {
  key[string]: any;
}

interface IParams {
  key[string]: any;
}

export const demo = createApi({
  reducerPath: 'demo',
  baseQuery: fetchBaseQuery({
    baseUrl: 'https://baseurl.com'
  }),
  tagTypes: ['tag'],
  endpoints: (builder) => ({
    getSingleData: builder.query<IReturn, IParams>({
      query: ({ slug, id }) => `/${slug}/items/${id}`
    })
  })
});

You need to add reducerPath just like you need to do for reducer slices. Now, there are two types of builder methods. query and mutation. Queries are used when you’re not trying to mutate or change any data. For Get requests in short. And mutations are used to change, update, delete type actions. Also the tagTypes here are like groups and you’ll need it when you make mutations. We’ll see it downwards.

  • Here you can see there are two interfaces. These are only required for TypeScript projects. You need to specify the types of the parameters and the returns inside the angle brackets.
  • Now, you might need more than these. Like you might need to pass your auth token. For that you just have to configure it inside the baseQuery property. Here’s how to do it.
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';

interface IReturn {
  key[string]: any;
}

interface IParams {
  key[string]: any;
}

const baseQuery = fetchBaseQuery({
  baseUrl: 'https://baseurl.com',
  prepareHeaders: (headers) => {
  const token = 'token';
    if (token) {
      headers.set('Content-Type', 'application/json');
      headers.set('Authorization', `Bearer ${token}`);
      headers.set('Cookies', token);
    }
    return headers;
  }
});

export const demo = createApi({
  reducerPath: 'demo',
  baseQuery: baseQuery,
  tagTypes: ['tag'],
  endpoints: (builder) => ({
    getSingleData: builder.query<IReturn, IParams>({
      query: ({ slug, id }) => `/${slug}/items/${id}`
    }),
    getAllData: builder.query<IReturn[], IParams>({
      query: ({ slug }) => `/${slug}/items`,
      providesTags: [{ type: 'tag', id: 'LIST' }]
    })
  })
});
  • We’re setting the headers if we find the token. The getAllData seems to be different right? It has an extra configuration providesTags. What is it doing here? Well, it is used to cache the fetched data. So, when you send a request using the same endpoint and the cache hasn’t been invalidated, it will return the cache instead. Which saves you from some API calls and makes the UI rendering look instant.
  • So far we’ve only seen how we can request queries. Let’s configure some methods to make mutations. Create, Update, Delete generally.
export const demo = createApi({
  reducerPath: 'demo',
  baseQuery: baseQuery,
  tagTypes: ['tag'],
  endpoints: (builder) => ({
    getSingleData: builder.query<IReturn, IParams>({
      query: ({ slug, id }) => `/${slug}/items/${id}`
    }),
    getAllData: builder.query<IReturn[], IParams>({
      query: ({ slug }) => `/${slug}/items`,
      providesTags: [{ type: 'tag', id: 'LIST' }]
    }),
    updateItem: builder.mutation<IReturn, IParam>({
      query: ({ slug, id, body }) => {
        return {
          url: `/${slug}/items/${id}`,
          method: 'PATCH',
          body
        };
      },
      invalidatesTags: [{ type: 'tag', id: 'LIST' }]
    }),
    deleteItem: builder.mutation<IReturn, IParams>({
      query: ({ slug, id }) => {
        return {
          url: `${slug}/items/${id}`,
          method: 'DELETE'
        };
      },
      invalidatesTags: [{ type: 'tag', id: 'LIST' }]
    })
  })
});
  • For mutations, we’re writing a bit more code. In the return statement of the query, you need to specify the URL, Method, and Body (optional). Just like you do in fetch or Axios, you pass the endpoint and method and body. Now, here’s an interesting thing. When you make mutations, you’re changing the data. So it means that the cache you have currently is old and stores old/wrong values. You need to update it. But first, you have to invalidate it so that it can be updated right! To do that, just add an extra property invalidatesTags and add the tags that it belongs to. Also, you don’t have to do it in the shown way. You can just add the tags directly inside the array like so,
invalidatesTags: ['tag']
  • After including this property, whenever you make changes, your cached data will be invalidated. Now you might think as your cache was invalidated, you need to somehow make another request to fetch new data and cache that again. Don’t worry. RTK query handles that for you too. Whenever a cache is invalidated it will automatically fetch new data with the method that provides that tag.
  • Now, will it run every one of the queries to fetch data? And when will it be executed? It will be executed when you visit a page where you have implemented a query method to fetch data. And all the loading and error handling will happen as the initial fetch.

This far we’ve only created an API slice. Now, let’s see how we can allow our project to use it. To do so, we need to wrap our root element with a provider and pass the API slice as a prop. Now here’s a thing. RTK suggests you use a single slice for the APIs but you’re not bound to do so. If you have a use case where you need to have separate API slices, you can do so. But it requires a bit more configuration. For now, let’s just focus on our minimal configuration.

2. Wrap with the provider :

import React from 'react';
import ReactDOM from 'react-dom/client';
import { ApiProvider } from '@reduxjs/toolkit/query/react';
import { demo } from 'store/api/demoSlice';
import Index from 'pages/'

function App() {
  return (
    <ApiProvider api={demo}>
      <Index />
    </ApiProvider>
  );
 }

To provide your slices you just have to import the exported slice from the store and pass it into the ApiProvider as the value of the api prop. For the sake of this minimal setup, assuming that you have only one API slice for your whole project. You’re now done here. Now let’s see how we can consume our API methods. And here comes the interesting part.

3. Consuming the API methods.

import { demo } from 'store/api/demoSlice';

function Todos = () => {
const { data, isLoading, error } = demo.useGetAllDataQuery()
const [deleteItem, status] = demo.useDeleteItemMutation()

const handleDelete = async (id) => {
  const res = await deleteItem(id);
};

if(isLoading){
  return <div>Loading. Please wait!</div>
}

if(isError){
  return <div>Sorry! Unable to load data.</div>
}

return (
  <div>
    {data.map(item => {
       return <div key={item.id} onClick={handleDelete}>{item.title}</div>
     })}
  </div>)
}

export default Todos;

Wait! What? Where do these useGetAllDataQuery and useDeleteItemMutation methods come from? The methods we created are different!

Looks different but at the same time similar right? Well, you’re right. Cause we did not create these methods but they sound familiar cause these are the ones we created with a bit of tweaking. RTK Query prefixed our methods with the keyword use and postfixed with the type of the method, either Query or Mutation. And to use those methods we need to write them this way.

Now, as you can see I’ve imported the demo module from demoSlice and used the endpoints. Here, for queries, it returns an object with some props like data, isLoading, isError, error, etc. These props are self-explanatory. To use the data you need to wait until the data is loaded. Till then show a loading message and error message for any kind of error.

For the mutation, you get a tuple in return. The first property is the handler and the second one is the status. You can also show loading states based on the status of the mutation. now you just have to call the handler passing the right param(s). And after the successful execution, the data will get invalidated and fetched new data automatically.

You can also refetch manually. Query methods return a property called refetch. It’s a function and you can just call it like other functions on a click event may be to refetch. All other API methods can be used in the same way.

Now you’re ready to jumpstart yourself and go beyond this minimal config.

Few things you should know :

  • You can make lazy queries too. What I mean is that with the way we’re consuming the endpoints now, the queries happen on page load automatically. But you might wanna do it only on a certain use case. To do that you can make lazy queries. The code will look like this,
const [ fetchAllData, {data, isLoading, isError} ] = demo.useLazyGetAllDataQuery()

Now just call the fetchData method as/when you please.

  • You need async await only when a method returns a tuple.
  • You can cache each element of an array with the same tag type but different IDs like so,
providesTags: (result, error, arg) => result 
? [...result.map(({ id }) => ({ type: 'tag', id })), 'tag'] 
: ['tag']

Invalidating will be similar.

invalidatesTags: (result, error, arg) => [{ type: 'Post', id: arg.id }]

The post RTK Query: Make fetching and caching data effortless first appeared on Prayers Connect.

]]>
https://www.prayersconnect.org/86-rtk-query-make-fetching-and-caching-data-effortless/feed/ 1