Skip to content

Commit aa35bc6

Browse files
authored
Merge pull request #9 from OlivierJM/add-double-field-component
Add double field component
2 parents 8dc44a6 + cb25d28 commit aa35bc6

File tree

15 files changed

+297
-30
lines changed

15 files changed

+297
-30
lines changed

.github/main.yml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
name: Node CI
2+
on: [push]
3+
jobs:
4+
test:
5+
name: Test on node ${{ matrix.node_version }} and ${{ matrix.os }}
6+
runs-on: ${{ matrix.os }}
7+
strategy:
8+
matrix:
9+
node_version: ['8', '10', '12']
10+
os: [ubuntu-latest]
11+
steps:
12+
- uses: actions/checkout@v1
13+
- name: Use Node.js ${{ matrix.node_version }}
14+
uses: actions/setup-node@v1
15+
with:
16+
node-version: ${{ matrix.node_version }}
17+
18+
- name: npm install, build and test
19+
run: |
20+
npm install
21+
npm run test

README.md

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,42 @@ npm install --save react-dynamic-fields
1414

1515
```tsx
1616
import React from 'react'
17-
import SingleField from 'react-dynamic-fields'
17+
import { SingleField, DoubleField } from 'react-dynamic-fields'
1818

1919
const App = () => {
20+
const nums = {
21+
value: '',
22+
category: ''
23+
}
2024
const [options, setOptions] = React.useState([''])
25+
const [numbers, setNumbers] = React.useState([nums])
26+
2127
return (
22-
<SingleField
23-
options={options}
24-
setOptions={setOptions}
25-
label='Types of fruits'
26-
/>
28+
<React.Fragment>
29+
<SingleField
30+
options={options}
31+
setOptions={setOptions}
32+
label='Types of fruits'
33+
/>
34+
35+
<DoubleField
36+
options={numbers}
37+
setOptions={setNumbers}
38+
initialValue={nums}
39+
data={{
40+
label: 'Fruits',
41+
name: 'fruits',
42+
types: options
43+
}}
44+
/>
45+
</React.Fragment>
2746
)
2847
}
2948
```
49+
Single Option demo
50+
![demo](example/assets/dynamic_field.gif)
3051

52+
Single and Double Field Options demo
3153
![demo](example/assets/dynamic_field.gif)
3254

3355
## License

example/assets/dynamic_field2.gif

345 KB
Loading

example/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"@types/node": "link:../node_modules/@types/node",
1818
"@types/react": "link:../node_modules/@types/react",
1919
"@types/react-dom": "link:../node_modules/@types/react-dom",
20-
"react": "^17.0.1",
20+
"react": "link:../node_modules/react",
2121
"react-dom": "link:../node_modules/react-dom",
2222
"react-dynamic-fields": "link:..",
2323
"react-scripts": "link:../node_modules/react-scripts",

example/src/App.tsx

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,38 @@
11
import React from 'react'
2-
import { SingleField } from 'react-dynamic-fields'
2+
import { SingleField, DoubleField } from 'react-dynamic-fields'
33

44
const App = () => {
5+
const nums = {
6+
value: '',
7+
category: ''
8+
}
59
const [options, setOptions] = React.useState([''])
10+
const [numbers, setNumbers] = React.useState([nums])
11+
612
return (
7-
<SingleField
8-
//@ts-ignore
9-
options={options}
10-
//@ts-ignore
11-
setOptions={setOptions}
12-
label='Types of fruits'
13-
/>
13+
<React.Fragment>
14+
<SingleField
15+
//@ts-ignore
16+
options={options}
17+
//@ts-ignore
18+
setOptions={setOptions}
19+
label='Types of fruits'
20+
/>
21+
22+
<DoubleField
23+
//@ts-ignore
24+
options={numbers}
25+
//@ts-ignore
26+
setOptions={setNumbers}
27+
initialValue={nums}
28+
data={{
29+
label: 'Fruits',
30+
name: 'fruits',
31+
//@ts-ignore
32+
types: options
33+
}}
34+
/>
35+
</React.Fragment>
1436
)
1537
}
1638

example/yarn.lock

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8754,13 +8754,9 @@ react-is@^17.0.1:
87548754
version "0.0.0"
87558755
uid ""
87568756

8757-
react@^17.0.1:
8758-
version "17.0.1"
8759-
resolved "https://registry.yarnpkg.com/react/-/react-17.0.1.tgz#6e0600416bd57574e3f86d92edba3d9008726127"
8760-
integrity sha512-lG9c9UuMHdcAexXtigOZLX8exLWkW0Ku29qPRU8uhF2R9BN96dLCt0psvzPLlHc5OWkgymP3qwTRgbnw5BKx3w==
8761-
dependencies:
8762-
loose-envify "^1.1.0"
8763-
object-assign "^4.1.1"
8757+
"react@link:../node_modules/react":
8758+
version "0.0.0"
8759+
uid ""
87648760

87658761
read-pkg-up@^2.0.0:
87668762
version "2.0.0"

