like-gold
like-gold14mo ago

I want to combine option labels in the useSelect hook of refine antd.

This only shows firstName as label as expected.
const { selectProps: userSelectProps } = useSelect({
resource: "users",
defaultValue: ordersData?.userId,
optionLabel: "firstName",
optionValue: "id",
});
const { selectProps: userSelectProps } = useSelect({
resource: "users",
defaultValue: ordersData?.userId,
optionLabel: "firstName",
optionValue: "id",
});
I want to show both firstName and lastName. Is there a way to acheive this? I wanted something like:
const { selectProps: userSelectProps } = useSelect({
resource: "users",
defaultValue: ordersData?.userId,
optionLabel: (option) => `${option.firstName} ${option.lastName}`,
optionValue: "id",
});
const { selectProps: userSelectProps } = useSelect({
resource: "users",
defaultValue: ordersData?.userId,
optionLabel: (option) => `${option.firstName} ${option.lastName}`,
optionValue: "id",
});
27 Replies
kapa.ai
kapa.ai14mo ago
Hi @srest.nishant. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave: ---------------------- Yes, you can achieve this by using the optionLabel property as a function. Here's how you can modify your useSelect hook to display both firstName and lastName as the label:
const { selectProps: userSelectProps } = useSelect({
resource: "users",
defaultValue: ordersData?.userId,
optionLabel: (option) => `${option.firstName} ${option.lastName}`,
optionValue: "id",
});
const { selectProps: userSelectProps } = useSelect({
resource: "users",
defaultValue: ordersData?.userId,
optionLabel: (option) => `${option.firstName} ${option.lastName}`,
optionValue: "id",
});
This will concatenate the firstName and lastName properties of each option and display them as the label in the select component. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai14mo ago
<@717251086196539412> kapa.ai is still learning and improving, please let us how it did by reacting below
like-gold
like-gold14mo ago
@kapa.ai It doesn't work.
kapa.ai
kapa.ai14mo ago
Let me check the knowledge base for you. One moment! :books:
kapa.ai
kapa.ai14mo ago
---------------------- I apologize for the confusion earlier. It seems that I made an error in my response. Based on the information provided in the extracts, the optionLabel property only accepts a string, not a function. Unfortunately, the extracts do not provide a direct solution for displaying both firstName and lastName as the label using the useSelect hook. However, you can refer to the refine useSelect documentation (https://refine.dev/docs/api-reference/antd/hooks/field/useSelect/) for more information on its usage and properties. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
useSelect | refine
useSelect hook allows you to manage Ant Design `` component when records in a resource needs to be used as select options.
kapa.ai
kapa.ai14mo ago
<@717251086196539412> kapa.ai is still learning and improving, please let us how it did by reacting below
like-gold
like-gold14mo ago
Unfortunately, useSelect can't do this right now, but you can map the values as you want before passing the options property to the '<Select>' component.
Omer
Omer14mo ago
useSelect | refine
useSelect hook allows you to manage Ant Design `` component when records in a resource needs to be used as select options.
like-gold
like-gold14mo ago
Thank you, I used similar approach!
Omer
Omer14mo ago
I'm happy to hear that 🎯
ambitious-aqua
ambitious-aqua14mo ago
@Omer isn't what Nishant posted exactly what you would want to do? Is there a feature-request for this? Otherwise I'd be happy to open one 🙂
ambitious-aqua
ambitious-aqua14mo ago
So I built this very ugly implementation on top of what Nishant did here:
const absenceData = queryResult?.data?.data;
const { queryResult: personQueryResult, selectProps: personSelectProps } = RefineAntd.useSelect({
resource: 'persons',
defaultValue: absenceData?.person_id,
dataProviderName: 'damon',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: TS2322: Type 'string' is not assignable to type 'undefined'.
optionLabel: getAccessorFor('person'),
});
personSelectProps.options =
personQueryResult.data?.data.map((item) => ({
label: getDisplayFor('person')(item),
value: (item as any).id,
})) || [];
const absenceData = queryResult?.data?.data;
const { queryResult: personQueryResult, selectProps: personSelectProps } = RefineAntd.useSelect({
resource: 'persons',
defaultValue: absenceData?.person_id,
dataProviderName: 'damon',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: TS2322: Type 'string' is not assignable to type 'undefined'.
optionLabel: getAccessorFor('person'),
});
personSelectProps.options =
personQueryResult.data?.data.map((item) => ({
label: getDisplayFor('person')(item),
value: (item as any).id,
})) || [];
As I need to access properties generically I use functions to get the correct properties out of a register for all of my models. Here I run into the problem that if I access a "Edit"-Page and the element that is received is not within the first 20 results, I don't see my label for the Select but just the ID (see screenshot).
No description
ambitious-aqua
ambitious-aqua14mo ago
There's a request running that filters for the correct ID, but the result is never properly used, as I have probably overwritten something that you do inside of Refine. The type on UseSelectProps of optionLabel?: keyof TQueryFnData extends string ? keyof TQueryFnData : never; is very restricting and casts the TQueryFnData if it's not defined btw. I don't know if that's your intention here. I don't know if somebody from @core could take a look here, I'd be very happy 🙂 @alicanerdurmaz do you maybe have a spare minute for this?
genetic-orange
genetic-orange14mo ago
i trying to understand
ambitious-aqua
ambitious-aqua14mo ago
I'm trying to reproduce using codesandbox as well.
genetic-orange
genetic-orange14mo ago
it would be great
ambitious-aqua
ambitious-aqua14mo ago
Mh I cannot reproduce it here (https://codesandbox.io/s/inspiring-rain-p5l47h?file=/src/pages/posts/edit.tsx) because the queryResult of useSelect<ICategory> (line 19) always returns all categories. I have a related table of around 1 million entities which I cannot preload. My core problem is that in this case where I am defining custom options like this:
personSelectProps.options =
personQueryResult.data?.data.map((item) => ({
label: getDisplayFor('person')(item),
value: item.id,
})) || [];
personSelectProps.options =
personQueryResult.data?.data.map((item) => ({
label: getDisplayFor('person')(item),
value: item.id,
})) || [];
personQueryResult never gets updated / requeried with the value I put into defaultValue Thus I am left with a number that's being displayed, as the item to be rendered cannot be found in personQueryResult.data?.data I got it 🤦‍♂️ useSelect also returns the defaultValueQueryResult which I can use to display the result of the defaultValue query. I feel like I can't be the only one trying to display multiple attributes of a model inside of a select box 😄 like title AND id AND some other things.
genetic-orange
genetic-orange14mo ago
is this solved ? 😄
ambitious-aqua
ambitious-aqua14mo ago
Not really unfortunately. I found a way to access the defaultValueQueryResult but now I'm stuck with it's value 😢 Is there a way to combine two QueryObserverResult<GetManyResponse<Person>>s ? I'm now reading off of defaultValueQueryResult which makes my select box unusable in terms of searching of updating any value. What I want is just to show ${item.title} ${item.id} for options inside of a select. What my workaround looks like by now:
const {
defaultValueQueryResult: personDefaultValueQueryResult,
queryResult: personQueryResult,
selectProps: personSelectProps,
} = RefineAntd.useSelect<Person>({
resource: 'persons',
defaultValue: absenceData?.person_id,
dataProviderName: 'damon',
optionLabel: "fullname",
onSearch: (value) => [
{
field: 'fullname',
operator: 'contains',
value,
},
],
});

personSelectProps.options =
personDefaultValueQueryResult.data?.data.map((item) => ({
label: `${item.fullname} ${item.id}`,
value: item.id,
})) || [];
const {
defaultValueQueryResult: personDefaultValueQueryResult,
queryResult: personQueryResult,
selectProps: personSelectProps,
} = RefineAntd.useSelect<Person>({
resource: 'persons',
defaultValue: absenceData?.person_id,
dataProviderName: 'damon',
optionLabel: "fullname",
onSearch: (value) => [
{
field: 'fullname',
operator: 'contains',
value,
},
],
});

personSelectProps.options =
personDefaultValueQueryResult.data?.data.map((item) => ({
label: `${item.fullname} ${item.id}`,
value: item.id,
})) || [];
genetic-orange
genetic-orange14mo ago
you can merge results after both useSelect's isLoading is false 🤔 instead of this
personSelectProps.options =
personDefaultValueQueryResult.data?.data.map((item) => ({
label: `${item.fullname} ${item.id}`,
value: item.id,
})) || [];
personSelectProps.options =
personDefaultValueQueryResult.data?.data.map((item) => ({
label: `${item.fullname} ${item.id}`,
value: item.id,
})) || [];
can you try this please. you can use parsedPersonSelectProps after that. when real value updated, this will be change too
const parsedPersonSelectProps = useMemo(() => {
return (
personDefaultValueQueryResult.data?.data.map((item) => ({
label: `${item.fullname} ${item.id}`,
value: item.id,
})) || []
);
}, [personDefaultValueQueryResult.data]);
const parsedPersonSelectProps = useMemo(() => {
return (
personDefaultValueQueryResult.data?.data.map((item) => ({
label: `${item.fullname} ${item.id}`,
value: item.id,
})) || []
);
}, [personDefaultValueQueryResult.data]);
ambitious-aqua
ambitious-aqua14mo ago
Thanks for jumping in, you're not using personQueryResult here, so this will probably yield the same result, right?
genetic-orange
genetic-orange14mo ago
you should change to personQueryResult if you want to use that data. my purpose was insteading mutate to real value, you need to create new array and you should use on render i mean you shouldn't manipulate personSelectProps.options this or this personQueryResult instead of manipulating, you should create new array according to your needs
ambitious-aqua
ambitious-aqua14mo ago
Ah okay, in any case I'm stuck with either personDefaultValueQueryResult or personQueryResult since I cannot render both. If I use personQueryResult the defaultValue is not respected. Here's the finished "solution" if you can call it that 😄
const {
defaultValueQueryResult: personDefaultValueQueryResult,
queryResult: personQueryResult,
selectProps: personSelectProps,
} = RefineAntd.useSelect<Person>({
resource: 'persons',
defaultValue: absenceData?.person_id,
dataProviderName: 'damon',
optionLabel: 'fullname',
});
const personSelectedOption =
personDefaultValueQueryResult.data?.data.map((item) => ({
label: `${item.fullname} (${item.id})`,
value: item.id,
})) || [];
const personOptions = personQueryResult.data?.data.map((item) => ({
label: `${item.fullname} (${item.id})`,
value: item.id,
}));
const mergedOptions = useMemo(
() => uniqBy([...personSelectedOption, ...(personOptions || [])], 'value'),
[personSelectedOption, personOptions],
);

...

return (
...
<Antd.Select {...personSelectProps} options={mergedOptions} />
...
)
const {
defaultValueQueryResult: personDefaultValueQueryResult,
queryResult: personQueryResult,
selectProps: personSelectProps,
} = RefineAntd.useSelect<Person>({
resource: 'persons',
defaultValue: absenceData?.person_id,
dataProviderName: 'damon',
optionLabel: 'fullname',
});
const personSelectedOption =
personDefaultValueQueryResult.data?.data.map((item) => ({
label: `${item.fullname} (${item.id})`,
value: item.id,
})) || [];
const personOptions = personQueryResult.data?.data.map((item) => ({
label: `${item.fullname} (${item.id})`,
value: item.id,
}));
const mergedOptions = useMemo(
() => uniqBy([...personSelectedOption, ...(personOptions || [])], 'value'),
[personSelectedOption, personOptions],
);

...

return (
...
<Antd.Select {...personSelectProps} options={mergedOptions} />
...
)
genetic-orange
genetic-orange14mo ago
yes I was thinking this :/
ambitious-aqua
ambitious-aqua14mo ago
I have to do this for every Select I want to adjust :D, but it's okay since we're generating this. Do you see a way to include this into Refine's useSelect? I think defining a custom way of displaying labels on a select box is something very common and would lead to a much cleaner solution in the end. In any case, thanks a lot for your help, that really was the last missing piece to my solution.
genetic-orange
genetic-orange14mo ago
hmm. It's hard to modify useSelect for this. maybe you can create custom hook like useMultipleIseSelect and move this logic to inside this :/
sensitive-blue
sensitive-blue12mo ago
that looks like a lot to just put a few fields together, why not just use a queryOptions right inside the select props? (sorry I am late to this lol)
const { selectProps: ownerSelectProps } = useSelect({
resource: "customer-profiles",

optionLabel: "fullName",
optionValue: "id",
metaData: {
populate: {
mailingAddress: {
populate: "*"
},
},
},
queryOptions: {
select: (data) => {
return {
data: data?.data?.map((item: any) => ({
...item,

fullName: `${item.firstName} ${item.lastName} - ${item.mailingAddress.street}`
})),
total: data.total
};
}
},
});
const { selectProps: ownerSelectProps } = useSelect({
resource: "customer-profiles",

optionLabel: "fullName",
optionValue: "id",
metaData: {
populate: {
mailingAddress: {
populate: "*"
},
},
},
queryOptions: {
select: (data) => {
return {
data: data?.data?.map((item: any) => ({
...item,

fullName: `${item.firstName} ${item.lastName} - ${item.mailingAddress.street}`
})),
total: data.total
};
}
},
});
I am using strapi and my mailing address is a relationship so I have to populate it before I can call it.