|
| 1 | +--- |
| 2 | +title: "useSupportCreateSuggestion" |
| 3 | +storybook_path: ra-core-controller-input-usesupportcreatesuggestion--use-support-create-suggestion |
| 4 | +--- |
| 5 | + |
| 6 | +This hook provides support for creating new suggestions in choice-based inputs like autocomplete components. It allows users to create new options when the desired choice doesn't exist in the available options, either through an `onCreate` callback or by rendering a creation form. |
| 7 | + |
| 8 | +## Usage |
| 9 | + |
| 10 | +```jsx |
| 11 | +import { useSupportCreateSuggestion } from 'ra-core'; |
| 12 | + |
| 13 | +const { |
| 14 | + createId, |
| 15 | + createHintId, |
| 16 | + getCreateItem, |
| 17 | + handleChange, |
| 18 | + createElement, |
| 19 | + getOptionDisabled, |
| 20 | +} = useSupportCreateSuggestion({ |
| 21 | + filter: searchValue, |
| 22 | + handleChange: (eventOrValue) => { |
| 23 | + // update your input value |
| 24 | + setValue(eventOrValue?.target?.value ?? eventOrValue?.id); |
| 25 | + }, |
| 26 | + onCreate: async (filter) => { |
| 27 | + // create a new option and return it |
| 28 | + return await createNewOption(filter); |
| 29 | + }, |
| 30 | +}); |
| 31 | +``` |
| 32 | +
|
| 33 | +The hook accepts a configuration object and returns utilities for handling suggestion creation in choice inputs. |
| 34 | +
|
| 35 | +## Parameters |
| 36 | +
|
| 37 | +| Prop | Required | Type | Default | Description | |
| 38 | +| ----------------- | -------- | ------------------------------------------- | -------------------- | ----------------------------------------------------------------------------- | |
| 39 | +| `create` | No | `ReactElement` | - | React element rendered when users choose to create a new choice | |
| 40 | +| `createLabel` | No | `ReactNode` | `'ra.action.create'` | Label for the create choice item | |
| 41 | +| `createItemLabel` | No | `string \| ((filter: string) => ReactNode)` | - | Dynamic label that receives the filter value as parameter | |
| 42 | +| `createValue` | No | `string` | `'@@ra-create'` | Value for the create choice item | |
| 43 | +| `createHintValue` | No | `string` | `'@@ra-create-hint'` | Value for the disabled hint item | |
| 44 | +| `filter` | No | `string` | - | Current filter/search value entered by the user | |
| 45 | +| `handleChange` | Yes | `(value: any) => void` | - | Function to call when the input value changes | |
| 46 | +| `onCreate` | No | `(filter?: string) => any \| Promise<any>` | - | Function called when creating a new option (if `create` element not provided) | |
| 47 | +| `optionText` | No | `OptionText` | `'name'` | Property name for the option text | |
| 48 | +
|
| 49 | +## Return Value |
| 50 | +
|
| 51 | +The hook returns an object with: |
| 52 | +
|
| 53 | +- `createId`: The ID value for the create option |
| 54 | +- `createHintId`: The ID value for the create hint (disabled) option |
| 55 | +- `getCreateItem`: Function that returns the create option object |
| 56 | +- `handleChange`: Enhanced change handler that intercepts create actions |
| 57 | +- `createElement`: React element to render for creation form (null when not creating) |
| 58 | +- `getOptionDisabled`: Function to determine if an option should be disabled (i.e. if it's a hint) |
| 59 | +
|
| 60 | +## Example with onCreate Callback |
| 61 | +
|
| 62 | +```jsx |
| 63 | +import { useSupportCreateSuggestion } from 'ra-core'; |
| 64 | +import { useState } from 'react'; |
| 65 | + |
| 66 | +const AuthorInput = () => { |
| 67 | + const [value, setValue] = useState(''); |
| 68 | + const [filter, setFilter] = useState(''); |
| 69 | + |
| 70 | + const { |
| 71 | + getCreateItem, |
| 72 | + handleChange, |
| 73 | + getOptionDisabled, |
| 74 | + } = useSupportCreateSuggestion({ |
| 75 | + filter, |
| 76 | + handleChange: (eventOrValue) => { |
| 77 | + setValue(eventOrValue?.target?.value ?? eventOrValue?.id); |
| 78 | + }, |
| 79 | + onCreate: async (authorName) => { |
| 80 | + // Call your API to create a new author |
| 81 | + return await createNewAuthor(authorName); |
| 82 | + }, |
| 83 | + createItemLabel: 'Create author "%{item}"', |
| 84 | + }); |
| 85 | + |
| 86 | + const createItem = getCreateItem(filter); |
| 87 | + const options = [ |
| 88 | + ...existingAuthors, |
| 89 | + createItem, |
| 90 | + ]; |
| 91 | + |
| 92 | + return ( |
| 93 | + <div> |
| 94 | + <input |
| 95 | + type="text" |
| 96 | + placeholder="Search authors..." |
| 97 | + value={filter} |
| 98 | + onChange={(e) => setFilter(e.target.value)} |
| 99 | + /> |
| 100 | + <select value={value} onChange={handleChange}> |
| 101 | + {options.map(option => ( |
| 102 | + <option |
| 103 | + key={option.id} |
| 104 | + value={option.id} |
| 105 | + disabled={getOptionDisabled(option)} |
| 106 | + > |
| 107 | + {option.name} |
| 108 | + </option> |
| 109 | + ))} |
| 110 | + </select> |
| 111 | + </div> |
| 112 | + ); |
| 113 | +}; |
| 114 | +``` |
| 115 | +
|
| 116 | +## Example with Create Element |
| 117 | +
|
| 118 | +When you need more control over the creation process, you can provide a React element to be rendered for creating new options. |
| 119 | +
|
| 120 | +This form component can use `useCreateSuggestionContext` to access: |
| 121 | +
|
| 122 | +- `filter`: The search filter that triggered the creation |
| 123 | +- `onCancel`: Function to cancel the creation and hide the form |
| 124 | +- `onCreate`: Function to call when creation succeeds, passing the new item |
| 125 | +
|
| 126 | +```jsx |
| 127 | +import { |
| 128 | + useSupportCreateSuggestion, |
| 129 | + useCreateSuggestionContext, |
| 130 | + CreateBase |
| 131 | +} from 'ra-core'; |
| 132 | + |
| 133 | +const CreateAuthorForm = () => { |
| 134 | + const { filter, onCancel, onCreate } = useCreateSuggestionContext(); |
| 135 | + |
| 136 | + return ( |
| 137 | + <CreateBase |
| 138 | + resource="authors" |
| 139 | + redirect={false} |
| 140 | + mutationOptions={{ |
| 141 | + onSuccess: onCreate, |
| 142 | + }} |
| 143 | + > |
| 144 | + <SimpleForm defaultValues={{ name: filter }}> |
| 145 | + <TextInput source="name" /> |
| 146 | + <TextInput source="email" /> |
| 147 | + <button type="button" onClick={onCancel}> |
| 148 | + Cancel |
| 149 | + </button> |
| 150 | + </SimpleForm> |
| 151 | + </CreateBase> |
| 152 | + ); |
| 153 | +}; |
| 154 | + |
| 155 | +const AuthorInput = () => { |
| 156 | + const [value, setValue] = useState(''); |
| 157 | + const [filter, setFilter] = useState(''); |
| 158 | + |
| 159 | + const { |
| 160 | + getCreateItem, |
| 161 | + handleChange, |
| 162 | + createElement, |
| 163 | + getOptionDisabled, |
| 164 | + } = useSupportCreateSuggestion({ |
| 165 | + filter, |
| 166 | + handleChange: (eventOrValue) => { |
| 167 | + setValue(eventOrValue?.target?.value ?? eventOrValue?.id); |
| 168 | + }, |
| 169 | + create: <CreateAuthorForm />, |
| 170 | + createItemLabel: 'Create author "%{item}"', |
| 171 | + }); |
| 172 | + |
| 173 | + const createItem = getCreateItem(filter); |
| 174 | + const options = [ |
| 175 | + ...existingAuthors, |
| 176 | + createItem, |
| 177 | + ]; |
| 178 | + |
| 179 | + return ( |
| 180 | + <div> |
| 181 | + <input |
| 182 | + type="text" |
| 183 | + placeholder="Search authors..." |
| 184 | + value={filter} |
| 185 | + onChange={(e) => setFilter(e.target.value)} |
| 186 | + /> |
| 187 | + <select value={value} onChange={handleChange}> |
| 188 | + {options.map(option => ( |
| 189 | + <option |
| 190 | + key={option.id} |
| 191 | + value={option.id} |
| 192 | + disabled={getOptionDisabled(option)} |
| 193 | + > |
| 194 | + {option.name} |
| 195 | + </option> |
| 196 | + ))} |
| 197 | + </select> |
| 198 | + {createElement} |
| 199 | + </div> |
| 200 | + ); |
| 201 | +}; |
| 202 | +``` |
| 203 | +
|
| 204 | +## `create` |
| 205 | +
|
| 206 | +Provides a React element that will be rendered when users choose to create a new option. When this prop is provided, the hook will render the element instead of calling `onCreate`. The element should use `useCreateSuggestionContext` to access the filter value and callback functions. |
| 207 | +
|
| 208 | +```jsx |
| 209 | +const CreateForm = () => { |
| 210 | + const { filter, onCancel, onCreate } = useCreateSuggestionContext(); |
| 211 | + |
| 212 | + const handleSubmit = async (data) => { |
| 213 | + try { |
| 214 | + const newItem = await createItem(data); |
| 215 | + onCreate(newItem); // This will select the new item and close the form |
| 216 | + } catch (error) { |
| 217 | + // Handle error |
| 218 | + } |
| 219 | + }; |
| 220 | + |
| 221 | + return ( |
| 222 | + <form onSubmit={handleSubmit}> |
| 223 | + <input defaultValue={filter} name="name" /> |
| 224 | + <button type="submit">Create</button> |
| 225 | + <button type="button" onClick={onCancel}> |
| 226 | + Cancel |
| 227 | + </button> |
| 228 | + </form> |
| 229 | + ); |
| 230 | +}; |
| 231 | + |
| 232 | +useSupportCreateSuggestion({ |
| 233 | + create: <CreateForm /> |
| 234 | +}); |
| 235 | +``` |
| 236 | +
|
| 237 | +## `createLabel` |
| 238 | +
|
| 239 | +Sets the label for the create option. Can be a string, translation key, or any React node. |
| 240 | +
|
| 241 | +```jsx |
| 242 | +useSupportCreateSuggestion({ |
| 243 | + createLabel: 'Add new item' |
| 244 | +}); |
| 245 | +``` |
| 246 | +
|
| 247 | +## `createItemLabel` |
| 248 | +
|
| 249 | +Provides a dynamic label that receives the filter value as a parameter. When provided, this creates two different behaviors: |
| 250 | +- With no filter: Shows a disabled hint option (using the `createLabel` text) |
| 251 | +- With filter: Shows an active create option with the filter value (using `createItemLabel`) |
| 252 | +
|
| 253 | +This provides better UX by guiding users on how to create new options. |
| 254 | +
|
| 255 | +```jsx |
| 256 | +useSupportCreateSuggestion({ |
| 257 | + createItemLabel: 'Create category "%{item}"', |
| 258 | + // When filter is "Sports", shows: "Create category 'Sports'" |
| 259 | +}); |
| 260 | + |
| 261 | +// Or as a function: |
| 262 | +useSupportCreateSuggestion({ |
| 263 | + createItemLabel: (filter) => `Add "${filter}" as new category` |
| 264 | +}); |
| 265 | +``` |
| 266 | +
|
| 267 | +## `createValue` |
| 268 | +
|
| 269 | +Customizes the value used internally to identify the create option. This is useful if the default value conflicts with your data. |
| 270 | +
|
| 271 | +```jsx |
| 272 | +useSupportCreateSuggestion({ |
| 273 | + createValue: '@@my-create-id' |
| 274 | +}); |
| 275 | +``` |
| 276 | +
|
| 277 | +## `createHintValue` |
| 278 | +
|
| 279 | +Customizes the value used for the disabled hint option when `createItemLabel` is provided and no filter is set. |
| 280 | +
|
| 281 | +```jsx |
| 282 | +useSupportCreateSuggestion({ |
| 283 | + createHintValue: '@@my-hint-id' |
| 284 | +}); |
| 285 | +``` |
| 286 | +
|
| 287 | +## `filter` |
| 288 | +
|
| 289 | +The current search/filter value entered by the user. This value is used to populate the create option label and is passed to the `onCreate` callback or the create element context. |
| 290 | +
|
| 291 | +```jsx |
| 292 | +const [searchValue, setSearchValue] = useState(''); |
| 293 | + |
| 294 | +useSupportCreateSuggestion({ |
| 295 | + filter: searchValue |
| 296 | +}); |
| 297 | +``` |
| 298 | +
|
| 299 | +## `handleChange` |
| 300 | +
|
| 301 | +The function to call when the input value changes. The hook will intercept changes that match the create value and handle them specially, otherwise it will call this function with the original event or value. |
| 302 | +
|
| 303 | +```jsx |
| 304 | +useSupportCreateSuggestion({ |
| 305 | + handleChange: (eventOrValue) => { |
| 306 | + setValue(eventOrValue?.target?.value ?? eventOrValue?.id); |
| 307 | + } |
| 308 | +}); |
| 309 | +``` |
| 310 | +
|
| 311 | +## `onCreate` |
| 312 | +
|
| 313 | +A function called when creating a new option, if the `create` element is not provided. Should return the newly created item. |
| 314 | +
|
| 315 | +```jsx |
| 316 | +useSupportCreateSuggestion({ |
| 317 | + onCreate: async (filterValue) => { |
| 318 | + // create a new option and return it |
| 319 | + return await createNewOption(filterValue); |
| 320 | + } |
| 321 | +}); |
| 322 | +``` |
| 323 | +
|
| 324 | +## `optionText` |
| 325 | +
|
| 326 | +Specifies which property of the option objects contains the display text. If your options use a different property than `name`, specify it here. |
| 327 | +
|
| 328 | +```jsx |
| 329 | +useSupportCreateSuggestion({ |
| 330 | + optionText: 'title', // Uses 'title' instead of 'name' |
| 331 | +}); |
| 332 | + |
| 333 | +// Also accepts function for more complex scenarios |
| 334 | +useSupportCreateSuggestion({ |
| 335 | + optionText: (option) => `${option.firstName} ${option.lastName}` |
| 336 | +}); |
| 337 | +``` |
0 commit comments