Deeper Dive into Frontend Architecture for AI-Native Form Builder

Smit Barmase

on

Apr 21, 2024

In our previous blog post, we discussed the advantages of utilizing bi-directional client-server connections, such as WebSockets, in developing Metaforms’ AI-native form builder. This approach facilitates real-time interactions that are essential for syncing continuous and dynamic updates between the client and server. However, it also introduces complexities, particularly in managing state and efficiently handling server-client communication. Today, we will delve into solutions for these challenges.

Talking to the Server: Optimizing for High-Frequency User Interactions

Problem: One significant challenge is managing high-frequency actions, such as when a user types to configure a form's question or context in Metaforms. Using traditional methods, such as debouncing, would delay all requests, even those that require immediate action—for instance, publishing the form. It would also unnecessarily delay actions that are less frequent, like toggling a setting.

Solution: To address this without adding complexity to the developer experience, we implemented a two-layer architecture. The first layer maps actions to all the local state updates needed to refresh the UI for the user. The second layer manages mapping these actions to WebSocket events that need to be sent. Actions can be set as an immediate trigger, which will send a socket request immediately, as opposed to a delayed one.

Listening from the Server: Handling Local State Updates

Problem: When changing the global app state upon receiving a WebSocket request is straightforward, updating some local states is not. Traditionally, one would receive a socket event, update a global state based on that, listen to this state update in a local component, and perform some action while also resetting this global state. This process works but introduces inefficiencies by requiring additional global state for minor UI updates of a small component.

Solution: We have set up a custom event listener that emits and listens for data. The socket component emits an event once a response is received. Child components subscribe to this event and respond when triggered, enabling them to update their local state directly. This approach avoids cumbersome parent state changes for component-level use cases.

Parent Component

websocket.onmessage = function(event) {
  // Parsing the message data from the WebSocket server
  const messageData = JSON.parse(event.data);
  
  // Create and dispatch the custom event on receiving a WebSocket message
  const customEvent = new CustomEvent('customEvent', {
    detail: { message: messageData.messageFromServer }
  });
  window.dispatchEvent(customEvent);
};


Deeply Nested Child Component

useEffect(() => {
  const handleCustomEvent = (event) => {
    console.log('Received Custom Event:', event.detail.message);
    // Process event.detail data as needed
  };
  window.addEventListener('customEvent', handleCustomEvent);
  return () => {
    window.removeEventListener('customEvent', handleCustomEvent);
  };
}, []);

Conclusion

By integrating instant state updates with debounced WebSocket communications and deftly managing state as though it were part of a request-response model using custom events, Metaforms' AI-native form builder achieves an exceptionally efficient and responsive user experience. This approach ensures that each user action is reflected in real-time, supporting Metaforms' goal to transform how forms are built and interacted with in a contextually rich, AI-driven environment.

Bangalore, India / San Francisco, US

WorkHack Inc. 2023