如何使用react admin不更改列表筛选上的url?

如何使用react admin不更改列表筛选上的url?,url,filter,react-admin,Url,Filter,React Admin,假设我有一个路由/客户,其中呈现了一组客户。在同一条路线上还有一份礼物。该抽屉包含一个包含帮助主题的列表 当我开始在侧抽屉中的过滤器中键入内容时,url会发生变化。这取决于react管理员列表过滤器的工作方式 问题是,客户列表注意到了路线的变化。实际上,它根据与帮助主题相关的搜索词开始查询和重新加载客户。当然没有找到顾客 我希望客户列表不会注意到我正在筛选帮助主题。我想要的解决方案是,当我输入帮助主题搜索词时,侧抽屉中的列表过滤器不会更改url 如何配置或自定义侧抽屉中的筛选器,使其在键入时不更

假设我有一个路由/客户,其中呈现了一组客户。在同一条路线上还有一份礼物。该抽屉包含一个包含帮助主题的列表

当我开始在侧抽屉中的过滤器中键入内容时,url会发生变化。这取决于react管理员列表过滤器的工作方式

问题是,客户列表注意到了路线的变化。实际上,它根据与帮助主题相关的搜索词开始查询和重新加载客户。当然没有找到顾客

我希望客户列表不会注意到我正在筛选帮助主题。我想要的解决方案是,当我输入帮助主题搜索词时,侧抽屉中的列表过滤器不会更改url

如何配置或自定义侧抽屉中的筛选器,使其在键入时不更改url,而是将当前筛选器值存储在类似组件状态的内容中

事实上,由于过滤器以一种形式存在(通过react final form),它保持自己的状态,因此我可以接受这样的解决方案。但是当然,
publishtour
不是过滤器可用的道具

const MyFilter = props => (
    <Filter {...props} publishToUrl={false} >
        <TextInput source="title" />
    </Filter>
);
constmyfilter=props=>(
);
相关的:

setFilters()属性从其父列表向下传递到筛选器组件

因此,您需要实现自己的useListParams钩子,并删除/包装条件注释行:

const changeParams = useCallback(action => {
        const newQuery = getQuery({
            location,
            params,
            filterDefaultValues,
            sort,
            perPage,
        });
        const newParams = queryReducer(newQuery, action);
        // history.push({
        //     search: `?${stringify({
        //         ...newParams,
        //         filter: JSON.stringify(newParams.filter),
        //     })}`,
        // });
        dispatch(changeListParams(resource, newParams));
    }, requestSignature); // eslint-disable-line react-hooks/exhaustive-deps
然后你必须实现useListController并调用你的钩子而不是react管理员的钩子

const [query, queryModifiers] = useListParams({
    resource,
    location,
    filterDefaultValues,
    sort,
    perPage,
    debounce,
});
最后,实现列表组件并传递新的cool useListController。筛选器值不会反映在查询字符串以及分页和排序中

另一种更简单的方法是截取过滤器组件中的setFilters调用并执行以下操作

dispatch(changeListParams(resource, newParams));

有了新的过滤器值,没有实现一堆钩子和组件。

多亏了d0berm4n提供的指导,我能够编译一个工作解决方案(针对react admin 3.x)。就它创建的查询而言,它相当有限(只是过滤器),但这正是我需要的

import React, { useCallback } from 'react';
import PropTypes from 'prop-types';
import { useDispatch } from 'react-redux';
import lodashDebounce from 'lodash/debounce';
import { Filter, TextInput, changeListParams } from 'react-admin';

const MyFilter = ({ resource, ...rest }) => {
    const dispatch = useDispatch();

    const debouncedSetFilters = lodashDebounce((filter = {}) => {
        const query = { filter };

        dispatch(changeListParams(resource, query));
    }, 500);

    const setFilters = useCallback(debouncedSetFilters, []);

    return (
        <Filter resource={resource} {...rest} setFilters={setFilters}>
            <TextInput source="title" alwaysOn resettable />
        </Filter>
    );
};
MyFilter.propTypes = {
    resource: PropTypes.string,
};
MyFilter.displayName = 'MyFilter';

