All Projects → wix-incubator → react-obsidian

wix-incubator / react-obsidian

Licence: other
Dependency injection framework for React and React Native applications

Programming Languages

typescript
32286 projects
java
68154 projects - #9 most used programming language
objective c
16641 projects - #2 most used programming language
javascript
184084 projects - #8 most used programming language
Starlark
911 projects
ruby
36898 projects - #4 most used programming language

Projects that are alternatives of or similar to react-obsidian

magnet
Dependency injection library for modular Android applications
Stars: ✭ 174 (+923.53%)
Mutual labels:  dependency-injection, dependency-inversion
test-tools
Improves PHPUnit testing productivity by adding a service container and self-initializing fakes
Stars: ✭ 25 (+47.06%)
Mutual labels:  dependency-injection
lightsaber
Compile time dependency injection framework for JVM languages. Especially for Kotlin.
Stars: ✭ 119 (+600%)
Mutual labels:  dependency-injection
solid
Solid Android components
Stars: ✭ 33 (+94.12%)
Mutual labels:  dependency-injection
mvp-architecture-kotlin-dagger-2-retrofit-android
Android Application MVP (Model-View-Presenter) architecture example using Dagger2 Dependency Injection (DI) and Retrofit Tutorial using Kotlin programming language.
Stars: ✭ 15 (-11.76%)
Mutual labels:  dependency-injection
DependencyInjector
Lightweight dependency injector
Stars: ✭ 30 (+76.47%)
Mutual labels:  dependency-injection
inject
[Archived] See https://github.com/goava/di.
Stars: ✭ 49 (+188.24%)
Mutual labels:  dependency-injection
inversify-koa-utils
inversify-koa-utils is a module based on inversify-express-utils. This module has utilities for koa 2 applications development using decorators and IoC Dependency Injection (with inversify)
Stars: ✭ 27 (+58.82%)
Mutual labels:  dependency-injection
KataSuperHeroesIOS
Super heroes kata for iOS Developers. The main goal is to practice UI Testing.
Stars: ✭ 69 (+305.88%)
Mutual labels:  dependency-injection
component-manager
component framework and dependency injection for golang
Stars: ✭ 21 (+23.53%)
Mutual labels:  dependency-injection
di-comparison
DI containers comparison
Stars: ✭ 20 (+17.65%)
Mutual labels:  dependency-injection
tsdi
Dependency Injection container (IoC) for TypeScript
Stars: ✭ 50 (+194.12%)
Mutual labels:  dependency-injection
Spork
Annotation processing and dependency injection for Java/Android
Stars: ✭ 77 (+352.94%)
Mutual labels:  dependency-injection
android-base-project
Android LateralView Base Project
Stars: ✭ 25 (+47.06%)
Mutual labels:  dependency-injection
rsdi
Dependency Injection Container
Stars: ✭ 45 (+164.71%)
Mutual labels:  dependency-injection
di
🛠 A full-featured dependency injection container for go programming language.
Stars: ✭ 156 (+817.65%)
Mutual labels:  dependency-injection
unbox
Fast, simple, easy-to-use DI container
Stars: ✭ 45 (+164.71%)
Mutual labels:  dependency-injection
Mimick.Fody
An integrated framework for dependency injection and aspect-oriented processing.
Stars: ✭ 15 (-11.76%)
Mutual labels:  dependency-injection
varie
A Typescript Framework For VueJS
Stars: ✭ 23 (+35.29%)
Mutual labels:  dependency-injection
di
Simple and yet powerful Dependency Injection for Go
Stars: ✭ 188 (+1005.88%)
Mutual labels:  dependency-injection

SWUbanner



React Obsidian

Dependency injection framework for React and React Native applications

🏗 This library is still under active development.
⚠️ Until we hit v1, Obsidian is not semver-compliant and all APIs are subject to change.

Introduction

React Obsidian is a dependency injection framework for React and React Native applications. It allows you to inject dependencies effortlessly into hooks, components or classes. Separating the construction and consumption of dependencies is crucial to maintaining a readable and testable codebase.

React Obsidian is guided by the principles of the Dependency Injection pattern, but does not strictly follow them. We allowed ourselves a degree of freedom when designing the library in order to reduce boilerplate code and library footprint.

