Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ListContext to ReferenceArrayInput #5886

Merged
merged 16 commits into from Feb 24, 2021
24 changes: 23 additions & 1 deletion docs/Inputs.md
Expand Up @@ -1406,7 +1406,7 @@ http://myapi.com/tags?id=[1,23,4]
http://myapi.com/tags?page=1&perPage=25
```

Once it receives the deduplicated reference resources, this component delegates rendering to a subcomponent, to which it passes the possible choices as the `choices` attribute.
Once it receives the deduplicated reference resources, this component delegates rendering to a subcomponent, by providing the possible choices through the `ReferenceArrayInputContext`. This context value can be accessed with the [`useReferenceArrayInputContext`](#usereferencearrayinputcontext) hook.

This means you can use `<ReferenceArrayInput>` with [`<SelectArrayInput>`](#selectarrayinput), or with the component of your choice, provided it supports the `choices` attribute.

Expand Down Expand Up @@ -1477,8 +1477,30 @@ You can tweak how this component fetches the possible values using the `perPage`
```
{% endraw %}

In addition to the `ReferenceArrayInputContext`, `<ReferenceArrayInput>` also sets up a `ListContext` providing access to the records from the reference resource in a similar fashion to that of the `<List>` component. This `ListContext` value is accessible with the [`useListContext`](/List.md#uselistcontext) hook.

`<ReferenceArrayInput>` also accepts the [common input props](./Inputs.md#common-input-props).

### `useReferenceArrayInputContext`

The [`<ReferenceArrayInput>`](#referencearrayinput) component take care of fetching the data, and put that data in a context called `ReferenceArrayInputContext` so that it’s available for its descendants. This context also stores filters, pagination, sort state, and provides callbacks to update them.

Any component decendent of `<ReferenceArryInput>` can grab information from the `ReferenceArrayInputContext` using the `useReferenceArrayInputContext` hook. Here is what it returns:

```js
const {
choices, // An array of records matching both the current input value and the filters
error, // A potential error that may have occured while fetching the data
warning, // A potential warning regarding missing references
loaded, // boolean that is false until the data is available
loading, // boolean that is true on mount, and false once the data was fetched
setFilter, // a callback to update the filters, e.g. setFilters({ q: 'query' })
setPagination, // a callback to change the pagination, e.g. setPagination({ page: 2, perPage: 50 })
setSort, // a callback to change the sort, e.g. setSort({ field: 'name', order: 'DESC' })
setSortForList, // a callback to set the sort with the same signature as the one from the ListContext. This is required to avoid breaking backward compatibility and will be removed in v4
} = useReferenceArrayInputContext();
```

### `<ReferenceInput>`

Use `<ReferenceInput>` for foreign-key values, for instance, to edit the `post_id` of a `comment` resource. This component fetches the related record (using `dataProvider.getMany()`) as well as possible choices (using `dataProvider.getList()` in the reference resource), then delegates rendering to a subcomponent, to which it passes the possible choices as the `choices` attribute.
Expand Down
Expand Up @@ -11,6 +11,7 @@ import usePaginationState from '../usePaginationState';
import useSelectionState from '../useSelectionState';
import useSortState from '../useSortState';
import { useResourceContext } from '../../core';
import { indexById } from '../../util/indexById';

interface Option {
basePath: string;
Expand Down Expand Up @@ -239,12 +240,4 @@ const useReferenceArrayFieldController = (
};
};

const indexById = (records: Record[] = []): RecordMap =>
records
.filter(r => typeof r !== 'undefined')
.reduce((prev, current) => {
prev[current.id] = current;
return prev;
}, {});

export default useReferenceArrayFieldController;
@@ -0,0 +1,29 @@
import { createContext } from 'react';
import { PaginationPayload, Record, SortPayload } from '../../types';

/**
* Context which provides access to the useReferenceArrayInput features.
*
* @example
* const ReferenceArrayInput = ({ children }) => {
* const controllerProps = useReferenceArrayInputController();
* return (
* <ReferenceArrayInputContextProvider value={controllerProps}>
* {children}
* </ReferenceArrayInputContextProvider>
* )
* }
*/
export const ReferenceArrayInputContext = createContext(undefined);

export interface ReferenceArrayInputContextValue {
choices: Record[];
error?: any;
warning?: any;
loading: boolean;
loaded: boolean;
setFilter: (filter: any) => void;
setPagination: (pagination: PaginationPayload) => void;
setSort: (sort: SortPayload) => void;
setSortForList: (sort: string, order?: string) => void;
}
@@ -0,0 +1,31 @@
import * as React from 'react';
import { ReactNode } from 'react';
import {
ReferenceArrayInputContext,
ReferenceArrayInputContextValue,
} from './ReferenceArrayInputContext';

/**
* Provider for the context which provides access to the useReferenceArrayInput features.
*
* @example
* const ReferenceArrayInput = ({ children }) => {
* const controllerProps = useReferenceArrayInputController();
* return (
* <ReferenceArrayInputContextProvider value={controllerProps}>
* {children}
* </ReferenceArrayInputContextProvider>
* )
* }
*/
export const ReferenceArrayInputContextProvider = ({
children,
value,
}: {
children: ReactNode;
value: ReferenceArrayInputContextValue;
}) => (
<ReferenceArrayInputContext.Provider value={value}>
{children}
</ReferenceArrayInputContext.Provider>
);