Skip to content

Latest commit

 

History

History
657 lines (542 loc) · 33.3 KB

File metadata and controls

657 lines (542 loc) · 33.3 KB

Forms

A form lets your app ask users to input and submit data. Forms can be defined with a simple form object that takes a list of fields, and an onSubmit handler.

A form dialog

Using forms

There are several ways to add a form to your app, and the method you'll use depends on what you’re doing.

Interactive posts

This example shows an interactive post with a text label and a button that triggers a form. When the form is submitted, the onSubmit handler takes the form input and manipulates state with useState. The state update triggers a rerender and the new data is displayed.

import { Devvit, useState, useForm } from '@devvit/public-api';

// addCustomPostType() is deprecated and will be unsupported. It will not work after June 30.
Devvit.addCustomPostType({
  name: 'TemplateName',
  render: (context) => {
    const [name, setName] = useState('unknown');

    const myForm = useForm(
      {
        fields: [
          {
            type: 'string',
            name: 'name',
            label: 'Name',
          },
        ],
      },
      (values) => {
        // onSubmit handler
        setName(values.name);
      }
    );

    return (
      <vstack gap="medium" height="100%" alignment="middle center">
        <text>Hello {name}!</text>
        <button
          onPress={() => {
            context.ui.showForm(myForm);
          }}
        >
          Set name
        </button>
      </vstack>
    );
  },
});

export default Devvit;

Explore example in playground.

Menu actions

This example defines a new form and a onSubmit handler called myForm using the devvit.createForm method. This form is shown via a menu action using context.ui.showForm().

import { Devvit } from '@devvit/public-api';

const myForm = Devvit.createForm(
  {
    fields: [
      {
        type: 'string',
        name: 'food',
        label: 'What is your favorite food?',
      },
    ],
  },
  (event, context) => {
    // onSubmit handler
    context.ui.showToast({ text: event.values.food });
  }
);

Devvit.addMenuItem({
  label: 'Show a form',
  location: 'subreddit',
  onPress: async (_event, context) => {
    context.ui.showForm(myForm);
  },
});

export default Devvit;

App configurations

For more details see the app configurations page.

Form object

The form object enables you to customize the form container and the list of form fields included. The form object is passed along to context.ui.useForm, devvit.createForm, or devvit.addSettings to create the form.

Usage

const myForm = {
  title: 'My form',
  description: 'This is my form. There are many like it, but this one is mine.',
  fields: [
    {
      type: 'string',
      name: 'food',
      label: 'What is your favorite food?',
    },
    {
      type: 'string',
      name: 'drink',
      label: 'What is your favorite drink?',
    },
  ],
  acceptLabel: 'Submit',
  cancelLabel: 'Cancel',
};

Supported properties

Property Supported types Description
title string undefined An optional title for the form
description string undefined An optional description for the form
fields FormField[] The fields that will be displayed in the form
acceptLabel string undefined An optional label for the submit button
cancelLabel string undefined An optional label for the cancel button

onSubmit handler

When creating a form, you also specify a callback function that is called when the form is submitted. It takes two parameters: event and context. The event parameter contains the results of the form submission, while the context parameter represents the current app context of the form submission event.

const onSubmitHandler = (event, context) => {
  context.ui.showToast({ text: event.values.food });
};

const myForm = Devvit.createForm(
  {
    fields: [
      {
        type: 'string',
        name: 'food',
        label: 'What is your favorite food?',
      },
    ],
  },
  onSubmitHandler
);

:::note Note: The context object is not available in onSubmit handlers within the useForm hook. For interactive posts, you already have access to the context element via the outer function's scope. :::

Supported fields types

The following field types are supported: String, Select, Paragraph, Number, Boolean, and Group.

String

A single-line text input.

String input

Usage

const stringField = {
  type: 'string',
  name: 'title',
  label: 'Tournament title',
};

Properties

Property Supported types Description
type string The desired field type.
name string The name of the field. This will be used as the key in the values object when the form is submitted.
label string The label of the field. This will be displayed to the user.
helpText string undefined An optional help text that will be displayed below the field.
required boolean undefined If true the field will be required and the user will not be able to submit the form without filling it in. Defaults to false.
disabled boolean undefined If true the field will be disabled. Defaults to false.
defaultValue ValueType undefined The default value of the field.
scope SettingScopeType undefined This indicates whether the field (setting) is an app level or install level setting. App setting values can be used by any installation. undefined by default.
placeholder string undefined Placeholder text for display before a value is present.
isSecret boolean undefined Makes the form field secret.

Select

A dropdown menu with predefined options.

Select input

Usage

const selectField = {
  type: 'select',
  name: 'interval',
  label: 'Update the leaderboard',
  options: [
    { label: 'Hourly', value: 'hourly' },
    { label: 'Daily', value: 'daily' },
    { label: 'Weekly', value: 'weekly' },
    { label: 'Monthly', value: 'monthly' },
    { label: 'Yearly', value: 'yearly' },
  ],
};

