Skip to content

Commit

Permalink
馃拡 useFieldArray (#768)
Browse files Browse the repository at this point in the history
* POC for useFieldArray

* clean up code

* update from code review

* adding unit tests

* include use context API

* update with more unit tests

* remove update

* use control prop instead of individual props

* fix delete issue with useFieldArray

* add new method swap

* include move

* chnage any to objec type

* chnage any to objec type

* fix type

* include more unit tests for methods

* automation working in progress

* WIP with automation

* pass automation test

* update generate ID function

* ts-ignore generate ID function

* add generics for useFieldArray

* rename type from WithOptionalId to WithFieldId

* fix useFieldArray app

* update unit test with default value useFieldArray

* fix an issue when user add or delete items

* update unit tests

* fix issue around reset with useForm

* code clean up

* clean up logic on reset

* fix a bug catched with automation

* remove generate id with crypto

* remove fields reference in control

* bring back the fields

* Update package.json

* fix issue around delete node

* fix automation and issue around api

* update unit test

* fix automation script

* code clean up

* fix a bug with move and update automation with submit data

* fix unit tests

* include generate id with performance.now

* remove unnecessary variable

* clean up generateID

* add sideEffect false

* revert sideeffect

* fix issue around defaultValues

* minor code clean up

* convert string check logic into a function

* clean up the code

* code clean up at reset

* more code clean up

* fix review comment

Co-authored-by: Kotaro Sugawara <kotarella1110@gmail.com>
  • Loading branch information
bluebill1049 and kotarella1110 committed Jan 14, 2020
1 parent 0525d8a commit a6cbd94
Show file tree
Hide file tree
Showing 16 changed files with 766 additions and 29 deletions.
2 changes: 2 additions & 0 deletions app/src/app.tsx
Expand Up @@ -20,6 +20,7 @@ import SetValueWithSchema from './setValueWithSchema';
import SetValueWithTrigger from './setValueWithTrigger';
import IsValid from './isValid';
import Controller from './controller';
import UseFieldArray from './useFieldArray';

const App: React.FC = () => {
return (
Expand Down Expand Up @@ -54,6 +55,7 @@ const App: React.FC = () => {
component={SetValueWithTrigger}
/>
<Route path="/conditionalField" exact component={ConditionalField} />
<Route path="/UseFieldArray/:mode" exact component={UseFieldArray} />
<Route path="/reset" exact component={Reset} />
<Route path="/setValue" exact component={SetValue} />
<Route path="/setValueWithSchema" exact component={SetValueWithSchema} />
Expand Down
112 changes: 112 additions & 0 deletions app/src/useFieldArray.tsx
@@ -0,0 +1,112 @@
import React from 'react';
import { useForm, useFieldArray, Controller } from 'react-hook-form';

let renderCount = 0;

const UseFieldArray: React.FC = (props: any) => {
const { control, handleSubmit, register } = useForm<{
data: { name: string }[];
}>({
...(props.match.params.mode === 'default'
? {
defaultValues: {
data: [{ name: 'test' }, { name: 'test1' }, { name: 'test2' }],
},
}
: {}),
});
const {
fields,
append,
prepend,
swap,
move,
insert,
remove,
} = useFieldArray<{ name: string }>({
control,
name: 'data',
});
const [data, setData] = React.useState([]);
const onSubmit = (data: any) => {
setData(data);
};

renderCount++;

return (
<form onSubmit={handleSubmit(onSubmit)}>
<ul>
{fields.map((data, index) => (
<li key={data.id}>
{index % 2 ? (
<input
key={data.id}
name={`data[${index}].name`}
defaultValue={data.name}
data-order={index}
ref={register}
/>
) : (
<Controller
as={<input />}
control={control}
name={`data[${index}].name`}
defaultValue={data.name}
data-order={index}
/>
)}
<button className={`delete${index}`}>delete</button>
</li>
))}
</ul>

<button
id="append"
type="button"
onClick={() => append({ name: renderCount.toString() })}
>
append
</button>

<button
id="prepend"
type="button"
onClick={() => prepend({ name: renderCount.toString() })}
>
prepend
</button>

<button id="swap" onClick={() => swap(1, 2)} type="button">
swap
</button>

<button id="move" onClick={() => move(2, 0)} type="button">
move
</button>

<button
id="insert"
type="button"
onClick={() => insert(1, { name: renderCount.toString() })}
>
insert
</button>

<button id="remove" type="button" onClick={() => remove(1)}>
remove
</button>

<button id="removeAll" type="button" onClick={() => remove()}>
remove all
</button>

<button id="submit">Submit</button>

<div id="renderCount">{renderCount}</div>
<div id="result">{JSON.stringify(data)}</div>
</form>
);
};

export default UseFieldArray;
216 changes: 216 additions & 0 deletions cypress/integration/useFieldArray.ts
@@ -0,0 +1,216 @@
context('useFieldArray', () => {
it('should behaviour correctly without defaultValues', () => {
cy.visit('http://localhost:3000/useFieldArray/normal');

cy.get('#append').click();
cy.get('ul > li')
.its('length')
.should('equal', 1);

cy.get('#submit').click();
cy.get('#result').contains('{"data":[{"name":"1"}]}');

cy.get('#prepend').click();
cy.get('ul > li')
.its('length')
.should('equal', 2);

cy.get('ul > li')
.eq(0)
.get('input')
.should('have.value', '4');

cy.get('#append').click();
cy.get('ul > li')
.its('length')
.should('equal', 3);

cy.get('ul > li')
.eq(2)
.find('input')
.should('have.value', '5');

cy.get('#submit').click();
cy.get('#result').contains(
'{"data":[{"name":"4"},{"name":"1"},{"name":"5"}]}',
);

cy.get('#swap').click();
cy.get('ul > li')
.eq(1)
.find('input')
.should('have.value', '5');
cy.get('ul > li')
.eq(2)
.find('input')
.should('have.value', '1');

cy.get('#submit').click();
cy.get('#result').contains(
'{"data":[{"name":"4"},{"name":"5"},{"name":"1"}]}',
);

cy.get('#move').click();
cy.get('ul > li')
.eq(0)
.find('input')
.should('have.value', '1');
cy.get('ul > li')
.eq(1)
.find('input')
.should('have.value', '4');

cy.get('#submit').click();
cy.get('#result').contains(
'{"data":[{"name":"1"},{"name":"4"},{"name":"5"}]}',
);

cy.get('#insert').click();
cy.get('ul > li')
.eq(1)
.find('input')
.should('have.value', '14');

cy.get('#submit').click();
cy.get('#result').contains(
'{"data":[{"name":"1"},{"name":"14"},{"name":"4"},{"name":"5"}]}',
);

cy.get('#remove').click();
cy.get('ul > li')
.eq(0)
.find('input')
.should('have.value', '1');
cy.get('ul > li')
.eq(1)
.find('input')
.should('have.value', '4');

cy.get('#submit').click();
cy.get('#result').contains(
'{"data":[{"name":"1"},{"name":"4"},{"name":"5"}]}',
);

cy.get('#removeAll').click();
cy.get('ul > li').should('have.length', 0);

cy.get('#submit').click();
cy.get('#result').contains('{}');

cy.get('#renderCount').contains('23');
});

it.only('should behaviour correctly with defaultValue', () => {
cy.visit('http://localhost:3000/useFieldArray/default');

cy.get('ul > li')
.its('length')
.should('equal', 3);

cy.get('ul > li')
.eq(0)
.find('input')
.should('have.value', 'test');

cy.get('ul > li')
.eq(1)
.find('input')
.should('have.value', 'test1');

cy.get('ul > li')
.eq(2)
.find('input')
.should('have.value', 'test2');

cy.get('#append').click();

cy.get('ul > li')
.eq(3)
.find('input')
.should('have.value', '1');

cy.get('#submit').click();
cy.get('#result').contains(
'{"data":[{"name":"test"},{"name":"test1"},{"name":"test2"},{"name":"1"}]}',
);

cy.get('#prepend').click();
cy.get('ul > li')
.its('length')
.should('equal', 5);

cy.get('ul > li')
.eq(0)
.get('input')
.should('have.value', '4');

cy.get('#submit').click();
cy.get('#result').contains(
'{"data":[{"name":"4"},{"name":"test"},{"name":"test1"},{"name":"test2"},{"name":"1"}]}',
);

cy.get('#swap').click();
cy.get('ul > li')
.eq(1)
.find('input')
.should('have.value', 'test1');
cy.get('ul > li')
.eq(2)
.find('input')
.should('have.value', 'test');

cy.get('#submit').click();
cy.get('#result').contains(
'{"data":[{"name":"4"},{"name":"test1"},{"name":"test"},{"name":"test2"},{"name":"1"}]}',
);

cy.get('#move').click();
cy.get('ul > li')
.eq(0)
.find('input')
.should('have.value', 'test');
cy.get('ul > li')
.eq(1)
.find('input')
.should('have.value', '4');

cy.get('#submit').click();
cy.get('#result').contains(
'{"data":[{"name":"test"},{"name":"4"},{"name":"test1"},{"name":"test2"},{"name":"1"}]}',
);

cy.get('#insert').click();
cy.get('ul > li')
.eq(1)
.find('input')
.should('have.value', '13');

cy.get('#submit').click();
cy.get('#result').contains(
'{"data":[{"name":"test"},{"name":"13"},{"name":"4"},{"name":"test1"},{"name":"test2"},{"name":"1"}]}',
);

cy.get('#remove').click();
cy.get('ul > li')
.eq(0)
.find('input')
.should('have.value', 'test');
cy.get('ul > li')
.eq(1)
.find('input')
.should('have.value', '4');

cy.get('#submit').click();
cy.get('#result').contains(
'{"data":[{"name":"test"},{"name":"4"},{"name":"test1"},{"name":"test2"},{"name":"1"}]}',
);

cy.get('#removeAll').click();
cy.get('ul > li').should('have.length', 0);

cy.get('#submit').click();
cy.get('#result').contains('{}');

cy.get('#renderCount').contains('22');
});
});
6 changes: 3 additions & 3 deletions package.json
@@ -1,10 +1,11 @@
{
"name": "react-hook-form",
"description": "Performant, flexible and extensible forms library for React Hooks",
"version": "4.4.8",
"version": "4.5.0-beta.12",
"main": "dist/react-hook-form.js",
"module": "dist/react-hook-form.es.js",
"types": "dist/index.d.ts",
"sideEffects": true,
"scripts": {
"clean": "rm -rf dist",
"prebuild": "yarn clean",
Expand All @@ -26,7 +27,6 @@
"prepublishOnly": "yarn lint && yarn test && yarn run clean && yarn build",
"start:app": "yarn build && yarn link && yarn --cwd node_modules/react link && yarn --cwd ./app link react react-hook-form && yarn --cwd ./app && yarn --cwd ./app run start"
},
"sideEffects": false,
"keywords": [
"react",
"hooks",
Expand Down Expand Up @@ -73,7 +73,7 @@
"bundlesize": [
{
"path": "./dist/react-hook-form.min.es.js",
"maxSize": "6.5 kB"
"maxSize": "7 kB"
}
],
"peerDependencies": {
Expand Down

0 comments on commit a6cbd94

Please sign in to comment.