docs/docs/en/plugin-development/client/component/index.md
In NocoBase, page components mounted on routes are ordinary React components. You can write them directly with React + Antd — no different from regular frontend development.
NocoBase additionally provides:
observable + observer — The recommended state management approach, better suited for the NocoBase ecosystem than useStateuseFlowContext() — Access NocoBase context capabilities (making requests, internationalization, route navigation, etc.)A minimal page component:
// pages/HelloPage.tsx
export default function HelloPage() {
return <h1>Hello, NocoBase!</h1>;
}
After writing the component, register it in the plugin's load() with this.router.add(). See Router for details.
NocoBase recommends using observable + observer to manage component state instead of React's useState. The benefits are:
setStateBasic usage: create a reactive object with observable.deep() and wrap the component with observer(). Both observable and observer are imported from @nocobase/flow-engine:
import React from 'react';
import { Input } from 'antd';
import { observable, observer } from '@nocobase/flow-engine';
// Create a reactive state object
const state = observable.deep({
text: '',
});
// Wrap the component with observer so it auto-updates when state changes
const DemoPage = observer(() => {
return (
<div>
<Input
placeholder="Type something..."
value={state.text}
onChange={(e) => {
state.text = e.target.value;
}}
/>
{state.text && <div style={{ marginTop: 8 }}>You typed: {state.text}</div>}
</div>
);
});
export default DemoPage;
Live preview:
For more usage, see Observable Reactive Mechanism.
useFlowContext() is the entry point for accessing NocoBase capabilities. Import it from @nocobase/flow-engine — it returns a ctx object:
import { useFlowContext } from '@nocobase/flow-engine';
export default function MyPage() {
const ctx = useFlowContext();
// ctx.api — make requests
// ctx.t — internationalization
// ctx.router — route navigation
// ctx.logger — logging
// ...
}
Below are examples of commonly used capabilities.
Use ctx.api.request() to call backend APIs, with the same usage as Axios:
const response = await ctx.api.request({
url: 'users:list',
method: 'get',
});
console.log(response.data);
Use ctx.t() to get translated text:
const label = ctx.t('Hello');
// Specify namespace
const msg = ctx.t('Save success', { ns: '@my-project/plugin-hello' });
Use ctx.router.navigate() for page navigation:
ctx.router.navigate('/some-page'); // -> /v/some-page
Get current route parameters:
// e.g., route defined as /users/:id
const { id } = ctx.route.params; // get dynamic parameter
Get current route name:
const { name } = ctx.route; // get route name
For more log levels and usage, see Context - Common Capabilities.
Combining observable, useFlowContext, and Antd — a page component that fetches data from the backend and displays it:
// pages/PostListPage.tsx
import React, { useEffect } from 'react';
import { Button, Card, List, Spin } from 'antd';
import { observable, observer, FlowContext, useFlowContext } from '@nocobase/flow-engine';
interface Post {
id: number;
title: string;
}
// Use observable to manage page state
const state = observable.deep({
posts: [] as Post[],
loading: true,
});
const PostListPage = observer(() => {
const ctx = useFlowContext();
useEffect(() => {
loadPosts(ctx);
}, []);
return (
<Card title={ctx.t('Post list')}>
<Spin spinning={state.loading}>
<List
dataSource={state.posts}
renderItem={(post: Post) => (
<List.Item
actions={[
<Button danger onClick={() => handleDelete(ctx, post.id)}>
{ctx.t('Delete')}
</Button>,
]}
>
{post.title}
</List.Item>
)}
/>
</Spin>
</Card>
);
});
async function loadPosts(ctx: FlowContext) {
state.loading = true;
try {
const response = await ctx.api.request({
url: 'posts:list',
method: 'get',
});
state.posts = response.data?.data || [];
} catch (error) {
ctx.logger.error('Failed to load post list', { error });
} finally {
state.loading = false;
}
}
async function handleDelete(ctx: FlowContext, id: number) {
await ctx.api.request({
url: `posts:destroy/${id}`,
method: 'post',
});
loadPosts(ctx); // Refresh the list
}
export default PostListPage;
useFlowContext — see Context