A better way to use services in React application

0

0

31/08/2022

Sometimes you have/want to create and use a service in your application for the DRY principle. But do you know exactly what is a service? And what is the best way to use services in React applications?

Hi everyone! It’s nice to meet you again in React related series. In the previous article, we discussed about effective state management in React, we learned about Context: what is it? when to use it? One of the use cases is using a context to share a collection of services through the components tree, but what kind of things can be called a service? And how is it integrated into React application? Let’s discover the following sections:

What is a Service?

Service is just a group of helper functions that handle something particularly and can be reused across application, for example:

  • Authentication Service that includes authentication status checking, signing in, signing out, getting user info …
  • Http Service that handles request header, request method, request body …

How to use?

There are different kinds of implementation and usage, I just provide my favorite one that follows Angular module/services provider concepts.

Create a service

A service is a JavaScript regular class, nothing related to React.

export default class ApiService {
  constructor() {}

  _setInterceptors() {}

  _handleResponse_() {}

  _handleError_() {}

  get() {}

  post() {}

  put() {}

  delete() {}
}
export default class AuthService {
  constructor() {}

  isAuthenticated() {}

  getUserInfo() {}

  signIn() {}

  signOut() {}
}

Create a Context

Create a context with provider and pass services as props.

import { createContext, useState } from 'react';
import { ApiService, AuthService } from '@services';

const services = {
  apiService: new ApiService(),
  authService: new AuthService()
};

const AppContext = createContext();
const { Provider } = AppContext;

const AppProvider = ({ children }) => {
  return <Provider value={{ services }}>{children}</Provider>;
};

export { AppContext, AppProvider }

Use a context

Place a context provider at an appropriate scope, all consumers should be wrapped in it.

import { AppProvider } from './context/AppContext';
import ComponentA from './components/ComponentA'

const App = () => {

  return (
    <AppProvider>
      <div className="app">
        <ComponentA />
      </div>
    </AppProvider>
  );
};

export default App;

Inside a child component, use React hooks useContext to access services as a context value.

import { useContext, useEffect } from 'react';
import { AppContext } from '../context/AppContext';

const ChildComponent = () => {

  const { 
    services: { 
      apiService,
      authService
    } 
  } = useContext(AppContext);

  useEffect(() => {
    if (authService.isAuthenticated()) {
      apiService.get();
    }
  }, []);

  return <></>;
}

export default ComponentA;

Above are simple steps to integrate services with context. But it would be complicated in the actual work. Consider organizing context with approriate level/scope. My recommendation is to have two kinds of service contexts in your application:

  • App Services Context to put all singleton services. Bootstrap it once at root, so only one instance for each is created while having them shared over the component tree.
  • For others, create separate contexts and use them anywhere you want and whenever you need.

Alternatives

Service is not the only one way to deal with reusable code, it depends on what you intend to do, highly recommend to:

  • Use a custom hook, if you want to do reusable logic and hook into a state.
  • Use a Higher-order component, if you want to do the same logic and then render UI differently.

Reference

Author: Vi Nguyen