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

[BUG] Conditional Triggering in useMany with Support for Query Arrays #6449

Closed
aress31 opened this issue Nov 2, 2024 · 6 comments
Closed
Labels
bug Something isn't working

Comments

@aress31
Copy link

aress31 commented Nov 2, 2024

Describe the bug

There are a few use cases that useMany does not currently seem to support, specifically:

  1. Conditional Rendering:
const DataGridView = () => {
  const supabaseColumns = useSupabaseColumns();
  const { dataGridProps } = useDataGrid();

  const [query, setQuery] = useState({ resource: "", ids: [] });

  useEffect(() => {
    const resources = supabaseColumns
      .filter((column) => column.foreign_key_reference)
      .map(({ column_name }) => `${column_name.split("_")[0]}s`);

    const query = resources.map((resource) => {
      const supabaseColumn = `${resource.slice(0, -1)}_id`;
      return {
        resource,
        ids: dataGridProps.rows.map((row) => row[supabaseColumn]),
      };
    })[0];

    setQuery(query);
  }, [dataGridProps.rows, supabaseColumns]);

  console.log({ query, dataGridProps });

  const { data } = useMany({
    ...query,
    queryOptions: { enabled: query.resource !== "" },
  });

  console.log({ data });

  return (
    <DataGridComponent
      columns={generateColumnDefinitions(supabaseColumns)}
      dataGridProps={dataGridProps}
    />
  );
};

In this scenario, I want to trigger useMany only when there is a column with a foreign key. I have attempted to adjust the hooks, but this particular use case does not seem to be supported yet.

  1. Support for Query Arrays: It would be beneficial to allow the passing of an array of queries, which would in turn return an array of data. This enhancement would improve efficiency and the developer experience.

Steps To Reproduce

  1. Look at the Code: Check out the DataGridView component where useMany is used.
  2. Check the query State: See how query gets set based on the foreign key columns.
  3. Run It: Render the component and watch the console for query and dataGridProps.
  4. Change Foreign Keys: Play around with the data to see how useMany reacts when there are no foreign key columns.

Expected behavior

The useMany hook should only run when certain conditions are met, like having foreign key columns or valid resource identifiers. This helps avoid unnecessary API calls when there's nothing relevant to fetch, making the app faster and smoother. Plus, it would be awesome to support an array of queries to grab multiple related resources at once, which would make data fetching even more efficient.

Packages

  • "@refinedev/core": "^4.55.0"
  • "@refinedev/inferencer": "^4.7.0"
  • "@refinedev/mui": "^5.21.0"
  • "@refinedev/react-router-v6": "^4.6.0"
  • "@refinedev/supabase": "^5.9.4"

Additional Context

No response

@aress31 aress31 added the bug Something isn't working label Nov 2, 2024
@BatuhanW
Copy link
Member

BatuhanW commented Nov 4, 2024

Hey @aress31 thanks for the issue. You can use supabase join strings to query relationships, you can check our documentation on this one here: https://refine.dev/docs/data/packages/supabase/#select---handling-one-to-many-relationship

For example:

const { dataGridProps } = useDataGrid({
  resource: "posts",
  meta: {
    select: "*, categories(title)",
  },
});

queryOptions.enabled should also disable the query, there might be a race condition with your resource state. But you shouldn't need two hooks to fetch a single resource. Since you aren't passing resource field to either hook, useDataGrid and useMany hooks are doing requests again current resource of the page, that could be the reason you are seeing multiple queries.

@aress31
Copy link
Author

aress31 commented Nov 4, 2024

@BatuhanW, thanks for the reply. I understand your point about sending only one request to the database, but that's not always feasible due to application design or other constraints, so enhanced support for multiple requests would be really helpful. Additionally, in my case, the resources are defined at the top level, though I’m not sure if that impacts anything here. Another issue I'm noticing is that the parent metadata doesn’t seem to be respected, resulting in the entire table being fetched rather than just the entries linked to the parent.

--- SNIP ---

const createResource = (name, parentResource) => {
  const basePath = parentResource
    ? `/:tenantId/${parentResource}/:${singular(parentResource)}Id/${name}`
    : `/:tenantId/${name}`;

  return {
    name,
    list: basePath,
    show: `${basePath}/show/:id`,
    edit: `${basePath}/edit/:id`,
    create: `${basePath}/create`,
    ...(parentResource && { meta: { parent: parentResource } }),
  };
};