export default MyFilter;
import React,{useCallback}来自“React”;
从“道具类型”导入道具类型;
从'react redux'导入{useDispatch};
从“lodash/debounce”进口lodashDebounce;
从“react admin”导入{Filter,TextInput,changeListParams};
常量MyFilter=({resource,…rest})=>{
const dispatch=usedpatch();
const debouncedSetFilters=lodashDebounce((filter={})=>{
const query={filter};
调度(changeListParams(资源、查询));
}, 500);
const setFilters=useCallback(debouncedSetFilters,[]);
返回(
);
};
MyFilter.propTypes={
资源:PropTypes.string,
};
MyFilter.displayName='MyFilter';
导出默认MyFilter;

更新:此解决方案不完整。同一页面上的其他过滤器现在开始查询MyFilter所在的列表。我想这是一个我可以解决的问题。稍后将详细介绍…

我尝试了一种不同的解决方案,也许它会对某些人有所帮助:

const FunctionsFilter=({resource,…rest})=>{
const classes=useStyles();
const location=useLocation();
常量[query,queryModifiers]=useMyListParams({
资源,,
位置,
filterDefaultValues:{},
排序:{
字段:“名称”,
订单:“asc”,
},
每页:5,
去盎司:500,
});
返回(
);

};我也有类似的问题。我有一条有三个标签的路线。在每个选项卡上,我呈现了不同的列表。一旦我在tab1上选择了过滤器,它们就会被传播到url并应用到tab2列表和tab3列表

我找到了解决办法: 我已经分析了react管理源代码。它正在useListParams.ts中使用方法“changeParams”。此方法使用的是
useHistory()来自“react router dom”,并将筛选器参数推送到url:

 history.push({
            search: `?${stringify({
                ...newParams,
                filter: JSON.stringify(newParams.filter),
                displayedFilters: JSON.stringify(newParams.displayedFilters),
            })}`,
        });
所以,我的解决方案是,在标签页上,我做了
history.push({search:''})(当然,您必须首先安装react路由器dom,导入useHistory,然后将历史设置为常量
const history=useHistory();


此解决方案在更改选项卡时清除url参数,因此搜索参数(过滤器、排序和范围)不再应用于其他选项卡(和列表)。

以下代码将删除列表组件与Redux存储和位置的附件,它可用于在单页上显示多个列表

使用此列表而不是react管理员列表

创建自己的列表组件

import * as React from 'react';
import PropTypes from 'prop-types';
import {
} from 'ra-core';
import {ListView} from 'ra-ui-materialui';
import {useListController} from '../../controller/useListController';

export const TitlePropType = PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.element,
]);

/**
 * List page component
 *
 * The <List> component renders the list layout (title, buttons, filters, pagination),
 * and fetches the list of records from the REST API.
 * It then delegates the rendering of the list of records to its child component.
 * Usually, it's a <Datagrid>, responsible for displaying a table with one row for each post.
 *
 * In Redux terms, <List> is a connected component, and <Datagrid> is a dumb component.
 *
 * The <List> component accepts the following props:
 *
 * - actions
 * - aside
 * - component
 * - filter (the permanent filter to apply to the query)
 * - filters (a React component used to display the filter form)
 * - pagination
 * - perPage
 * - sort
 * - title
 *
 * @example
 *
 * const PostFilter = (props) => (
 *     <Filter {...props}>
 *         <TextInput label="Search" source="q" alwaysOn />
 *         <TextInput label="Title" source="title" />
 *     </Filter>
 * );
 * export const PostList = (props) => (
 *     <List {...props}
 *         title="List of posts"
 *         sort={{ field: 'published_at' }}
 *         filter={{ is_published: true }}
 *         filters={PostFilter}
 *     >
 *         <Datagrid>
 *             <TextField source="id" />
 *             <TextField source="title" />
 *             <EditButton />
 *         </Datagrid>
 *     </List>
 * );
 */
export const List = props => {
    return <ListView {...props} {...useListController(props)} />;
}

