Is there a way to check nested resources in react-admin's matchSuggestion?

Viewed 229

The goal is to make an AutocompleteInput check for the filter value not only in the suggestion list directly, but also in the suggestions' references to different resources. Specifically, say a Quote has a reference to a Contact and to an Address, and the user enters 'abc' in the input. Now, a Quote whose address contains 'abc' should also be displayed in the suggestion list. The most elegant way would be to use the useGetOne hook like in the following code snippet but you can't call that hook from outside a React component.

const matchAnyNested = (filter, value) => {
  if (matchAnyField(filter, value)) return true;
  const { data: contact } = useGetOne('contacts', value.contact_id);
  if (matchAnyField(filter, contact)) return true;
  const { data: account } = useGetOne('accounts', contact.account_id);
  if (matchAnyField(filter, account)) return true;
  for (let item of value.part_items) {
    const part = useGetOne('parts', item.part_id);
    if (matchAnyField(filter, part)) return true;
  }
  return false;
};
[...]
<AutocompleteInput ... matchSuggestion={matchAnyNested} />

Is there a way to fetch records from within the matchSuggestion function or some other way to validate suggestions based on nested records ? Thanks for any help

2 Answers

Because of the React rules of hooks, this doesn't seem to be possible. I ended up implementing this filtering functionality in the backend.

The useGetOne hook, just like other dataProvider hooks, accepts an enabled option. The example from the react-admin documentation shows its usage:

// fetch posts
const { ids, data: posts, loading: isLoading } = useGetList(
    'posts',
    { page: 1, perPage: 20 },
    { field: 'name', order: 'ASC' },
    {}
);

// then fetch categories for these posts
const { data: categories, loading: isLoadingCategories } = useGetMany(
    'categories',
    ids.map(id=> posts[id].category_id),
    // run only if the first query returns non-empty result
    { enabled: ids.length > 0 }
);

It applies to your case:

const matchAnyNested = (filter, value) => {
  const { data: contact } = useGetOne(
    'contacts',
    value.contact_id,
    { enabled: !matchAnyField(filter, value) }
  );
  const { data: account } = useGetOne(
    'accounts',
    contact.account_id,
    { enabled: !matchAnyField(filter, contact) }
  );
  // ...
};

This won't solve your problem in the loop, though, because of the rules of hooks.

If you do need that loop, your best bet is to use the useDataProvider hook to call the dataProvider directly:

const matchAnyNested = async (filter, value) => {
  const dataProvider = useDataProvider();
  if (matchAnyField(filter, value)) return true;
  const { data: contact } = await dataProvider.getOne('contacts', { id: value.contact_id });
  if (matchAnyField(filter, contact)) return true;
  const { data: account } = await dataProvider.getOne('accounts', { id: contact.account_id });
  if (matchAnyField(filter, account)) return true;
  for (let item of value.part_items) {
    const part = await dataProvider.getOne('parts', { id: item.part_id });
    if (matchAnyField(filter, part)) return true;
  }
  return false;
};
Related