jest.config.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
module.exports = {
2+
roots: ['<rootDir>'],
3+
transform: {
4+
'^.+\\.ts?$': 'ts-jest'
5+
},
6+
testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.ts?$',
7+
moduleFileExtensions: ['ts', 'js', 'json', 'node'],
8+
collectCoverage: true,
9+
clearMocks: true,
10+
coverageDirectory: 'coverage'
11+
}

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "react-dynamic-fields",
3-
"version": "0.1.0",
3+
"version": "1.0.0",
44
"description": "A package to make it easy to auto add fields in react and collect the data",
55
"author": "OlivierJM",
66
"license": "MIT",
@@ -36,7 +36,6 @@
3636
"@types/react-dom": "^16.9.7",
3737
"@typescript-eslint/eslint-plugin": "^2.26.0",
3838
"@typescript-eslint/parser": "^2.26.0",
39-
"microbundle-crl": "^0.13.10",
4039
"babel-eslint": "^10.0.3",
4140
"cross-env": "^7.0.2",
4241
"eslint": "^6.8.0",
@@ -50,6 +49,7 @@
5049
"eslint-plugin-react": "^7.17.0",
5150
"eslint-plugin-standard": "^4.0.1",
5251
"gh-pages": "^2.2.0",
52+
"microbundle-crl": "^0.13.10",
5353
"npm-run-all": "^4.1.5",
5454
"prettier": "^2.0.4",
5555
"react": "^16.13.1",

src/DoubleField.test.js

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import React from 'react'
2+
import DoubleField from './DoubleField'
3+
import { fireEvent, render, screen } from '@testing-library/react'
4+
import '@testing-library/jest-dom/extend-expect'
5+
6+
describe('DoubleField Component', () => {
7+
it('it renders and updates correctly', () => {
8+
const updateMock = jest.fn()
9+
const nums = [
10+
{
11+
value: '',
12+
category: ''
13+
},
14+
{
15+
value: '',
16+
category: ''
17+
}
18+
]
19+
const options = ['ora', 'pui', 'somfer']
20+
const container = render(
21+
<DoubleField
22+
options={nums}
23+
setOptions={updateMock}
24+
initialValue={nums}
25+
data={{
26+
label: 'Fruits',
27+
name: 'fruits',
28+
types: options
29+
}}
30+
/>
31+
)
32+
expect(container.queryByText('add Fruits')).toBeInTheDocument()
33+
expect(container.queryByText('add Fruits')).not.toBeDisabled()
34+
expect(container.queryAllByText('remove')[0]).not.toBeDisabled()
35+
expect(container.queryAllByText('remove')).toHaveLength(2)
36+
expect(
37+
screen.getAllByPlaceholderText('type your Fruits')[0]
38+
).toBeInTheDocument()
39+
expect(screen.getAllByPlaceholderText('type your Fruits')).toHaveLength(2)
40+
41+
expect(container.queryByTestId('add_field')).toHaveTextContent('add Fruits')
42+
43+
fireEvent.change(container.queryAllByTestId('option_field')[0], {
44+
target: { value: 'some value' }
45+
})
46+
expect(container.queryAllByTestId('option_field')[0].value).not.toBeNull()
47+
expect(updateMock).toBeCalled()
48+
fireEvent.click(container.queryAllByText('remove')[0])
49+
expect(updateMock).toBeCalled()
50+
})
51+
})

src/DoubleField.tsx

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import React from 'react'
2+
3+
interface DataProps {
4+
name: string
5+
label: string
6+
types: [string]
7+
}
8+
9+
interface OptionProps {
10+
category: string
11+
}
12+
13+
interface Props {
14+
data: DataProps
15+
options: [OptionProps]
16+
setOptions: (x: any) => {}
17+
label: string
18+
initialValue: OptionProps
19+
}
20+
21+
export default function DoubleField({
22+
data,
23+
initialValue,
24+
options,
25+
setOptions
26+
}: Props) {
27+
function handleRemoveField(index: number) {
28+
const values = options
29+
values.splice(index, 1)
30+
setOptions([...values])
31+
}
32+
33+
function handleAddField() {
34+
setOptions([...options, initialValue])
35+
}
36+
function handleChange(
37+
event:
38+
| React.ChangeEvent<HTMLInputElement>
39+
| React.ChangeEvent<HTMLSelectElement>,
40+
index: number
41+
) {
42+
const newValue = { [event.target.name]: event.target.value }
43+
setOptions([
44+
...options.slice(0, index),
45+
{ ...options[index], ...newValue },
46+
...options.slice(index + 1)
47+
])
48+
}
49+
return (
50+
<React.Fragment>
51+
{options.map((_option: OptionProps, i: number) => (
52+
<div key={i}>
53+
<input
54+
placeholder={`type your ${data.label}`}
55+
value={options[i][data.name]}
56+
onChange={(event) => handleChange(event, i)}
57+
name={data.name}
58+
data-testid='option_field'
59+
autoFocus
60+
/>
61+
<select
62+
name='category'
63+
value={_option.category}
64+
onChange={(event) => handleChange(event, i)}
65+
>
66+
{data.types.map((type: string) => (
67+
<option key={type} value={type}>
68+
{type}
69+
</option>
70+
))}
71+
</select>
72+
<button onClick={() => handleRemoveField(i)}>remove</button>
73+
</div>
74+
))}
75+
<button
76+
data-testid='add_field'
77+
onClick={handleAddField}
78+
>{`add ${data.label}`}</button>
79+
</React.Fragment>
80+
)
81+
}

0 commit comments

Comments
 (0)