You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

问题根源

  1. useEffect依赖项为空数组,仅在组件挂载时执行一次,无法监听localStorage的变化
  2. React无法感知localStorage的手动修改,登录后更新localStorage的操作不会触发组件重渲染
  3. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 05:27:54