Skip to content

Commit 302ede7

Browse files
committed
initial commit; v0.0.1
1 parent 5ec3e85 commit 302ede7

22 files changed

+8255
-1
lines changed

.eslintignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/dist
2+
/src/vendor

.eslintrc.json

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
{
2+
"parser": "@typescript-eslint/parser",
3+
"plugins": [
4+
"@typescript-eslint",
5+
"prettier"
6+
],
7+
"extends": [
8+
"plugin:@typescript-eslint/recommended",
9+
"airbnb",
10+
"plugin:prettier/recommended"
11+
],
12+
"env": {
13+
"browser": true
14+
},
15+
"globals": {
16+
"JSX": "readonly"
17+
},
18+
"settings": {
19+
"import/resolver": {
20+
"node": {
21+
"extensions": [".js", ".ts", ".tsx"]
22+
}
23+
}
24+
},
25+
"rules": {
26+
"react/jsx-uses-react": "off",
27+
"react/react-in-jsx-scope": "off",
28+
"@typescript-eslint/explicit-function-return-type": "off",
29+
"@typescript-eslint/explicit-module-boundary-types": "off",
30+
"@typescript-eslint/no-explicit-any": "off",
31+
"react/jsx-filename-extension": ["error", { "extensions": [".js", ".tsx"] }],
32+
"react/prop-types": "off",
33+
"react/jsx-one-expression-per-line": "off",
34+
"import/extensions": ["error", "never"],
35+
"import/prefer-default-export": "off",
36+
"import/no-unresolved": ["error", { "ignore": ["jotai-form"] }],
37+
"no-param-reassign": "off",
38+
"no-plusplus": "off",
39+
"no-bitwise": "off",
40+
"symbol-description": "off",
41+
"prefer-object-spread": "off",
42+
"no-use-before-define": "off",
43+
"no-unused-vars": "off",
44+
"no-redeclare": "off",
45+
"react/function-component-definition": ["error", { "namedComponents": "arrow-function" }]
46+
},
47+
"overrides": [{
48+
"files": ["__tests__/**/*"],
49+
"env": {
50+
"jest": true
51+
},
52+
"rules": {
53+
"import/no-extraneous-dependencies": ["error", { "devDependencies": true }]
54+
}
55+
}, {
56+
"files": ["examples/**/*"],
57+
"rules": {
58+
"import/no-extraneous-dependencies": "off"
59+
}
60+
}]
61+
}

.github/workflows/cd.yml

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
name: CD
2+
3+
on:
4+
push:
5+
tags:
6+
- v*
7+
8+
jobs:
9+
publish:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- uses: actions/checkout@v2
13+
14+
- name: Setup Node
15+
uses: actions/setup-node@v1
16+
with:
17+
node-version: '12.x'
18+
registry-url: 'https://registry.npmjs.org'
19+
20+
- name: Get yarn cache
21+
id: yarn-cache
22+
run: echo "::set-output name=dir::$(yarn cache dir)"
23+
24+
- name: Cache dependencies
25+
uses: actions/cache@v1
26+
with:
27+
path: ${{ steps.yarn-cache.outputs.dir }}
28+
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
29+
restore-keys: |
30+
${{ runner.os }}-yarn-
31+
32+
- name: Install dependencies
33+
run: yarn install
34+
35+
- name: Test
36+
run: yarn test
37+
38+
- name: Compile
39+
run: yarn run compile
40+
41+
- name: Publish
42+
run: npm publish
43+
env:
44+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

.github/workflows/ci.yml

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
pull_request:
6+
7+
jobs:
8+
test:
9+
runs-on: ubuntu-latest
10+
steps:
11+
- uses: actions/checkout@v2
12+
13+
- name: Setup Node
14+
uses: actions/setup-node@v1
15+
with:
16+
node-version: '12.x'
17+
18+
- name: Get yarn cache
19+
id: yarn-cache
20+
run: echo "::set-output name=dir::$(yarn cache dir)"
21+
22+
- name: Cache dependencies
23+
uses: actions/cache@v1
24+
with:
25+
path: ${{ steps.yarn-cache.outputs.dir }}
26+
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
27+
restore-keys: |
28+
${{ runner.os }}-yarn-
29+
30+
- name: Install dependencies
31+
run: yarn install
32+
33+
- name: Test
34+
run: yarn test

.gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
*~
2+
*.swp
3+
node_modules
4+
/dist

.prettierrc

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"singleQuote": true,
3+
"trailingComma": "all"
4+
}

LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2022 Daishi Kato
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in
13+
all copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
THE SOFTWARE.

README.md

+6-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,6 @@
1-
# jotai-form
1+
# jotai-form
2+
3+
👻📃
4+
5+
## Tweets
6+

