/  Frontend  /  RTK Query & Mock Service Worker
decorative green and navy animation of swirls with code embedded
Frontend

RTK Query and Leveraging New Libraries

At adjoe, we use RTK Query for several reasons. It can simplify state management, reduce boilerplate code, improve type safety and middleware integration, and enhance the overall development experience.

Besides RTK Query, our team uses Mock Service Worker (mswjs), which offers significant benefits by simplifying and enhancing the testing and development process for web applications that interact with APIs. It has several benefits including mocking API responses and isolating FE and BE development.These are my best practices for the RTK Query and Mock Service Worker libraries.

No Writing Data-Fetching and Caching Logic Yourself

RTK Query provides me with different hooks that can easily be reused for the logic of my component and fetching its data. And besides that, with RTK Query, I don’t need to

  • write caching mechanisms from scratch
  • spend time and energy writing the code myself – we don’t need to write some logic from scratch, as this library provides us with some custom hooks

Since implementing this advanced data-fetching and caching tool, I have so far been able to reduce time spent writing code by around 70 percent. That’s 70 percent less time writing or worrying about caching or fetching – just from implementing and running RTK Query on production for around three months. 

Cleaner Code and Cache Mutations

Let’s say I want to display a list of todos in adjoe’s application. I need to fetch them from the API. To do this, I can use a React app using TypeScript and start a new project by running the command below in terminal.

npx create-react-app todos --template typescript

1. Once the demo project is ready, I need to install some dependencies.

npm install redux react-redux
npm install @reduxjs/toolkit

2. I define the “Todo” type (if I’m using TypeScript in the application): src/types.d.ts.

type Todo = {
 userId: number;
 id: number;
 title: string;
 completed: boolean;
};

3. Now, I’m ready to create an API slice: src/api/apiSlice.ts.

import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
 
export const apiSlice = createApi({
 reducerPath: "api",
 baseQuery: fetchBaseQuery({
   baseUrl: "https://jsonplaceholder.typicode.com",
 }),
 endpoints: (builder) => ({
   getTodos: builder.query<Todo[], void>({
     query: () => "/todos",
     transformResponse: (response: Todo[]) => {
       let filtered = response.filter((r) => r.id <= 10);
       return filtered.sort((a, b) => b.id - a.id);
     },
   }),
 }),
});
 
// custom hooks based on the methods that we provide
export const {
 useGetTodosQuery,
} = apiSlice;

4. After this, I need to wrap the application component with “ApiProvider”: src/index.tsx.

import { ApiProvider } from "@reduxjs/toolkit/query/react";
import { apiSlice } from "./api/apiSlice";
root.render(
 <React.StrictMode>
   <ApiProvider api={apiSlice}>
     <App />
   </ApiProvider>
 </React.StrictMode>
);

5. Then I can use the custom hook created by RTK Query in Todos.tsx component src/Todos.tsx.

import { useGetTodosQuery } from "../api/apiSlice";


export const Todos = () => {
 const { data: todos, isError, isFetching, error } = useGetTodosQuery();


 if (isFetching) return <p>Loading...</p>;
 if (isError) return <p>{JSON.stringify(error)}</p>;
 return (
   <>
     {todos?.map((todo) => (
       <p key={todo.id}>
         {todo.title}
       </p>
     ))}
   </>
 );
};

Utilize Custom Hooks for CRUD Operations

I can also add API calls for add, delete, and update operations and utilize custom hooks available from RTK Query. I can add code below to the API slice endpoints object src/api/apiSlice.ts.

  addTodo: builder.mutation({
     query: (todo: Todo) => ({
       url: "/todos",
       method: "POST",
       body: todo,
     }),
   }),
   updateTodo: builder.mutation({
     query: (todo: Todo) => ({
       url: `todos/${todo.id}`,
       method: "PUT",
       body: todo,
     }),
   }),
   deleteTodo: builder.mutation({
     query: (id: number) => ({
       url: `/todos/${id}`,
       method: "DELETE",
     }),
   }),
export const {
 useAddTodoMutation,
 useDeleteTodoMutation,
 useUpdateTodoMutation
} = apiSlice;

