RTK Query with Redux toolkit(RTK) package

RTK Query is an optional addon included in the Redux Toolkit package. It uses RTK's
API's like createSlice and createAsyncThunk under the hood for it's implementation.

Creating API Service

Redux recommends having a separate folder called services for keeping the RTK query file.

\> src/redux/services

The services have one folder each dedicated to managing API for one page/resource.
The folder then will have a file for making API calls. For managing the user related API it's folder and file structure will be like this:

\>src/redux/services/user/userApi.ts

The RTK Query exposes createApi function to make Api call. We'll see one example of CRUD operation.

import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
import { IPortfolio } from '../../../pages/portfolio/entity';

export const portfolioApi = createApi({
  reducerPath: 'portfolioApi',
  keepUnusedDataFor: 60,
  baseQuery: fetchBaseQuery({baseUrl: `http://localhost:3030`,}),
  endpoints: (builder) => ({
    getStockName: builder.query<IStockName[], void>({
       query: () => ({
        url: 'stock/getAllStockName',
        method: 'GET',
      })
    }),

    createPortfolio: builder.mutation<IPortfolio, IPortfolio>({
       query: (data) =>({
        url: 'portfolio',
        method:'POST',
        body:data
       })
    })

  }),
});

export const {
  useGetStockNameQuery,
  useLazyGetStockNameQuery,
  useCreatePortfolioMutation
} = portfolioApi;

export interface IStockName{
    value:string,
    label:string
}

createApi takes the option object with following keys:

  1. reducerPath : tells the Redux Toolkit where we want to keep all of the data from the API in our store. We'll register it in the store using the same name given to reducerPath.

  2. baseQuery : just the base url of the api we'll be calling

  3. endpoints : we'll perform multiple API operations and we defined each one in this key. We do so by using builder object.

    For reading data we use builder.query and for data modification we use builder.mutation as in the example above.

builder.query<IStockName[], void>
builder.mutation<IPortfolio, IPortfolio>

In the above code, for query, an array of IStockName will be the response from getStockName endpoint and it takes no parameter hence void is supplied.

In the second example , the endpoint createPortfolio will return one object of type IPortfolio and it takes an object of type IPortfolio as input.

Looking at the createPortfolio end point we can see the query takes one parameter. This is how we pass value to this endPoint. We have already mentioned this endpoint takes an object of type IPortfolio as input paramter. Hence the data parameter will be
IPortfolio object.

 createPortfolio: builder.mutation<IPortfolio, IPortfolio>({
       query: (data) =>({
        url: 'portfolio',
        method:'POST',
        body:data
       })
    })

In the getStockName endpoint, there is no input parameter.

In the url we mention the url endpoint we will be calling. Combining it with base url defined within the createApi will make a complete url.

We have base url : localhost:3030
combined with the url : portfolio
creates a complete url : localhost:3030/portfolio
data is passed in the request body of this POST method

Defining these endpoints causes React Toolkit to generate a set of hooks to interact with our API.

Finally we are exposing these endpoints as hooks to consume in the React component as:

export const {
  useGetStockNameQuery,
  useLazyGetStockNameQuery,
  useCreatePortfolioMutation
} = portfolioApi;

See the pattern is
\> use + endpoint + Query for data retrieval, called the useQuery hook
\> use + endpoint + Mutation for data modification, called the useMutation hook.

Registering the RTK Query to the store

The createApi function creates a reducer and a middleware and we'll attach it to our store so that it can be used in our component.

import { configureStore } from '@reduxjs/toolkit';
import { useDispatch } from 'react-redux';
import { portfolioApi } from './services/portfolio/portfolioApi';


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

export type RootState = ReturnType<typeof store.getState>;
export const useAppDispatch = () => useDispatch<typeof store.dispatch>();

We have two endpoints ready to consume.

function Portfolio() {

  const [form] = Form.useForm();
  const [, forceUpdate] = useState({});
  const [includeDp, setIncludeDp] = useState(false);

  const dispatch = useAppDispatch()
  const [selectedOption, setSelectedOption] = useState();

  const [createPortfolio] = useCreatePortfolioMutation();
  const { data, isLoading, isFetching, isError } = useGetStockNameQuery();
   //setSelectedOption(data)
  let options = data;


  const handleChange = (e) =>{
    setSelectedOption(e)
  }


  const onFinish = (portfolio: IPortfolio) => {
    portfolio.Symbol = portfolio?.stock?.value
    portfolio.Sector = portfolio?.stock?.sectorID
     dispatch(createPortfolio(portfolio))
  };

See the difference in consuming both types of hook, useMutation hook and useQuery hook.

//useMutation hook
const [createPortfolio] = useCreatePortfolioMutation();  
dispatch(createPortfolio(portfolio))

//useQuery hook
const { data, isLoading, isFetching, isError } = useGetStockNameQuery();