如何动态创建React Async Select?能否动态传数据源并流式消费数据?
Great question! Let's break this down step by step to get your dynamic AsyncSelect components working with dynamic URLs and streaming data.
1. Fixing Dynamic Component Creation
First, in your initial code, you forgot to return the JSX from the map function—this is why your components aren't rendering properly. Here's the corrected version:
let fields = this.state.fields.map((field, i) => { // Add return (or use implicit return by removing curly braces) return ( <div key={i} className={"field-wrapper"}> <AsyncSelect cacheOptions loadOptions={(inputValue, callback) => this.loadOptions(field.source, inputValue, callback)} placeholder={field.label} onInputChange={this.handleInputChange} /> </div> ); });
2. Passing Dynamic URLs to loadOptions
Your approach to using field.source directly in loadOptions is spot-on, but we can improve it with better error handling, request cancellation (to avoid race conditions), and cleaner code.
First, add an instance variable to track abort controllers (to cancel old requests when the input changes):
abortControllers = new Map();
Then create a reusable method to load options from the dynamic URL:
loadOptions = async (sourceUrl, inputValue, callback) => { // Cancel any ongoing request for this field const existingController = this.abortControllers.get(sourceUrl); if (existingController) { existingController.abort(); } const controller = new AbortController(); this.abortControllers.set(sourceUrl, controller); try { // Append the input value as a query parameter (adjust based on your API's requirements) const url = new URL(sourceUrl); url.searchParams.set('query', inputValue); const response = await fetch(url.toString(), { signal: controller.signal }); if (!response.ok) { throw new Error(`Failed to fetch options: ${response.status}`); } // Assuming your API returns an array of { label: string, value: any } objects const options = await response.json(); callback(null, { options }); } catch (error) { // Ignore abort errors (they're expected when the user types quickly) if (error.name !== 'AbortError') { callback(error); } } finally { this.abortControllers.delete(sourceUrl); } };
This method ensures that only the latest request for each field is processed, preventing stale data from appearing when the user types quickly.
3. Streaming Data Consumption
If your API returns data in a streaming format (like chunked JSON or Server-Sent Events), we need to adapt the loadOptions method to process data incrementally and update the select options as chunks arrive.
Here's how to handle chunked streaming with fetch:
loadStreamingOptions = async (sourceUrl, inputValue, callback) => { const existingController = this.abortControllers.get(sourceUrl); if (existingController) { existingController.abort(); } const controller = new AbortController(); this.abortControllers.set(sourceUrl, controller); try { const url = new URL(sourceUrl); url.searchParams.set('query', inputValue); const response = await fetch(url.toString(), { signal: controller.signal }); if (!response.ok) { throw new Error(`Streaming request failed: ${response.status}`); } const reader = response.body.getReader(); const decoder = new TextDecoder(); let accumulatedOptions = []; // Process each chunk as it arrives while (true) { const { done, value } = await reader.read(); if (done) break; // Parse the chunk (adjust this based on your API's streaming format) // For example, if each chunk is a JSON array of options: const chunkOptions = JSON.parse(decoder.decode(value)); accumulatedOptions = [...accumulatedOptions, ...chunkOptions]; // Update the AsyncSelect with the accumulated options callback(null, { options: accumulatedOptions }); } } catch (error) { if (error.name !== 'AbortError') { callback(error); } } finally { this.abortControllers.delete(sourceUrl); } };
Important Notes for Streaming:
- API Format: Your API must send data in a format that can be parsed incrementally (e.g., newline-separated JSON objects or chunked arrays). If using Server-Sent Events (SSE), replace the
fetchlogic withEventSource. - Option Format: Ensure each chunk of data matches AsyncSelect's expected format (array of
{ label, value }objects). - Cleanup: Don't forget to cancel ongoing streams when the component unmounts:
componentWillUnmount() { this.abortControllers.forEach(controller => controller.abort()); }
Final Usage
Now you can use the streaming method in your AsyncSelect component:
<AsyncSelect loadOptions={(inputValue, callback) => this.loadStreamingOptions(field.source, inputValue, callback)} isDisabled={disabled} defaultOptions placeholder={field.label} onInputChange={this.handleSelectChange} />
内容的提问来源于stack exchange,提问作者fefe

