UserPicker not working correctly

I cannot add and/or remove users from the UserPicker component after it’s saved. If I refresh the whole page, eventually, I can edit the list of users. Sometimes I cannot. I didn’t see any error on the browser console.
The steps to reproduce are:

  1. Create a page, add the macro and save the page
  2. Add some users and click the save button on the macro.
  3. Click on the “load” button in the macro.
  4. Try to add or remove users. You won’t be able to add or remove users. Eventually, the variable “users” changes because the values might change if you click on save and on the load button.
  5. If the page is reloaded, the component might work.

I’m using this component in production, inside a modal, and it’s not working at all.

Screen_Recording

Front end code

import React, { useEffect, useState } from 'react';
import ForgeReconciler, {
  Button, Stack, Box, SectionMessage,
  Heading, UserPicker
} from '@forge/react';
import { invoke } from '@forge/bridge';

const App = () => {
  const [users, setUsers] = useState(undefined)
  const [loaded, setLoaded] = useState(false)

  useEffect(() => {
    load()
  }, []);

  const load = () => {
    invoke("getUsers").then(response => {
      setUsers(response.users)
      setLoaded(true)
    })
  }

  const save = async () => {
    await invoke("saveUsers", { users })
    setLoaded(false)
  }

  if (!loaded) {
    return <Box>
      <SectionMessage>Data is not loaded...</SectionMessage>
      <Button onClick={ load }>Load</Button>
    </Box>
  }

  return (
    <Box >
      <Stack space="space.200">
        <Heading as="h3">Users</Heading>
        <UserPicker
          defaultValue={ users }
          id="users"
          isMulti
          onChange={ (options) =>
            setUsers(options.map(option => option.id))
          }
          label="Users">
        </UserPicker>
      </Stack>
      <Box>
        <Button onClick={ save }>Save</Button>
      </Box>
    </Box >
  )
}

ForgeReconciler.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

Resolver:

import Resolver from "@forge/resolver";
import { storage } from "@forge/api";

const resolver = new Resolver();

resolver.define("getUsers", async ({ context }) => {
  return storage.get("saved-user" + context.localId);
});

resolver.define("saveUsers", async ({ context, payload }) => {
  return storage.set("saved-user" + context.localId, payload);
});

export const handler = resolver.getDefinitions();

Has anyone faced a similar issue?
Thanks

Hello chicofaccin,

I’m currently working on a Jira app and I also have a lot of trouble with the user picker.

I’m using it inside a custom field edit view. When editing in the issue view using inline edit, it works somehow but the onChange method of the user picker is too late to update my components state before the submit of the CustomFieldEdit is called, so it’s only working when I store the value in a global variable. This happens only when isMulti is false, so as a single user picker.

On the issue create screen I can not use the component at all. When the issue is created the value of the user picker is somehow magically added to the payload of the issue creation as a custom field. But to store the custom field value you should normally use the view.submit API. Also the data format of the user picker is wrong for sure. On the other side when view.submit is called the user picker get’s updated somehow and results in showing undefined user symbols as selected value.

Maybe someone from Atlassian has an idea what’s wrong with this component.

Feel free to contact me if you want some code examples.

Kind regards,
Daniel

1 Like

Hi @DanielJohn , I think I figure out what was the issue. It looks like the UserPicker is messing up with the state. So I provided a key. This solve the issue in my case. In my real application, I have more than one UserPicker in the same component. Setting a unique key for each UserPicker solved the issue.

import React, { useEffect, useState } from 'react';
import ForgeReconciler, {
  Button, Stack, Box, SectionMessage,
  Heading, UserPicker
} from '@forge/react';
import { invoke } from '@forge/bridge';
import { v4 as uuidv4 } from 'uuid';

const App = () => {
  const [users, setUsers] = useState(undefined)
  const [loaded, setLoaded] = useState(false)
  const userPickerKey = uuidv4();

  useEffect(() => {
    load()
  }, []);

  const load = () => {
    invoke("getUsers").then(response => {
      setUsers(response.users)
      setLoaded(true)
    })
  }

  const save = async () => {
    await invoke("saveUsers", { users })
    setLoaded(false)
  }

  if (!loaded) {
    return <Box>
      <SectionMessage>Data is not loaded...</SectionMessage>
      <Button onClick={ load }>Load</Button>
    </Box>
  }

  return (
    <Box >
      <Stack space="space.200">
        <Heading as="h3">Users</Heading>
        <UserPicker
          key={ userPickerKey }
          defaultValue={ users }
          id="users"
          isMulti
          onChange={ (options) =>
            setUsers(options.map(option => option.id))
          }
          label="Users">
        </UserPicker>
      </Stack>
      <Box>
        <Button onClick={ save }>Save</Button>
      </Box>
    </Box >
  )
}

ForgeReconciler.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

Please let me know if this workaround would work for you.

Hi @chicofaccin ,

thanks for your response and the idea. I tried it out and it does not help in my case. In Jira I need to set a name for the UserPicker to get it working. But on the issue create screen when using it inside the custom field edit ui, it gets crazy. My code is not that complex:

<CustomFieldEdit
          onSubmit={saveData}
          hideActionButtons
        >
          <UserPicker
            label={`Please select the ${fieldName} person(s)`}
            key={fieldId}
            name={fieldId}
            defaultValue={value}
            isMulti
            description={fieldDescription}
            onChange={u => setValue(u.map(({ id }) => id))}
          />
        </CustomFieldEdit>

Actually, when I just open the create issue dialog, enter a summary and press submit I get the error:

When I give the UserPicker an other name like “banana” when I submit the form the POST message to the create issue API looks like this:

{
  "fields": {
    "project": {
      "id": "10000"
    },
    "issuetype": {
      "id": "10002"
    },
    "summary": "123",
    "customfield_10088": {
      "data": "c717655e476a99c3812c9c6e0455211650c090364e54584e0b65ffbebfc4ae20a8617502cd9d91a82068cefd3854512d0174a20307c512e716582075f16afa6d42179652eec644e5f424b562a9a5e975",
      "iv": "1258140b2f2a54c14b83e1cc8187a2d7",
      "timestamp": 1740556218727,
      "placeholder": "Data can’t be displayed in this view"
    },
    "banana": [
      {
        "name": "Tempo Timesheets",
        "avatarUrl": "https://secure.gravatar.com/avatar/182fc208a1a2e6cc41393ab6c9363d9c?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FTT-6.png",
        "id": "557058:295406f3-a1fc-4733-b906-dd15d021bd79",
        "type": "user"
      },
      {
        "name": "Jira Spreadsheets",
        "avatarUrl": "https://secure.gravatar.com/avatar/92dbb0e2c4bdf7a4e079ce410ddb6029?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FJS-2.png",
        "id": "5cf112d31552030f1e3a5905",
        "type": "user"
      }
    ]
  },
  "update": {},
  "externalToken": "0.34333976660855114"
}

So the “fields” entry contains a key banana with the value of the user picker. But actually I never set this value. In my submit callback I do:

const saveData = async () => {
    if (!createScreen) {
      setLoading(true);
    }
    // Single User Picker Data is too late for submit...
    const data = moduleKey === 'bvsec-single-user' ? userValue[fieldId] : value;
    if (data !== undefined) {
      const encryptedData = await invoke('setData', { data });
      if (encryptedData) {
        encryptedData.placeholder = PLACEHOLDER;
      }
      await view.submit(encryptedData);
    } else {
      await view.submit({ timestamp: new Date().getTime() });
    }
  };

You can find the result in the customfield_10088 payload.

So I never set a value like this. So the user picker is wasting the payload why ever resulting in the error message:

{
    "errorMessages": [],
    "errors": {
        "banana": "Field 'banana' cannot be set. It is not on the appropriate screen, or unknown."
    }
}

When I use the customfield id as name it works in some rare cases, but this is actually no solution.

Kind regards,
Daniel