src/Todo.tsx

 const [addTodo] = useAddTodoMutation();
 const [deleteTodo] = useDeleteTodoMutation();
   const [updateTodo] = useUpdateTodoMutation();
 deleteTodo(todo.id);
 addTodo({id: 201, userId: 10, title: 'new todo is added', completed: false});
   updateTodo({ id: 193, userId: 10, title: "my first todo item", completed: true });

Assigning a Tag to the Cache

In this process, results get cached, and I don‘t invalidate the previous cache. This means the component doesn’t display the new changes upon adding, deleting, or updating a todo item. To resolve this issue, I can assign a tag to the cache to let it know which mutation invalidates the cache to ensure it automatically refetches that data.

src/api/apiSlice.ts

import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
 
export const apiSlice = createApi({
 reducerPath: "api",
 baseQuery: fetchBaseQuery({
   baseUrl: "https://jsonplaceholder.typicode.com",,
 }),
 tagTypes: ["Todos"], //defining tags' type
 endpoints: (builder) => ({
   getTodos: builder.query<Todo[], void>({
     query: () => "/todos",
     transformResponse: (response: Todo[]) => {
       let copy = response.filter((r) => r.id <= 10);
       return copy.sort((a, b) => b.id - a.id);
     },
     providesTags: ["Todos"], // defining a tag for this call
   }),
   addTodo: builder.mutation({
     query: (todo: Todo) => ({
       url: "/todos",
       method: "POST",
       body: todo,
     }),
     invalidatesTags: ["Todos"], // invalidate this tag for this mutation so that data can automatically re-fetched
   }),
   updateTodo: builder.mutation({
     query: (todo: Todo) => ({
       url: `todos/${todo.id}`,
       method: "PUT",
       body: todo,
     }),
     invalidatesTags: ["Todos"], // invalidate this tag for this mutation so that data can automatically re-fetched
   }),
   deleteTodo: builder.mutation({
     query: (id: number) => ({
       url: `/todos/${id}`,
       method: "DELETE",
     }),
     invalidatesTags: ["Todos"], // invalidate this tag for this mutation so that data can automatically re-fetched
   }),
 }),
});
 
// custom hooks based on the methods that we provide
export const {
 useGetTodosQuery,
 useAddTodoMutation,
 useDeleteTodoMutation,
 useUpdateTodoMutation,
} = apiSlice;

Besides eliminating the need to hand-write code yourself, the tool has more tricks up its sleeve to further optimize development processes. 

I have ideas on how to further leverage RTK Query by use case. When the frequency of data change is high (for example, one second or less than 30 seconds), and the application should show data instantly; it should not use the cached data. However, when the data frequency is less and the response object is big (for example, more than 1MB), the team can enable caching. This enables a better user experience and also reduces the burden on adjoe’s servers.

mswjs: API Mocking for Testing, Development, and Debugging

In an ecosystem where there is little documentation on testing RTK Query hooks, our frontend team found the mswjs library while researching what other React developers are utilizing.

We decided to use mswjs for two reasons. 1) To use mock data and develop new features in the frontend when the endpoint is not ready on the server side and 2) to write unit tests for components using RTK to fetch data since it intercepts requests on the network level. When it comes to Mock Service Worker, you should know the following:

  • The library is designed to intercept requests on the network level, and mock data helps write the test for RTK Query to test the API and components.
  • This way, we don’t need to call the API on the server, and mocking is completely seamless.

Test Your Component in mswjs 

When I want to test a component that fetches data from an adjoe API with the mswjs library and mock the API response, I need to install the necessary dependencies.

npm install msw --save-dev

2. Then I can define the mocks: src/test/mocks/todoMocks.ts.

export const todoMocks: Todo[] = [
 {
   id: 1,
   userId: 10,
   title:
     "temporibus atque distinctio omnis eius impedit tempore molestias pariatur",
   completed: true,
 },
 {
   id: 2,
   userId: 10,
   title: "ut quas possimus exercitationem sint voluptates",
   completed: false,
 },
 {
   id: 3,
   userId: 10,
   title: "rerum debitis voluptatem qui eveniet tempora distinctio a",
   completed: false,
 },
 {
   id: 4,
   userId: 10,
   title: "sed ut vero sit molestiae",
   completed: true,
 },
];