📖 Read more about Dependency Injection and Obsidian in Breaking complexity with Dependency Injection: Introducing Obsidian on Medium.

Installation

npm install react-obsidian

See the #Prerequisites section for additional requirements.

Usage

Declare an object graph

Before we can inject dependencies into hooks, components and classes, we first need to declare our dependencies. Dependencies are declared in classes called "Graphs" where the relationships between the dependencies are outlined.

In the ApplicationGraph example below, we declare two dependencies:

  1. httpClient
  2. biLogger

Both functions are annotated by the @Provides() annotation. This signals Obsidian that the results of these functions are provided by the graph and can be injected.

Notice how the biLogger function receives an httpClient as an argument. This means that biLogger is dependent on httpClient. Obsidian will create an httpClient when biLogger is injected.

@Singleton() @Graph()
class ApplicationGraph extends ObjectGraph {
  @Provides()
  httpClient(): HttpClient {
    return new HttpClient();
  }

  @Provides()
  biLogger(httpClient: HttpClient): BiLogger {
    return new BiLogger(httpClient);
  }
}

Component injection

import {injectComponent} from 'react-obsidian';

interface InjectedComponentProps {
  biLogger: BiLogger;
}

const InjectedComponent = ({ biLogger }: InjectedComponentProps) => (
  <>
    <button onclick={biLogger.logButtonClick}>Click Me</button>
  </>
);

export default injectComponent(InjectedComponent, ApplicationGraph);

Hooks injection

interface UseButtonPressProps {
  biLogger: BiLogger;
}

interface UseButtonPress {
  usePress: () => void;
}

// Similarly to how dependencies are injected into hooks, we must use destructuring for Obsidian to be able to inject the dependencies.
const useButtonClick = ({ biLogger }: UseButtonPressProps): UseButtonPress => {
  const onClick = useCallback(() => {
    biLogger.logButtonClick();
  }, [biLogger]);
  
  return { onClick };
};

// Dependencies are injected from ApplicationGraph
export default injectHook(usePress, ApplicationGraph);

// Now that we exported the injected hook, we can use it in a component without the needed so provide its dependencies manually
const Component = () => (
  // No need to specify dependencies as they are injected automatically
  const { onClick } = useButtonClick();
  <>
    <button onclick={onClick}>Click Me</button>
  </>
);

Class injection

Obsidian supports injecting both class properties and constructor arguments.

Injecting properties

@Injectable(ApplicationGraph)
class ButtonController {
  @Inject() biLogger!: BiLogger;

  onClick() {
    this.biLogger.logButtonClick();
  }
}

Injecting constructor arguments

@Injectable(ApplicationGraph)
class Presenter {
  constructor(@Inject() public biLogger: BiLogger) { }
}

The TypeScript compiler won't let you construct the class without providing the biLogger argument as it's not optional. If you want to be able to instantiate the class yourself without providing arguments, you'll also need to declare a constructor overload that receives optional arguments.

@Injectable(ApplicationGraph)
class Presenter {
  constructor(biLogger?: BiLogger);
  constructor(@Inject() public biLogger: BiLogger) { }
}

// Now you can instantiate the class without providing it's constructor dependencies
const presenter = new Presenter();

Obtaining dependencies from a graph

Dependencies can also be obtained by accessing the graph that provides them.

Obsidian.obtain(ApplicationGraph).biLogger();

Note: While the function that provides the biLogger accepts an argument of type HttpClient, we don't provide dependencies ourselves when obtaining dependencies directly from the graph, as they are resolved by Obsidian.

Advanced usage

Accessing props in graphs

If a graph is instantiated in order to inject a component, then it will receive the component's props in the constructor.

@Graph()
class ProfileScreenGraph extends ObjectGraph<ProfileScreenProps> {
  private profile: UserProfile;

  constructor(props: ProfileScreenProps) {
    super(props);
    this.profile = props.profile;
  }

  @Provides()
  profileFetcher(): ProfileFetcher {
    return new ProfileFetcher(this.profile);
  }
}

Singleton graphs and providers

