React-Admin动态资源更新未触发组件重渲染问题求助
React Admin 登录后授权资源未自动更新的问题
我在复现React Admin的运行时声明资源功能,用户登录后,授权资源会被获取并存入localStorage。我通过自定义方法读取localStorage中的授权资源来填充React Admin的资源,但<Admin>组件在用户登录前就已渲染,登录后授权资源更新时组件不会重渲染,必须刷新页面才能更新,体验很差。
我试过useEffect、useCallback等方案,以下是最接近官方示例的代码:
import * as React from 'react'; import {useEffect, useState} from 'react'; import {AdminContext, AdminUI, Loading, localStorageStore, useAuthProvider} from 'react-admin'; import myAuthProvider from '../authProvider'; import myDataProvider from '../dataproviders/dataProvider'; import MyLoginPage from "./Login"; import i18nProvider from "../i18nProvider"; import Layout from "../Layout"; const store = localStorageStore(); function MyUI() { return ( <AdminContext authProvider={myAuthProvider} dataProvider={myDataProvider} i18nProvider={i18nProvider} store={store}> <AsyncResources /> </AdminContext> ); } function AsyncResources() { const authProvider = useAuthProvider(); const [authorizedOrganizations, setAuthorizedOrganizations] = useState([] as string[]); useEffect(() => { setAuthorizedOrganizations(authProvider.getAuthorizedOrgIds()) // 问题:这里只执行一次,如何订阅变化? }, []) /* 每个组织都需要定义为资源,否则当我们尝试显示第一个以外的组织元素时, react-admin找不到对应的路由。但我们只需要一个列表视图,所以将其他组织 声明为资源但隐藏它们。 */ const hiddenResources = authorizedOrganizations.slice(1).map(buildHiddenResource) const visibleResources = authorizedOrganizations.slice(0, 1).map(buildResource) const allResources = [...visibleResources, ...hiddenResources] // 登录后因为没有更新,这里始终是空数组,加上ready={Loading}导致UI一直处于加载状态 return ( <AdminUI loginPage={MyLoginPage} layout={Layout} requireAuth={true} ready={Loading}> { allResources } </AdminUI> ); } // [...] buildResource、buildHiddenResource的定义 export default MyUI
我的authProvider代码:
import {AuthProvider, fetchUtils} from 'react-admin'; import {getApiUrl} from "./environmentConfig"; import User from './User' const httpClient = fetchUtils.fetchJson; const TOKEN_KEY = 'token' const PROFILE_KEY = 'profile' const AUTHORIZED_ORGS_KEY = 'authorizedOrganizations' const authProvider: AuthProvider = { /** * 获取用户信息,因为react-admin要求接收凭证的方法名为login,所以这里命名为login * @param token 访问令牌 */ login: ({ password: token }) => { const url = `${getApiUrl()}/accounts/profile` return httpClient( url, { method: 'GET', headers: new Headers({ Authorization: `Bearer ${token}` }) }) .then(({ json }) => { const parsedJson = json as User console.debug(`Successfully logged in as ${parsedJson.username}`) localStorage.setItem(PROFILE_KEY, JSON.stringify(json)) localStorage.setItem(TOKEN_KEY, token) }) .then(fetchAuthorizedOrgIds) .then(authorizedOrgIds => { localStorage.setItem(AUTHORIZED_ORGS_KEY, JSON.stringify(authorizedOrgIds)) console.debug(`User is authorized for ${authorizedOrgIds.length} organization(s): ${authorizedOrgIds}`) }) }, logout: () => { localStorage.removeItem(TOKEN_KEY) localStorage.removeItem(PROFILE_KEY) localStorage.removeItem(AUTHORIZED_ORGS_KEY) return Promise.resolve() }, // [...] getAuthorizedOrgIds: () => { let storedValue = localStorage.getItem(AUTHORIZED_ORGS_KEY) if (storedValue === null) { console.debug('Authorized organizations unset, falling back to empty array') storedValue = '[]' } const result = JSON.parse(storedValue) as string[] return result } }; /** * 获取当前访问令牌 * @return 访问令牌,无则返回null */ function getAccessToken(): String|null { return localStorage.getItem(TOKEN_KEY) } /** * 请求API获取用户有权限访问的组织ID列表 * @return 解析为组织ID数组的Promise */ function fetchAuthorizedOrgIds(): Promise<string[]> { const token = getAccessToken() if (token === null) { return Promise.reject('Not logged in') } const url = `${getApiUrl()}/accounts/authorized-organizations` return httpClient( url, { method: 'GET', headers: new Headers({ Authorization: `Bearer ${token}` }) }) .then(({ json }) => json.data as string[]) } export default authProvider
问题根源
useEffect依赖项为空数组,仅在组件挂载时执行一次,无法监听localStorage的变化- React无法感知localStorage的手动修改,登录后更新localStorage的操作不会触发组件重渲染
AdminUI的ready属性绑定Loading,但资源数组始终为空,导致登录后一直显示加载状态
解决步骤
1. 监听localStorage变化与自定义事件
修改AsyncResources组件,同时监听storage事件(跨标签页同步)和自定义事件(当前标签页更新):
function AsyncResources() { const authProvider = useAuthProvider(); const [authorizedOrganizations, setAuthorizedOrganizations] = useState([] as string[]); const fetchAuthorizedOrgs = () => { setAuthorizedOrganizations(authProvider.getAuthorizedOrgIds()); }; useEffect(() => { // 组件挂载时初始化数据 fetchAuthorizedOrgs(); // 监听自定义事件(当前标签页登录成功后触发) const handleOrgsUpdated = () => fetchAuthorizedOrgs(); window.addEventListener('authorized-orgs-updated', handleOrgsUpdated); // 监听storage事件(跨标签页同步数据) window.addEventListener('storage', fetchAuthorizedOrgs); // 组件卸载时清理监听 return () => { window.removeEventListener('authorized-orgs-updated', handleOrgsUpdated); window.removeEventListener('storage', fetchAuthorizedOrgs); }; }, [authProvider]); // ... 其余代码不变 }
2. 登录成功后触发自定义事件
修改authProvider的login方法,在写入localStorage后触发自定义事件通知组件更新:
const authProvider: AuthProvider = { login: ({ password: token }) => { // ... 现有代码 .then(authorizedOrgIds => { localStorage.setItem(AUTHORIZED_ORGS_KEY, JSON.stringify(authorizedOrgIds)); console.debug(`User is authorized for ${authorizedOrgIds.length} organization(s): ${authorizedOrgIds}`); // 触发自定义事件 window.dispatchEvent(new CustomEvent('authorized-orgs-updated')); }) }, // ... 其余代码 };
3. 优化加载状态逻辑
修改AdminUI的ready属性,仅在资源为空时显示加载:
return ( <AdminUI loginPage={MyLoginPage} layout={Layout} requireAuth={true} ready={authorizedOrganizations.length > 0 ? null : Loading}> { allResources } </AdminUI> );
内容的提问来源于stack exchange,提问作者lipusal
相关产品推荐
相关产品推荐

