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
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.