__tests__/01_basic_spec.tsx

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { fireEvent, render, waitFor } from '@testing-library/react';
2+
import React from 'react';
3+
import { useAtom } from 'jotai';
4+
import { atomWithValidate } from '../src/index';
5+
6+
describe('atomWithValidate spec', () => {
7+
it('only number', async () => {
8+
const dataAtom = atomWithValidate(0, {
9+
validate: (v: unknown) => {
10+
const n = Number(v);
11+
if (Number.isNaN(n)) {
12+
throw new Error('not a number');
13+
}
14+
return n;
15+
},
16+
});
17+
const Component = () => {
18+
const [{ value, isValid }, setValue] = useAtom(dataAtom);
19+
return (
20+
<>
21+
<div>value: {value}</div>
22+
<div>isValid: {JSON.stringify(isValid)}</div>
23+
<button type="button" onClick={() => setValue(1)}>
24+
set 1
25+
</button>
26+
<button type="button" onClick={() => setValue('a' as any)}>
27+
set a
28+
</button>
29+
</>
30+
);
31+
};
32+
33+
const { getByText } = render(
34+
<div>
35+
<Component />
36+
</div>,
37+
);
38+
39+
await waitFor(() => {
40+
getByText('value: 0');
41+
getByText('isValid: true');
42+
});
43+
44+
fireEvent.click(getByText('set 1'));
45+
await waitFor(() => {
46+
getByText('value: 1');
47+
getByText('isValid: true');
48+
});
49+
50+
fireEvent.click(getByText('set a'));
51+
await waitFor(() => {
52+
getByText('value: a');
53+
getByText('isValid: false');
54+
});
55+
});
56+
});

examples/01_minimal/package.json

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"name": "jotai-form-example",
3+
"version": "0.1.0",
4+
"private": true,
5+
"dependencies": {
6+
"jotai": "latest",
7+
"jotai-form": "latest",
8+
"react": "latest",
9+
"react-dom": "latest",
10+
"react-scripts": "latest"
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+
"browserslist": [
19+
">0.2%",
20+
"not dead",
21+
"not ie <= 11",
22+
"not op_mini all"
23+
]
24+
}

examples/01_minimal/public/index.html

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<html>
2+
<head>
3+
<title>jotai-form example</title>
4+
</head>
5+
<body>
6+
<div id="app"></div>
7+
</body>
8+
</html>

examples/01_minimal/src/index.js

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import React from 'react';
2+
import { createRoot } from 'react-dom/client';
3+
import { useAtom } from 'jotai';
4+
import { atomWithValidate } from 'jotai-form';
5+
6+
const fieldAtom = atomWithValidate(0, {
7+
validate: (v) => {
8+
const n = Number(v);
9+
if (Number.isNaN(n)) {
10+
throw new Error('not a number');
11+
}
12+
return n;
13+
},
14+
});
15+
16+
const Field = () => {
17+
const [state, setValue] = useAtom(fieldAtom);
18+
return (
19+
<div>
20+
<span>{state.isDirty && '*'}</span>
21+
<input value={state.value} onChange={(e) => setValue(e.target.value)} />
22+
<span>{state.isValid ? 'Valid' : `${state.error}`}</span>
23+
</div>
24+
);
25+
};
26+
27+
const App = () => {
28+
return (
29+
<div>
30+
<Field />
31+
</div>
32+
);
33+
};
34+
35+
createRoot(document.getElementById('app')).render(<App />);

examples/02_typescript/package.json

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"name": "jotai-form-example",
3+
"version": "0.1.0",
4+
"private": true,
5+
"dependencies": {
6+
"@types/react": "latest",
7+
"jotai": "latest",
8+
"jotai-form": "latest",
9+
"react": "latest",
10+
"react-dom": "latest",
11+
"react-scripts": "latest",
12+
"typescript": "latest",
13+
"yup": "latest"
14+
},
15+
"scripts": {
16+
"start": "react-scripts start",
17+
"build": "react-scripts build",
18+
"test": "react-scripts test",
19+
"eject": "react-scripts eject"
20+
},
21+
"browserslist": [
22+
">0.2%",
23+
"not dead",
24+
"not ie <= 11",
25+
"not op_mini all"
26+
]
27+
}
+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<html>
2+
<head>
3+
<title>jotai-form example</title>
4+
</head>
5+
<body>
6+
<div id="app"></div>
7+
</body>
8+
</html>

examples/02_typescript/src/App.tsx

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import React from 'react';
2+
import { useAtom } from 'jotai';
3+
import { atomWithValidate } from 'jotai-form';
4+
import { number } from 'yup';
5+
6+
const sleep = (ms: number) =>
7+
new Promise((resolve) => {
8+
setTimeout(resolve, ms);
9+
});
10+
11+
const schema = number().required().min(0).max(100);
12+
13+
const fieldAtom = atomWithValidate(0, {
14+
validate: async (v) => {
15+
await sleep(500);
16+
return schema.validate(v);
17+
},
18+
});
19+
20+
const Field = () => {
21+
const [state, setValue] = useAtom(fieldAtom);
22+
return (
23+
<div>
24+
<span>{state.isDirty && '*'}</span>
25+
<input
26+
value={state.value}
27+
onChange={(e) => setValue(e.target.value as any)}
28+
/>
29+
<span>{state.isValidating && 'Validating...'}</span>
30+
<span>{!state.isValidating && state.isValid && 'Valid'}</span>
31+
<span>{!state.isValidating && !state.isValid && `${state.error}`}</span>
32+
</div>
33+
);
34+
};
35+
36+
const App = () => {
37+
return (
38+
<div>
39+
<Field />
40+
</div>
41+
);
42+
};
43+
44+
export default App;

0 commit comments

Comments
 (0)