• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ginkgobioworks / react-json-schema-form-builder / 20673325228

03 Jan 2026 06:09AM UTC coverage: 72.451% (+20.9%) from 51.563%
20673325228

push

github

raymond-lam
Refactor codebase: modernize stack, reorganize structure, and improve consistency

Stack modernization:
- Upgrade from React 18 to React 19 with automatic JSX runtime
- Replace Bootstrap/reactstrap with Material-UI (MUI) v7
- Replace @hello-pangea/dnd with @dnd-kit for drag-and-drop
- Replace react-jss with MUI styling system
- Replace Enzyme with React Testing Library
- Migrate from Rollup to tsup for library bundling
- Migrate example app from Next.js to Vite
- Update TypeScript to ES2022 with bundler module resolution
- Modernize ESLint to flat config format

Code changes:
- Convert all class components to functional components with hooks
- Improve type safety: replace 'any' with JsonSchema/UiSchema types
- Convert all interfaces to types
- Remove redundant nested containers
- Add SortableItem component with drag handle support
- Improve memoization with useMemo and useCallback
- Restructure example app to follow standard Vite directory structure
- Export types from library and update example app imports (Fixes #604)
- Reorganize component files: move FBCheckbox and PlaceholderInput to formBuilder root
- Standardize label casing to Title Case throughout (Default Value, Column Size, Dependencies, etc.)
- Optimize re-renders by fixing dependency arrays (remove setState from deps, fix CardModal useEffect, fix hideAddButton)
- Remove unnecessary container elements and standardize fragment syntax (React.Fragment -> <>)
- Update dependencies to latest versions and clean up code

Documentation:
- Update Contributing.md: replace Enzyme with React Testing Library, fix types.js to types.ts, remove outdated Travis CI references, restore coveralls.io reference
- Update all documentation and examples

Issues addressed:
- #369: Array field cursor loses focus when typing
- #467: Description field lost when changing Input Type
- #476: Unreachable code in ValueSelector
- #478: False positive unit tests and validator issues
- #412: Alert not exported from reactstrap... (continued)

788 of 1120 branches covered (70.36%)

Branch coverage included in aggregate %.

357 of 490 new or added lines in 24 files covered. (72.86%)

8 existing lines in 1 file now uncovered.

882 of 1185 relevant lines covered (74.43%)

22.68 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

56.67
/src/formBuilder/defaults/arrayInputs.tsx
1
import React, { useState } from 'react';
2
import TextField from '@mui/material/TextField';
3
import Box from '@mui/material/Box';
4
import Typography from '@mui/material/Typography';
5
import {
6
  excludeKeys,
7
  generateElementComponentsFromSchemas,
8
  generateCategoryHash,
9
} from '../utils';
10
import Card from '../Card';
11
import Section from '../Section';
12
import FBCheckbox from '../FBCheckbox';
13
import shortAnswerInputs from './shortAnswerInputs';
14
import longAnswerInputs from './longAnswerInputs';
15
import numberInputs from './numberInputs';
16
import defaultInputs from './defaultInputs';
17
import { getRandomId } from '../utils';
18
import type {
19
  FormInput,
20
  CardComponentType,
21
  CardComponentPropsType,
22
} from '../types';
23

24
// specify the inputs required for a string type object
25
const CardArrayParameterInputs: CardComponentType = ({
7✔
26
  parameters,
27
  onChange,
28
}) => {
29
  return (
×
30
    <Box>
31
      <Typography variant='subtitle2' fontWeight='bold'>
32
        Minimum Items
33
      </Typography>
34
      <TextField
35
        value={parameters.minItems || ''}
×
36
        placeholder='ex: 2'
37
        type='number'
38
        onChange={(ev) => {
39
          onChange({
×
40
            ...parameters,
41
            minItems: parseInt(ev.target.value, 10),
42
          });
43
        }}
44
        size='small'
45
        fullWidth
46
        sx={{ mb: 2 }}
47
      />
48
      <Typography variant='subtitle2' fontWeight='bold'>
49
        Maximum Items
50
      </Typography>
51
      <TextField
52
        value={parameters.maxItems || ''}
×
53
        placeholder='ex: 2'
54
        type='number'
55
        onChange={(ev) => {
56
          onChange({
×
57
            ...parameters,
58
            maxItems: parseInt(ev.target.value, 10),
59
          });
60
        }}
61
        size='small'
62
        fullWidth
63
        sx={{ mb: 2 }}
64
      />
65
    </Box>
66
  );
67
};
68

69
const InnerCard: CardComponentType = ({ parameters, onChange, mods }) => {
7✔
70
  const [elementId] = useState(getRandomId);
1✔
71
  const newDataProps: { [key: string]: any } = {};
1✔
72
  const newUiProps: { [key: string]: any } = {};
1✔
73
  const allFormInputs = excludeKeys(
1✔
74
    Object.assign({}, defaultFormInputs, (mods && mods.customFormInputs) || {}),
2!
75
    mods && mods.deactivatedFormInputs,
2✔
76
  );
77

78
  // parse components into data and ui relevant pieces
79
  Object.keys(parameters).forEach((propName: string) => {
1✔
80
    if (propName.startsWith('ui:*')) {
15✔
81
      newUiProps[propName.substring(4)] =
1✔
82
        parameters[propName as keyof CardComponentPropsType];
83
    } else if (propName.startsWith('ui:')) {
14!
84
      newUiProps[propName] =
×
85
        parameters[propName as keyof CardComponentPropsType];
86
    } else if (!['name', 'required'].includes(propName)) {
14✔
87
      newDataProps[propName] =
12✔
88
        parameters[propName as keyof CardComponentPropsType];
89
    }
90
  });
91

92
  const definitionData = parameters.definitionData
1!
93
    ? parameters.definitionData
94
    : {};
95
  const definitionUi = parameters.definitionUi ? parameters.definitionUi : {};
1!
96
  const [cardOpen, setCardOpen] = React.useState(false);
1✔
97
  if (parameters.type !== 'array') {
1!
NEW
98
    return (
×
99
      <Typography variant='body2' color='error'>
100
        Not an array
101
      </Typography>
102
    );
103
  }
104
  return (
1✔
105
    <>
106
      <FBCheckbox
107
        onChangeValue={() => {
108
          if (newDataProps.items.type === 'object') {
×
109
            onChange({
×
110
              ...parameters,
111
              items: {
112
                ...newDataProps.items,
113
                type: 'string',
114
              },
115
            });
116
          } else {
117
            onChange({
×
118
              ...parameters,
119
              items: {
120
                ...newDataProps.items,
121
                type: 'object',
122
              },
123
            });
124
          }
125
        }}
126
        isChecked={newDataProps.items.type === 'object'}
127
        label='Section'
128
      />
129
      {generateElementComponentsFromSchemas({
130
        schemaData: { properties: { item: newDataProps.items } },
131
        uiSchemaData: { item: newUiProps.items },
132
        onChange: (schema, uischema) => {
133
          onChange({
×
134
            ...parameters,
135
            items: schema.properties.item,
136
            'ui:*items': uischema.item || {},
×
137
          });
138
        },
139
        path: elementId,
140
        definitionData,
141
        definitionUi,
142
        hideKey: true,
143
        cardOpenArray: [cardOpen],
144
        setCardOpenArray: (updater) => {
145
          const newArr =
NEW
146
            typeof updater === 'function' ? updater([cardOpen]) : updater;
×
NEW
147
          setCardOpen(newArr[0]);
×
148
        },
149
        allFormInputs,
150
        mods,
151
        categoryHash: generateCategoryHash(allFormInputs),
152
        Card: (props) => <Card {...props} showObjectNameInput={false} />,
1✔
153
        Section,
154
      })}
155
    </>
156
  );
157
};
158

159
function getInnerCardComponent(): CardComponentType {
160
  return InnerCard;
14✔
161
}
162

163
const defaultFormInputs: { [key: string]: FormInput } = {
7✔
164
  ...defaultInputs,
165
  ...shortAnswerInputs,
166
  ...longAnswerInputs,
167
  ...numberInputs,
168
};
169
defaultFormInputs.array = {
7✔
170
  displayName: 'Array',
171
  matchIf: [
172
    {
173
      types: ['array'],
174
    },
175
  ],
176
  defaultDataSchema: {
177
    items: { type: 'string' },
178
  },
179
  defaultUiSchema: {},
180
  type: 'array',
181
  cardBody: getInnerCardComponent(),
182
  modalBody: CardArrayParameterInputs,
183
};
184

185
const ArrayInputs: { [key: string]: FormInput } = {
7✔
186
  array: {
187
    displayName: 'Array',
188
    matchIf: [
189
      {
190
        types: ['array'],
191
      },
192
    ],
193
    defaultDataSchema: {
194
      items: { type: 'string' },
195
    },
196
    defaultUiSchema: {},
197
    type: 'array',
198
    cardBody: getInnerCardComponent(),
199
    modalBody: CardArrayParameterInputs,
200
  },
201
};
202

203
export default ArrayInputs;
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc