Skip to main content

React

With the React bundle we hook the Foundation Core into React giving us ability to have Dependency Injection and a common ecosystem of module administration on the frontend.

Usage

We begin by defining our kernel, and our UIAppBundle:

kernel.ts
import { Kernel, Bundle } from "@bluelibs/core";import { XUIReactBundle } from "@bluelibs/x-ui-react-bundle";
// All UI bundles need to be prefixed with UI// All X-Framework bundles have the first prefix Xexport const kernel = new Kernel({  bundles: [    new XUIReactBundle({      // While the kernel is loading show a specific loader      initialisingComponent: AppLoadingComponent;    }),  ],});

In order to render we'll use XUIProvider with the kernel as a property:

import { kernel } from "./kernel";import { XUIProvider } from "@bluelibs/x-ui-react-bundle";import React from "react";import ReactDOM from "react-dom";
ReactDOM.render(  <XUIProvider kernel={kernel} />,  document.getElementById("root"));

Dependency Injection

We brough Dependency Injection into React and it's great. While it feels a little bit of over-engineering at first, the advantages are immediately seen:

import { Service, Inject } from "@bluelibs/core";import { ApolloClient, useContainer, useRouter, use } from "@bluelibs/x-ui";
@Service()class API {  @Inject("apiHost")  apiHost: string;}
function Component() {  // Gets the main `ContainerInstance` of the kernel.  const container = useContainer();
  // You fetch the singleton instance of A  const api = use(API);}

Results are automatically memo-ized unless you specify transient: true, especially when you use transient services (services which instantiate everytime you get them from the container)

function Component() {  // On every re-render form will be fetched  const form = use(FormClass, { transient: true });
  // If you want transience once, not on every rerender, use `useMemo()`}

This gives us the ability to have configurable logic across the whole react application. Giving you the ability to modify your app and create re-usable modules which can be modified through DI to do what you need.

UI Components

Imagine an application where every component you see is "swappable" and everything remains type-safe. This is quite possible with our mechanism of UIComponents:

function Component() {  const UIComponents = useUIComponents();
  return <UIComponents.Loading />;}

Override

Overriding them can be done in the init() phase of your bundle:

import { Bundle } from "@bluelibs/core";import { XUIReactBundle } from "@bluelibs/x-ui-react-bundle";
class UIAppBundle extends Bundle {  async init() {    const xuiReactBundle = this.container.get(XUIReactBundle);
    xuiReactBundle.updateUIComponents({      Loading: MyLoadingComponent,    });    // Now everywhere it's used it will render it correctly  }}

Or, you can also do it inside the XUIReactBundle config:

const kernel = new Kernel({  bundles: [    // ...    new XUIReactBundle({      components: {        Loading: MyLoadingComponent,      },    }),  ],});

Creating new components is done in two steps, first we extend the interface, second we update the components as shown in the overriding phase:

defs.ts
import "@bluelibs/x-ui-react-bundle";
declare module "@bluelibs/x-ui-react-bundle" {  export interface IComponents {    MyCustomOne: React.ComponentType<OptionalPropsType>;  }}

Table

The current default components created by this bundle:

SyntaxPropsDescription
Loading-Used as a generic Loading component
Errorerror as string or ErrorA common way to display errors
ErrorBoundary-The generic error boundary and how to handle components that throw
NotAuthorizedroles: "anonymous" or string[]When there isn't a permission match
NotFound-When the page is not found

Wrappers

Wrappers are used to programatically wrap your application in different layers, for example, if you are creating a new bundle in which you do need a parent provider for your components, you can use this:

const kernel = new Kernel({  bundles: [    new XUIReactBundle({      wrappers: [        {          component: MyProvider,          props: {},          order: 10, // Order of the wrapper as they are put in ascending order (<1><2></2></1>)        },      ],    }),  ],});

Or via bundles:

class UIAppBundle extends Bundle {  async prepare() {    const xuiReactBundle = this.container.get(XUIReactBundle);
    // or addWrappers if you want an array    xuiReactBundle.addWrapper({      component: MyProvider,      props: {},      order: 10, // Order of the wrapper as they are put in ascending order (<1><2></2></1>)    });  }}

We use this strategy for example when we integrate Apollo as a separate bundle, it automatically extends the wrappers giving us the ability to work with it without hassles.

Smart

Smart is a very small library which allows you to merge logic and state together in a separated class, integrated with the container.

import { EventManager } from "@bluelibs/core";import { Smart, useSmart, newSmart } from "@bluelibs/x-ui-react-bundle";
class MySmart extends Smart<any, any> {  @Inject()  eventManager: EventManager;}
function Component() {  const [mySmart, Provider] = newSmart(MySmart);
  // mySmart has been instantiated by the container, seemlessly}

Events

You can use the good ol' reliable EventManager to emit events, but if you want to have a component listen to events during its lifespan (until it gets unmounted), you can use the hook: useListener.

Let's emit dem' vents:

import { useListener, listen, useEventManager } from "@bluelibs/x-ui";import { Event } from "@bluelibs/core";
class SomethingInTheWorldJustHappenedEvent extends Event<{  what: string;}> {}
function SomeComponent() {  const eventManager = useEventManager();
  const onButtonClick = () => {    eventManager.emit(      new SomethingInTheWorldJustHappenedEvent({        what: "Time has passed.",      })    );  };}
function OtherComponent() {  // Use memo to avoid duplication of functions, or use an outside function  const handler = useMemo(() => {    return (e: SomethingInTheWorldJustHappenedEvent) => {      // handle the event, change the state, or whatever
      // flashbacks:      alert(e.data.what);    };  });
  // The built-in hook lets you listen to events while the component is mounted  useListener(MyEvent, handler);  // alias function that does the same thing:  listen(MyEvent, handler);}