List.propTypes = {
    // the props you can change
    actions: PropTypes.element,
    aside: PropTypes.element,
    bulkActionButtons: PropTypes.oneOfType([PropTypes.element, PropTypes.bool]),
    children: PropTypes.node,
    classes: PropTypes.object,
    className: PropTypes.string,
    filter: PropTypes.object,
    filterDefaultValues: PropTypes.object,
    filters: PropTypes.element,
    pagination: PropTypes.element,
    perPage: PropTypes.number.isRequired,
    sort: PropTypes.shape({
        field: PropTypes.string,
        order: PropTypes.string,
    }),
    title: TitlePropType,
    // the props managed by react-admin
    authProvider: PropTypes.func,
    hasCreate: PropTypes.bool.isRequired,
    hasEdit: PropTypes.bool.isRequired,
    hasList: PropTypes.bool.isRequired,
    hasShow: PropTypes.bool.isRequired,
    location: PropTypes.object,
    match: PropTypes.object,
    path: PropTypes.string,
    resource: PropTypes.string.isRequired,
};

List.defaultProps = {
    filter: {},
    perPage: 10,
};
import*as React from'React';
从“道具类型”导入道具类型;
进口{
}来自“ra核心”;
从“ra ui materialui”导入{ListView};
从“../../controller/useListController”导入{useListController};
导出常量TitlePropType=PropTypes.oneOfType([
PropTypes.string,
PropTypes.element,
]);
/**
*列表页组件
*
*组件呈现列表布局(标题、按钮、过滤器、分页),
*并从RESTAPI获取记录列表。
*然后,它将记录列表的呈现委托给其子组件。
*通常,它是一个,负责为每个帖子显示一行表。
*
*在Redux术语中,是一个连接的组件,是一个哑组件。
*
*组件接受以下道具:
*
*-行动
*-旁白
*-组件
*-过滤器(应用于查询的永久过滤器)
*-过滤器(用于显示过滤器窗体的React组件)
*-分页
*-每页
*-分类
*-标题
*
*@example
*
*常量后过滤器=(道具)=>(
*     
*         
*         
*     
* );
*导出常量PostList=(道具)=>(
*     
*         
*             
*             
*             
*         
*
import { isValidElement, ReactElement, useEffect, useMemo } from 'react';
import inflection from 'inflection';
import { useSelector } from 'react-redux';
import get from 'lodash/get';

import {useCheckMinimumRequiredProps, useTranslate,
    useNotify, useGetList, CRUD_GET_LIST, useVersion, useRecordSelection } from 'react-admin';
import { ListParams } from 'ra-core';
import { Sort, RecordMap, Identifier, ReduxState, Record } from 'ra-core';

import {SORT_ASC} from 'ra-core/esm/reducer/admin/resource/list/queryReducer';
import useListParams from './useListParams';

export interface ListProps {
    // the props you can change
    filter?: object;
    filters?: ReactElement<any>;
    filterDefaultValues?: object;
    pagination?: ReactElement<any>;
    perPage?: number;
    sort?: Sort;
    // the props managed by react-admin
    basePath: string;
    debounce?: number;
    hasCreate?: boolean;
    hasEdit?: boolean;
    hasList?: boolean;
    hasShow?: boolean;
    path?: string;
    query: ListParams;
    resource: string;
    [key: string]: any;
}

const defaultSort = {
    field: 'id',
    order: SORT_ASC,
};

const defaultData = {};

export interface ListControllerProps<RecordType = Record> {
    basePath: string;
    currentSort: Sort;
    data: RecordMap<RecordType>;
    defaultTitle: string;
    displayedFilters: any;
    filterValues: any;
    hasCreate: boolean;
    hideFilter: (filterName: string) => void;
    ids: Identifier[];
    loading: boolean;
    loaded: boolean;
    onSelect: (ids: Identifier[]) => void;
    onToggleItem: (id: Identifier) => void;
    onUnselectItems: () => void;
    page: number;
    perPage: number;
    resource: string;
    selectedIds: Identifier[];
    setFilters: (filters: any, displayedFilters: any) => void;
    setPage: (page: number) => void;
    setPerPage: (page: number) => void;
    setSort: (sort: string) => void;
    showFilter: (filterName: string, defaultValue: any) => void;
    total: number;
    version: number;
}

/**
 * Prepare data for the List view
 *
 * @param {Object} props The props passed to the List component.
 *
 * @return {Object} controllerProps Fetched and computed data for the List view
 *
 * @example
 *
 * import { useListController } from 'react-admin';
 * import ListView from './ListView';
 *
 * const MyList = props => {
 *     const controllerProps = useListController(props);
 *     return <ListView {...controllerProps} {...props} />;
 * }
 */