src/test/mocks/handlers.ts

import { rest } from "msw";
import { todoMocks } from "./todoMocks";
 
export const handlers = [
 rest.get("https://jsonplaceholder.typicode.com/todos", (_, res, ctx) =>
   res(ctx.status(200), ctx.json<Todo[]>(todoMocks))
 ),
];

3. Afterward, I set up the server: src/test/server.ts.

import { setupServer } from "msw/node";
import { handlers } from "./handlers";
 
export const server = setupServer(...handlers);

4. Finally, I can implement the unit test: src/Todos.spec.tsx.

import { screen, waitFor, render } from "@testing-library/react";
import { ApiProvider } from "@reduxjs/toolkit/query/react";
import { server } from "../test/server";
import { Todos } from "./Todos";
import { apiSlice } from "../api/apiSlice";
 
describe("Todos", () => {
 beforeAll(() => {
   server.listen();
 });
 
 afterEach(() => {
   server.resetHandlers();
 });
 
 afterAll(() => {
   server.close();
 });
 
 it("should display todos", async () => {
   render(
     <ApiProvider api={apiSlice}>
       <Todos />
     </ApiProvider>
   );
 
   await waitFor(() => {
     expect(screen.getByText("sed ut vero sit molestiae")).toBeInTheDocument();
   });
 });
});

At adjoe, we will be able to develop mswjs incrementally in order to improve the unit tests of components that make API calls by defining mocks at the network level. We can also seamlessly reuse the same mock definition for testing, development, and debugging.

Quality Code and Faster Frontend Processes

Using RTK and mswjs in combination with each other streamlines development, we enhance code quality and promote efficient testing. This results in a more robust and faster development process ​​without having to hand-write code ourselves.

With the RTK caching mechanism, you can improve behind-the-scenes performance when dealing with large amounts of daily user data and decrease costs when requesting data from the BE. With mswjs it is possible to isolate FE and BE development, ensuring consistent testing and reducing test execution time.

Tech Lead (Playtime Supply) (f/m/d)

  • adjoe
  • Playtime Supply
  • Full-time
adjoe is a leading mobile ad platform developing cutting-edge advertising and monetization solutions to meet the diverse needs of app publishers. Being part of the applike group ecosystem plus the significant financial backing from Bertelsmann empowers us to do this. At adjoe, we are proud of our advanced tech stack and dynamic and innovative workforce.

Meet Your Team: Playtime Supply

