javascriptreactjsarchitectureservice-layer

Private methods in React service function component


I'm a bit new to React and I'm currently developing a Proyect which has a service layer. To do so, I created a function component which would have the methods as variables:

const CustomComponent = () => {
    
    method1 = () => {...},

    method2 = () => {...},

    method3 = () => {...}

}

export default CustomComponent;

This component would then be imported to the component that will use it.

To make my architecture as clean as possible, I wanted to make some of the methods private. However, as you may already know, that is not possible to do in the solution I proposed. Do hoy have an idea on how to achieve this, or maybe there is a convention to make a service layer I'm not aware of?

Thank you so much in advance!


Solution

  • The architecture which I find particularly clean and maintainable is one where you split off logic from presentation into two files like this:

    Service layer (ts):

    export class Service implements ServiceInterface {
      constructor(private instanceVariable: string = "foo") { }
    
      private methodOne(): string {
        return this.instanceVariable
      }
    
      public methodTwo(argumentVariable: string): string {
        const importantString = this.methodOne();
        
        return importantString + argumentVariable;
      }
    }
    
    interface ServiceInterface {
      methodTwo(argumentVariable: string): string;
    }
    
    export default new Service();
    

    Service layer (js):

    export class Service {
      instanceVariable;
      constructor(contructorArgument) { 
        this.instanceVariable = contructorArgument;
      }
    
      methodOne() {
        return this.instanceVariable
      }
    
      methodTwo(argumentVariable) {
        const importantString = this.methodOne();
        
        return importantString + argumentVariable;
      }
    }
    
    export default new Service();
    

    Presentation layer:

    import Service from "./service.ts";
    
    const FunctionalComponent = () => {
      const [localState, setLocalState] = useState(localStateInit);
    
      return (
        <>
          <div>{Service.methodTwo("bar")}</div>
        </>
      )
    }
    

    Few things happen here (mostly regarding ts implementation).

    1. Keep component's service and presentation layers in separate files.
    2. Use an interface to describe the service class and its methods. This will help to work with your service layer in your component as you'll get Typescript's IntelliSense.
    3. For this example I'm exporting an instance of the service as default export from its file. This gives you a cleaner API in your component's file, where you can call methods without having to "pollute" component file with instance creation. This has at least the following two drawbacks:

    If any of above are a no go, then clean up the constructor, export just the class itself and instantiate it as required in component file.

    1. I'm also exporting the class itself. This is for testing purposes. In testing you want to be able to swap out arguments passed into class' constructor and you need to have class definition to do that.
    2. You'll notice the shorthand notation for declaring and instantiating a private class variable in the constructor: private instanceVariable: string = "foo". This is equivalent to something like this:
    class Service {
      private instanceVariable: string;
    
      constructor(constructorArgument: string) { 
        this.instanceVariable = constructorArgument;
      }
    

    Such notation is particularly nice when used with dependency injection.

    1. Overall, this setup will help you with unit testing logic in your service layer, as you can test it like any other class. This comes particularly handy when there's a lot of conditional rendering logic.

    Let me know if this is what you've been looking for. Maybe we could tailor it better for your use case.