1.0.0 • Published 2 years ago

recontainer-react v1.0.0

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

gzip size

Can be used in any Node.js or browser applications. Built-in integration with React.

Why?

In most cases, ES6 modules are efficient way to share dependencies throughout application.

There are some situations though, where you need a single point of configuration to achieve better decoupling between multiple components. One of these cases is server-side rendering, in which you might want to change implementation that depends on browser APIs to something more suitable for a Node.js application.

Moreover, using global instances imported as ES6 module can lead to memory-leaks and bugs on server-side. Take for example a global instance of event publisher, to which application subscribes on each request. Since it is not disposed after each request, any failure to unsubscribe will cause a linear leak of memory.

server.jsx

import React from 'react';
import express from 'express';
import { renderToString } from 'react-dom/server';
import { createContainer } from 'recontainer';
import { createInject, ContainerProvider } from 'recontainer/lib/react';

class Greeting extends React.Component { render() { return (

  <h1>{this.props.greeting}</h1>
)

} }

const inject = createInject(); const GreetingContainer = inject('greeting')(Greeting);

const greetingFactory = container => Hello, ${container.get('name')}!; const app = express();

app.use((req, res, next) => { const container = createContainer({ // Container gets disposed after each request name: () => 'John', greeting: greetingFactory, });

const html = renderToString( {/ Hello, John! /} );

res.send(html); });

app.listen(3000, () => { console.log(Listening on port 3000.); });

</details>

### Why not Redux?

Redux is great at distributing state throughout application and decoupling your React components. Although you could share almost any object via store, it is generally a good practice to keep the state serializable. Many redux-related libraries (eg. [next-redux-wrapper](https://github.com/kirill-konshin/next-redux-wrapper)) will not work if the state couldn't be serialized.

Functions and object instances (eg. `Promise`) are not serializable, thus we should keep them elsewhere.

### Features

* 100% Type-safe
  > When used with **TypeScript**, recontainer protects you from type errors and allows you to extract the full potential of your editor's code completion features
* Light-weight
  > Total module size is 914 bytes gzipped
* Simple


## Examples

<details open>
<summary>TypeScript</summary>

<br>

> types.ts
```typescript
export interface User {
  id: string;
  name: string;
}

container.ts

import { ContainerConfig } from 'recontainer';
import { createInject, createContainerHook } from 'recontainer/lib/react';
import { User } from './types';

export interface Dependencies { user: User; greeting: string; greetingProvider: (user: User) => string; }

const user = { id: 'john-doe', name: 'John Doe', };

export const config: ContainerConfig = { user: () => user, greetingProvider: () => (user: User) => Hello ${user.name}!, greeting: container => { const greetingProvider = container.get('greetingProvider'); const user = container.get('user');

return greetingProvider(user);

}, };

export const useContainer = createContainerHook(); export const inject = createInject();

<br>

> Greeter.tsx
<details open>
<summary>Using <b>useContainer</b> hook</summary>

```tsx
import * as React from 'react';
import { useContainer } from './container';

const Greeter: React.FunctionComponent = () => {
  const { greeting, user } = useContainer('greeting', 'user');
  
  return (
    <div>
      <h1>{greeting}</h1>
      ID: {user.id}
    </div>
  );
}

// OR
const Greeter: React.FunctionComponent = () => {
  // Note that useContainer returns container instance when called without arguments
  const container = useContainer();
  
  return (
    <div>
      <h1>{container.get('greeting')}</h1>
      ID: {container.get('user').id}
    </div>
  );
}

export default Greeter;
import * as React from 'react';
import { inject } from './container';

interface GreeterProps {
  greeting: string;
  user: User;
}

const Greeter: React.FunctionComponent<GreeterProps> = ({
  user,
  greeting,
}) => (
  <div>
    <h1>{greeting}</h1>
    ID: {user.id}
  </div>
);

export default inject('greeting', 'user')(Greeter);
import * as React from 'react';
import { withContainer, ContainerProps } from 'recontainer/lib/react';
import { Dependencies } from './container';

interface GreeterProps extends ContainerProps<Dependencies> {
  
}

const Greeter: React.FunctionComponent<GreeterProps> = ({
  container
}) => (
  <div>
    <h1>{container.get('greeting')}</h1>
    ID: {container.get('user').id}
  </div>
);

export default withContainer(Greeter);

App.tsx

import * as React from 'react';
import { createContainer } from 'recontainer';
import { ContainerProvider } from 'recontainer/lib/react';
import { config } from './container';
import Greeter from './Greeter';

const container = createContainer(config);

export const App: React.FunctionComponent = () => ( {/*

    <div>
      <h1>Hello John Doe!</h1>
      ID: john-doe
    </div>
*/}

container.js

import { createInject, createContainerHook } from 'recontainer/lib/react';

const user = { id: 'john-doe', name: 'John Doe', };

export const config = { user: () => user, greetingProvider: () => user => Hello ${user.name}!, greeting: container => { const greetingProvider = container.get('greetingProvider'); const user = container.get('user');

return greetingProvider(user);

}, };

export const useContainer = createContainerHook(); export const inject = createInject();

<br>

> Greeter.jsx
<details open>
<summary>Using <b>useContainer</b> hook</summary>

```jsx
import React from 'react';
import { useContainer } from './container';

const Greeter = () => {
  const { greeting, user } = useContainer('greeting', 'user');

  return (
    <div>
      <h1>{greeting}</h1>
      ID: {user.id}
    </div>
  );
}

export default Greeter;
import React from 'react';
import { inject } from './container';

const Greeter = ({
  user,
  greeting,
}) => (
  <div>
    <h1>{greeting}</h1>
    ID: {user.id}
  </div>
);

export default inject('greeting', 'user')(Greeter);
import React from 'react';
import { withContainer } from 'recontainer';

const Greeter = ({
  container
}) => (
  <div>
    <h1>{container.get('greeting')}</h1>
    ID: {container.get('user').id}
  </div>
);

export default withContainer(Greeter);

App.jsx

import React from 'react';
import { createContainer, ContainerProvider } from 'recontainer';
import { config } from './container';
import Greeter from './Greeter';

const container = createContainer(config);

export const App = () => ( {/*

    <div>
      <h1>Hello John Doe!</h1>
      ID: john-doe
    </div>
*/}

Installation

With yarn

$ yarn add recontainer

With npm

$ npm install recontainer

Please note that types are included, thus there is no need to install @types/recontainer package

Requirements

Docs

Container methods

get
 const container = createContainer({
   logger: () => message => console.log(message),
 });

 const log = container.get('logger');

 log('Hello World!'); // Console output: Hello World!
getAll
 const container = createContainer({
   logger: () => message => console.log(message),
   message: () => 'Foo bar',
 });

 const { logger, message } = container.getAll();

 logger(message); // Console output: Foo bar