export const useListController = <RecordType = Record>(
    props: ListProps
): ListControllerProps<RecordType> => {
    useCheckMinimumRequiredProps('List', ['basePath', 'resource'], props);

    const {
        basePath,
        resource,
        hasCreate,
        filterDefaultValues,
        sort = defaultSort,
        perPage = 10,
        filter,
        debounce = 500,
    } = props;

    if (filter && isValidElement(filter)) {
        throw new Error(
            '<List> received a React element as `filter` props. If you intended to set the list filter elements, use the `filters` (with an s) prop instead. The `filter` prop is internal and should not be set by the developer.'
        );
    }

    const translate = useTranslate();
    const notify = useNotify();
    const version = useVersion();

    const [query, queryModifiers] = useListParams({
        resource,
        filterDefaultValues,
        sort,
        perPage,
        debounce
    });

    const [selectedIds, selectionModifiers] = useRecordSelection(resource);

    /**
     * We want the list of ids to be always available for optimistic rendering,
     * and therefore we need a custom action (CRUD_GET_LIST) that will be used.
     */
    const { ids, total, loading, loaded } = useGetList<RecordType>(
        resource,
        {
            page: query.page,
            perPage: query.perPage,
        },
        { field: query.sort, order: query.order },
        { ...query.filter, ...filter },
        {
            action: CRUD_GET_LIST,
            onFailure: error =>
                notify(
                    typeof error === 'string'
                        ? error
                        : error.message || 'ra.notification.http_error',
                    'warning'
                ),
        }
    );

    const data = useSelector(
        (state: ReduxState): RecordMap<RecordType> =>
            get(
                state.admin.resources,
                [resource, 'data'],
                defaultData
            ) as RecordMap<RecordType>
    );

    // When the user changes the page/sort/filter, this controller runs the
    // useGetList hook again. While the result of this new call is loading,
    // the ids and total are empty. To avoid rendering an empty list at that
    // moment, we override the ids and total with the latest loaded ones.
    const defaultIds = [];
    const defaultTotal = 0;

    useEffect(() => {
        if (
            query.page <= 0 ||
            (!loading && query.page > 1 && (ids || []).length === 0)
        ) {
            // query for a page that doesn't exist, set page to 1
            queryModifiers.setPage(1);
        }
    }, [loading, query.page, ids, queryModifiers]);

    const currentSort = useMemo(
        () => ({
            field: query.sort,
            order: query.order,
        }),
        [query.sort, query.order]
    );

    const resourceName = translate(`resources.${resource}.name`, {
        smart_count: 2,
        _: inflection.humanize(inflection.pluralize(resource)),
    });
    const defaultTitle = translate('ra.page.list', {
        name: resourceName,
    });

    return {
        basePath,
        currentSort,
        data,
        defaultTitle,
        displayedFilters: query.displayedFilters,
        filterValues: query.filterValues,
        hasCreate,
        hideFilter: queryModifiers.hideFilter,
        ids: typeof total === 'undefined' ? defaultIds : ids,
        loaded: loaded || defaultIds.length > 0,
        loading,
        onSelect: selectionModifiers.select,
        onToggleItem: selectionModifiers.toggle,
        onUnselectItems: selectionModifiers.clearSelection,
        page: query.page,
        perPage: query.perPage,
        resource,
        selectedIds,
        setFilters: queryModifiers.setFilters,
        setPage: queryModifiers.setPage,
        setPerPage: queryModifiers.setPerPage,
        setSort: queryModifiers.setSort,
        showFilter: queryModifiers.showFilter,
        total: typeof total === 'undefined' ? defaultTotal : total,
        version,
    };
};

export const injectedProps = [
    'basePath',
    'currentSort',
    'data',
    'defaultTitle',
    'displayedFilters',
    'filterValues',
    'hasCreate',
    'hideFilter',
    'ids',
    'loading',
    'loaded',
    'onSelect',
    'onToggleItem',
    'onUnselectItems',
    'page',
    'perPage',
    'refresh',
    'resource',
    'selectedIds',
    'setFilters',
    'setPage',
    'setPerPage',
    'setSort',
    'showFilter',
    'total',
    'version',
];