Properties

Property Supported types Description
type string The desired field type.
name string The name of the field. This will be used as the key in the values object when the form is submitted.
label string The label of the field. This will be displayed to the user.
options FieldConfig_Selection_Item[] The list of options available.
helpText string undefined An optional help text that will be displayed below the field.
required boolean undefined If true the field will be required and the user will not be able to submit the form without filling it in. Defaults to false.
disabled boolean undefined If true the field will be disabled. Defaults to false.
defaultValue string[] undefined The default value of the field. Note that the default value is wrapped in an array to support multiple selected values.
scope SettingScopeType undefined This indicates whether the field (setting) is an app level or install level setting. App setting values can be used by any installation. undefined by default.
multiSelect boolean undefined Enables users to select more than 1 item from the set.

Paragraph

A multi-line text input for longer responses.

Paragraph input

Usage

const paragraphField = {
  type: 'paragraph',
  name: 'description',
  label: 'Description',
};

Properties

Property Supported types Description
type string The desired field type.
name string The name of the field. This will be used as the key in the values object when the form is submitted.
label string The label of the field. This will be displayed to the user.
helpText string undefined An optional help text that will be displayed below the field.
required boolean undefined If true the field will be required and the user will not be able to submit the form without filling it in. Defaults to false.
disabled boolean undefined If true the field will be disabled. Defaults to false.
defaultValue ValueType undefined The default value of the field.
scope SettingScopeType undefined This indicates whether the field (setting) is an app level or install level setting. App setting values can be used by any installation. undefined by default.
placeholder string undefined Placeholder text for display before a value is present.
lineHeight number undefined Sets the field height by number of lines.

Number

An input for numerical values.

Number input

Usage

const numberField = {
  type: 'number',
  name: 'tokens',
  label: 'Token balance',
};

Properties

Property Supported types Description
type string The desired field type.
name string The name of the field. This will be used as the key in the values object when the form is submitted.
label string The label of the field. This will be displayed to the user.
helpText string undefined An optional help text that will be displayed below the field.
required boolean undefined If true the field will be required and the user will not be able to submit the form without filling it in. Defaults to false.
disabled boolean undefined If true the field will be disabled. Defaults to false.
defaultValue ValueType undefined The default value of the field.
scope SettingScopeType undefined This indicates whether the field (setting) is an app level or install level setting. App setting values can be used by any installation. undefined by default.

Boolean

A yes/no or true/false type input.

Boolean input

Usage

const booleanField = {
  type: 'boolean',
  name: 'enable',
  label: 'Enable the event',
};

Properties

Property Supported types Description
type string The desired field type.
name string The name of the field. This will be used as the key in the values object when the form is submitted.
label string The label of the field. This will be displayed to the user.
helpText string undefined An optional help text that will be displayed below the field.
disabled boolean undefined If true the field will be disabled. Defaults to false.
defaultValue ValueType undefined The default value of the field.
scope SettingScopeType undefined This indicates whether the field (setting) is an app level or install level setting. App setting values can be used by any installation. undefined by default.

Image

An image upload field.

Image input

Usage

const imageField = {
  type: 'image', // This tells the form to expect an image
  name: 'myImage',
  label: 'Image goes here',
  required: true,
};

Properties

Property Supported types Description
type string The desired field type.
name string The name of the field. This will be used as the key in the values object when the form is submitted.
label string The label of the field. This will be displayed to the user.
helpText string undefined An optional help text that will be displayed below the field.
required boolean undefined If true the field will be required and the user will not be able to submit the form without filling it in. Defaults to false.
disabled boolean undefined If true the field will be disabled. Defaults to false.
scope SettingScopeType undefined This indicates whether the field (setting) is an app level or install level setting. App setting values can be used by any installation. undefined by default.
placeholder string undefined Placeholder text for display before a value is present.
isSecret boolean undefined Makes the form field secret.

Group

A collection of related fields that allows for better readability.

Usage

const groupField = {
  type: 'group',
  label: 'This is a group of input fields',
  fields: [
    {
      type: 'paragraph',
      name: 'description',
      label: 'How would you describe what happened?',
    },
    {
      type: 'number',
      name: 'score',
      label: 'How would you rate your meal on a scale from 1 to 10?',
    },
  ],
};

Properties

Property Supported types Description
type string The desired field type.
label string The label of the group that will be displayed to the user.
fields FormField[] The fields that will be displayed in the group.
helpText string undefined An optional help text that will be displayed below the group.

Examples

Below is a collection of common use cases and patterns.

Dynamic forms

Instead of passing a static Form, you can create a function that returns a Form. This enables us to dynamically determine which fields to show and what default values to populate them with. We can also pass along any data via the context.ui.showForm method's optional 2nd argument.

import { Devvit } from '@devvit/public-api';

Devvit.configure({
  redditAPI: true,
});

const myForm = Devvit.createForm(
  (data) => {
    return {
      fields: [
        {
          type: 'string',
          name: 'username',
          label: 'Username',
          defaultValue: data.username,
        },
      ],
      // Adding `as const` helps you get accurate types in the onSubmit function below
      // This will only work if the function does not have any branching logic
    } as const;
  },
  (event, context) => {
    context.ui.showToast({
      text: `Hello ${event.values.username}`,
    });
  }
);

Devvit.addMenuItem({
  label: 'Show a dynamic form',
  location: 'subreddit',
  onPress: async (_event, context) => {
    const user = await context.reddit.getCurrentUser();
    const username = user?.username;
    context.ui.showForm(myForm, { username });
  },
});

export default Devvit;

One of everything

This example includes one of each of the supported field types.

import { Devvit } from '@devvit/public-api';

const exampleForm = Devvit.createForm(
  {
    title: 'My favorites',
    description: 'Tell us about your favorite food!',
    fields: [
      {
        type: 'string',
        name: 'food',
        label: 'What is your favorite food?',
        helpText: 'Must be edible',
        required: true,
      },
      {
        label: 'About that food',
        type: 'group',
        fields: [
          {
            type: 'number',
            name: 'times',
            label: 'How many times a week do you eat it?',
            defaultValue: 1,
          },
          {
            type: 'paragraph',
            name: 'what',
            label: 'What makes it your favorite?',
          },
          {
            type: 'select',
            name: 'healthy',
            label: 'Is it healthy?',
            options: [
              { label: 'Yes', value: 'yes' },
              { label: 'No', value: 'no' },
              { label: 'Maybe', value: 'maybe' },
            ],
            defaultValue: ['maybe'],
          },
        ],
      },
      {
        type: 'boolean',
        name: 'again',
        label: 'Can we ask again?',
      },
    ],
    acceptLabel: 'Submit',
    cancelLabel: 'Cancel',
  },
  (event, context) => {
    console.log(event.values);
    context.ui.showToast('Thanks!');
  }
);

Devvit.addMenuItem({
  location: 'subreddit',
  label: 'One of everything form',
  onPress: (_event, context) => {
    context.ui.showForm(exampleForm);
  },
});

export default Devvit;

Multi-step forms

Add a multi-step dynamic form to an interactive post

import { Devvit, useState, useForm } from '@devvit/public-api';

Devvit.configure({
  redditAPI: true,
});

// addCustomPostType() is deprecated and will be unsupported. It will not work after June 30.
Devvit.addCustomPostType({
  name: 'Multi-step Form',
  render: (context) => {
    const [name, setName] = useState('');
    const [food, setFood] = useState('');
    const [drink, setDrink] = useState('');

    const form3 = useForm(
      {
        fields: [
          {
            type: 'string',
            name: 'drink',
            label: "What's your favorite drink?",
            required: true,
          },
        ],
      },
      (values) => {
        setDrink(values.drink);
      }
    );

    const form2 = useForm(
      {
        fields: [
          {
            type: 'string',
            name: 'food',
            label: "What's your favorite food?",
            required: true,
          },
        ],
      },
      (values) => {
        setFood(values.food);
        context.ui.showForm(form3);
      }
    );

    const form1 = useForm(
      {
        fields: [
          {
            type: 'string',
            name: 'name',
            label: "What's your name?",
            required: true,
          },
        ],
      },
      (values) => {
        setName(values.name);
        context.ui.showForm(form2);
      }
    );

    function restart() {
      setName('');
      setFood('');
      setDrink('');
      context.ui.showForm(form1);
    }

    const isAnswered = name && food && drink;

    return (
      <vstack height="100%" alignment="center middle" gap="none">
        {isAnswered && (
          <>
            <text>Name: {name}</text>
            <text>Favorite food: {food}</text>
            <text>Favorite drink: {drink}</text>
            <spacer size="large" />
            <button onPress={restart}>Restart</button>
          </>
        )}
        {!isAnswered && <button onPress={restart}>Take questionnaire</button>}
      </vstack>
    );
  },
});

Devvit.addMenuItem({
  location: 'subreddit',
  label: 'Add post with multi-step form',
  onPress: async (_event, context) => {
    const currentSubreddit = await context.reddit.getCurrentSubreddit();
    await context.reddit.submitPost({
      title: 'Interactive post with multi-step form',
      subredditName: currentSubreddit.name,
      preview: (
        <vstack width="100%" height="100%" alignment="middle center">
          <text>Loading ...</text>
        </vstack>
      ),
    });
    context.ui.showToast('Submitted post!');
  },
});

export default Devvit;

Explore example in a playground.

Image uploads

Add an image upload input to a form.

import { Devvit } from '@devvit/public-api';

const form = Devvit.createForm(
  {
    title: 'Upload an image!',
    fields: [
      {
        name: 'myImage',
        type: 'image', // This tells the form to expect an image
        label: 'Image goes here',
        required: true,
      },
    ],
  },
  (event, context) => {
    const imageUrl = event.values.myImage;
    // Use the mediaUrl to store in redis and display it in an <image> block, or send to external service to modify
  }
);