other-emerald
other-emerald2y 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.ai2y 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.ai2y ago
<@717251086196539412> kapa.ai is still learning and improving, please let us how it did by reacting below
other-emerald
other-emeraldOP2y ago
@kapa.ai It doesn't work.
kapa.ai
kapa.ai2y ago
Let me check the knowledge base for you. One moment! :books:
kapa.ai
kapa.ai2y 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.ai2y ago
<@717251086196539412> kapa.ai is still learning and improving, please let us how it did by reacting below
fascinating-indigo
fascinating-indigo2y 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
Omer2y ago
useSelect | refine
useSelect hook allows you to manage Ant Design `` component when records in a resource needs to be used as select options.
other-emerald
other-emeraldOP2y ago
Thank you, I used similar approach!
Omer
Omer2y ago
I'm happy to hear that 🎯
foreign-sapphire
foreign-sapphire2y 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 🙂
foreign-sapphire
foreign-sapphire2y 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
foreign-sapphire
foreign-sapphire2y 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?
passive-yellow
passive-yellow2y ago
i trying to understand
foreign-sapphire
foreign-sapphire2y ago
I'm trying to reproduce using codesandbox as well.
passive-yellow
passive-yellow2y ago
it would be great
foreign-sapphire
foreign-sapphire2y 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.
passive-yellow
passive-yellow2y ago
is this solved ? 😄
foreign-sapphire
foreign-sapphire2y 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,
})) || [];
passive-yellow
passive-yellow2y 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]);
foreign-sapphire
foreign-sapphire2y ago
Thanks for jumping in, you're not using personQueryResult here, so this will probably yield the same result, right?
passive-yellow
passive-yellow2y 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
foreign-sapphire
foreign-sapphire2y 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} />
...
)
passive-yellow
passive-yellow2y ago
yes I was thinking this :/
foreign-sapphire
foreign-sapphire2y 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.
passive-yellow
passive-yellow2y 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 :/
fair-rose
fair-rose2y 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.

Did you find this page helpful?