Atlaskit forms + asyncselect, form data and default values

Good evening,
I’m going to start this out with saying I’m a bit of a newbie here, I’ve done some projects in Vue with Quasar.js but not much in react. I’ve been able to complete a few things so far but I’m getting frustrated and would love some guidance.

I’ve got a CustomUI app using bridge with an invoke to list jira projects, that populates an asyncselect component inside of a form.
I have a save button that would ideally save the selections through the storage api, and when reloaded, those options are pulled back from the storage api and loaded into the asyncselect component.

The problem I’m running into relates to being able to capture form data, or load the previously selected values as the selected options in the asyncselect component. I can’t do both.

I’m using the value prop to set the options that are selected and that works fine – but when I submit the form, the data object is empty. It all depends on where my fieldProp spread is set and I’m confused as to why some posts I’ve read tell me to use the spread at the end of my prop list, while others use it at the beginning (from what I’ve read, some of the options are being overwritten by default prop spread). Am I missing some default values that are provided with the spread before/after the rest of the data?

My section with the form:

<Form onSubmit={(data) => submitHandler(data)}>
   {({ formProps }) => (
          <form {...formProps}>
              <Field
                    label="Select projects"
                    name="ProjectSelection">
                        {({ fieldProps: { ...rest } }) => (
                             <Fragment>
                                 <AsyncSelect
                                       {...rest} // When here, I can load my saved options from from the storage api, its loading an array of values, similar to [{"name":"Test","key":"TEST"}] 
                                       // the problem here is that the submit data is blank
                                       getOptionLabel={option => option.key + ' - ' + option.name}
                                       getOptionValue={option => option.key}
                                       defaultOptions
                                       isClearable
                                       isMulti
                                       loadOptions={loadOptions}
                                       onChange={option => onProjectChange(option)}
                                       value={projectSelected}
                                       //  I can put the ...rest spread here, data from the form is fine, but I can't preset the selected options 
                                 />
                              </Fragment>
                       )}
               </Field>
            <FormFooter>
                 <ButtonGroup label="Project submit options">
                        <Button type="submit" appearance="primary">Save</Button>
                 </ButtonGroup>
            </FormFooter>
        </form>
      )}
 </Form>

The basic structure

static/src/xxx

App.js
  <SearchProjectDropdown />

Helpers.js
  requestJira api call - v3/project/search
  helper functions - parse api response

searchProject.jsx
  state variables
  loadOptions, submitHandler
  invoke(s) for storage api
  the form above
  exports SearchProjectDropdown

src/
  index.js
    resolver.defines for get/store using storage api

First, it’s really unclear why you are using a destructuring assignment on fieldProps. It looks like you are renaming “fieldProps” to “rest”? Code style is a matter of taste but I would argue that this makes your code harder to understand because it adds a level of misdirection to what you’re doing.

Second, if you want the formData object to be populated, you will have to either not override onChange in the AsyncSelect or call fieldProps.onChange yourself. You can do this a number of ways. For example, pass in fieldProps.onChange to your onProjectChange and call it after you’ve done whatever that function is currently doing.

Thank you for the reply Ryan, I think the destructuring assignment was a result of frustration and attempts to mimic what I’ve seen others set up.

I’m able to see form data and also fill in default selected values based on what’s being pulled from an invoke. The problem is that I can’t submit the form with the default selected values I’ve set. If I remove one of the default selected options and add it back, the form data looks good. Do I need to trigger the onChange somehow when loading the defaults so the form/react can see them?

Here’s the full code, again, apologies for the newbie mistakes and thank you for the help!

import React, { useState, useEffect, Fragment } from 'react';
import { AsyncSelect } from '@atlaskit/select';
import { getProjects } from "./helpers.js";
import Form, { Field, FormFooter } from '@atlaskit/form';
import Button from '@atlaskit/button/new';
import ButtonGroup from '@atlaskit/button/button-group';
import { invoke } from '@forge/bridge';

