@faybl/react-utils

中文文档
2.5.2发布于 2026/11/13TBDES Module近 30 天下载 3143

React hooks and components for Faybl web apps

原始使用文档
文档目录3 节
  1. An expired session
  2. The top bar
  3. Deployment info

@faybl/react-utils

React hooks and components for Faybl web apps.

npm install @faybl/react-utils

Every hook but useSession takes the @faybl/client client as client and does nothing until it has one; useSession reads it from SessionProvider. useConnection and SessionProvider need client 4.1 or later. A list of rids or ids is compared by its content, so a new array with the same ids does not resubscribe.

import {
  useChannelsLatest,
  useChannelsSeries,
  useConnection,
  usePresence,
  useSession,
} from '@faybl/react-utils';

const presenceById = usePresence({ client, identityIds });
const latest = useChannelsLatest({ client, rids, commitMs: 1000, onError });
const series = useChannelsSeries({ client, rids, windowSeconds: 3600 });
const connected = useConnection({ client });
const { expired, cause, user, signIn, signOut } = useSession();
HookReturns
usePresence{ [identityId]: { ts, connected } }, live
useChannelsLatest{ [rid]: { ts, pl } }, the last stored point, then live
useChannelsSeries{ [rid]: points }, the last windowSeconds, oldest first, then live
useConnectionwhether the MQTT connection is up
useSessionthe session, see An expired session
useDeploymentInfothe tenant's web apps, see Deployment info

connected and expired are undefined until the client has reported them.

The two channel hooks take two options:

  • commitMs collects live points and renders once per that many milliseconds. Without it, every point renders as it arrives.
  • onError(error, rid) is called when a history read fails. The hooks report nothing on their own.

An expired session

SessionProvider follows the session behind the client and reacts when it is gone. Put it around the app once the client exists:

<SessionProvider client={client}>
  <App />
</SessionProvider>

It finds an expiry two ways, and each has its own prop:

PropWhenDefault
expiredOnReturnthe user comes back to the tab; a request is sent then to checkreload
expiredOnRequesta request is refused while the user works on the pagedialog

Each takes reload, dialog or none, also exported as REACTION:

  • reload reloads the page. While the identity provider still has a session the user is back on the same page at once; otherwise the sign-in page shows.
  • dialog shows SessionExpiredDialog, which keeps the page, and whatever the user was typing, until the user chooses to sign in again.
  • none does nothing, for an app that shows the expiry its own way through useSession.

An expiry found while the tab is hidden counts as a return and waits until the user comes back. After an automatic reload, a tab reloads on its own again only once reloadCooldownMs (default 30 000) has passed; an expiry before that shows the dialog, so a 401 that is not about the session cannot reload in a loop. dialogLabels sets the dialog's { title, action }, which are English by default. The dialog takes its colours from the page's CSS color-scheme, so a dark app has to declare it, with MUI through <CssBaseline enableColorScheme />.

A tenant can run without sign-in. Give the provider the deployment info's address as deploymentInfoUrl, and it follows the session and fetches the user only once the info says sign-in is on; in a tenant without it, it does neither, and the user menu is not there. Without deploymentInfoUrl sign-in counts as on.

useSession() returns:

  • authEnabled: false in a tenant without sign-in, undefined while the deployment info is read.
  • expired: undefined until the client has reported, then whether the session is gone.
  • cause: how the expiry was found, returned or request, also exported as CAUSE.
  • user: { id, firstName, lastName, email } from auth.getUserInfo, fetched the first time a component asks and kept. Undefined until it arrives.
  • userUnavailable: true while the user could not be fetched.
  • signIn(): reloads the page.
  • signOut(): goes to the login proxy's sign_out on the app's own host, which ends the session there and at the identity provider, and comes back to the page the user left, which asks the user to sign in again. signOutUrl on the provider sends it somewhere else instead.

dialogComponent replaces the dialog with one of the app's own, which gets open, onSignIn and labels. An MUI app passes the one from @faybl/react-utils/mui, which takes the app's theme:

import { MuiSessionExpiredDialog } from '@faybl/react-utils/mui';

<SessionProvider client={client} dialogComponent={MuiSessionExpiredDialog}>

An app that handles the expiry itself can still use the dialog:

const SessionNotice = () => {
  const { expired, signIn } = useSession();
  return <SessionExpiredDialog open={expired === true} onSignIn={signIn} />;
};

The top bar

@faybl/react-utils/mui has the pieces of the bar every Faybl app shares, drawn with MUI and the app's theme. They need @mui/material and @mui/icons-material.

ComponentWhat it shows
MuiUserMenuthe user's avatar, with a menu that shows the account and signs out
MuiAppSwitcherthe tenant's web apps as a grid of links, the current one marked
MuiConnectionStatusLive or Offline, from connected
MuiColorSchemeTogglea switch between light and dark
MuiTopBarthe app's icon, title and content, then the four pieces above

