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&apos;s not really editable but you get the idea :)</p>
				<p>
					<Link href="./view">View {widget.name}</Link>
				</p>
			</div>
		);
	},
});
Rakaks 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.