0.0.1-beta1 ā€¢ Published 2 years ago

@anissoft/react-service v0.0.1-beta1

Weekly downloads
1
License
MIT
Repository
-
Last release
2 years ago

Welcome to react-service šŸ‘‹

Version License: MIT

My attempt to make inversion of control solution for React apps with Reflect metadata and Typescript;

Install

npm install @anissoft/react-service --save

Usage

First of all - you need to declare interfaces for your services:

// interfaces.ts
export interface ISessionClient {
  createSession(): Promise<Response>
  removeSession(): Promise<Response>
}

export interface IUser {
  username: string;
  email: string;

  sessionClient: ISessionClient;

  login(): Promise<void>;
  logout(): Promise<void>;
}

Then, you should write some classes to implement these interfaces. Use @Service() to mark them as service, and @Inject(tagname) to define dependency:

// services.ts
import { Service, Inject } from '@anissoft/react-service';
import { IUser, ISessionClient } from './interfaces';

@Service()
export class SessionClient implements ISessionClient {
  public async createSession() {
    return fetch('/api/method/to/create/session');
  }

  public async removeSession() {
    return fetch('/api/method/to/remove/session');
  }
}

@Service()
export class SessionClientMock implements ISessionClient {
  public async createSession() {
    return new Promise<Response>((res) => {
      setTimeout(() => res(new Response(MOCKED_CREATE)), 1000);
    });
  }

  public async removeSession() {
    return new Promise<Response>((res) => {
      setTimeout(() => res(new Response(MOCKED_REMOVE)), 1000);
    });
  }
}

@Service()
export class User implements IUser {
  public username = 'Boris Sshec';
  public email = 'boris1991@example.com';

  @Inject('SESSION_CLIENT') 
  private sessionClient!: SessionClient; 

  public async login() {
    await this.sessionClient.createSession();
  }; 

  public async logout() {
    await this.sessionClient.removeSession();
  }; 
}

Write react components, which should have acces to your services inside them. Use useService hook for that:

// Userview.tsx
import { Provider } from '@anissoft/react-service';
import { Iuser } from './interfaces';

export const UserView = () => {
  const user = useService<IUser>('USER');

  return (
    <>
      <span>
        Username - {user.username}
      </span>
      <span>
        User email - {user.email}
      </span>
      <button type="button" onClick={user.logout}>Logout</button>
    </>
  );
};

And finally - pass in ServiceProvider those classes that you need for services. You can configure, which implementation will be resolved by tag-names:

import { ServiceProvider } from '@anissoft/react-service';
import { User, SessionClient, SessionClientMock } from './services';
import { UserView } from './Userview';

const RootComponent = () => {
  return (
    <ServiceProvider 
      services={{ 
        'USER': User, 
        'SESSION_CLIENT': SessionClient 
      }}
    >
      <Userview />
    </ServiceProvider>
  );
};

// ...OR

const DevRootComponent = () => {
  return (
    <ServiceProvider 
      services={{ 
        'USER': User, 
        'SESSION_CLIENT': SessionCLientMock,
      }}
    >
      <Userview />
    </ServiceProvider>
  );
};

@Service and @Inject

You can use @Inject on constructors parameters as well as on class properties:

@Service()
class Test {
  @Inject('DEPENDENCY_1') 
  private propertyDependency!: Dependency1; 
  // note that "!" after property name
  // it helps to get rid of some typescript errors

  constructor(
    @Inject('DEPENDENCY_2') public parameterDependency: Dependency2,
  ){
    // ...
  }
}

It's also highly recommended to keep tag-names in separate object, instead of using string literals every time:

const tags = {
  dependency: Symbol('dependency'),
  master: Symbol('master'),
}

@Service()
class Dependency {
  constructor(){}
}

@Service()
class Master {
  constructor(
    @Inject(tags.dependency) public dependency: Dependency,
  ){ }
}

ServiceProvider and ServiceConsumer

If yu using React.Components you can use ServiceConsumer instead of useService hook:

const tags = {
  dependency: Symbol('dependency'),
  master: Symbol('master'),
}

@Service()
class Dependency {
  constructor(){}
}

@Service()
class Master {
  constructor(
    @Inject(tags.dependency) public dependency: Dependency,
  ){ }
}

const Child = () => {
  return (
    <ServiceConsumer>
      {(container) => {
        console.log(container); // <--
        return <>Child</>;
      }}
    </ServiceConsumer>
  )
}

const Root = () => (
  <ServiceProvider 
    services={{ 
      [tags.dependency]: Dependency, 
      [tags.master]: Master,
    }}
  >
    <Child />
  </ServiceProvider>,
);

Author

šŸ‘¤ anissoftkun@gmail.com

šŸ¤ Contributing

Contributions, issues and feature requests are welcome!

Feel free to check issues page.

Show your support

Give a ā­ļø if this project helped you!

0.0.1-beta1

2 years ago