您知道如何创建客户端列表控制器吗?
我检查了StackOverflow-没有找到问题的答案。
是否有一种简单的方法来创建资源,该资源在第一页加载时从服务器读取所有记录。 然后在客户端完成排序,筛选,分页。 还可以选择禁用分页。
我试图复制ra-core的ListController.js
和ra-ui-materialui的List.js
并对其进行自定义,但是出错了,我可以继续。我是新来做出反应并进行PoC的人。
这是错误:
TypeError:无法读取未定义的属性“ apply”
(匿名函数)
node_modules / recompose / compose.js:22
19 |
20 | return funcs.reduce(function(a,b){
21 |返回函数(){
22 |返回a(b.apply(undefined,arguments));
23 | };
24 | });
25 | }
查看编译的
./src/ListController.js
src / ListController.js:431
428 |导出默认的compose(
429 | connect(
430 | mapStateToProps,
431 | {
432 | crudGetList:crudGetListAction,
433 | changeListParams:changeListParamsAction,
434 | setSelectedIds:setListSelectedIdsAction,
查看已编译
我已将导入从相对路径更改为导入react-admin
;
有关如何解决该错误的任何建议?
这是更改后的ListController.js
文件:
/* eslint no-console: ["error", { allow: ["warn", "error"] }] */
import { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { parse, stringify } from 'query-string';
import { push as pushAction } from 'react-router-redux';
import compose from 'recompose/compose';
import { createSelector } from 'reselect';
import inflection from 'inflection';
import debounce from 'lodash/debounce';
import isEqual from 'lodash/isEqual';
import pickBy from 'lodash/pickBy';
import removeEmpty from 'react-admin';
import queryReducer, {
SET_SORT,
SET_PAGE,
SET_PER_PAGE,
SET_FILTER,
SORT_DESC,
} from 'react-admin';
import { crudGetList as crudGetListAction } from 'react-admin';
import {
changeListParams as changeListParamsAction,
setListSelectedIds as setListSelectedIdsAction,
toggleListItem as toggleListItemAction,
} from 'react-admin';
import translate from 'react-admin';
import removeKey from 'react-admin';
/**
* 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.
*
* Props:
* - title
* - perPage
* - sort
* - filter (the permanent filter to apply to the query)
* - actions
* - filters (a React Element used to display the filter form)
* - pagination
*
* @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 class ListController extends Component {
state = {};
componentDidMount() {
if (
!this.props.query.page &&
!(this.props.ids || []).length &&
this.props.params.page > 1 &&
this.props.total > 0
) {
this.setPage(this.props.params.page - 1);
return;
}
this.updateData();
if (Object.keys(this.props.query).length > 0) {
this.props.changeListParams(this.props.resource, this.props.query);
}
}
componentWillUnmount() {
this.setFilters.cancel();
}
componentWillReceiveProps(nextProps) {
if (
nextProps.resource !== this.props.resource ||
nextProps.query.sort !== this.props.query.sort ||
nextProps.query.order !== this.props.query.order ||
nextProps.query.page !== this.props.query.page ||
nextProps.query.filter !== this.props.query.filter
) {
this.updateData(
Object.keys(nextProps.query).length > 0
? nextProps.query
: nextProps.params
);
}
if (nextProps.version !== this.props.version) {
this.updateData();
}
}
shouldComponentUpdate(nextProps, nextState) {
if (
nextProps.translate === this.props.translate &&
nextProps.isLoading === this.props.isLoading &&
nextProps.version === this.props.version &&
nextState === this.state &&
nextProps.data === this.props.data &&
nextProps.selectedIds === this.props.selectedIds &&
nextProps.total === this.props.total
) {
return false;
}
return true;
}
/**
* Merge list params from 4 different sources:
* - the query string
* - the params stored in the state (from previous navigation)
* - the filter defaultValues
* - the props passed to the List component
*/
getQuery() {
const query =
Object.keys(this.props.query).length > 0
? this.props.query
: { ...this.props.params };
const filterDefaultValues = this.props.filterDefaultValues || {};
query.filter = { ...filterDefaultValues, ...query.filter };
if (!query.sort) {
query.sort = this.props.sort.field;
query.order = this.props.sort.order;
}
if (!query.perPage) {
query.perPage = this.props.perPage;
}
if (!query.page) {
query.page = 1;
}
return query;
}
updateData(query) {
const params = query || this.getQuery();
const { sort, order, page = 1, perPage, filter } = params;
const pagination = {
page: parseInt(page, 10),
perPage: parseInt(perPage, 10),
};
const permanentFilter = this.props.filter;
this.props.crudGetList(
this.props.resource,
pagination,
{ field: sort, order },
{ ...filter, ...permanentFilter }
);
}
setSort = sort => this.changeParams({ type: SET_SORT, payload: sort });
setPage = page => this.changeParams({ type: SET_PAGE, payload: page });
setPerPage = perPage =>
this.changeParams({ type: SET_PER_PAGE, payload: perPage });
setFilters = debounce(filters => {
if (isEqual(filters, this.props.filterValues)) {
return;
}
// fix for redux-form bug with onChange and enableReinitialize
const filtersWithoutEmpty = removeEmpty(filters);
this.changeParams({ type: SET_FILTER, payload: filtersWithoutEmpty });
}, this.props.debounce);
showFilter = (filterName, defaultValue) => {
this.setState({ [filterName]: true });
if (typeof defaultValue !== 'undefined') {
this.setFilters({
...this.props.filterValues,
[filterName]: defaultValue,
});
}
};
hideFilter = filterName => {
this.setState({ [filterName]: false });
const newFilters = removeKey(this.props.filterValues, filterName);
this.setFilters(newFilters);
};
handleSelect = ids => {
this.props.setSelectedIds(this.props.resource, ids);
};
handleUnselectItems = () => {
this.props.setSelectedIds(this.props.resource, []);
};
handleToggleItem = id => {
this.props.toggleItem(this.props.resource, id);
};
changeParams(action) {
const newParams = queryReducer(this.getQuery(), action);
this.props.push({
...this.props.location,
search: `?${stringify({
...newParams,
filter: JSON.stringify(newParams.filter),
})}`,
});
this.props.changeListParams(this.props.resource, newParams);
}
render() {
const {
basePath,
children,
resource,
hasCreate,
data,
ids,
total,
isLoading,
translate,
version,
selectedIds,
} = this.props;
const query = this.getQuery();
const queryFilterValues = query.filter || {};
const resourceName = translate(`resources.${resource}.name`, {
smart_count: 2,
_: inflection.humanize(inflection.pluralize(resource)),
});
const defaultTitle = translate('ra.page.list', {
name: `${resourceName}`,
});
return children({
basePath,
currentSort: {
field: query.sort,
order: query.order,
},
data,
defaultTitle,
displayedFilters: this.state,
filterValues: queryFilterValues,
hasCreate,
hideFilter: this.hideFilter,
ids,
isLoading,
onSelect: this.handleSelect,
onToggleItem: this.handleToggleItem,
onUnselectItems: this.handleUnselectItems,
page: parseInt(query.page || 1, 10),
perPage: parseInt(query.perPage, 10),
refresh: this.refresh,
resource,
selectedIds,
setFilters: this.setFilters,
setPage: this.setPage,
setPerPage: this.setPerPage,
setSort: this.setSort,
showFilter: this.showFilter,
translate,
total,
version,
});
}
}
ListController.propTypes = {
// the props you can change
children: PropTypes.func.isRequired,
filter: PropTypes.object,
filters: PropTypes.element,
filterDefaultValues: PropTypes.object, // eslint-disable-line react/forbid-prop-types
pagination: PropTypes.element,
perPage: PropTypes.number.isRequired,
sort: PropTypes.shape({
field: PropTypes.string,
order: PropTypes.string,
}),
// the props managed by react-admin
authProvider: PropTypes.func,
basePath: PropTypes.string.isRequired,
changeListParams: PropTypes.func.isRequired,
crudGetList: PropTypes.func.isRequired,
data: PropTypes.object, // eslint-disable-line react/forbid-prop-types
debounce: PropTypes.number,
filterValues: PropTypes.object, // eslint-disable-line react/forbid-prop-types
hasCreate: PropTypes.bool.isRequired,
hasEdit: PropTypes.bool.isRequired,
hasList: PropTypes.bool.isRequired,
hasShow: PropTypes.bool.isRequired,
ids: PropTypes.array,
selectedIds: PropTypes.array,
isLoading: PropTypes.bool.isRequired,
location: PropTypes.object.isRequired,
path: PropTypes.string,
params: PropTypes.object.isRequired,
push: PropTypes.func.isRequired,
query: PropTypes.object.isRequired,
resource: PropTypes.string.isRequired,
setSelectedIds: PropTypes.func.isRequired,
toggleItem: PropTypes.func.isRequired,
total: PropTypes.number.isRequired,
translate: PropTypes.func.isRequired,
version: PropTypes.number,
};
ListController.defaultProps = {
debounce: 500,
filter: {},
filterValues: {},
perPage: 10,
sort: {
field: 'id',
order: SORT_DESC,
},
};
const injectedProps = [
'basePath',
'currentSort',
'data',
'defaultTitle',
'displayedFilters',
'filterValues',
'hasCreate',
'hideFilter',
'ids',
'isLoading',
'onSelect',
'onToggleItem',
'onUnselectItems',
'page',
'perPage',
'refresh',
'resource',
'selectedIds',
'setFilters',
'setPage',
'setPerPage',
'setSort',
'showFilter',
'total',
'translate',
'version',
];
/**
* Select the props injected by the ListController
* to be passed to the List children need
* This is an implementation of pick()
*/
export const getListControllerProps = props =>
injectedProps.reduce((acc, key) => ({ ...acc, [key]: props[key] }), {});
/**
* Select the props not injected by the ListController
* to be used inside the List children to sanitize props injected by List
* This is an implementation of omit()
*/
export const sanitizeListRestProps = props =>
Object.keys(props)
.filter(props => !injectedProps.includes(props))
.reduce((acc, key) => ({ ...acc, [key]: props[key] }), {});
const validQueryParams = ['page', 'perPage', 'sort', 'order', 'filter'];
const getLocationPath = props => props.location.pathname;
const getLocationSearch = props => props.location.search;
const selectQuery = createSelector(
getLocationPath,
getLocationSearch,
(path, search) => {
const query = pickBy(
parse(search),
(v, k) => validQueryParams.indexOf(k) !== -1
);
if (query.filter && typeof query.filter === 'string') {
try {
query.filter = JSON.parse(query.filter);
} catch (err) {
delete query.filter;
}
}
return query;
}
);
function mapStateToProps(state, props) {
const resourceState = state.admin.resources[props.resource];
return {
query: selectQuery(props),
params: resourceState.list.params,
ids: resourceState.list.ids,
selectedIds: resourceState.list.selectedIds,
total: resourceState.list.total,
data: resourceState.data,
isLoading: state.admin.loading > 0,
filterValues: resourceState.list.params.filter,
version: state.admin.ui.viewVersion,
};
}
export default compose(
connect(
mapStateToProps,
{
crudGetList: crudGetListAction,
changeListParams: changeListParamsAction,
setSelectedIds: setListSelectedIdsAction,
toggleItem: toggleListItemAction,
push: pushAction,
}
),
translate
)(ListController);
答案 0 :(得分:1)
使用自定义dataProvider
处理此用例会更容易。
每次在列表视图中执行操作(排序,过滤等)时,React Admin都会向数据提供者询问数据。
例如,假设我们要列出用户,然后按性别过滤用户,然后按年龄排序。数据提供者将使用以下参数调用三次:
dataProvider('GET_LIST', 'user', { sort: defaultSort, filters: defaultFilters });
dataProvider('GET_LIST', 'user', { sort: defaultSort, filters: { gender: 'm' } });
dataProvider('GET_LIST', 'user', { sort: { field: 'age', order: 'DESC' }, filters: { gender: 'm' } });
因此,为了实现所需的行为,您的自定义数据提供程序将类似于:
// customDataResolver.js
import simpleRestProvider from 'ra-data-simple-rest';
const restDataProvider = simpleRestDataProvider('http://example.com/api');
let users; // In-memory cache (but you can write in storage or something)
export default (type, resource, params) => {
if (type === 'GET_LIST' && resource === 'users') {
if (!users) {
return restDataProvider(type, resource, params)
.then((data) => {
users = data;
return data;
});
}
return users
.sort(sortBy(params.sort))
.filter(filterBy(params.filter));
};
return restDataProvider(type, resource, params);
};