React-Admin创建/更新后列表不刷新,缓存读取异常求助
React-Admin 创建/更新记录后列表不自动刷新问题
创建或更新Office记录后,列表组件无法自动刷新,必须手动硬刷新才能看到最新数据。尽管创建/更新请求返回成功,但List组件始终读取缓存数据,而非重新请求API。
已尝试的排查操作:
- 配置Nginx禁用缓存
- 调用react-query的
queryClient.invalidateQueries()方法 - 在创建成功回调中调用
refresh()
相关代码
1. OfficeList.tsx
export const OfficeList = () => { const refresh = useRefresh(); const location = useLocation(); const matchCreate = matchPath({ path: '/offices/create', end: true }, location.pathname); const matchUpdate = matchPath({ path: '/offices/:id', end: false }, location.pathname); const isCreateOpen = !!matchCreate; const isEditOpen = !!matchUpdate && location.pathname !== '/offices/create'; const recordId = isEditOpen ? matchUpdate?.params.id : null; return ( <> <List sort={{ field: "login", order: "ASC" }} perPage={10} actions={<OfficeActions/>} sx={{ marginTop: '30px', marginRight: '10px' }} > <Datagrid bulkActionButtons={false} onSubmit={refresh}> <TextField source="name" label={'Nom'}/> <EmailField source="email" /> <TextField label="Adresse" source="address" /> <NumberField label="Dentistes" source="users.length" textAlign="center" sx={{fontSize: '1.1em', fontWeight: '500'}}/> <TextField source="phoneNumber" fontStyle={'italic'} /> <TextField source="city" /> <DateField label="Date MàJ" source="updatedAt" textAlign="center" fontStyle={'italic'}/> <CustomActionsField /> </Datagrid> </List> { isCreateOpen && <OfficeCreate open={isCreateOpen} /> } { isEditOpen && <OfficeEdit open={isEditOpen} recordId={recordId} /> } </> ); };
2. OfficeCreate.tsx
interface OfficeCreateProps { open: boolean; } export const OfficeCreate = ({ open } : OfficeCreateProps) => { const notify = useNotify(); const refresh = useRefresh(); const redirect = useRedirect(); const handleClose = () => redirect('/offices'); const onSuccess = () => { notify(`Le cabinet a été créé avec succès.`, {type: 'success'}); redirect('/offices'); refresh(); }; const onError = () => { notify(`Une erreur est survenue, vérifiez les données.`, {type: 'error'}); }; const validateName = [required(), minLength(3, 'Nom: 3 caractères minimum.'), maxLength(50, 'Nom: 50 caractères maximum.')]; const validateEmail = email('Email: Format non valide.'); const validatePhoneNumber = regex(/^((\+|00)33\s?|0)[3467](\s?\d{2}){4}$/, 'Téléphone: Format non valide commencez par 04, 06 ou 07 suivis de de 8 chiffres.'); const validateZipCode = regex(/^(?:0[1-9]|[1-9]\d|9[0-8])\d{3}$/, 'Code postal: 2 chiffres minimum et 5 chiffres maximum.'); return ( <Dialog open={open} onClose={handleClose} > <DialogTitle>Créer un cabinet :</DialogTitle> <Create<Office> mutationOptions={{onSuccess, onError}} resource="offices" title=" " sx={{ width: 500, '& .RaCreate-main': { mt: 0 } }} > <SimpleForm toolbar={<SaveButton label="Enregistrer" sx={{ margin: 2 , borderRadius: '40px'}}/>}> <TextInput label='Nom' source="name" validate={validateName} fullWidth /> <TextInput label='Email' source="email" validate={validateEmail} fullWidth /> <TextInput label='Adresse' source="address" fullWidth /> <Box display={{ xs: 'block', sm: 'flex', width: '100%' }}> <Box flex={1} mr={{ xs: 0, sm: '0.5em' }}> <TextInput label='Téléphone' source="phone" validate={validatePhoneNumber} fullWidth /> </Box> <Box flex={1} mr={{ xs: 0, sm: '0.5em' }}> <TextInput label='Ville' source="city" fullWidth /> </Box> </Box> <TextInput label='Code postal' source="zipcode" validate={validateZipCode} /> </SimpleForm> </Create> </Dialog> ); };
3. dataProvider中的httpClient和getList方法
const httpClient = (url: string, options: RequestInit = {}) => { if (!options.headers) { options.headers = new Headers({ Accept: 'application/ld+json' }) as Headers; } if (!(options.body instanceof FormData)) { options.headers = new Headers({ 'Content-Type': 'application/json'}) as Headers; } const token = localStorage.getItem('token'); if (token) { (options.headers as Headers).set('Authorization', `Bearer ${token}`); } return fetchUtils.fetchJson(url, options); }; getList: async ( resource: string, params: { pagination: { page: any; perPage: any; }; sort: { field: any; order: any; }; filter: any; } ) => { const { page, perPage } = params.pagination; const { field, order } = params.sort; let query = { ...fetchUtils.flattenObject(params.filter), page: page, itemsPerPage: perPage }; if (order) { query[`order[${field}]`] = order.toLowerCase(); } const url = `${import.meta.env.VITE_API_BASE}/${resource}?${stringify(query)}`; return httpClient(url).then(({ json }) => { if (!json['hydra:member']) { throw new Error('No hydra:member in response'); } return { data: json['hydra:member'], total: json['hydra:totalItems'], }; }); },
解决方案
1. 为记录添加唯一id字段
React-Admin依赖每个记录的id字段管理缓存,Api Platform返回的hydra:member使用@id而非id,需在getList中映射:
getList: async (resource, params) => { // ... 原有代码 return httpClient(url).then(({ json }) => { if (!json['hydra:member']) { throw new Error('No hydra:member in response'); } // 从@id中提取id const data = json['hydra:member'].map(item => ({ ...item, id: item['@id'].split('/').pop() })); return { data, total: json['hydra:totalItems'], }; }); },
2. 手动触发缓存失效
在Create的onSuccess中调用dataProvider的invalidate方法,强制失效offices资源的缓存:
export const OfficeCreate = ({ open } : OfficeCreateProps) => { const notify = useNotify(); const refresh = useRefresh(); const redirect = useRedirect(); const dataProvider = useDataProvider(); // 新增 const handleClose = () => redirect('/offices'); const onSuccess = () => { notify(`Le cabinet a été créé avec succès.`, {type: 'success'}); dataProvider.invalidate('offices'); // 新增:失效缓存 redirect('/offices'); refresh(); }; // ... 原有代码 };
3. 配置List组件的查询选项
显式设置List的queryOptions,确保缓存及时失效:
<List sort={{ field: "login", order: "ASC" }} perPage={10} actions={<OfficeActions/>} sx={{ marginTop: '30px', marginRight: '10px' }} queryOptions={{ staleTime: 0, cacheTime: 5 * 60 * 1000 }} > {/* ... Datagrid内容 */} </List>
4. 优化httpClient请求头
添加禁止缓存的请求头,避免浏览器缓存响应:
const httpClient = (url: string, options: RequestInit = {}) => { if (!options.headers) { options.headers = new Headers({ Accept: 'application/ld+json', 'Cache-Control': 'no-cache, no-store, must-revalidate' // 新增 }) as Headers; } if (!(options.body instanceof FormData)) { (options.headers as Headers).set('Content-Type', 'application/json'); } const token = localStorage.getItem('token'); if (token) { (options.headers as Headers).set('Authorization', `Bearer ${token}`); } return fetchUtils.fetchJson(url, options); };
内容的提问来源于stack exchange,提问作者Lucien Arbieu
相关产品推荐
相关产品推荐

