Layout context
A Rakkas layout is not just a simple wrapper component, it also acts as data fetching middleware for nested layouts and pages. A layout's load
function, unlike a page's, can return a context
object which will be passed down to lower level layouts and pages. Both the load
functions and the React components receive this as a prop. context
objects returned from nested layouts are shallowly merged with the parent one. All context values must be serializable by devalue
.
⚠️ There's no relation between the React context API and Rakkas's layout context system.
Let's say you have two pages, /widget/[widgetId]/view
and /widget/[widgetId]/edit
which views and edits a widget respectively. Both pages will need the contents of the widget. You can achieve this, without duplicating your code in both pages, by creating a layout /widget/[widgetId]/layout.jsx
that fetches the widget data and passes it down to the pages using the context
mechanism. You don't even have to define a component for your layout if it's only used for data fetching:
pages/widget/[widgetId]/layout.tsx
import { defineLayout, DefineLayoutTypes } from "rakkasjs"; // DefineLayoutTypes and DefinePageTypes are utility types // to ensure strict type checking when defining pages and layouts. export type WidgetLayoutTypes = DefineLayoutTypes<{ params: { widgetId: string }; contextOverrides: { widget: { name: string; description: string; }; }; }>; export default defineLayout<WidgetLayoutTypes>({ load({ params: { widgetId } }) { // Fetch the widget data (we'll just fake it here) and pass it down in the context return { // We still have to provide a data prop even if we don't need it data: undefined, context: { widget: { name: widgetId, description: `Description of the widget ${widgetId}`, }, }, }; }, // Don't worry about this for now, we'll explain it later. getCacheKey: ({ params: { widgetId } }) => widgetId, // We don't have to provide a wrapper component if we don't need to! });
pages/widget/[widgetId]/view.page.tsx
import React from "react"; import { definePage, DefinePageTypesUnder, Link } from "rakkasjs"; import { WidgetLayoutTypes } from "./layout"; // DefinePageTypesUnder and DefineLayoutTypesUnder are utility types // to ensure strict types for the parent context type WidgetViewPageTypes = DefinePageTypesUnder< WidgetLayoutTypes, { params: { widgetId: string }; } >; export default definePage<WidgetViewPageTypes>({ Component: function WidgetViewPage({ context: { widget } }) { return ( <div> <h1>View {widget.name}</h1> <p>{widget.description}</p> <p> <Link href="./edit">Edit {widget.name}</Link> </p> </div> ); }, });
pages/widget/[widgetId]/edit.page.tsx
import React from "react"; import { definePage, DefinePageTypesUnder, Link } from "rakkasjs"; import { WidgetLayoutTypes } from "./layout"; type WidgetEditPageTypes = DefinePageTypesUnder< WidgetLayoutTypes, { params: { widgetId: string }; } >; export default definePage<WidgetEditPageTypes>({ Component: function WidgetEditPage({ context: { widget } }) { return ( <div> <h1>Edit {widget.name}</h1> <p> <textarea defaultValue={widget.description} style={{ width: "300px" }} /> </p> <p>OK, it's not really editable but you get the idea :)</p> <p> <Link href="./view">View {widget.name}</Link> </p> </div> ); }, });
Rakkas Demo App
Root context
The context passed to the outermost page or layout is initialized on the server-side when the page is first served. See server-side customization hooks to see how. If not provided, its value will default to {}
.
The root context value can be updated on the client by calling the setRootContext
function exported from rakkasjs
module. The value will not be persisted on the server, you'll have to implement that yourself if you need such functionality.
The root context can be used for session management among other things.