export default useListController;
import {useCallback, useMemo, useState} from 'react';
import lodashDebounce from 'lodash/debounce';
import set from 'lodash/set';
import { ListParams } from 'ra-core';
import { Sort, removeKey, removeEmpty } from 'ra-core';
import queryReducer from 'ra-core/esm/reducer/admin/resource/list/queryReducer';
import {SORT_ASC, SET_SORT, SET_PAGE, SET_PER_PAGE, SET_FILTER} from 'ra-core/esm/reducer/admin/resource/list/queryReducer';

interface ListParamsOptions {
    resource: string;
    perPage?: number;
    sort?: Sort;
    filterDefaultValues?: object;
    debounce?: number;
}

interface Parameters extends ListParams {
    filterValues: object;
    displayedFilters: {
        [key: string]: boolean;
    };
    requestSignature: any[];
}

interface Modifiers {
    changeParams: (action: any) => void;
    setPage: (page: number) => void;
    setPerPage: (pageSize: number) => void;
    setSort: (sort: string) => void;
    setFilters: (filters: any, displayedFilters: any) => void;
    hideFilter: (filterName: string) => void;
    showFilter: (filterName: string, defaultValue: any) => void;
}

const emptyObject = {};

const defaultSort = {
    field: 'id',
    order: SORT_ASC,
};

const defaultParams = {};

/**
 * Get the list parameters (page, sort, filters) and modifiers.
 *
 * These parameters are merged from 3 sources:
 *   - the query string from the URL
 *   - the params stored in the state (from previous navigation)
 *   - the options passed to the hook (including the filter defaultValues)
 *
 * @returns {Array} A tuple [parameters, modifiers].
 * Destructure as [
 *    { page, perPage, sort, order, filter, filterValues, displayedFilters, requestSignature },
 *    { setFilters, hideFilter, showFilter, setPage, setPerPage, setSort }
 * ]
 *
 * @example
 *
 * const [listParams, listParamsActions] = useListParams({
 *      resource: 'posts',
 *      location: location // From react-router. Injected to your component by react-admin inside a List
 *      filterDefaultValues: {
 *          published: true
 *      },
 *      sort: {
 *          field: 'published_at',
 *          order: 'DESC'
 *      },
 *      perPage: 25
 * });
 *
 * const {
 *      page,
 *      perPage,
 *      sort,
 *      order,
 *      filter,
 *      filterValues,
 *      displayedFilters,
 *      requestSignature
 * } = listParams;
 *
 * const {
 *      setFilters,
 *      hideFilter,
 *      showFilter,
 *      setPage,
 *      setPerPage,
 *      setSort,
 * } = listParamsActions;
 */