The Playtime Supply team focuses on creating great experiences for the 200+ million users who interact with Playtime. This includes everything from the gamified experiences within the mobile apps to the dashboards used to configure and customise these experiences. From the unique product features and ML algorithms to the analytical systems used to analyse their effects. Our cross-functional team has all the expertise needed to realise our product goals.
What You Will Do
  • Translate business needs into projects and features to be implemented across backend, frontend, and infrastructure systems.
  • Lead a cross-functional team of 3-6 backend, frontend, and test engineers including check-ins, feedback, and performance reviews.
  • Identify growth needs and hire to grow the team when the need is identified.
  • Work with a product lead to plan, implement and report objectives and key reports for your team.
  • Implement features for the 70+ microservices in our Go backend.
  • Contribute improvements to the infrastructure through IaC changes on Terraform.
  • Investigate issues by analysing logs and data.
  • Who You Are
  • A senior-level software developer with 6+ years of experience working on the backend of large scale production systems built in Go, Java, PHP, Rust, C#, Python or TypeScript.
  • An expert Go developer, or someone willing to learn and ramp up proficiency in Go within a short period of time.
  • A team lead or mentor who is well-versed in guiding developers, giving feedback, and directing the technical aspects of products.
  • A proficient troubleshooter with the ability to identify issues through sifting through application logs and data in SQL-compatible data stores.
  • A curious individual who seeks to understand the business in-depth and how technology can be used to solve problems and enhance business outcomes.
  • A technical generalist who has mastered one aspect of development but also is able to jump into any technology or stack as the need arises. For example, an expert Go backend developer who can quickly identify and fix bugs in a web application built in Typescript and React.
  • An expert at deploying cloud technologies with an understanding of the abstractions and the underlying systems on which applications run.
  • Heard of Our Perks?
  • Tech Package: Create game-changing technologies and work with the newest technologies out there.
  • Wealth Building: Benefit from virtual stock options.
  • Work–Life Package: Work remotely for 2 days per week, enjoy flexible working hours and 30 vacation days, work remotely for 3 weeks per year, modern office in the city center, dog-friendly.
  • Relocation Package: Receive visa and legal support, a generous relocation subsidy, and free German classes in the office.
  • Never-Go-Hungry Package: Graze on regular company breakfasts and events, and a selection of free snacks and drinks.
  • Physical & Mental Health Package: In-house gym with a personal trainer, various classes like Yoga with expert teachers & free of charge access to our EAP (Employee Assistance Program) to support your mental health and well-being
  • Activity Package: Enjoy a host of team events, hackathons, and company trips.
  • Career Growth Package: Benefit from a dedicated growth budget to attend relevant conferences and online seminars of your choosing.
  • Build our signature product

    See vacancies

    Tech Lead (Playtime Supply) (f/m/d)

    • adjoe
    • Playtime Supply
    • Full-time
    adjoe is a leading mobile ad platform developing cutting-edge advertising and monetization solutions to meet the diverse needs of app publishers. Being part of the applike group ecosystem plus the significant financial backing from Bertelsmann empowers us to do this. At adjoe, we are proud of our advanced tech stack and dynamic and innovative workforce.

    Meet Your Team: Playtime Supply

    The Playtime Supply team focuses on creating great experiences for the 200+ million users who interact with Playtime. This includes everything from the gamified experiences within the mobile apps to the dashboards used to configure and customise these experiences. From the unique product features and ML algorithms to the analytical systems used to analyse their effects. Our cross-functional team has all the expertise needed to realise our product goals.
    What You Will Do
  • Translate business needs into projects and features to be implemented across backend, frontend, and infrastructure systems.
  • Lead a cross-functional team of 3-6 backend, frontend, and test engineers including check-ins, feedback, and performance reviews.
  • Identify growth needs and hire to grow the team when the need is identified.
  • Work with a product lead to plan, implement and report objectives and key reports for your team.
  • Implement features for the 70+ microservices in our Go backend.
  • Contribute improvements to the infrastructure through IaC changes on Terraform.
  • Investigate issues by analysing logs and data.
  • Who You Are
  • A senior-level software developer with 6+ years of experience working on the backend of large scale production systems built in Go, Java, PHP, Rust, C#, Python or TypeScript.
  • An expert Go developer, or someone willing to learn and ramp up proficiency in Go within a short period of time.
  • A team lead or mentor who is well-versed in guiding developers, giving feedback, and directing the technical aspects of products.
  • A proficient troubleshooter with the ability to identify issues through sifting through application logs and data in SQL-compatible data stores.
  • A curious individual who seeks to understand the business in-depth and how technology can be used to solve problems and enhance business outcomes.
  • A technical generalist who has mastered one aspect of development but also is able to jump into any technology or stack as the need arises. For example, an expert Go backend developer who can quickly identify and fix bugs in a web application built in Typescript and React.
  • An expert at deploying cloud technologies with an understanding of the abstractions and the underlying systems on which applications run.
  • Heard of Our Perks?
  • Tech Package: Create game-changing technologies and work with the newest technologies out there.
  • Wealth Building: Benefit from virtual stock options.
  • Work–Life Package: Work remotely for 2 days per week, enjoy flexible working hours and 30 vacation days, work remotely for 3 weeks per year, modern office in the city center, dog-friendly.
  • Relocation Package: Receive visa and legal support, a generous relocation subsidy, and free German classes in the office.
  • Never-Go-Hungry Package: Graze on regular company breakfasts and events, and a selection of free snacks and drinks.
  • Physical & Mental Health Package: In-house gym with a personal trainer, various classes like Yoga with expert teachers & free of charge access to our EAP (Employee Assistance Program) to support your mental health and well-being
  • Activity Package: Enjoy a host of team events, hackathons, and company trips.
  • Career Growth Package: Benefit from a dedicated growth budget to attend relevant conferences and online seminars of your choosing.
  • Build our signature product

    See vacancies