Introducing FOMA, a block powered form builder inspired by Notion UI

Introducing FOMA, a block powered form builder inspired by Notion UI

Hi, this is my first post here on Hashnode and it is about my experience building a form builder which is inspired by the Notion UI. Had this idea for the HarperDB hackathon but had not time to build it so today I worked on it and managed to create an MVP. I hope you like this article and feel free to try Foma on the link at the end.

📄 Introducing Foma

Foma is an online form builder which lets you easily add blocks of text or insert form elements to start collecting data, its UI is inspired by Notion. Foma it's currently an MVP but you can add as many text blocks as you want or choose between two types of form elements.

Preview.png

To collect user data, there are currently two block options:

  • Short answer, to collect names, email addresses, or phone numbers.
  • Long answer, which lets you collect more detailed information such as comments or user feedback.

Once you hit the Publish button you will be redirected to your now online form which you can then share its URL to anyone. Currently there's no auth or privacy integrations, you can see the form entries by adding /results at the end of the URL.

📁 The stack

Built this app using the following stack of services and frameworks or libraries.

  • React
  • Netlify
  • HarperDB
  • styled-components
  • React Router

It is one of the first apps I have used React for and it was a good try for vitejs of which I am impressed of, I tried CRA in the past but vite feels better and faster. 👍

🥊 Challenges

Faced some challenges while building this little app, here's how I worked on them.

Define structure

One of the first challenges I faced was to figure out how I was going to structure the form data. Never built something like this, so I assumed I could work with arrays of objects and then render each element according to its attributes.

Went with this data structure:

[{
  id: id,
  type: type,
  placeholder: placeholder,
}]

The body of a form would be composed of blocks represented as a JSON object. Each block have a unique id which is useful to identify it and modify it within the full array of blocks; Each block also has a type attribute which defines what type of element it is, and a placeholder attribute which can be the value of a text block or a form element placeholder value, it is rendered later as a string depending on the block type.

With this type of structure, I could easily modify, save, and then conditionally render each block to function as the defined element.

Editable blocks

First, I thought "how would I make editable blocks?". Easiest way was to add form inputs for each block and just style with CSS to make it look like plain text. But inputs do not grow horizontally or vertically to adjust text, it got disappeared.

So, the solution was to make editable HTML elements such as span for blocks or h1 for the title. Adding the contenteditable to a element lets you freely modify its contents. I just had to style each span too make it look like a input or textarea, or let it be in full width as a text block and it grows to adjust its text content.

<span
  data-type={type}
  className={type}
  onInput={handleInput}
  contentEditable={true}
  placeholder={ type === "text_block" ? "Type '/' to add a block element" : "Add a placeholder"
  }
/>

Keyboard events

In Notion, we can move over each block using keyboard arrows, insert a new block by pressing Enter key or remove a block by pressing backspace.

Adding these keyboard events required to work more with the blocks array, first I must listen to every keydown event and identify which key was being pressed, depending on it I had to get the current element and then add a new block next the current one or remove the current element and add focus on the previous one.

/**
 * handles arow down key event, when pressed finds current focused
 * block and adds focus on the next one if
 */
if (e.keyCode === 40) {
  e.preventDefault();
  const current = e.target.parentNode.id;
  const index = formBlocks.findIndex((block) => block.id === current);
  document
    .getElementById(formBlocks[index + 1].id)
    .querySelector("span")
    .focus();
}

🏁 Conclusion

Building this app has been helpful to learn more and practice with React as well as working with HarperDB which is especially useful as you don't have to care about creating each table columns, you can add new data to your schema and send to HarperDB and it will add a new column for your data.

Foma is not fully complete, over the next week I will try to add more block types and integrate auth features, in the meantime you can try Foma on the links:

Foma is now Typiform

Hey!👋 Thanks for reading, it's not only my first article on Hashnode, but also the first one I do in English. I don't write very often but I would like to change that, I want to know if I can improve anything so feel free to give me honest feedback in the comments... I'll be reading. 😀

Did you find this article valuable?

Support Freddy González by becoming a sponsor. Any amount is appreciated!