Transforming Legacy Data

Transforming Legacy Data

- Development of an editor to transform vanilla JS based survey forms into a modern structure-

In this post, I would like to share my experience in developing a survey form editor for a recent project and the core architecture behind it.

1. Background and Problem Situation

The existing survey form in the legacy system was composed in a very traditional way, using Vanilla JavaScript to directly render the screen in HTML and injecting code based on unique numeric IDs assigned to each object to find the DOM. While this legacy structure had no issues simply rendering the survey on screen and collecting responses, it had a critical flaw. Specifically, Describe the hierarchy of actual questions and answersthrough the obtained dataunderstandwas hard to do, Processing the collected data for statistics and visualizationtoIt was verydifficult.

Therefore, beyond simply porting the legacy to React, a new format editor was needed to transform the existing flat and fragmented data into a modern structure that aligns with the actual question-answer format (Label, Question, Option).

2. Editor component architecture and rigorous separation of concerns

As an editor that handles complex data, the most emphasized part is to thoroughly separate components so that business logic and screen status do not mix in each editing step.It was.

The overall edit flow and tab switching are controlled by the top-level container, TemplateEditorContainer, under which each step's wrapper components are independently arranged.

  • STEP 01. FormMapping: Specify properties such as label, question, and answer for legacy elements
  • STEP 02. FormGroupingA step that logically groups and organizes elements.
  • STEP 03. RuleMaking: Assigning dynamic events and calculation logic

[Separation of Mapper and Form]Entering the wrapper component of each stage, the screen goes back to the left side.Edit sidebar (Mapper)and on the right side of the format screen (Form)It is divided into View components. The Mapper is completely unique for each STEP, but the Form is kept clean as a simple rendering component to enhance reusability.

  • MapperA manipulation UI that changes attributes or gives actions to elements.
  • FormThis is the main screen where the actual survey form is rendered, and elements can be directly clicked and selected in the editor.

[Why was it designed this way?]The editor undergoes numerous state changes based on user clicks and inputs. There are many elements drawn on the screen, each with its own unique style or state values.

If all of this had been handled in a single component or a loosely structured way, the code in the 'element specification' stage could have unintentionally caused side effects in the 'rule-making' stage, significantly reducing state processing performance and increasing the risk of screen lag.

By having completely independent wrapper components for each editing step (STEP 01~03) and combining the Mapper and Form within them, we effectively blocked the specific stage's domain logic from encroaching on other stages. This allowed us to reduce the complexity of the code and greatly enhance maintainability and rendering stability.

Additionally, the flow of data was optimized into the stages of element change (React State) → apply (holding intermediate state with React Hook Form) → save (transforming RHF values to meet server API specifications for transmission), and the subcomponents were appropriately separated considering reusability, while also utilizing useCallback and memo to prevent unnecessary re-rendering that burdens heavy screens.

3. Data Structuring Process in Step 3

The process of transforming legacy DOM-dependent data into structured statistical data is carried out in three main steps.

STEP 01. Specify Elements (Mapping)First, map the legacy data in a flat, non-nested format to either a label, question, or option. A question is an element that requires a direct response from the user, while an option has the actual 'value' of that response. In particular, to receive response values, it was enforced that the element must be designated as an 'option' to ensure data integrity.

STEP 02. Creating Groups It groups the elements with specified characteristics to create a logical hierarchy. 'Group composition' bundles related questions into a single card view (section), 'Answer groups' allow multiple responses to a single question to be grouped together, enabling the behavior of Radio buttons, which were previously controlled individually with pure JavaScript, to be designated as RadioGroup. The 'Single line arrangement' feature visually arranges the elements while allowing these arranged elements to be specified in thermometer, slider, and table formats, thereby enhancing both the data hierarchy, the UI's completeness, and the usability of the survey.

STEP 03. Rule Making It grants dynamic actions (logic) to static surveys. We implemented an 'event' that manipulates different targets based on changes in criteria elements (e.g., activating an input field when checking 'Other'), and a 'calculation' function that aggregates or scores static and dynamic numerical values from specific responses.

4. Key Troubleshooting: The Mixing of Two ID Systems

One of the most concerning aspects of this project was Identifier (ID) systemElements that migrated from the legacy system had existing unique numbers (e.g., formClauSn). However, the new modern architecture required a new identifier (UUID) to identify the structured states of L/Q/O.

If the user changes the element from 'Question' to 'Label' in the editor, the nature of the element changes and a new UUID will be issued. In this case, there could be a serious problem where the existing 'Rules' applied to that element lose their reference target and cease to function. To prevent this, the editor has separated the two types of IDs for their respective purposes.

  1. templateItemIdThis is an immutable value based on the identifiers of the existing legacy system. In the element specification (Mapping) and rule creation (Rule) stages, this immutable value is fixed as an identifier to ensure that existing rules are maintained even if the nature of the elements is reversed.
  2. id (UUID)From the stage of creating groups where the hierarchy is established (Grouping), the UUID was naturally used as an identifier suitable for the new data architecture.

5. Implementation of Data Communication at the Code Level

In order to smoothly handle data manipulation in the frontend environment, we fully adopted TypeScript and built data communication based on custom hooks. Upon entering the editor, the function logic for querying legacy data from the backend and processing it into a format that is easy to handle in the frontend is as follows.

export const findQuestionnaireEditingViewByQuestionnaireTemplateId = async (payload: {
  questionnaireTemplateId: string;
}): Promise => {
  const url = `${templateLoadResource}/find-questionnaire-template-editing-view-by-questionnaire-template-id/query`;
  const response = await surveyAxios.post(url, payload);
  return transformTemplateTextAlign(response.data);
};

I also managed server state declaratively using React Query for CUD operations occurring during rule editing.

export const useRuleDeleteMutation = (questionnaireTemplateId: string) => {
  const queryClient = useQueryClient();
  return useMutation({
    mutationFn: (payload: RemoveItemRuleCommand) => removeItemRule(payload),
    onSuccess: () => {
      queryClient.invalidateQueries({
        queryKey: ruleQueryKeys.list(questionnaireTemplateId),
      });
    },
  });
};

In conclusion

This project, which transformed a legacy form trapped in the past vanilla JavaScript DOM manipulation into a modern frontend architecture and turned it into statistically valuable 'data', was a very interesting challenge from a development perspective. I hope this experience serves as a useful reference when you undertake similar migrations or complex editor view designs in the future!

Hazel

Site footer