DEV Community

Cover image for Dojo Containers
Rene Rubalcava
Rene Rubalcava

Posted on • Originally published at learn-dojo.com

2

Dojo Containers

Once you start building applications that begin to compose multiple widgets and you are trying to manage state across those widgets, you might want to start looking at Dojo Containers. Containers allow you to inject values into widget properties, without having to import state directly into your widget.

To do this, Dojo provides a higher order component, similar to what you might use with React. That HOC is located in the @dojo/framework/widget-core/Container.

Let's say that you wanted to work with a streaming API and update your widget when the stream returns new data. We want to display this data in a simple list.

// src/widgets/Items.ts
export class Items extends WidgetBase<ItemsProperties> {
  protected render() {
    const { items } = this.properties;
    return v(
      "ul",
      { classes: css.root },
      items.map((x, idx) =>
        v("li", { innerHTML: x.name, key: `${x.name}-${idx}` })
      )
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

This widget has an items array in the properties. You could bind this widget directly a data store and update the widget when new data comes in, but again, maybe we want that data available in the parent widget, or other widgets in use.

Let's create a parent Application container that will render this widget.

// src/containers/AppContainer.ts
class AppContainer extends WidgetBase<ItemsProperties> {
  protected render() {
    return v("div", {}, [w(Items, { items: this.properties.items })]);
  }
}
Enter fullscreen mode Exit fullscreen mode

This particular container is not doing much other than passing its properties to the child Items widget.

To use the Dojo Container, we need to create a getProperties function that defines the properties returned to the Container.

// src/containers/AppContainer.ts
function getProperties(inject: Context, properties: any) {
  const { items } = inject;
  return { items };
}
Enter fullscreen mode Exit fullscreen mode

Now we can wrap our AppContainer in the Dojo Container.

// src/containers/AppContainer.ts
export default Container(AppContainer, "state", { getProperties });
Enter fullscreen mode Exit fullscreen mode

In this case "state" is the name I'm providing for my context, which I refer to as my injector since it allows me to inject values into my widgets.

At this point, you have an option for how to manage your state. You can use Dojo stores or you can create a class that accepts an invalidator and you can use this invalidator to let the higher order component know that state has changed and it will pass it to the widget that it has wrapped.

For now, let's go with a class that takes an invalidator and call it a context for our container. We can cover Dojo stores in another post.

// src/context.ts
export default class Context {
  private _items: Item[];

  private _invalidator: () => void;

  constructor(invalidator: () => {}, items: Item[] = []) {
    this._items = items;
    this._invalidator = invalidator;
    // subscribe to updates from our stream
    stream.subscribe((a: Item) => {
      this._addItem(a);
    });
  }

  get items(): Item[] {
    return this._items;
  }

  private _addItem(item: Item) {
    this._items = [...this._items, item];
    // call the invalidator to update wrapped container
    this._invalidator();
  }
}
Enter fullscreen mode Exit fullscreen mode

It's in this Context that I am subscribing to my data stream and updating the items array when new data is streamed in.

Ok, let's tie it all together in our main.ts that kick starts the whole application.

// src/main.ts
const registry = new Registry();
// the `defineInjector` will provider the invalidator
registry.defineInjector("state", (invalidator: () => any) => {
  // create a new context and return it
  const context = new Context(invalidator);
  return () => context;
});

const Projector = ProjectorMixin(AppContainer);
const projector = new Projector();
// pass the registry to the projector
projector.setProperties({ registry });

projector.append();
Enter fullscreen mode Exit fullscreen mode

When the Registry is passed to the projector, it will make sure everything is wired up as needed.

This may seem like a few steps, but it makes state management very flexible in your widgets without having to bind widgets to a data source, which makes them incredibly reusable.

You could create containers for each individual widget in your application and manage their state independently, this would be very powerful!

You can see a sample of this application above on CodeSandbox.

Be sure to subscribe to the newsletter and stay up to date with the latest content!

Heroku

Build apps, not infrastructure.

Dealing with servers, hardware, and infrastructure can take up your valuable time. Discover the benefits of Heroku, the PaaS of choice for developers since 2007.

Visit Site

Top comments (0)

Image of AssemblyAI

Automatic Speech Recognition with AssemblyAI

Experience near-human accuracy, low-latency performance, and advanced Speech AI capabilities with AssemblyAI's Speech-to-Text API. Sign up today and get $50 in API credit. No credit card required.

Try the API

👋 Kindness is contagious

Discover a treasure trove of wisdom within this insightful piece, highly respected in the nurturing DEV Community enviroment. Developers, whether novice or expert, are encouraged to participate and add to our shared knowledge basin.

A simple "thank you" can illuminate someone's day. Express your appreciation in the comments section!

On DEV, sharing ideas smoothens our journey and strengthens our community ties. Learn something useful? Offering a quick thanks to the author is deeply appreciated.

Okay