const SearchProjectDropdown = () => {
    // getProjects does a requestJira(`/rest/api/3/project/search`) call below, gets the list of projects available
    const [projectData] = useState(() => getProjects());
    const [projectSelected, setProjectSelected] = useState();
    // loads projects into the async
    const loadOptions = (inputValue) =>
        new Promise(resolve => {
            setTimeout(() => {
                resolve(projectData)
                console.log('Projects retrieved')
            }, 1000);
        }
        );
    const submitHandler = (data) => {
        // form data 
        console.log(JSON.stringify(data))
        setStoredProjects(data['ProjectSelection'])
    };
    function setStoredProjects(data) {
        // send the selected projects from async to storage api
        invoke('setStoredProjects', data).then((response) => {
            console.log('Response from store: ' + JSON.stringify(response))
        });
    }
    function getStored() {
        // just a utility func to show the state on the main page for debug
        return JSON.stringify({ projectSelected })
    }
    const onProjectChange = (selectedOption, onChange) => {
        // set the selected projects to state
        setProjectSelected(selectedOption)
        console.log('Onchange ' + JSON.stringify(selectedOption));
        onChange(selectedOption)
        console.log(onChange)
    }
    useEffect(() => {
        console.log('Starting')
        // grab stored projects
        invoke('getStoredProjects').then((projects) => {
            console.log('before: ' + JSON.stringify(projects.storageData));
            if (projects.storageData.length !== undefined) {
                // this returns [{"name":"PHD","key":"PHD"},{"name":"Test","key":"TEST"}]
                setProjectSelected(projects.storageData);
            }
            else {
                console.log('Empty')
            }
        });
    }, []);

    return projectData.length !== 0
        ?
        <Fragment>
            {getStored()}
            <div
                style={{
                    display: 'flex',
                    width: '400px',
                    maxWidth: '100%',
                    margin: '0 auto',
                    flexDirection: 'column',
                }}
            >

                <Form
                    onSubmit={(data) => submitHandler(data)}
                >
                    {({ formProps }) => (
                        <form {...formProps}>
                            <Field
                                label="Select projects"
                                name="ProjectSelection">
                                {({ fieldProps }) => (
                                    <Fragment>
                                        <AsyncSelect
                                            {...fieldProps}
                                            name="test"
                                            id="test"
                                            getOptionLabel={option => option.key + ' - ' + option.name}
                                            getOptionValue={option => option.key}
                                            defaultOptions
                                            isClearable
                                            isMulti
                                            loadOptions={loadOptions}
                                            value={projectSelected}
                                            onChange={(option) => onProjectChange(option, fieldProps.onChange)}
                                        />
                                    </Fragment>
                                )}
                            </Field>
                            <FormFooter>
                                <ButtonGroup label="Project submit options">
                                    <Button type="submit" appearance="primary">Save</Button>
                                </ButtonGroup>
                            </FormFooter>
                        </form>
                    )}
                </Form>
            </div>
        </Fragment>
        : <Text content="No data available" />
}

export default SearchProjectDropdown

Try setting defaultValue instead of value.

1 Like

Ryan,

Was able to get this working eventually – I was setting defaultValue in the asyncselect the entire time instead of setting it in the field element one level up. Also removed the onChange handler as I realized it wasn’t necessary, the react and the form handles it just fine.

Thank you again for your help!

<Field
        label="Select projects"
        name="ProjectSelection"
        defaultValue={storedOptions} // <-- invoke call that returns the array of saved projects
>
        {({ fieldProps }) => (
           <Fragment>
             <AsyncSelect
                {...fieldProps}
                name="test"
                id="test"
                getOptionLabel={option => option.key + ' - ' + option.name}
                getOptionValue={option => option.key}
                defaultOptions
                isClearable
                isMulti
                loadOptions={loadOptions}
1 Like