Skip to content

Add flag to ContainerProvider to pass container value directly without cloning #43

@iobuhov

Description

@iobuhov

It would be nice to have special flag for ContextProvider that will forbid cloning the container. Below I'm trying to description why this might be useful and why current behavior is bit confusing (IMO).

With current behavior provided container is cloned and there no way to stop ContainerProvider from doing it.
I agree that this is how most of the users want it - combined with inContainerScope this logic always provide fresh clone so components can be independent. However, this approach works great if you don't have any "extra" bindings that depend on props. When all bindings declared prior cloning, then it works fine. In case where you want to a) create clone yourself and b) manage some init state of container/bindings this behavior causes some (IMO) unexpected behavior. To illustrate that I created simple example.

import { Container, token } from 'brandi';
import { useState } from 'react';
import { useInjection, ContainerProvider } from 'brandi-react';

let count = 0;

class MyService {
  id: string;
  key: string | null = null;

  constructor() {
    this.id = `MyService@${++count}`;
    console.log(`${this.id}: Service created`);
  }

  setStorageKey(value: string): void {
    console.log(this.id, 'setStorageKey', value);
    this.key = value;
  }
}

const TOKENS = {
  myService: token<MyService>('myService'),
};

const baseContainer = new Container();
// Note: MyService uses container scope
baseContainer.bind(TOKENS.myService).toInstance(MyService).inContainerScope();

function useWidgetContainer(props: { storageKey: string }) {
  const [containerCopy] = useState(function init() {
    const copy = baseContainer.clone();
    // ...
    // ... some bindings that depends on props
    // ...
    // ... service initialization
    copy.get(TOKENS.myService).setStorageKey(props.storageKey);
    return copy;
  });

  return containerCopy;
}

function SomeChild() {
  const srv = useInjection(TOKENS.myService);

  return (
    <div>
      {srv.id}: {srv.key === null ? 'null' : srv.key}
    </div>
  );
}

export function Widget(props: { storageKey: string }) {
  const container = useWidgetContainer(props);
  // Because provider implicitly clones container, SomeChild will recive new instance of
  // MyService (MyService@2) without storage key.
  return (
    <ContainerProvider container={container}>
      <SomeChild />
    </ContainerProvider>
  );
}

In this example we have manually clone container in useWidgetContainer hook and using props to set some init state. The code in useWidgetContainer configure MyService in init function. Unfortunately because of hidden clone, SomeChild component will receive new instance of MyService that was not configured properly.

So, I hope this example shows that proposed enhancement is worth the work.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions