Forge Form gives me Validation Error no matter what I do


No matter what I do, I end up with this error:

ONe more thing is, when I use useState and useEffect to get values from a function - I don’t see the values when I display them.
But when I use it this way, it works:

Why won’t it show me the value of getAllBoards() (this returns an array of objects)?
When I iterate through it and display it with useState and useEffect, it doesn’t work

This works with the code I attached not the combination of useState and useEffect

Could you let me know what’s wrong with my form and usage of useState and useEffect. I’m quite new to forge and react

Hey @alekyak

Can you tell me - what Forge module are you trying to create? and are you using UI Kit, or UI Kit 2 or Custom UI for your app?

Cheers!
Mel

Hi @mpaisley. I used UI Kit 2. I could resolve my form error. It was a problem with the Button.
But the useState and useEffect never renders the results from my function

The useState should be initialized with a synchronous function.
If you want the state to be updated with async calls, you can either delegate your data fetching to libraries such as react-query, or you can do something like

const [boardNames, setBoardNames] = useState([])

useEffect(() => {
  getAllBoards().then(data => setBoardNames(data))
}, [])
1 Like

Hey @alekyak

Awesome, in UI Kit 2 apps, you’re actually using React useState and useEffect rather than the forge implementation - they can work a little differently, especially when working with async functions.

In this case, it looks like you’re not using useState correctly.

const [boardnames] = useState(async () => getAllBoards())

I’m assuming what you’re seeing here is that boardnames promise that hasn’t been fulfilled yet. As @PaoloCampanelli mentioned, this needs to be initialized with a synchronous function.

You can use a combination of useState and useEffect here to update the state - Paolo gave a good example above.

You can read more about using Hooks like useState and useEffect in forge in my blog A Deeper Look at Hooks in Forge - Atlassian Developer Blog

Cheers!
Mel

Hi! I will check out your post. However, when I did what Paolo did, I still didn’t get the boardnames.

It doesn’t console.log anything and there’s no options in my dropdown
image

Just to be sure, I’d place a console.log before const fetchBoardNames... so that you can check that the useEffect is actually being invoked.
In general you don’t need to define a const functions to specify an effect, you can just do something like

useEffect(() => {
  try {
     const boardNamesData = await getAllBoards()
     ...
  } catch(e) {
    console.error(e)
  }
}, [])

Without the extra call.

One morething that I can suggest trying is adding setBoardNames and getAllBoards as dependencies for the hook. In React, the setters are considered stable and should never change… but who knows, you can do that by specifying [setBoardNames, getAllBoards] in the second parameter for useEffect

Hey, thanks! That works. One more question is
I’m trying to render the second dropdown values after user selects the first one. So onChange = {handleboardSelect}

and that function looks like this:

Do I have to do useEffect for sprintNamesData as well for it to work and render the options in the dropdown?

In that case you don’t need a separate useEffect: when you’re reacting to an event – in this case onChange – you should generally handle all the side effects in the event handler: it’s easier to follow the flow that way.

Of course, if you have an useEffect that has selectedBoardId in its dependencies, it would work too, but it’s not something I’d personally recommend.

I did this but I don’t think this is populating the sprintnames - and I’m not sure why but I cant console.log the selectedboardId either - it says undefined

const App = () => {
const [boardnames, setBoardNames] = useState([]);
    const [selectedBoardId, setSelectedBoardId] = useState(undefined);
    const [selectedSprintId, setSelectedSprintId] = useState(undefined);
    const [selectedBoardName, setSelectedBoardName] = useState(undefined);
    const [selectedSprintName, setSelectedSprintName] = useState(undefined);
    const [sprintnames, setSprintNames] = useState([]);

    useEffect(async () => {
      try {
        const boardNamesData = await getAllBoards();
        //console.log('Board Names Data:', boardNamesData);
        setBoardNames(boardNamesData);
      } catch (error) {
        console.error(error);
      }
    }, []);
  
    const handleBoardSelect = (value) => {
      setSelectedBoardId(value);
      //setSelectedBoardName(boardnames.find(board => board.boardId === value).boardName);
      const selectedBoard = boardnames.find((board) => board.boardId === value);
      if (selectedBoard) {
        console.log("Selected BoardID: " + selectedBoard.boardId);
        console.log("Selected BoardName: " + selectedBoard.boardName);
      }
    };

    useEffect(async () => {
      try {
        if (selectedBoardId) {
          const sprintNameData = await getAllSprintsinBoard(selectedBoardId);
          setSprintNames(sprintNameData);
          console.log("Sprint Data: " + sprintNameData);
        }
      } catch (error) {
        console.error(error);
      }
    }, [selectedBoardId]);
  
    const handleSprintSelect = (value) => {
      setSelectedSprintId(value);
      setSelectedSprintName(sprintnames.find(sprint => sprint.sprintId === value).sprintName);
    };

  
    const handleFormSubmit = () => {
      const boardId = selectedBoardId;
      const boardName = selectedBoardName;
      const sprintId = selectedSprintId;
      const sprintName = selectedSprintName;
      onFormSubmit(boardId, boardName, sprintId, sprintName);
    };
  
    const onFormSubmit = (boardId, boardName, sprintId, sprintName) => {
      console.log('Board ID:', boardId);
      console.log('Board Name:', boardName);
      console.log('Sprint ID:', sprintId);
      console.log('Sprint Name:', sprintName);
  
      // Call other APIs
    };
  
    return (
      <Fragment>
        <Form onSubmit={handleFormSubmit} text="Submit">
          {Array.isArray(boardnames) ? (
            <Select
              label="Select a Board"
              name="boardId"
              value={selectedBoardId}
              onChange={handleBoardSelect}
            >
              {boardnames.map((board) => (
                <Option
                  key={board.boardId}
                  label={board.boardName}
                  value={board.boardId}
                />
              ))}
            </Select>
          ) : (
            <Text>Board Names are not available</Text>
          )}
  
          {Array.isArray(sprintnames) ? (
            <Select
              label="Select a Sprint"
              name="sprintId"
              value={selectedSprintId}
              onChange={handleSprintSelect}
            >
              {sprintnames.map((sprint) => (
                <Option
                  key={sprint.sprintId}
                  label={sprint.sprintName}
                  value={sprint.sprintId}
                />
              ))}
            </Select>
          ) : (
            <Text>Sprint Names are not available</Text>
          )}
        </Form>
      </Fragment>
    );
  };

Not sure why this doesn’t update the sprint names drop down. I don’t see forge logs either. board data is fetched but selectedBoardId, sprintdata is not showing up at all.
Could you please tell me what I’m doing wrong?

You can check here the signature of the onChange prop: https://developer.atlassian.com/platform/forge/ui-kit-2/select/

If I had to guess, your handleBoardSelect function is getting value={label: "Your board name", value: your_board_id}, which means that selectedBoard is always going to be null.
You can easily verify that with console.log(value) in handleBoardSelect.
If that’s indeed the case, you can trivially fix that with handleBoardSelect = ({value}) => { ...

I’m trying to console.log it but the onChange console.log doesn’t even show up

Yeah, as I’ve explained, const selectedBoard = boardnames.find((board) => board.boardId === value); is probably null, so the if (selectedBoard) is always false.
You can check that by logging value immediately in handleBoardSelect

1 Like

Ah okay, thank you