MuiTopBar puts them in a fixed order: the app's start, icon, title, subtitle and children on the left, then connection, colour scheme, app switcher and user menu. Each piece has a prop of its own name. Left out or false, the piece is not there; true, it is there with its defaults; an object, it is there with those props. Only userMenu is there by default. Every other prop goes to MUI's AppBar, so the bar can be position="fixed" or take its own sx.

import { useConnection, useDeploymentInfo } from '@faybl/react-utils';
import { MuiTopBar, MuiUserMenu } from '@faybl/react-utils/mui';

// A customer app: title, connection and the user menu.
<MuiTopBar title="Maskin Mekano" connectionStatus={{ connected: useConnection({ client }) }} />

// An internal tool: also the other apps and the colour scheme.
const info = useDeploymentInfo({ url: `${import.meta.env.BASE_URL}faybl/deployment-info.json` });
<MuiTopBar icon="settings" title="Configuration" appSwitcher={{ apps: info?.apps }} colorSchemeToggle />

// With what the app shows now beside its name.
<MuiTopBar icon="timeline" title="Data streamer" subtitle={dashboardTitle} appSwitcher={{ apps }} />

// A bar of the app's own.
<Toolbar>…<MuiUserMenu picture="gravatar" showAccount={false} /></Toolbar>

icon is a name from the list under Deployment info, the same one the app has in the app switcher. subtitle is what the app shows now, such as a dashboard's name. When room runs out the subtitle shortens first, then the title, both with an ellipsis; the pieces never shrink. A piece keeps its place, hidden, while what it shows is loading, so the bar does not move when the user or the app list arrives.

MuiUserMenu reads the user from useSession, so it needs SessionProvider above it. It shows the initials, or with picture="gravatar" the user's Gravatar, falling back to the initials. Gravatar is opt-in because it sends a hash of every user's email to an outside service. Beside the avatar it shows the name and email, except on narrow screens; showAccount={false} leaves them out. children adds menu items above sign-out. When the user cannot be fetched the menu shows a plain avatar and still signs out.

MuiConnectionStatus counts only connected === false as offline, since useConnection is undefined before the first report. On narrow screens it shows the icon alone.

MuiColorSchemeToggle needs a theme with colorSchemes.

Every piece takes labels, English by default:

Componentlabels
MuiUserMenu{ account, signOut }
MuiAppSwitcher{ apps }
MuiConnectionStatus{ live, offline }
MuiColorSchemeToggle{ dark, light }, named after the scheme a click switches to

Deployment info

Each Faybl web app serves what the deployment says about the tenant at <app path>/faybl/deployment-info.json. useDeploymentInfo({ url }) reads it once per url:

{
  "faybl": { "version": "1.0.0" },
  "auth": { "enabled": true },
  "apps": [
    {
      "name": "conf-ui",
      "label": "Configuration",
      "icon": "settings",
      "path": "/conf-ui/",
      "version": "1.0.3"
    }
  ]
}

It is undefined while it is read. Without a url, or when the file cannot be read, it has no apps, so the app switcher is not there; pass no url in local development. Only these fields are kept, an app without a name, label or a path on the page's own host is dropped, and at most 50 apps are read.

icon is one of apps, settings, chart, play, dashboard, factory, map, queryStats, timeline and science; any other becomes apps.

版本与发布记录

@faybl/react-utils 共发布 18 个版本,首次发布于 2026/10/22;过去 12 个月发布了 18 个版本。

版本发布日期距今
2.5.2latest2026/11/131 天前
2.5.12026/11/122 天前
2.4.02026/11/113 天前
2.3.02026/11/0410 天前
2.2.02026/10/2718 天前
2.2.0-rc.52026/10/2718 天前

关于 @faybl/react-utils 的常见问题

@faybl/react-utils 是什么?

React hooks and components for Faybl web apps

如何安装 @faybl/react-utils?

在项目目录运行 npm install @faybl/react-utils,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:react(^19.2.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@faybl/react-utils 支持 TypeScript 吗?

该包的 package.json 没有声明类型入口。需要类型提示时,可以在 npm 上查找社区维护的 @types/faybl__react-utils 是否存在。

@faybl/react-utils 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@faybl/react-utils 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@faybl/react-utils 可以商用吗?

@faybl/react-utils 使用 TBD 许可。声明的许可为 TBD,请阅读许可原文确认使用条件。以上为概括,不构成法律意见,请以许可原文为准。

@faybl/react-utils 的下载量有多少?

根据 npm 官方下载统计,@faybl/react-utils 在 2026/10/15 – 2026/11/13 这 30 天内被下载 3,143 次,日均约 105 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@faybl/react-utils 还在维护吗?

最新版本 2.5.2 发布于 2026/11/13(1 天前);至今共发布 18 个版本,其中过去 12 个月发布了 18 个;首次发布于 2026/10/22。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。