Graphs and Providers can be marked as singletons with the @Singleton decorator. If a graph is marked as a singleton, when an instance of such graph is requested, Obsidian will reuse the existing instance. Graphs that are not annotated with the @Singleton decorator will be instantiated each time they are needed for injection.

Singleton providers are shared between all instances of a graph.

@Graph()
class PushedScreenGraph { // A new PushedScreenGraph instance is created each time the corresponding screen is created
  @Provides()
  presenter(): PushedScreenPresenter {
    return new PushedScreenPresenter(); // Created each time PushedGraph is created
  }

  @Provides() @Singleton()
  someUseCase(): SomeUseCase {
    return new SomeUseCase(); // Created once for all PushedGraph instances
  }
}

In this example we declared a singleton graph. This means that all of its providers are also singleton.

@Singleton() @Graph()
class ApplicationGraph {
  @Provides()
  biLogger(): BiLogger {
    return new BiLogger() // Created once because the graph is a singleton
  }
}

Graph middleware

When working on large scale applications, we often need to to hook into various low level operations. Obsidian lets you hook into the graph creation process by adding middleware(s).

Those middleware are invoked in LIFO order and can be used for various purposes:

  1. Create a graph yourself instead of letting Obsidian to instantiate it.
  2. Add logging to graph creation.
  3. Handle errors when Obsidian instantiates graphs.
  4. Replace graphs with mocks for testing purposes.

Middleware follow the Chain of Responsibility pattern and therefore must always return a graph, either by creating one explicitly or by returning the instance created by another member in the resolve chain.

Adding a logging middleware

The following example demonstrates how to add a middleware that's used for logging purposes.

const loggingMiddleware = new class extends GraphMiddleware {
      resolve<Props>(resolveChain: GraphResolveChain, Graph: Constructable<T>, props?: Props) {
        const t1 = Date.now();
        const graph = resolveChain.proceed(Graph, props);
        const t2 = Date.now();
        console.log(`Graph created in ${t2 - t1} milliseconds`);
        return graph;
      }
    }();
    Obsidian.addGraphMiddleware(loggingMiddleware);

Clear graphs

Graphs can be cleared by invoking Obsidian.clearGraphs(). This is useful in tests or when you need to reset the system to it's original state, for example when a user logs out.

Clearing graphs automatically during execution of Jest tests

Create a jest.setup.js file and add it to setupFilesAfterEnv. Then, import the following file when ensures graphs are cleared before each test.

import 'react-obsidian/clearGraphs';

Prerequisites

Obsidian is highly opinionated and is developed with a specific environment in mind. Therefore, it has a few prerequisites for projects that want to integrate it.

TypeScript

Obsidian targets TypeScript projects. There are no plans to officially support pure JS environments.

Reflect-metadata

Install and enable the reflect-metadata polyfill.

  • npm install reflect-metadata
  • import 'reflect-metadata'; - this needs to be done once, typically in your application's entry point (index.ts).

Enable experimental decorators

Obsidian uses the Decorators feature whose proposal is still stage 2.

Add the following options to your tsconfig.json file.

{
  "compilerOptions": {
    "types": ["reflect-metadata", "jest"],
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

Babel

Obsidian relies on reflection to resolve dependencies. Production code is typically mangled to reduce bundle size. This means that some names Obsidian expects are changed during the mangling process. To work around this, Obsidian persists the names of methods annotated with the @Provides decorator with a Babel transformer.

Add Obsidian's babel transformer

Add the transformer to the list of plugins in your .babel file.

module.exports = {
  presets: [
    'module:metro-react-native-babel-preset',
    ['@babel/preset-typescript', {'onlyRemoveTypeImports': true}]
  ],
  plugins: [
    react-obsidian/dist/transformers/babel-plugin-obsidian,
    ['@babel/plugin-proposal-decorators', {legacy: true}],
    ['@babel/plugin-proposal-class-properties', { legacy: true }],
    'babel-plugin-parameter-decorator'
  ]
};

Jest

react-obsidian publishes untranspiled code. If you're using Jest, you'll need to add react-obsidian to transformIgnorePatterns so it's transpiled before tests are executed.

Peer Dependencies

Obsidian has a peer dependency on lodash.

Related

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].