const useListParams = ({
                           resource,
                           filterDefaultValues,
                           sort = defaultSort,
                           perPage = 10,
                           debounce = 500,
                       }: ListParamsOptions): [Parameters, Modifiers] => {

    const [params, setParams] = useState(defaultParams);

    const requestSignature = [
        resource,
        params,
        filterDefaultValues,
        JSON.stringify(sort),
        perPage
    ];

    const query = useMemo(
        () =>
            getQuery({
                params,
                filterDefaultValues,
                sort,
                perPage
            }),
        requestSignature // eslint-disable-line react-hooks/exhaustive-deps
    );

    const changeParams = useCallback(action => {
        const newParams = queryReducer(query, action);
        setParams(newParams);
    }, requestSignature); // eslint-disable-line react-hooks/exhaustive-deps

    const setSort = useCallback(
        (newSort: string) =>
            changeParams({ type: SET_SORT, payload: { sort: newSort } }),
        requestSignature // eslint-disable-line react-hooks/exhaustive-deps
    );

    const setPage = useCallback(
        (newPage: number) => changeParams({ type: SET_PAGE, payload: newPage }),
        requestSignature // eslint-disable-line react-hooks/exhaustive-deps
    );

    const setPerPage = useCallback(
        (newPerPage: number) =>
            changeParams({ type: SET_PER_PAGE, payload: newPerPage }),
        requestSignature // eslint-disable-line react-hooks/exhaustive-deps
    );

    const filterValues = query.filter || emptyObject;
    const displayedFilterValues = query.displayedFilters || emptyObject;

    const debouncedSetFilters = lodashDebounce(
        (newFilters, newDisplayedFilters) => {
            let payload = {
                filter: removeEmpty(newFilters),
                displayedFilters: undefined,
            };
            if (newDisplayedFilters) {
                payload.displayedFilters = Object.keys(
                    newDisplayedFilters
                ).reduce((filters, filter) => {
                    return newDisplayedFilters[filter]
                        ? { ...filters, [filter]: true }
                        : filters;
                }, {});
            }
            changeParams({
                type: SET_FILTER,
                payload,
            });
        },
        debounce
    );

    const setFilters = useCallback(
        (filters, displayedFilters) =>
            debouncedSetFilters(filters, displayedFilters),
        requestSignature // eslint-disable-line react-hooks/exhaustive-deps
    );

    const hideFilter = useCallback((filterName: string) => {
        const newFilters = removeKey(filterValues, filterName);
        const newDisplayedFilters = {
            ...displayedFilterValues,
            [filterName]: undefined,
        };

        setFilters(newFilters, newDisplayedFilters);
    }, requestSignature); // eslint-disable-line react-hooks/exhaustive-deps

    const showFilter = useCallback((filterName: string, defaultValue: any) => {
        const newFilters = set(filterValues, filterName, defaultValue);
        const newDisplayedFilters = {
            ...displayedFilterValues,
            [filterName]: true,
        };
        setFilters(newFilters, newDisplayedFilters);
    }, requestSignature); // eslint-disable-line react-hooks/exhaustive-deps

    return [
        {
            displayedFilters: displayedFilterValues,
            filterValues,
            requestSignature,
            ...query,
        },
        {
            changeParams,
            setPage,
            setPerPage,
            setSort,
            setFilters,
            hideFilter,
            showFilter,
        },
    ];
};

/**
 * Check if user has already set custom sort, page, or filters for this list
 *
 * User params come from the Redux store as the params props. By default,
 * this object is:
 *
 * { filter: {}, order: null, page: 1, perPage: null, sort: null }
 *
 * To check if the user has custom params, we must compare the params
 * to these initial values.
 *
 * @param {Object} params
 */
export const hasCustomParams = (params: ListParams) => {
    return (
        params &&
        params.filter &&
        (Object.keys(params.filter).length > 0 ||
            params.order != null ||
            params.page !== 1 ||
            params.perPage != null ||
            params.sort != null)
    );
};

/**
 * Merge list params from 3 different sources:
 *   - the query string
 *   - the params stored in the state (from previous navigation)
 *   - the props passed to the List component (including the filter defaultValues)
 */
export const getQuery = ({
                             filterDefaultValues,
                             params,
                             sort,
                             perPage,
                         }) => {
    const query: Partial<ListParams> =
        hasCustomParams(params)
            ? { ...params } : { filter: filterDefaultValues || {} };

    if (!query.sort) {
        query.sort = sort.field;
        query.order = sort.order;
    }
    if (!query.perPage) {
        query.perPage = perPage;
    }
    if (!query.page) {
        query.page = 1;
    }
    return {
        ...query,
        page: getNumberOrDefault(query.page, 1),
        perPage: getNumberOrDefault(query.perPage, 10),
    } as ListParams;
};

export const getNumberOrDefault = (
    possibleNumber: string | number | undefined,
    defaultValue: number
) =>
    (typeof possibleNumber === 'string'
        ? parseInt(possibleNumber, 10)
        : possibleNumber) || defaultValue;

export default useListParams;
 <ResourceContextProvider value="posts">
   <List syncWithLocation basePath="/posts">
            <Datagrid>
                ... Sync with url
            </Datagrid>
      </List>
 </ResourceContextProvider>

 <ResourceContextProvider value="users">
      <List  basePath="/users" >
          <Datagrid>
               ... Not Sync with url
          </Datagrid>
      </List>
 </ResourceContextProvider>