Skip to content

Composing a React app

In a React app, each feature — or a group of features — is a Task. A Task can return anything — this example uses { ui, api }.

One Task at the end — a Render Task — mounts the UI into the page. It usually reads upstream Tasks directly: every Task is known here, so a Tag adds nothing.

What the example does:

  • ThemeSelect is a feature — it returns the chosen theme and a way to choose it.
  • LanguageSelect is a feature — it returns the chosen language and a way to choose it.
  • Appearance is a layout — through its context it reads the language from a shared Tag and the widgets from another Tag.
  • ThemeProvider is a provider — through its context it reads the chosen theme.
  • The Render Task renders the Layout from Appearance wrapped in the Provider from ThemeProvider.
  • compose() wires it all; the Render Task runs last.

nanostores here is only for the task.api demo. Features can use any state manager.

import { compose, createTask, createWire, literal } from "@grlt-hub/app-compose"
import { createRoot } from "react-dom/client"
import { Appearance, appearanceWidgets } from "./appearance"
import { LanguageSelect, ThemeSelect } from "./features"
import { selectedTheme, selectedLanguage } from "./shared-tags"
import { ThemeProvider } from "./theme-provider"
// page-level — reads Tasks directly
const renderApp = createTask({
name: "render-app",
run: {
context: {
AppearanceLayout: Appearance.result.ui.Layout,
ThemeProvider: ThemeProvider.result.ui.Provider,
},
fn: (ctx) => {
const root = createRoot(document.getElementById("root"))
root.render(
<ctx.ThemeProvider>
<ctx.AppearanceLayout />
</ctx.ThemeProvider>,
)
},
},
})
compose()
.step([ThemeSelect, LanguageSelect])
// fill features data into shared tags
.step([
createWire({
from: {
theme: ThemeSelect.result.api.$theme,
language: LanguageSelect.result.api.$language,
},
to: { theme: selectedTheme, language: selectedLanguage },
}),
])
// collect feature widgets into one tag
.step(
createWire({
from: [
{
Widget: ThemeSelect.result.ui.Widget,
key: literal(ThemeSelect.name),
},
{
Widget: LanguageSelect.result.ui.Widget,
key: literal(LanguageSelect.name),
},
],
to: appearanceWidgets,
}),
)
.step([Appearance, ThemeProvider])
.step(renderApp)
.run()