const resources = [
  createResource("projects"),
  createResource("applications", "projects"),
  --- SNIP ---
];

const SidebarLayout = () => {
  --- SNIP ---

  return (
    <Refine
      dataProvider={dataProvider(supabaseClient)}
      liveProvider={liveProvider(supabaseClient)}
      routerProvider={routerProvider}
      options={{
        liveMode: "auto",
        syncWithLocation: true,
        warnWhenUnsavedChanges: true,
      }}
      --- SNIP ---
      resources={resources}
    >
      --- SNIP ---
    </Refine>
  );
};

export default SidebarLayout;

@alicanerdurmaz
Copy link
Member

alicanerdurmaz commented Nov 5, 2024

Hello @ aress31, I tried reproducing your issue, and everything works as expected.
Am I missing something?

import { useMany } from "@refinedev/core";
import { useState } from "react";

export const UseManyExample = () => {
  const [query, setQuery] = useState<{
    resource: string;
    ids: number[];
  }>({
    resource: "",
    ids: [],
  });

  const { data, isLoading } = useMany({
    ...query,
    queryOptions: { enabled: query.resource !== "" },
  });

  return (
    <div>
      <button onClick={() => setQuery({ resource: "posts", ids: [1, 2, 3] })}>
        Load Posts
      </button>
      <button
        onClick={() => setQuery({ resource: "categories", ids: [1, 2, 3] })}
      >
        Load Categories
      </button>

      <div>useMany isLoading: {isLoading.toString()}</div>

      <br />

      <div>
        {data?.data.map((item) => (
          <div
            key={item.id}
            style={{
              display: "flex",
              gap: "4px",
            }}
          >
            <div>{item.id}</div>
            <div>{item.title}</div>
          </div>
        ))}
      </div>
    </div>
  );
};
Screen.Recording.2024-11-05.at.10.06.17.mov

For supporting multiple resources, I don’t think useMany needs this built-in feature. You can handle it by making a new React component.

For example:

import { useMany } from "@refinedev/core";

export const UseManyMultipleResourceExample = () => {
  return (
    <div>
      <GetManyResources resource="posts" ids={[1, 2, 3]} title="Posts" />
      <br />
      <GetManyResources
        resource="categories"
        ids={[1, 2, 3]}
        title="Categories"
      />
    </div>
  );
};

export const GetManyResources = (props: {
  title?: string;
  resource: string;
  ids: number[];
}) => {
  const { data, isLoading } = useMany({
    resource: props.resource,
    ids: props.ids,
  });

  return (
    <div>
      <h1>{props.title}</h1>

      {data?.data.map((item) => (
        <div
          key={item.id}
          style={{
            display: "flex",
            gap: "4px",
          }}
        >
          <div>{item.id}</div>
          <div>{item.title}</div>
        </div>
      ))}
    </div>
  );
};

Do you see any downsides to this method that I might be missing?

@aress31
Copy link
Author

aress31 commented Nov 5, 2024

@alicanerdurmaz I understand now; the idea is to let specific components handle the loading process. Initially, I was loading multiple sets of data in the parent component using multiple useMany calls and passing the data down. My approach was to fetch data from tables and, if those tables had foreign key references, use useMany to load the related data. However, I ended up using the !inner solution, which is cleaner and more efficient (thanks for the tip, @BatuhanW - only downside is longer initial loading/rendering time as more data are transmitted).

In cases where there are multiple foreign keys and therefore several linked data sets, is it possible to chain inner statements? I haven't seen documentation on this, as far as I know.

EDIT: @alicanerdurmaz, one drawback of useMany not supporting multiple queries is when you need data that isn’t linked by any relationship but is required within the same component. Thoughts?

@alicanerdurmaz
Copy link
Member

@aress31 I believe it’s possible, but I didn’t fully understand the scenario. If you provide more details about it, I might be able to come up with some example code.

@BatuhanW
Copy link
Member

@aress31 I believe there is no bug here. You don't want to join in other tables in the initial query, so you would need to fetch separately. Closing the issue now. Feel free to re-open it if needed.

@BatuhanW BatuhanW closed this as not planned Won't fix, can't repro, duplicate, stale Nov 20, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants