├── .gitignore
├── README.md
├── docs
├── asset-manifest.json
├── index.html
├── robots.txt
└── static
│ ├── css
│ ├── main.829e0053.css
│ └── main.829e0053.css.map
│ └── js
│ ├── main.f57bf6a8.js
│ ├── main.f57bf6a8.js.LICENSE.txt
│ └── main.f57bf6a8.js.map
├── package-lock.json
├── package.json
├── public
├── index.html
└── robots.txt
├── screenshot.png
└── src
├── App.js
├── components
└── forms
│ ├── AdvancedForm.js
│ ├── Checkbox.js
│ ├── ConditionalField.js
│ ├── FormField.js
│ ├── RadioGroup.js
│ ├── Select.js
│ ├── TextArea.js
│ ├── TextField.js
│ ├── helpers.js
│ └── index.js
├── index.css
└── index.js
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /build
3 | .DS_Store
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React Advanced Form
2 |
3 | An example of a schema-based form system in React. Define your schema, and pass it into the form. Supports basic conditional schema.
4 |
5 | 
6 |
7 | ### Example
8 |
9 | The schema system support text input, checkbox, radio group, select, textarea, and conditonal fields. It is presented in these examples with plain HTML and CSS based forms, but can easily be modified for use with any React UI framework, such as Material UI, Semantic UI, etc.
10 |
11 | ```js
12 | const formSchema = [
13 | { name: 'name', label: 'Name', componentType: 'text', required: true },
14 | { name: 'playable', label: 'Playable', componentType: 'checkbox' },
15 | {
16 | name: 'race',
17 | label: 'Race',
18 | componentType: 'radioGroup',
19 | options: [
20 | { label: 'Human', value: 'human' },
21 | { label: 'Dwarf', value: 'dwarf' },
22 | { label: 'Elf', value: 'elf' },
23 | ],
24 | },
25 | {
26 | name: 'class',
27 | label: 'Class',
28 | componentType: 'select',
29 | options: [
30 | { label: 'Ranger', value: 'ranger' },
31 | { label: 'Wizard', value: 'wizard' },
32 | { label: 'Healer', value: 'healer' },
33 | ],
34 | },
35 | {
36 | name: 'spell',
37 | label: 'Spell',
38 | componentType: 'select',
39 | options: [
40 | { label: 'Fire', value: 'fire' },
41 | { label: 'Ice', value: 'ice' },
42 | ],
43 | condition: { key: 'class', value: 'wizard', operator: '=' },
44 | },
45 | {
46 | name: 'description',
47 | label: 'Description',
48 | componentType: 'textarea',
49 | },
50 | ]
51 | ```
52 |
53 | Simply pass the schema into the component, and handle the submit event.
54 |
55 | ```jsx
56 |
57 | ```
58 |
--------------------------------------------------------------------------------
/docs/asset-manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "files": {
3 | "main.css": "/static/css/main.829e0053.css",
4 | "main.js": "/static/js/main.f57bf6a8.js",
5 | "index.html": "/index.html",
6 | "main.829e0053.css.map": "/static/css/main.829e0053.css.map",
7 | "main.f57bf6a8.js.map": "/static/js/main.f57bf6a8.js.map"
8 | },
9 | "entrypoints": [
10 | "static/css/main.829e0053.css",
11 | "static/js/main.f57bf6a8.js"
12 | ]
13 | }
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Advanced Form
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/docs/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/docs/static/css/main.829e0053.css:
--------------------------------------------------------------------------------
1 | *,:after,:before{box-sizing:border-box}body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;background:#f0f0f0;font-family:-apple-system,BlinkMacSystemFont,sans-serif;margin:0;padding:1rem}h1{margin:1rem auto 2rem;max-width:1000px}pre{word-wrap:break-word;font-family:Menlo,monospace;font-size:1rem;white-space:pre-wrap}.flex{display:flex;gap:2rem;margin:0 auto;max-width:1000px;width:100%}.section{background:#fff;border:1px solid #dedede;border-radius:8px;padding:2rem}.form.section{flex:0.5 1}.results.section{flex:1 1}.advanced-form input,.advanced-form select,.advanced-form textarea{border:1px solid #aaa;border-radius:4px;margin:0;padding:.5rem}.advanced-form input[type=radio]{margin-right:.5rem}.advanced-form input[type=text],.advanced-form select,.advanced-form textarea{width:100%}.advanced-form textarea{min-height:100px}.advanced-form label{display:block;font-weight:600;margin-bottom:.5rem}.advanced-form .field{margin-bottom:1rem}.advanced-form .error,.advanced-form .required{color:#d63642}.advanced-form .error{display:block;margin:.25rem 0}.advanced-form button{background:#3642d6;border:1px solid #3642d6;border-radius:4px;color:#fff;cursor:pointer;font-size:1rem;font-weight:600;padding:.5rem 1rem}.advanced-form:hover{-webkit-filter:brightness(1.1);filter:brightness(1.1)}.advanced-form button:disabled{opacity:.5}
2 | /*# sourceMappingURL=main.829e0053.css.map*/
--------------------------------------------------------------------------------
/docs/static/css/main.829e0053.css.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"static/css/main.829e0053.css","mappings":"AAAA,iBAGE,qBACF,CAEA,KAKE,kCAAmC,CACnC,iCAAkC,CAHlC,kBAAmB,CACnB,uDAA0D,CAH1D,QAAS,CACT,YAKF,CAEA,GACE,qBAAsB,CACtB,gBACF,CAEA,IAGE,oBAAqB,CADrB,2BAA6B,CAD7B,cAAe,CAGf,oBACF,CAEA,MAGE,YAAa,CACb,QAAS,CAHT,aAAc,CACd,gBAAiB,CAGjB,UACF,CAEA,SACE,eAAiB,CACjB,wBAAyB,CACzB,iBAAkB,CAClB,YACF,CAEA,cACE,UACF,CAEA,iBACE,QACF,CAEA,mEAGE,qBAAsB,CACtB,iBAAkB,CAElB,QAAS,CADT,aAEF,CAEA,iCACE,kBACF,CAEA,8EAGE,UACF,CAEA,wBACE,gBACF,CAEA,qBAEE,aAAc,CADd,eAAgB,CAEhB,mBACF,CAEA,sBACE,kBACF,CAEA,+CAEE,aACF,CAEA,sBAEE,aAAc,CADd,eAEF,CAEA,sBAGE,kBAAmB,CAFnB,wBAAyB,CACzB,iBAAkB,CAElB,UAAY,CAIZ,cAAe,CADf,cAAe,CADf,eAAgB,CADhB,kBAIF,CAEA,qBACE,8BAAuB,CAAvB,sBACF,CAEA,+BACE,UACF","sources":["index.css"],"sourcesContent":["*,\n*::before,\n*::after {\n box-sizing: border-box;\n}\n\nbody {\n margin: 0;\n padding: 1rem;\n background: #f0f0f0;\n font-family: -apple-system, BlinkMacSystemFont, sans-serif;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n\nh1 {\n margin: 1rem auto 2rem;\n max-width: 1000px;\n}\n\npre {\n font-size: 1rem;\n font-family: Menlo, monospace;\n word-wrap: break-word;\n white-space: pre-wrap;\n}\n\n.flex {\n margin: 0 auto;\n max-width: 1000px;\n display: flex;\n gap: 2rem;\n width: 100%;\n}\n\n.section {\n background: white;\n border: 1px solid #dedede;\n border-radius: 8px;\n padding: 2rem;\n}\n\n.form.section {\n flex: 0.5;\n}\n\n.results.section {\n flex: 1;\n}\n\n.advanced-form input,\n.advanced-form select,\n.advanced-form textarea {\n border: 1px solid #aaa;\n border-radius: 4px;\n padding: 0.5rem;\n margin: 0;\n}\n\n.advanced-form input[type='radio'] {\n margin-right: 0.5rem;\n}\n\n.advanced-form input[type='text'],\n.advanced-form textarea,\n.advanced-form select {\n width: 100%;\n}\n\n.advanced-form textarea {\n min-height: 100px;\n}\n\n.advanced-form label {\n font-weight: 600;\n display: block;\n margin-bottom: 0.5rem;\n}\n\n.advanced-form .field {\n margin-bottom: 1rem;\n}\n\n.advanced-form .required,\n.advanced-form .error {\n color: #d63642;\n}\n\n.advanced-form .error {\n margin: 0.25rem 0;\n display: block;\n}\n\n.advanced-form button {\n border: 1px solid #3642d6;\n border-radius: 4px;\n background: #3642d6;\n color: white;\n padding: 0.5rem 1rem;\n font-weight: 600;\n font-size: 1rem;\n cursor: pointer;\n}\n\n.advanced-form:hover {\n filter: brightness(1.1);\n}\n\n.advanced-form button:disabled {\n opacity: 0.5;\n}\n"],"names":[],"sourceRoot":""}
--------------------------------------------------------------------------------
/docs/static/js/main.f57bf6a8.js.LICENSE.txt:
--------------------------------------------------------------------------------
1 | /*
2 | object-assign
3 | (c) Sindre Sorhus
4 | @license MIT
5 | */
6 |
7 | /** @license React v0.20.2
8 | * scheduler.production.min.js
9 | *
10 | * Copyright (c) Facebook, Inc. and its affiliates.
11 | *
12 | * This source code is licensed under the MIT license found in the
13 | * LICENSE file in the root directory of this source tree.
14 | */
15 |
16 | /** @license React v16.13.1
17 | * react-is.production.min.js
18 | *
19 | * Copyright (c) Facebook, Inc. and its affiliates.
20 | *
21 | * This source code is licensed under the MIT license found in the
22 | * LICENSE file in the root directory of this source tree.
23 | */
24 |
25 | /** @license React v17.0.2
26 | * react-dom.production.min.js
27 | *
28 | * Copyright (c) Facebook, Inc. and its affiliates.
29 | *
30 | * This source code is licensed under the MIT license found in the
31 | * LICENSE file in the root directory of this source tree.
32 | */
33 |
34 | /** @license React v17.0.2
35 | * react-jsx-runtime.production.min.js
36 | *
37 | * Copyright (c) Facebook, Inc. and its affiliates.
38 | *
39 | * This source code is licensed under the MIT license found in the
40 | * LICENSE file in the root directory of this source tree.
41 | */
42 |
43 | /** @license React v17.0.2
44 | * react.production.min.js
45 | *
46 | * Copyright (c) Facebook, Inc. and its affiliates.
47 | *
48 | * This source code is licensed under the MIT license found in the
49 | * LICENSE file in the root directory of this source tree.
50 | */
51 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-advanced-form",
3 | "version": "0.1.0",
4 | "private": false,
5 | "dependencies": {
6 | "formik": "^2.2.9",
7 | "react": "^17.0.2",
8 | "react-dom": "^17.0.2",
9 | "react-scripts": "5.0.0",
10 | "yup": "^0.32.11"
11 | },
12 | "scripts": {
13 | "start": "react-scripts start",
14 | "build": "react-scripts build",
15 | "test": "react-scripts test",
16 | "eject": "react-scripts eject"
17 | },
18 | "eslintConfig": {
19 | "extends": [
20 | "react-app",
21 | "react-app/jest"
22 | ]
23 | },
24 | "browserslist": {
25 | "production": [
26 | ">0.2%",
27 | "not dead",
28 | "not op_mini all"
29 | ],
30 | "development": [
31 | "last 1 chrome version",
32 | "last 1 firefox version",
33 | "last 1 safari version"
34 | ]
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Advanced Form
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/taniarascia/react-advanced-form/7ba60565d478d1566be378144adb99df03b86698/screenshot.png
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import { useState } from 'react'
2 | import { AdvancedForm } from './components/forms/AdvancedForm'
3 |
4 | export default function App() {
5 | const [formValues, setFormValues] = useState([])
6 |
7 | const handleSubmit = async (values, { setSubmitting }) => {
8 | setSubmitting(true)
9 | setFormValues(values)
10 | await new Promise((r) => setTimeout(r, 1000))
11 | setSubmitting(false)
12 | }
13 |
14 | const formSchema = [
15 | { name: 'name', label: 'Name', componentType: 'text', required: true },
16 | { name: 'playable', label: 'Playable', componentType: 'checkbox' },
17 | {
18 | name: 'race',
19 | label: 'Race',
20 | componentType: 'radioGroup',
21 | options: [
22 | { label: 'Human', value: 'human' },
23 | { label: 'Dwarf', value: 'dwarf' },
24 | { label: 'Elf', value: 'elf' },
25 | ],
26 | },
27 | {
28 | name: 'class',
29 | label: 'Class',
30 | componentType: 'select',
31 | options: [
32 | { label: 'Ranger', value: 'ranger' },
33 | { label: 'Wizard', value: 'wizard' },
34 | { label: 'Healer', value: 'healer' },
35 | ],
36 | },
37 | {
38 | name: 'spell',
39 | label: 'Spell',
40 | componentType: 'select',
41 | options: [
42 | { label: 'Fire', value: 'fire' },
43 | { label: 'Ice', value: 'ice' },
44 | ],
45 | condition: { key: 'class', value: 'wizard', operator: '=' },
46 | },
47 | {
48 | name: 'description',
49 | label: 'Description',
50 | componentType: 'textarea',
51 | },
52 | ]
53 |
54 | return (
55 | <>
56 | Advanced Form
57 |
58 |
59 |
62 |
63 |
{JSON.stringify(formValues, null, 2)}
64 |
65 |
66 | >
67 | )
68 | }
69 |
--------------------------------------------------------------------------------
/src/components/forms/AdvancedForm.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Formik, Field } from 'formik'
3 |
4 | import {
5 | Checkbox,
6 | Select,
7 | TextArea,
8 | TextField,
9 | RadioGroup,
10 | ConditionalField,
11 | } from '.'
12 | import {
13 | getInitialValues,
14 | getDefaultValues,
15 | getValidationSchema,
16 | } from './helpers'
17 |
18 | const components = [
19 | { componentType: 'text', component: TextField },
20 | { componentType: 'textarea', component: TextArea },
21 | { componentType: 'select', component: Select },
22 | { componentType: 'checkbox', component: Checkbox },
23 | { componentType: 'radioGroup', component: RadioGroup },
24 | ]
25 |
26 | export const AdvancedForm = ({
27 | schema,
28 | onSubmit,
29 | initialValues,
30 | onClose,
31 | buttonLabel = 'Submit',
32 | ...props
33 | }) => {
34 | const defaultValues = getDefaultValues(schema)
35 | const validationSchema = getValidationSchema(schema)
36 |
37 | return (
38 |
45 | {({
46 | handleSubmit,
47 | isSubmitting,
48 | isValid,
49 | setFieldValue,
50 | setFieldTouched,
51 | values,
52 | }) => {
53 | return (
54 |
108 | )
109 | }}
110 |
111 | )
112 | }
113 |
--------------------------------------------------------------------------------
/src/components/forms/Checkbox.js:
--------------------------------------------------------------------------------
1 | import { FormField } from './FormField'
2 |
3 | export const Checkbox = ({
4 | label,
5 | field: { name, value, ...fieldProps },
6 | form,
7 | required,
8 | ...props
9 | }) => {
10 | return (
11 |
12 |
20 |
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/src/components/forms/ConditionalField.js:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react'
2 |
3 | export const ConditionalField = ({ show, onCollapse, onShow, children }) => {
4 | useEffect(() => {
5 | if (show) {
6 | onShow()
7 | } else {
8 | onCollapse()
9 | }
10 | // eslint-disable-next-line react-hooks/exhaustive-deps
11 | }, [show])
12 |
13 | return show ? children : null
14 | }
15 |
--------------------------------------------------------------------------------
/src/components/forms/FormField.js:
--------------------------------------------------------------------------------
1 | export const FormField = ({
2 | id,
3 | label,
4 | required,
5 | children,
6 | formProps: { touched, errors },
7 | }) => {
8 | const hasError = errors[id] && touched[id]
9 |
10 | return (
11 |
12 |
16 | {children}
17 | {hasError && {errors[id]}}
18 |
19 | )
20 | }
21 |
--------------------------------------------------------------------------------
/src/components/forms/RadioGroup.js:
--------------------------------------------------------------------------------
1 | import { FormField } from './FormField'
2 |
3 | export const RadioGroup = ({
4 | label,
5 | field: { name, value, ...fieldProps },
6 | form,
7 | required,
8 | options,
9 | ...props
10 | }) => {
11 | return (
12 |
13 | {options.map((option) => (
14 |
15 |
25 | {option.label}
26 |
27 | ))}
28 |
29 | )
30 | }
31 |
--------------------------------------------------------------------------------
/src/components/forms/Select.js:
--------------------------------------------------------------------------------
1 | import { FormField } from './FormField'
2 |
3 | export const Select = ({
4 | label,
5 | field: { name, value, ...fieldProps },
6 | form,
7 | required,
8 | options,
9 | ...props
10 | }) => {
11 | return (
12 |
13 |
21 |
22 | )
23 | }
24 |
--------------------------------------------------------------------------------
/src/components/forms/TextArea.js:
--------------------------------------------------------------------------------
1 | import { FormField } from './FormField'
2 |
3 | export const TextArea = ({
4 | label,
5 | field: { name, value, ...fieldProps },
6 | form,
7 | required,
8 | ...props
9 | }) => {
10 | return (
11 |
12 |
19 |
20 | )
21 | }
22 |
--------------------------------------------------------------------------------
/src/components/forms/TextField.js:
--------------------------------------------------------------------------------
1 | import { FormField } from './FormField'
2 |
3 | export const TextField = ({
4 | label,
5 | field: { name, value, ...fieldProps },
6 | form,
7 | required,
8 | ...props
9 | }) => {
10 | return (
11 |
12 |
20 |
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/src/components/forms/helpers.js:
--------------------------------------------------------------------------------
1 | import * as Yup from 'yup'
2 |
3 | export const getInitialValues = (defaultValues, initialValues) => {
4 | if (!initialValues) return defaultValues
5 |
6 | return { ...defaultValues, ...initialValues }
7 | }
8 |
9 | export const getDefaultValues = (schema) => {
10 | return schema
11 | .filter((val) => !val.condition)
12 | .reduce((acc, val) => {
13 | let defaultValue
14 | switch (val.componentType) {
15 | case 'text':
16 | case 'textarea':
17 | case 'select':
18 | case 'radioGroup':
19 | defaultValue = ''
20 | break
21 | case 'checkbox':
22 | defaultValue = false
23 | break
24 | default:
25 | defaultValue = ''
26 | }
27 |
28 | return {
29 | ...acc,
30 | [val.name]: val.defaultValue || defaultValue,
31 | }
32 | }, {})
33 | }
34 |
35 | export const getValidationSchema = (schema) => {
36 | const validationObject = schema.reduce((acc, val) => {
37 | let validationType
38 |
39 | switch (val.componentType) {
40 | case 'text':
41 | case 'textarea':
42 | case 'select':
43 | case 'radioGroup':
44 | validationType = Yup.string()
45 | break
46 | case 'checkbox':
47 | default:
48 | validationType = null
49 | }
50 |
51 | if (val.required && validationType) {
52 | validationType = validationType.required(`${val.label} is required`)
53 | }
54 |
55 | return { ...acc, ...(validationType && { [val.name]: validationType }) }
56 | }, {})
57 |
58 | return Yup.object().shape(validationObject)
59 | }
60 |
--------------------------------------------------------------------------------
/src/components/forms/index.js:
--------------------------------------------------------------------------------
1 | import { Checkbox } from './Checkbox.js'
2 | import { Select } from './Select.js'
3 | import { TextField } from './TextField.js'
4 | import { TextArea } from './TextArea.js'
5 | import { RadioGroup } from './RadioGroup.js'
6 | import { ConditionalField } from './ConditionalField.js'
7 |
8 | export { Checkbox, Select, TextField, TextArea, RadioGroup, ConditionalField }
9 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | *,
2 | *::before,
3 | *::after {
4 | box-sizing: border-box;
5 | }
6 |
7 | body {
8 | margin: 0;
9 | padding: 1rem;
10 | background: #f0f0f0;
11 | font-family: -apple-system, BlinkMacSystemFont, sans-serif;
12 | -webkit-font-smoothing: antialiased;
13 | -moz-osx-font-smoothing: grayscale;
14 | }
15 |
16 | h1 {
17 | margin: 1rem auto 2rem;
18 | max-width: 1000px;
19 | }
20 |
21 | pre {
22 | font-size: 1rem;
23 | font-family: Menlo, monospace;
24 | word-wrap: break-word;
25 | white-space: pre-wrap;
26 | }
27 |
28 | .flex {
29 | margin: 0 auto;
30 | max-width: 1000px;
31 | display: flex;
32 | gap: 2rem;
33 | width: 100%;
34 | }
35 |
36 | .section {
37 | background: white;
38 | border: 1px solid #dedede;
39 | border-radius: 8px;
40 | padding: 2rem;
41 | }
42 |
43 | .form.section {
44 | flex: 0.5;
45 | }
46 |
47 | .results.section {
48 | flex: 1;
49 | }
50 |
51 | .advanced-form input,
52 | .advanced-form select,
53 | .advanced-form textarea {
54 | border: 1px solid #aaa;
55 | border-radius: 4px;
56 | padding: 0.5rem;
57 | margin: 0;
58 | }
59 |
60 | .advanced-form input[type='radio'] {
61 | margin-right: 0.5rem;
62 | }
63 |
64 | .advanced-form input[type='text'],
65 | .advanced-form textarea,
66 | .advanced-form select {
67 | width: 100%;
68 | }
69 |
70 | .advanced-form textarea {
71 | min-height: 100px;
72 | }
73 |
74 | .advanced-form label {
75 | font-weight: 600;
76 | display: block;
77 | margin-bottom: 0.5rem;
78 | }
79 |
80 | .advanced-form .field {
81 | margin-bottom: 1rem;
82 | }
83 |
84 | .advanced-form .required,
85 | .advanced-form .error {
86 | color: #d63642;
87 | }
88 |
89 | .advanced-form .error {
90 | margin: 0.25rem 0;
91 | display: block;
92 | }
93 |
94 | .advanced-form button {
95 | border: 1px solid #3642d6;
96 | border-radius: 4px;
97 | background: #3642d6;
98 | color: white;
99 | padding: 0.5rem 1rem;
100 | font-weight: 600;
101 | font-size: 1rem;
102 | cursor: pointer;
103 | }
104 |
105 | .advanced-form:hover {
106 | filter: brightness(1.1);
107 | }
108 |
109 | .advanced-form button:disabled {
110 | opacity: 0.5;
111 | }
112 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 |
4 | import './index.css'
5 |
6 | import App from './App'
7 |
8 | ReactDOM.render(
9 |
10 |
11 | ,
12 | document.getElementById('root'),
13 | )
14 |
--------------------------------------------------------------------------------