xenial-black
xenial-blackā€¢5mo ago

Architectural question regarding nested Lists and Selects

Hey everyone! Here's a common problem where I hope there is a common solution in refine. Imagine you have a custom row template component that looks close to something like this:
import { useSelect } from '@refinedev/antd';
import { Input, Select } from 'antd';

const MyCustomRowTemplate: React.FC = (name: number) => {
const { selectProps } = useSelect<ICategory>({
resource: "categories",
});

return (
<>
<Form.Item name={[name, "category"]}>
<Select placeholder="Select a category" style={{ width: 300 }} {...selectProps} />
</Form.Item>
<Form.Item name={[name, "another_field"]}>
<Input />
</Form.Item>
</>
);
};
import { useSelect } from '@refinedev/antd';
import { Input, Select } from 'antd';

const MyCustomRowTemplate: React.FC = (name: number) => {
const { selectProps } = useSelect<ICategory>({
resource: "categories",
});

return (
<>
<Form.Item name={[name, "category"]}>
<Select placeholder="Select a category" style={{ width: 300 }} {...selectProps} />
</Form.Item>
<Form.Item name={[name, "another_field"]}>
<Input />
</Form.Item>
</>
);
};
I want to use this component as a template for a Form.List (https://ant.design/components/form#components-form-demo-dynamic-form-item) component which enables you to dynamically add entries to a list. Say we have a list of 10 items then this component blocks the main-thread for ~5 seconds rendering all components because of the useSelect. I haven't found a way around this behavior yet, which as said completely blocks the main thread in my application. Maybe @core can help me figure this out šŸ‘¼
Form - Ant Design
An enterprise-class UI design language and React UI library with a set of high-quality React components, one of best React UI library for enterprises
14 Replies
kapa.ai
kapa.aiā€¢5mo ago
Hi @bastianwegge. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave: ---------------------- The useSelect hook in Refine is designed to fetch data from a resource and provide it as options for a select input. In your case, it seems like you're using useSelect inside a component that is being rendered multiple times, which could be causing performance issues due to multiple network requests. One possible solution could be to fetch the data for the select input at a higher level in your component hierarchy and pass it down to your MyCustomRowTemplate component as a prop. This way, you only make one network request regardless of how many MyCustomRowTemplate components you render. Here's an example of how you could do this:
import { useSelect } from '@refinedev/antd';
import { Input, Select } from 'antd';

interface MyCustomRowTemplateProps {
name: number;
selectProps: any; // Replace 'any' with the correct type
}

const MyCustomRowTemplate: React.FC<MyCustomRowTemplateProps> = ({ name, selectProps }) => {
return (
<>
<Form.Item name={[name, "category"]}>
<Select placeholder="Select a category" style={{ width: 300 }} {...selectProps} />
</Form.Item>
<Form.Item name={[name, "another_field"]}>
<Input />
</Form.Item>
</>
);
};

// In the parent component
const ParentComponent: React.FC = () => {
const { selectProps } = useSelect<ICategory>({
resource: "categories",
});

// Render your list of MyCustomRowTemplate components here, passing selectProps as a prop
};
import { useSelect } from '@refinedev/antd';
import { Input, Select } from 'antd';

interface MyCustomRowTemplateProps {
name: number;
selectProps: any; // Replace 'any' with the correct type
}

const MyCustomRowTemplate: React.FC<MyCustomRowTemplateProps> = ({ name, selectProps }) => {
return (
<>
<Form.Item name={[name, "category"]}>
<Select placeholder="Select a category" style={{ width: 300 }} {...selectProps} />
</Form.Item>
<Form.Item name={[name, "another_field"]}>
<Input />
</Form.Item>
</>
);
};

// In the parent component
const ParentComponent: React.FC = () => {
const { selectProps } = useSelect<ICategory>({
resource: "categories",
});

// Render your list of MyCustomRowTemplate components here, passing selectProps as a prop
};
This way, you're only making one network request to fetch the categories, regardless of how many MyCustomRowTemplate components you render. For more information on how to use the useSelect hook, you can refer to the Refine documentation (https://refine.dev/docs/ui-integrations/ant-design/hooks/use-select). ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.aiā€¢5mo ago
<@317433576721219584> kapa.ai is still learning and improving, please let us know how it did by reacting below
xenial-black
xenial-blackā€¢5mo ago
@kapa.ai So lets say I have 20 instances of this assignment and each one uses a different category, for simplicity lets say the used categories are 1, 10, 100 and 1000. How would I have to extend this example for the categories to be loaded? It's true that this might be a way to solve the freezing issue, but the categories are not being populated.
kapa.ai
kapa.aiā€¢5mo ago
Let me check the knowledge base for you. One moment! :books: ---------------------- If each instance of your component needs to fetch a different category, you could modify the useSelect hook to fetch a specific category based on a prop. You would then pass the category ID as a prop to each instance of your component. Here's an example of how you could do this:
import { useSelect } from '@refinedev/antd';
import { Input, Select } from 'antd';

interface MyCustomRowTemplateProps {
name: number;
categoryId: number;
}

const MyCustomRowTemplate: React.FC<MyCustomRowTemplateProps> = ({ name, categoryId }) => {
const { selectProps } = useSelect<ICategory>({
resource: "categories",
defaultValue: categoryId,
});

return (
<>
<Form.Item name={[name, "category"]}>
<Select placeholder="Select a category" style={{ width: 300 }} {...selectProps} />
</Form.Item>
<Form.Item name={[name, "another_field"]}>
<Input />
</Form.Item>
</>
);
};
import { useSelect } from '@refinedev/antd';
import { Input, Select } from 'antd';

interface MyCustomRowTemplateProps {
name: number;
categoryId: number;
}

const MyCustomRowTemplate: React.FC<MyCustomRowTemplateProps> = ({ name, categoryId }) => {
const { selectProps } = useSelect<ICategory>({
resource: "categories",
defaultValue: categoryId,
});

return (
<>
<Form.Item name={[name, "category"]}>
<Select placeholder="Select a category" style={{ width: 300 }} {...selectProps} />
</Form.Item>
<Form.Item name={[name, "another_field"]}>
<Input />
</Form.Item>
</>
);
};
In the parent component, you would then pass the category ID as a prop to each instance of MyCustomRowTemplate:
// In the parent component
const ParentComponent: React.FC = () => {
// Render your list of MyCustomRowTemplate components here, passing the category ID as a prop
return (
<>
<MyCustomRowTemplate name={1} categoryId={1} />
<MyCustomRowTemplate name={2} categoryId={10} />
<MyCustomRowTemplate name={3} categoryId={100} />
<MyCustomRowTemplate name={4} categoryId={1000} />
{/* ... */}
</>
);
};
// In the parent component
const ParentComponent: React.FC = () => {
// Render your list of MyCustomRowTemplate components here, passing the category ID as a prop
return (
<>
<MyCustomRowTemplate name={1} categoryId={1} />
<MyCustomRowTemplate name={2} categoryId={10} />
<MyCustomRowTemplate name={3} categoryId={100} />
<MyCustomRowTemplate name={4} categoryId={1000} />
{/* ... */}
</>
);
};
This way, each instance of MyCustomRowTemplate will fetch and display the category specified by its categoryId prop. For more information on how to use the useSelect hook, you can refer to the Refine documentation (https://refine.dev/docs/ui-integrations/ant-design/hooks/use-select). ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.aiā€¢5mo ago
<@317433576721219584> kapa.ai is still learning and improving, please let us know how it did by reacting below
xenial-black
xenial-blackā€¢5mo ago
@kapa.ai that's basically what I did in the first place what caused the freezing or blocking-main-thread issue.
kapa.ai
kapa.aiā€¢5mo ago
Let me check the knowledge base for you. One moment! :books: ---------------------- I see, I misunderstood your initial question. If each instance of your component needs to fetch a different category and you have a large number of these components, this could indeed lead to performance issues due to the large number of network requests. One possible solution could be to fetch all categories at a higher level in your component hierarchy and then filter the categories for each instance of your component based on the category ID. This way, you only make one network request regardless of how many components you render. Here's an example of how you could do this:
import { useQuery } from '@refinedev/antd';
import { Input, Select } from 'antd';

interface MyCustomRowTemplateProps {
name: number;
categoryId: number;
categories: ICategory[];
}

const MyCustomRowTemplate: React.FC<MyCustomRowTemplateProps> = ({ name, categoryId, categories }) => {
const category = categories.find(category => category.id === categoryId);

return (
<>
<Form.Item name={[name, "category"]}>
<Select placeholder="Select a category" style={{ width: 300 }} defaultValue={category?.id}>
{categories.map(category => (
<Select.Option key={category.id} value={category.id}>
{category.name}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item name={[name, "another_field"]}>
<Input />
</Form.Item>
</>
);
};

// In the parent component
const ParentComponent: React.FC = () => {
const { data: categories } = useQuery<ICategory[]>({
resource: "categories",
});

// Render your list of MyCustomRowTemplate components here, passing the categories and category ID as props
};
import { useQuery } from '@refinedev/antd';
import { Input, Select } from 'antd';

interface MyCustomRowTemplateProps {
name: number;
categoryId: number;
categories: ICategory[];
}

const MyCustomRowTemplate: React.FC<MyCustomRowTemplateProps> = ({ name, categoryId, categories }) => {
const category = categories.find(category => category.id === categoryId);

return (
<>
<Form.Item name={[name, "category"]}>
<Select placeholder="Select a category" style={{ width: 300 }} defaultValue={category?.id}>
{categories.map(category => (
<Select.Option key={category.id} value={category.id}>
{category.name}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item name={[name, "another_field"]}>
<Input />
</Form.Item>
</>
);
};

// In the parent component
const ParentComponent: React.FC = () => {
const { data: categories } = useQuery<ICategory[]>({
resource: "categories",
});

// Render your list of MyCustomRowTemplate components here, passing the categories and category ID as props
};
In this example, the ParentComponent fetches all categories and passes them as a prop to each instance of MyCustomRowTemplate. Each instance of MyCustomRowTemplate then finds the category with the matching ID from the categories prop. This approach should help to reduce the number of network requests and improve performance. However, it assumes that the number of categories is not too large. If you have a very large number of categories, this approach could still lead to performance issues due to the large amount of data being fetched and processed. For more information on how to use the useQuery hook, you can refer to the Refine documentation (https://refine.dev/docs/api-references/hooks/use-query). ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.aiā€¢5mo ago
<@317433576721219584> kapa.ai is still learning and improving, please let us know how it did by reacting below
xenial-black
xenial-blackā€¢5mo ago
@batuhanw @Omer do you have a solution for this? Mounting several components in a row that all need to have unique state regarding useSelect results in a complete freeze of the application. Moving the state into the parent components breaks the behavior of uniquely searchable selects for each row. Is there a common way you solve this problem?
xenial-black
xenial-blackā€¢5mo ago
@core I have created a repository with a detailed description here: https://github.com/bastianwegge/refine-dynamic-form-state
GitHub
GitHub - bastianwegge/refine-dynamic-form-state: This is a reproduc...
This is a reproduction of a problem we had in january 2024 in a project where we used refine - GitHub - bastianwegge/refine-dynamic-form-state: This is a reproduction of a problem we had in january...
ambitious-aqua
ambitious-aquaā€¢5mo ago
Hey bastian, in the repository, I couldn't see multiple selects but I think you can conditionally enable select query as a solution.
No description
ambitious-aqua
ambitious-aquaā€¢5mo ago
When you provide defaultValue, useSelect makes request to get category with given ID, you can pass default value directly to Select component if you already have it.
xenial-black
xenial-blackā€¢5mo ago
Thanks, that doesn't work unfortunately.
xenial-black
xenial-blackā€¢5mo ago
To make it clear, this is what I want to achieve: https://stackblitz.com/edit/react-g3vgzg?file=src%2FApp.js But instead of the Phone No. I want to able to use a Select component like the one I used in my reproduction.
Dipesh Bajgain
StackBlitz
antd multi-level nested form - StackBlitz
A create-react-app project based on react and react-dom.