Windows.List in react-window

2 days ago 1
ARTICLE AD BOX

I am trying to build a form-builder using Next.js, but I encounter this issue when trying to create a LivePreview. I am using a three-column layout (sidebar, canvas, LivePreview). It is supposed to be an open-source form-builder website, where people can create a form and share a link to the form.

The problem is that when I drag a field and drop on the canvas, the whole website freezes, I have isolated the problem to the LivePreview component, but I still do not know what the problem is.

I will add my folder structure. I am using a third-party package called react-window to try and memoize my array when mapping

folder structures of my projectI

## Error Type Runtime TypeError ## Error Message Cannot convert undefined or null to object at Object.values (<anonymous>:null:null) at LivePreview (src/app/_components/builder/LivePreview.jsx:105:21) at BuilderLayout (src/app/_components/builder/BuilderLayout.jsx:68:9) at FormBuilderPage (src/app/builder/[formId]/page.js:39:10) ## Code Frame 103 | <p className="text-gray-400 text-center py-8">Add fields to see preview</p> 104 | ) : ( > 105 | <List | ^ 106 | height={600} // Adjust to your preview container's height 107 | itemCount={deferredFields.length} 108 | itemSize={120} // Approximate height of each field row (in pixels) Next.js version: 16.1.6 (Turbopack) 'use client'; import React from "react"; import { Input, TextArea, Checkbox, FieldLabel } from "@/components/ui"; import { List } from 'react-window'; import { useDeferredValue } from 'react'; // This is a separate component that renders ONE field (used by the List) const FieldRow = ({ index, style, data }) => { const field = data[index]; if (!field || !field.config) { return <div style={style} />; } ; return ( <div style={style} key={field.id} className="space-y-2"> <FieldLabel label={field.config.label} required={field.config.required} htmlFor={field.id} /> {/* Render appropriate input based on field type */} {['EMAIL', 'PHONE'].includes(field.type) && ( <Input type={field.type === 'EMAIL' ? 'email' : 'text'} placeholder={field.config.placeholder} disabled={true} className="px-3 py-2 border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500" /> )} {field.type === 'NUMBER' && ( <Input type="number" placeholder={field.config.placeholder} disabled className="px-3 py-2 border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500" /> )} {field.type === 'SHORT_TEXT' && ( <Input type="text" placeholder={field.config.placeholder} disabled className="px-3 py-2 border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500" /> )} {field.type === 'TEXTAREA' && ( <TextArea id={field.id} value="" onChange={() => {}} placeholder={field.config.placeholder} rows={3} disabled /> )} {field.type === 'DATE' && ( <Input type="date" disabled className="border border-gray-300 focus:ring-2 focus:ring-blue-500" /> )} {field.type === 'CHECKBOX' && Array.isArray(field.config.options) && ( <div className="space-y-2"> {field.config.options.map((option, idx) => ( <Checkbox key={idx} label={option} disabled checked={false} /> ))} </div> )} </div> ); }; const LivePreview = ({ fields = []}) => { const safeFields = (fields || []).filter(Boolean); const deferredFields = useDeferredValue(safeFields); return ( <aside className="w-96 bg-gray-100 border-l border-gray-200 p-6 overflow-y-auto"> <div className="mb-4"> <h2 className="text-lg font-bold text-gray-900 mb-2">Preview</h2> <p className="text-sm text-gray-500">Live preview of your form</p> </div> <div className="bg-white rounded-lg shadow-sm p-6 border border-gray-200"> {deferredFields.length === 0 ? ( <p className="text-gray-400 text-center py-8">Add fields to see preview</p> ) : ( <List height={600} // Adjust to your preview container's height itemCount={deferredFields.length} itemSize={120} // Approximate height of each field row (in pixels) itemData={deferredFields} width="100%" > {FieldRow} </List> )} </div> </aside> ); }; export default React.memo(LivePreview); 'use client'; import { DndContext, DragOverlay, closestCenter } from '@dnd-kit/core'; import { useState, useCallback } from 'react'; import FieldsSidebar from './FieldsSidebar'; import FormCanvas from './FormCanvas'; import LivePreview from './LivePreview'; import { FIELD_TYPES } from '@/lib/builder/fieldRegistry'; export default function BuilderLayout({ builderState, formId }) { const [activeId, setActiveId] = useState(null); const handleDragStart = useCallback((event) => { setActiveId(event.active.id); }, []); const handleDragEnd = useCallback((event) => { const { active, over } = event; if (!over) { setActiveId(null); return; } // Dragging from sidebar to canvas if (active.id.startsWith('sidebar-')) { const fieldType = active.id.replace('sidebar-', ''); builderState.addField(fieldType); } // Reordering within canvas if (active.id.startsWith('field-') && over.id.startsWith('field-')) { const oldIndex = builderState.fields.findIndex( (f) => `field-${f.id}` === active.id ); const newIndex = builderState.fields.findIndex( (f) => `field-${f.id}` === over.id ); if (oldIndex !== -1 && newIndex !== -1 && oldIndex !== newIndex) { builderState.reorderFields(oldIndex, newIndex); } } setActiveId(null); }, [builderState]); console.log('BuilderLayout render', builderState.fields.length); const activeField = activeId?.startsWith('sidebar-') ? FIELD_TYPES[activeId.replace('sidebar-', '')] : null; return ( <DndContext collisionDetection={closestCenter} onDragStart={handleDragStart} onDragEnd={handleDragEnd} > <div className="flex h-screen bg-gray-50"> {/* Left Sidebar - Field Types */} <FieldsSidebar /> {/* Center Canvas - Drop Zone */} <FormCanvas builderState={builderState} /> {/* Right Preview - Live View */} <LivePreview fields={builderState.fields} /> {/* Drag Overlay */} <DragOverlay> {activeField ? ( <div className="bg-white p-4 rounded-lg shadow-lg border-2 border-blue-500"> {activeField.icon} {activeField.label} </div> ) : null} </DragOverlay> </div> </DndContext> ); }
Read Entire Article