Search code examples
reduxreact-reduxredux-toolkit

RTK implement query in createAPI headers of another API


I have two APIs. One is provided by Apple and one by my own server. In order to get the JWT token for the WeatherKitAPI provided by Apple I need to contact my server. I implemented an API for that. However, I need to have this data in the prepareHeaders of my other createApi, which I don't know how to implement:

General API

import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';

export const generalApi = createApi({
    reducerPath: 'api',
    tagTypes: ['settings' ],
    baseQuery: fetchBaseQuery({
        baseUrl: 'https://api.myapp.com/v1/',
        prepareHeaders: (headers, { getState })=> {
            const token = getState().session.token;

            if (token) {
                headers.set('authorization', `Bearer ${token}`);
            }

            return headers;
        }
    }),
    endpoints: (builder)=> ({
        getWeatherKitToken: builder.query({
            query: ()=> 'getWeatherKitToken',
            providesTags: [ 'settings' ]
        }),
        login: builder.mutation({
            query: (body)=> ({
                url: 'login',
                method: 'POST',
                body: body
            }),
            invalidatesTags: [ 'settings' ]
        }),
    }),
});

export const {
    useGetWeatherKitTokenQuery,
    useLoginMutation,
} = generalApi;

WeatherKit API

import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';

export const weatherKitApi = createApi({
    reducerPath: 'weatherKit',
    baseQuery: fetchBaseQuery({
        baseUrl: 'https://weatherkit.apple.com/api/v1/',
        prepareHeaders: (headers, {})=> {    
            /* Implement logic to get token here */
            return headers;
        }
    }),
    endpoints: (builder)=> ({
        availability: builder.query({
            query: ({ lat, lon })=> `availability/${lat}/${lon}`,
        })
    })
});

export const {
    useAvailabilityQuery
} = weatherKitApi;

I tried to implement the useGetWeatherKitTokenQuery directly inside the prepareHeaders but this is unfortunately not allowed.

prepareHeaders: async (headers, {  })=> {
    const { data: token } = await useGetWeatherKitTokenQuery();

    if (token) {
        headers.set('authorization', `Bearer ${token}`);
    }

    return headers;
}

Solution

  • so first of prepeareHeaders function, runs before every request happens on current api. So implementing query inside prepeareHeaders gonna mean that you are going to run this function before every api request you do.

    To make it work like that you are going to do it like so:

    baseQuery: (args, { signal, dispatch, getState }, extraOptions, ...additionalArgs)=> fetchBaseQuery({
        baseUrl: 'https://weatherkit.apple.com/api/v1/',
        prepareHeaders: async (headers)=> {
            const { data } = await dispatch(generalApi.endpoints.getWeatherKitToken.initiate(undefined, { forceRefetch: true }));
    
            if (data?.token) {
                headers.set('Authorization', `Bearer ${data.token}`);
            }
    
            return headers;
        }
    })(args, { signal, dispatch, getState }, extraOptions, ...additionalArgs),
    

    Note, this is more of concept since i belive you don't want it that way.

    To do that properly, you need to introduce an slice where you gonna keep your token, like that:

    const slice = createSlice({
      name: 'auth',
      initialState: { user: null, token: null } as AuthState,
      reducers: {},
      //  extraReducer
      },
    })
    

    Next thing to do is to dispatch token in there on some query:

      ...
      extraReducers: (builder) => {
        builder.addMatcher(
          api.endpoints.login.matchFulfilled,
          (state, { payload }) => {
            state.token = payload.token
            state.user = payload.user
          }
        )
      ...
    

    next you need to update your prepeareHeaders function like so:

     prepareHeaders: (headers, { getState }) => {
         const token = (getState() as RootState).auth.token
          if (token) {
            headers.set('authorization', `Bearer ${token}`)
          }
          return headers
     }
    

    Lemme know if more explanations needed. Working example Working example