Ваш вопрос уже отвечал здесь , но я хотел бы рассказать вам о моем обходном пути для работы React-Admin с отношениями "многие ко многим".
Как сказано в упомянутом ответе, вы должны расширить DataProvider, чтобы он мог извлекать ресурсы отношения «многие ко многим». Однако вам нужно использовать новый глагол REST, давайте предположим, что GET_MANY_MANY_REFERENCE
где-то в вашем приложении. Поскольку разные службы / API REST могут иметь разные форматы маршрутов для извлечения связанных ресурсов, я не пытался создать новый DataProvider, я знаю, что это не лучшее решение, но для коротких сроков это довольно просто.
Мое решение вдохновлялось <ReferenceManyField>
и созданием нового компонента <ReferenceManyManyField>
для отношений «многие ко многим». Этот компонент извлекает связанные записи в componentDidMount
, используя fetch API . При ответе использует данные ответа для построения на объектах, один из которых представляет собой объект с ключами, являющимися идентификаторами записей, и оценивает соответствующий объект записи, а также массив идентификаторов с идентификаторами записей. Это передается дочерним элементам вместе с другими переменными состояния, такими как page, sort, perPage, total, для обработки разбиения на страницы и упорядочения данных. Помните, что изменение порядка данных в Datagrid означает новый запрос к API. Этот компонент разделен на контроллер и представление, например <ReferencemanyField>
, где контроллер извлекает данные, управляет ими и передает их дочерним элементам, а представление, которое получает данные контроллера и передает их дочерним элементам, отображает его содержимое. Это позволило мне отобразить данные отношений «многие ко многим» в Datagrid, даже если с некоторыми ограничениями это компонент для агрегирования в мой проект, и он работает только с моим текущим API, если что-то изменится, и я должен изменить поле на: но на данный момент он работает и может быть использован в моем приложении.
Детали реализации идут следующим образом:
//ReferenceManyManyField
export const ReferenceManyManyField = ({children, ...prop}) => {
if(React.Children.count(children) !== 1) {
throw new Error( '<ReferenceManyField> only accepts a single child (like <Datagrid>)' )
}
return <ReferenceManyManyFieldController {...props}>
{controllerProps => (<ReferenceManyManyFieldView
{...props}
{...{children, ...controllerProps}} /> )}
</ReferenceManyManyFieldController>
//ReferenceManyManyFieldController
class ReferenceManyManyFieldController extends Component {
constructor(props){
super(props)
//State to manage sorting and pagination, <ReferecemanyField> uses some props from react-redux
//I discarded react-redux for simplicity/control however in the final solution react-redux might be incorporated
this.state = {
sort: props.sort,
page: 1,
perPage: props.perPage,
total: 0
}
}
componentWillMount() {
this.fetchRelated()
}
//This could be a call to your custom dataProvider with a new REST verb
fetchRelated({ record, resource, reference, showNotification, fetchStart, fetchEnd } = this.props){
//fetchStart and fetchEnd are methods that signal an operation is being made and make active/deactivate loading indicator, dataProvider or sagas should do this
fetchStart()
dataProvider(GET_LIST,`${resource}/${record.id}/${reference}`,{
sort: this.state.sort,
pagination: {
page: this.state.page,
perPage: this.state.perPage
}
})
.then(response => {
const ids = []
const data = response.data.reduce((acc, record) => {
ids.push(record.id)
return {...acc, [record.id]: record}
}, {})
this.setState({data, ids, total:response.total})
})
.catch(e => {
console.error(e)
showNotification('ra.notification.http_error')
})
.finally(fetchEnd)
}
//Set methods are here to manage pagination and ordering,
//again <ReferenceManyField> uses react-redux to manage this
setSort = field => {
const order =
this.state.sort.field === field &&
this.state.sort.order === 'ASC'
? 'DESC'
: 'ASC';
this.setState({ sort: { field, order } }, this.fetchRelated);
};
setPage = page => this.setState({ page }, this.fetchRelated);
setPerPage = perPage => this.setState({ perPage }, this.fetchRelated);
render(){
const { resource, reference, children, basePath } = this.props
const { page, perPage, total } = this.state;
//Changed basePath to be reference name so in children can nest other resources, not sure why the use of replace, maybe to maintain plurals, don't remember
const referenceBasePath = basePath.replace(resource, reference);
return children({
currentSort: this.state.sort,
data: this.state.data,
ids: this.state.ids,
isLoading: typeof this.state.ids === 'undefined',
page,
perPage,
referenceBasePath,
setPage: this.setPage,
setPerPage: this.setPerPage,
setSort: this.setSort,
total
})
}
}
ReferenceManyManyFieldController.defaultProps = {
perPage: 25,
sort: {field: 'id', order: 'DESC'}
}
//ReferenceManyManyFieldView
export const ReferenceManyManyFieldView = ({
children,
classes = {},
className,
currentSort,
data,
ids,
isLoading,
page,
pagination,
perPage,
reference,
referenceBasePath,
setPerPage,
setPage,
setSort,
total
}) => (
isLoading ?
<LinearProgress className={classes.progress} />
:
<Fragment>
{React.cloneElement(children, {
className,
resource: reference,
ids,
data,
basePath: referenceBasePath,
currentSort,
setSort,
total
})}
{pagination && React.cloneElement(pagination, {
page,
perPage,
setPage,
setPerPage,
total
})}
</Fragment>
);
//Assuming the question example, the presentation of many-to-many relationship would be something like
const UserShow = ({...props}) => (
<Show {...props}>
<TabbedShowLayout>
<Tab label='User Roles'>
<ReferenceManyManyField source='users' reference='roles' addLabel={false} pagination={<Pagination/>}>
<Datagrid>
<TextField source='name'/>
<TextField source='code'/>
</Datagrid>
</ReferenceManyManyField>
</Tab>
</TabbedShowLayout>
</Show>
)
//Used <TabbedShowLayout> because is what I use in my project, not sure if works under <Show> or <SimpleShowLayout>, but I think it work since I use it in other contexts
Я думаю, что реализация может быть улучшена и более совместима с React-Admin. В других ссылочных полях выборка данных хранится в состоянии реакции-избыточности, в этой реализации это не так. Отношение не сохраняется нигде, кроме компонента, заставляющего приложение не работать в автономном режиме, поскольку не может получать данные, даже упорядочение невозможно.