react-jsonschema-form
react-jsonschema-form copied to clipboard
Asynchronously fetch options (or dynamic select options in general)
I couldn't find an issue for this or anything in the docs, but is there any way to have a field asynchronously fetch the options for a select dropdown?
I'm sure actually making http requests would be outside the scope of this library but is there any way to do something along the lines of giving a field a promise that resolves to an array of options? Or even synchronously passing a dynamic list of options that are not known when creating the schema?
I don't think that is possible to do right now.
You can already change the form or schema in real time, to do that, just update the schema/uiSchema and rerender the component, all of the form values will be kept
Has there been any progress on this?
Im also interested to know if there has any been progress in this area?
select drown options loading from an external source is pretty common for any UI application, is this support available yet as this issue was created long back?
I am also interested. Now, I have to explore other libraries.
This hasn't been built into the core library at the moment (though would welcome a PR), but you can do this with a custom field. Here's an example:
https://github.com/epicfaace/CFF/blob/9add15f2ce49480c10ecae508ddb920f6e64b7a2/scripts/src/form/form_widgets/AutoPopulateField.tsx
http://docs.chinmayamission.com/cff/uischema/fields/auto-populate/
This hasn't been built into the core library at the moment (though would welcome a PR), but you can do this with a custom field. Here's an example:
https://github.com/epicfaace/CFF/blob/9add15f2ce49480c10ecae508ddb920f6e64b7a2/scripts/src/form/form_widgets/AutoPopulateField.tsx
http://docs.chinmayamission.com/cff/uischema/fields/auto-populate/
Anyone has a sandbox example of this? :cat:
This hasn't been built into the core library at the moment (though would welcome a PR), but you can do this with a custom field. Here's an example:
https://github.com/epicfaace/CFF/blob/9add15f2ce49480c10ecae508ddb920f6e64b7a2/scripts/src/form/form_widgets/AutoPopulateField.tsx
http://docs.chinmayamission.com/cff/uischema/fields/auto-populate/
@epicfaace It seems like there's a heap of functionality in the Chinmayamission/CFF repo that would be really really useful in react-jsonschema-form core. Is there a good reason why you haven't pulled it in given you're involved in both projects?
It takes time to do that. Feel free to do it if you'd like though.
import { Select } from "antd";
import axios from "axios";
import { WidgetProps } from "@rjsf/utils";
import { useId } from "@mantine/hooks";
import { queryParams } from "../../../api";
import { useEffectAsync } from "../../../common/utils";
interface SelectOptions {
value: string;
label: string;
}
const { Option } = Select;
const AsyncSelectWidget: React.FC<
WidgetProps & {
uiSchema: {
"ui:options": {
api: (queryParams?: queryParams | undefined) => string;
searchKey: string;
labelKey: string;
valueKey: string;
};
};
}
> = (props) => {
const handleChange = (selectedValue: any) => {
props.onChange(selectedValue);
};
const { api, searchKey, labelKey, valueKey } = props.uiSchema["ui:options"];
const [loading, setLoading] = useState(false);
const [options, setOptions] = useState<SelectOptions[]>([]);
const fetchData = async (search: { [key: string]: any }) => {
try {
setLoading(true);
const response = await axios.post(api({ page: 1, page_size: 5 }), search);
const data: SelectOptions[] = response.data.results.map((item: any) => ({
label: item[labelKey],
value: item[valueKey],
}));
setLoading(false);
return data;
} catch (error) {
setLoading(false);
console.error("Error fetching data:", error);
return [];
}
};
const handleSearch = async (searchTerm: string) => {
const data = await fetchData({
[labelKey]: {
$regex: searchTerm,
$options: "i",
},
});
setOptions(data);
};
useEffectAsync(async () => {
const data = await fetchData({});
setOptions(data);
}, []);
return (
<div style={{ width: "100%" }}>
<div>
{props.schema.title}
{props.schema.required ? "*" : null}
</div>
<Select
loading={loading}
id={props.id}
value={props.value}
disabled={props.disabled || props.readonly}
allowClear={!props.required}
onChange={handleChange}
onSearch={handleSearch}
showSearch
optionFilterProp="children" // Enables search by option label
style={{ width: "100%" }}
defaultValue={props?.uiSchema?.["ui:options"]?.defaultValue}
>
{options?.map((option: SelectOptions, index) => (
<Option key={index.toString()} value={option.value}>
{option.label}
</Option>
))}
</Select>
</div>
);
};
export { AsyncSelectWidget };
``` working on this, will update the whole widget once done, i use antd for the select, you can use whatever you want,
import React, { useState, useEffect, useRef } from "react";
import { Select } from "antd";
import axios from "axios";
import { WidgetProps } from "@rjsf/utils";
import { queryParams } from "../../../api";
import { useEffectAsync } from "../../../common/utils";
interface SelectOptions {
value: string;
label: string;
}
const { Option } = Select;
const AsyncSelectWidget: React.FC<
WidgetProps & {
uiSchema: {
"ui:options": {
api: (queryParams?: queryParams | undefined) => string;
searchKey: string;
labelKey: string;
valueKey: string;
};
};
}
> = (props) => {
const handleChange = (selectedValue: any) => {
props.onChange(selectedValue);
};
const { api, searchKey, labelKey, valueKey } = props.uiSchema["ui:options"];
const [loading, setLoading] = useState(false);
const [options, setOptions] = useState<SelectOptions[]>([]);
const [page, setPage] = useState(1);
const [total, setTotal] = useState(0);
const scrollRef = useRef<HTMLDivElement | null>(null);
const fetchData = async (
search: { [key: string]: any },
nextPage: number
) => {
try {
setLoading(true);
const response = await axios.post(
api({ page: nextPage, page_size: 3 }),
search
);
const data: SelectOptions[] = response.data.results.map((item: any) => ({
label: item[labelKey],
value: item[valueKey],
}));
setLoading(false);
setTotal(response.data.total);
return data;
} catch (error) {
setLoading(false);
console.error("Error fetching data:", error);
return [];
}
};
const handleSearch = async (searchTerm: string) => {
const data = await fetchData(
{
[labelKey]: {
$regex: searchTerm,
$options: "i",
},
},
1
);
setOptions(data);
setPage(1);
};
const handleScroll = async () => {
const scrollElement = scrollRef.current;
if (scrollElement) {
const { scrollTop, clientHeight, scrollHeight } = scrollElement;
if (scrollTop + clientHeight === scrollHeight && options.length < total) {
const nextPage = page + 1;
const newData = await fetchData({}, nextPage);
setOptions((prevOptions) => [...prevOptions, ...newData]);
setPage(nextPage);
}
}
};
useEffectAsync(async () => {
// console.log(`props value, for select`, props.formData);
let default_data: SelectOptions[] = [];
if (props.formData) {
const data = await fetchData(
{
[searchKey]: props.formData,
},
1
);
default_data = data;
}
const data = await fetchData({}, 1);
setOptions([...data, ...default_data]);
setPage(1);
}, []);
return (
<div style={{ width: "100%" }}>
<div>
{props.schema.title}
{props.schema.required ? "*" : null}
</div>
<Select
loading={loading}
id={props.id}
value={props.value}
disabled={props.disabled || props.readonly}
allowClear={!props.required}
onChange={handleChange}
onSearch={handleSearch}
showSearch
optionFilterProp="children" // Enables search by option label
style={{ width: "100%" }}
defaultValue={props.formData}
onPopupScroll={handleScroll}
ref={scrollRef}
>
{options?.map((option: SelectOptions, index) => (
<Option key={index.toString()} value={option.value}>
{option.label}
</Option>
))}
</Select>
</div>
);
};
export { AsyncSelectWidget };
This is the final code that i am gonna use in my lib, working like charm.
const uiSchema: UiSchema = {
category_id: {
"ui:field": "AsyncSelectWidget",
"ui:options": {
api: Api.CategoriesList,
searchKey: "category_id",
labelKey: "name",
valueKey: "category_id",
},
},
}
This is how am using it
// Function to focus the select element const openSelect = () => { if (selectRef.current) { selectRef.current.showPicker(); } };