ConfettiDocs
Migration guides

Migrating to 0.6

Surveys that show every question at once keep their submit button enabled and reveal validation errors after a refused submit, and isCompleted is removed.

Version 0.6 changes how surveys that show every question at once report an unanswered question. Previously the embedded and modal templates hid the submit button until every required question had an answer, and no validation error text was ever rendered. A respondent who missed a question saw a survey that simply would not proceed, with nothing on screen explaining why.

The submit button is now always rendered and enabled. Pressing it on an incomplete survey refuses the submission, reveals the blocking errors, and moves focus to the first unanswered question. Errors then update live as the respondent fixes them.

The happy path. If you embed a drop-in template (EmbeddedConfetti, ModalConfetti, PopoverConfetti, StepperConfetti), you can upgrade with no code changes. The new behaviour is built in.

Update the package

Install 0.6

npm install @opengovsg/confetti@^0.6.0
# or
pnpm add @opengovsg/confetti@^0.6.0
# or
yarn add @opengovsg/confetti@^0.6.0

Update your code if you build your own survey UI

Only callers importing from @opengovsg/confetti/components need changes. See Are you affected?.

Are you affected?

No changes if you use a drop-in template. The templates handle the new behaviour internally. You may want to check that your styling still reads well with an error message under a question — see Styling the error message.

You must update your code if you render Embed or drive ConfettiController yourself. isCompleted is removed from both, errors is now gated on the first submit attempt, and submit() returns whether the submission was accepted.

Building-block changes (@opengovsg/confetti/components)

isCompleted is removed

EmbedProps.isCompleted and ConfettiState.isCompleted are both gone. Its only realistic use was disabling or hiding a submit control, which is the behaviour this release replaces.

Render an always-enabled submit control and let submit() decide.

Before (0.5):

<ConfettiController>
  {({ questions, update, isCompleted, submit, errors }) => (
    <>
      {/* ...questions... */}
      <button onClick={submit} disabled={!isCompleted}>
        Submit
      </button>
    </>
  )}
</ConfettiController>

After (0.6):

<ConfettiController>
  {({ questions, update, submit, errors }) => (
    <>
      {/* ...questions... */}
      <button onClick={submit}>Submit</button>
    </>
  )}
</ConfettiController>

If you used Embed, drop the prop:

// Before (0.5)
<Embed onSubmit={submit} isCompleted={isCompleted}>

// After (0.6)
<Embed onSubmit={submit}>

submit() returns whether the submission was accepted

submit() now returns false when it refuses an incomplete or invalid survey, and true when the response was sent. Anything that should only happen on a real submission — navigation, analytics, closing a container — must be gated on the return value.

const handleSubmit = () => {
  if (!submit()) {
    // Refused. The blocking errors are now exposed through `errors`.
    return
  }
  trackSurveyCompleted()
}

Code that assumed the press always went through will now fire on a refused submit. This is the most likely source of a silent bug when upgrading.

errors is gated on the first submit attempt

ConfettiState.errors previously reflected live validity from the moment the survey rendered. It is now empty until the respondent first presses submit, and live from then on.

This means you can render it directly without tracking a "has the user tried yet" flag of your own:

<SurveyQuestionFactory
  question={question}
  error={errors[question.id] || ''}
  onChange={(answer) => update({ question: question.position, answer })}
/>

You must forward errors into your question components. Without it a refused submit has nothing to display, and the respondent is back to a button that appears to do nothing.

If you relied on errors as a live validity signal before any submit — for example to drive a progress indicator — derive that from answers in your own component instead.

Styling the error message

Error text renders inside each question as .cf-error and is revealed by a data-show-errors attribute the controller sets on its root element once a submit has been refused.

A new --confetti-error-color custom property sets the colour:

.confetti {
  --confetti-error-color: #dc2626;
}

Surveys that show one question at a time (popover and stepper) are unchanged in this release. They still disable the Next button while the current answer is invalid.

Validation message changes

Unanswered required rating and single-select questions previously produced raw schema text. They now read as respondent-facing copy:

Question typeBeforeAfter
RatingInvalid input: expected number, received undefinedPlease select a rating
Single selectInvalid input: expected string, received undefinedPlease select an option

These messages are also returned by the response-submission API, so server-side error handling that matched on the old strings needs updating.

Need help?

Was this page helpful?

On this page