-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Form/#16 #39
Form/#16 #39
Conversation
๐storybook: https://65ccb85d5afe55a024495bc0-ipmsclyvct.chromatic.com/ |
import { createContext } from 'react'; | ||
import type { FormState } from './form-root'; | ||
|
||
type FormContext = FormState & { formId: string }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
์ ๋ ํ์ฅ์ด ์ฝ๋ค๋ ์ด์ ๋ก ๊ฐ์ฒด์ ํ์
์ ์ ์ธํ๋ ๊ฒฝ์ฐ์ interface
๋ก ํ์
์ ์ ์ธํ๋ ๊ฒ์ ์ ํธํฉ๋๋ค. ์ธ๋ฑ์ค ์๊ทธ๋์ฒ์ ์ฌ์ฉ๊ณผ ๊ฐ์ด type
์ด ํ์ํ ๊ฒฝ์ฐ์๋ ์์ด์ ์ฌ์ฉ์ ํด์์๋๋ฐ์.
์ ํฌ ํ๋ก์ ํธ์์์ ์ ๋ฐ์ ์ผ๋ก ์ปจ๋ฒค์
ํต์ผ์ด ํ์ํ ๊ฒ ๊ฐ๋ค๋ ์๊ฐ์ด ๋๋ค์. ํน์ type
์ผ๋ก ์ ์ธํ๋ ๊ฒ์ ์ ํธํ์๋ ์ด์ ๊ฐ ์๋ค๋ฉด ๊ถ๊ธํฉ๋๋ค.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- ์ ๋ฒ ํ์ ๊ฒฐ๊ณผ๋๋ก interface๋ก ๋ง์ด๊ทธ๋ ์ด์
ํ์ต๋๋ค.
3e5be8c
form={formId} | ||
size={size} | ||
variant={variant} | ||
type="submit" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
button์ default
๊ฐ์ด submit์ด๋ผ๋ ์ด์ ๋ก ์ ๋ ๋ณ๋๋ก ์์ฑ์ ํ์ง ์๋ ํธ์ธ๋ฐ, ํน์ ์์ฑํ์๋ ํน๋ณํ ์ด์ ๊ฐ ์์ผ์๋ค๋ฉด ๊ถ๊ธํฉ๋๋ค !
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- ๊ธฐ๋ณธ๊ฐ์ด๋ผ๋ ๋ช ์์ ์ผ๋ก ์ ์ธํ๋ ๊ฒ์ ๋ ์ ํธํฉ๋๋ค. ๊ทธ๋ ๊ฒ ํ๋ฉด ์ฝ๋๋ฅผ ์ฝ๋ ์ฌ๋์ด ๋ด๋ถ ๊ธฐ๋ณธ๊ฐ์ด ๋ฌด์์ธ์ง ํ์ธํ ํ์๊ฐ ์๊ธฐ ๋๋ฌธ์ ๋๋ค.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FormNumberInput, FormPasswordInput, FormTextInput๊ฐ type๋ง ๋ณ๋๋ก ๊ฐ๋ ๋์ผํ ์ญํ ์ ํ๋ ์ปดํฌ๋ํธ์ธ ๊ฒ ๊ฐ์๋ฐ ๋ณ๋๋ก ๋ถ๋ฆฌํ์ ์ด์ ๊ฐ ๊ถ๊ธํฉ๋๋ค.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- ์ด ๋ถ๋ถ์ ๋ํด ์ ๋ ๋ง์ด ๊ณ ๋ฏผํ์ต๋๋ค. ์ค๊ณ ๋น์์ 3๊ฐ์ง input์ด ๊ฑฐ์ ๋์ผํ ๋ก์ง์ ๊ฐ์ง ๊ฒ์ด๋ผ๊ณ ์๊ฐํ์ฌ, type์ ์ด์ฉํด ํ๋์ input์ผ๋ก ๋ถ๋ฆฌํ ์ง ์ฌ๋ถ๋ฅผ ๊ณ ๋ฏผํ์ต๋๋ค.
- ๊ทธ๋ฌ๋ ์ปดํฌ๋ํธ๋ฅผ ๋ถ๋ฆฌํ ์ด์ ๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
- ํ์ฌ ์ํฉ์์๋ 3๊ฐ์ง ์ปดํฌ๋ํธ๊ฐ ์ค๋ณต๋ ๊ฒ์ฒ๋ผ ๋ณด์ผ ์ ์์ผ๋, ํ์ฅ์ด ์ด๋ฃจ์ด์ง ๊ฒฝ์ฐ์๋ ์ถฉ๋ถํ ๋ณ๊ฒฝ๋ ์ ์๋ค๊ณ ๋ด ๋๋ค. ์ด๋ type์ผ๋ก ๋ถ๋ฆฌํด ๋๋ฉด ๋์ค์ ์์ ํ๊ธฐ๊ฐ ๋งค์ฐ ์ด๋ ค์์ง๋๋ค.
- ์ฌ์ง์ด ํ์ฅ์ด ์ด๋ฃจ์ด์ง์ง ์๋๋ค ํด๋, type์ผ๋ก ๋ถ๋ฆฌํ๋ ๊ฒ๋ณด๋ค ์ปดํฌ๋ํธ ์์ค์ผ๋ก ๋ถ๋ฆฌํ๋ ๊ฒ์ด ์ฌ์ฉ์์๊ฒ ๋ ์ ์ธ์ ์ด๊ณ ๋ช ํํ๋ฉฐ, ์ปดํ์ด๋ ํจํด์๋ ์ ํฉํ๋ค๊ณ ํ๋จํฉ๋๋ค. (์: form ์ปดํฌ๋ํธ ๋ด์ input ํ์ ์ปดํฌ๋ํธ์์ type์ผ๋ก ๋ถ๊ธฐํ๋ ๊ฒ๋ณด๋ค, ํ์ ์ปดํฌ๋ํธ ์์ค์์ Number, Password, Text input์ด ์๋ ๊ฒ์ด ์ด form์์ ์ง์ํ๋ ๊ฒ์ ํ์ ํ๊ธฐ ๋ ์ฝ๋ค๊ณ ์๊ฐํฉ๋๋ค.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
๊ณ ์ํ์ จ์ต๋๋ค
app/business/auth/user.command.ts
Outdated
@@ -0,0 +1,67 @@ | |||
'use server'; | |||
|
|||
import { State } from '@/app/ui/view/molecule/form/form-root'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
form root ํ์ผ์์ state ๋ฅผ export ํ์ง ์๋๋ค๋ ์๋ฌ๊ฐ ๋จ๋ค์ ํ์ธ ๋ถํ๋๋ฆฝ๋๋ค!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- ์์ ํ์ต๋๋ค. ๊ณ ๋ง์ต๋๋ค.
6b0bef3
app/business/auth/user.command.ts
Outdated
type User = z.infer<typeof SignUpFormSchema>; | ||
|
||
export async function createUser(prevState: State, formData: FormData): Promise<State> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
User ํ์ ๊ณผ prevState๋ ์ฌ์ฉํ์ง ์๋ ๊ฒ ๊ฐ์๋ฐ ๋ฐ๋ก ์ ์ธํ์ ์ด์ ๊ฐ ์์๊น์ฉ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- User ํ์ ์ ํ์๊ฐ์ ๋ก์ง์ ๊ตฌํํ ๋ ์ฌ์ฉํ ์์ ์ ๋๋ค.
- react-dom์ useFormState๋ฅผ ์ฌ์ฉํ ๋ ์ปจ๋ฒค์ ์ ๋๋ค
.max(20, { | ||
message: 'User ID must be at most 20 characters', | ||
}), | ||
password: z.string().regex(/^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!^%*#?&])[A-Za-z\d@$!^%*#?&]{8,}$/, { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
placeholder์๋ ๊ธฐํธ๋ฅผ ํฌํจํ 8์ ์ด์ 20์ ์ดํ์ธ๋ฐ ์ ๊ท์์๋ ์ซ์๊ฐ ๋ฌด์กฐ๊ฑด ํฌํจ๋๋๋ก ๋ง๋์ ๊ฒ ๊ฐ์ต๋๋ค! ํ์ธ ๋ถํ๋๋ฆฝ๋๋น
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- ๊ธฐ์กด placeholder๋ฅผ ๊ทธ๋๋ก ์ฎ๊ฒผ์ง๋ง, ์ด๋ฒ์ ๋น๋ฐ๋ฒํธ ๊ท์น์ ์ ๊ทํํ์์ ๋ฐ๋ผ ์์ ํ ๊ณํ์ ๋๋ค. ๋ก์ง์ ๊ตฌํํ ๋ placeholder๋ ์์ ํ๊ฒ ์ต๋๋ค.
app/ui/view/molecule/form/index.tsx
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Required ํ๋์๋ ํ์ ์ ๋ ฅ ํ์ (ex ๋ณํ)๊ฐ ์์ผ๋ฉด ux์ ์ผ๋ก ๋ ์ข์ ๊ฒ ๊ฐ์ต๋๋ค!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- ์์ ํ์ต๋๋ค. ๋๋ถ์ ๋ ์ข์ ๊ฒฐ๊ณผ๋ฌผ์ด ๋์๋ค์:)
a65ada4
variant?: 'primary' | 'secondary'; | ||
size?: 'xs' | 'sm' | 'md' | 'lg' | 'default'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
button component props์์ ๊ฐ์ ธ์์ ์ฌ์ฉํด๋ ๋ ๊ฒ ๊ฐ์์~!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
||
export const SignUpFormActionWithFailure: Story = { | ||
...SingUpFormTemplate, | ||
play: async ({ canvasElement }) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
play ๊ธฐ๋ฅ ์ ๋ง ์ ๊ธฐํ๋ค์ ๐๐ป
import type { Meta, StoryObj } from '@storybook/react'; | ||
|
||
import Form from '.'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
import๋ฌธ ๋์ด์ฐ๊ธฐ๋ก ๋ถ๋ฆฌํ์ ๊ฒ ๊ฐ๋ ์ฑ์ ์ํด์ ์๋ํ์ ๊ฑฐ ๋ง์๊น์?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- ๋ค, ๋ง์ต๋๋ค. ์ค์ ๋ก๋ ๋ค๋ฅธ ์ฑ์ง์ import๋ฅผ ๋ถ๋ฆฌํ๋ ๊ฒ์ ์ ํธํ๋ฉฐ, storybook docs์ ์คํ์ผ์ ๋ฐ๋ฅธ ๊ฒ์ด๊ธฐ๋ ํฉ๋๋ค.
https://storybook.js.org/docs/writing-stories/args#story-args
errors: Record<string, string[] | undefined>; | ||
}; | ||
|
||
// Refactor: ์ ๋ฅผ ๋ฐ๋ก ๋นผ๊ณ ์ถ์๋ฐ utils ํด๋์ ์ด๋ค ํ์ผ๋ช ์ด ์ข์๊น? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ํ์ ์ปดํฌ๋ํธ๋ค์ ๋ค๋ฃจ๋ ํจ์์ด๊ธฐ์ component.util.ts๋ ๊ด์ฐฎ์ ๊ฒ ๊ฐ๋ค์ฉ
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
form์์๋ง ์ฌ์ฉ๋ ๊ฒ์ผ๋ก ์์๋๋ค๋ฉด ๊ฐํ์ด๊ฐ ์๊ฒฌ ๋ด์ค ๊ฒ ์ฒ๋ผ form.util.ts๋ ๊ด์ฐฎ์ ๊ฒ ๊ฐ๊ณ , ์ฌ์ฌ์ฉ์ด ์์๋๋ค๋ฉด filter๊ธฐ๋ฅ์ ํ๋ค๋ ์๋ฏธ์์ filter.ts๋ ์ด๋ค๊ฐ์?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
์ ์ ๋ form.util.ts์ฒ๋ผ ์ปดํฌ๋ํธ_์ด๋ฆ.util.ts๊ฐ ์๋ component.util.ts ๋ค์ด๋ฐ์ ์๋ฏธํ์ต๋๋ค!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- component.util.ts๋ก ์ถ์ถํ์ต๋๋ค. ์ฌ์ค ํผ๋์ ์ฌ์ง๊ฐ ์ข ์๋ ๊ฒ ๊ฐ์์ ์์ฃผ ๋ง์์ ๋ค์ง ์๋๋ฐ, ์ฐจ์ ์ธ ๊ฒ ๊ฐ์ต๋๋ค. ํ๋ค๊ฐ ๋ ์ข์ ๋ค์ด๋ฐ์ด ๋ ์ค๋ฅด๋ฉด ๋ณ๊ฒฝํ๊ฒ ์ต๋๋ค
71a471c
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ํ์ฌ submit button์ ํด๋ฆญํ์ ๋ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ์งํํ๋ ๊ฒ์ผ๋ก ๋ณด์
๋๋ค!
onChange ๋๋ ๋ค๋ฅธ ํ๋๋ฅผ ์ ํํ ๊ฒฝ์ฐ์๋ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ์งํํ ์ ์๋๋ฐ ์ ๋ฐฉ์์ ์ ํํ์ ์ด์ ๊ฐ ๋ฐ๋ก ์๋ ์ง ๊ถ๊ธํฉ๋๋ค!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Next docs์ ๋์ค๋ Server Actions๋ฅผ ์ฌ์ฉํ์ฌ ํผ์ ๊ตฌํํ๋ ค ํ์ต๋๋ค. ์ด ๋ฐฉ๋ฒ์ด RSC ์ฌ์ฉ ๋ชฉ์ ์ ๋ ์ ํฉํ๋ค๊ณ ์๊ฐํ์ต๋๋ค.
- ์์งํ, ์ฌ์ฉํด๋ณด๋ Server Actions์ ์ฅ์ ์ ์์ง ์ ๋ชจ๋ฅด๊ฒ ์ต๋๋ค. onChange๋ ๋ค๋ฅธ ํ๋ ์ ํ ์ ๋ณด์ฌ์ฃผ๋ UX ๊ด์ ์์๋ ๋ ์ข์ง ์์๊น ์๊ฐ๋ฉ๋๋ค.
- ์ด์ ๊ด๋ จํ์ฌ Next์ ๋ฌธ์๋ฅผ ์ฝ์ด๋ดค๋๋ฐ, ์บ์ฑ, ๋ณด์, ์ ์ง์ ํฅ์ ์ธก๋ฉด์์ ์ฅ์ ์ด ์๋ค๊ณ ํฉ๋๋ค.
- ๋๋ถ๋ถ์ ๊ณต๊ฐํ๊ธฐ ์ด๋ ต์ง๋ง, ๊ธฐ์กด์ ํด๋ผ์ด์ธํธ ์ธก์์ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ์งํํ๋ ๊ฒ์ ๋ฐํด ์๋ฒ ์ก์ ์ ์ฌ์ฉํ๋ฉด ์๋ฒ ์ธก์์ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ์งํํ๋ ์ํคํ ์ฒ๋ก ๋ณ๊ฒฝ๋๋ฉฐ, ์ด๋ ๋ณด์ ์ธก๋ฉด์์ ํ์คํ ์ข์ ๊ฒ ๊ฐ์ต๋๋ค.
https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations#server-side-validation-and-error-handling
https://nextjs.org/blog/security-nextjs-server-components-actions
https://nextjs.org/learn/dashboard-app/mutating-data
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
server action์ผ๋ก ๊ตฌํํ์ ๋ ๋ณด์๊ณผ ์ฑ๋ฅ์ ์ฅ์ ์ด ์๋ค์
๊ธฐ์ ์ ์ผ๋ก server action์ ์ฌ์ฉํจ์ผ๋ก์จ ์ข์ ์๋๋ผ๊ณ ์๊ฐํฉ๋๋ค!
ux์ ์ผ๋ก๋ on change๊ฐ ๋ ์ข์๊ฒ ๊ฐ์ง๋ง ์ ์ ์ฃผ๊ด์ ์ธ ํ๋จ์ด ์๋๊น ์ถ๋ค์
์กธ๋ถ ํ์๊ฐ์
form์ด ์ฌ์ฉ์์๊ฒ ๊ทธ๋ ๊ฒ ์ด๋ ต์ง ์์ ์ , placeholder ์ด ์์ฑํด์ผ ํ๋ ๋ฐฉํฅ์ ์๋ ค์ฃผ๋ ์ ์ ๊ณ ๋ คํด๋ดค์ ๋ ์ผ๋จ์ onsubmit์ผ๋ก ์งํํด๋ ์ข์ ๊ฒ ๊ฐ๋ค์!
errors: Record<string, string[] | undefined>; | ||
}; | ||
|
||
// Refactor: ์ ๋ฅผ ๋ฐ๋ก ๋นผ๊ณ ์ถ์๋ฐ utils ํด๋์ ์ด๋ค ํ์ผ๋ช ์ด ์ข์๊น? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
form์์๋ง ์ฌ์ฉ๋ ๊ฒ์ผ๋ก ์์๋๋ค๋ฉด ๊ฐํ์ด๊ฐ ์๊ฒฌ ๋ด์ค ๊ฒ ์ฒ๋ผ form.util.ts๋ ๊ด์ฐฎ์ ๊ฒ ๊ฐ๊ณ , ์ฌ์ฌ์ฉ์ด ์์๋๋ค๋ฉด filter๊ธฐ๋ฅ์ ํ๋ค๋ ์๋ฏธ์์ filter.ts๋ ์ด๋ค๊ฐ์?
@@ -35,10 +47,40 @@ export const SelectRoot = React.forwardRef<HTMLInputElement, SelectProps>(functi | |||
|
|||
return ( | |||
<div className="w-full min-w-[10rem] relative text-base"> | |||
<select | |||
title="select-hidden" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ํด๋น ์จ๊ฒจ์ง select๋ ์ด๋ป๊ฒ ์ฐ์ด๋์?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Headless UI์ listbox๋ฅผ form์์ ์ฌ์ฉํ ๋, hidden input์ ๋ง๋ค์ด์ ๊ฐ์ ์จ๊ธฐ๋ ๋ฐฉ์์ด ๋ง์์ ๋ค์ง ์์์, ์ง์ hidden select๋ฅผ ๋ง๋ค์์ต๋๋ค.
https://headlessui.com/react/listbox#using-with-html-forms
- ์ด๋ฐ ๋ฐฉ์์ด ๋ง์์ ๋ค์ง ์๋ ์ด์ ๋ ๋ค์๊ณผ ๊ฐ์์.
- Select์ ๊ฐ์ด ๋์ํ์ง๋ง, input์ value๋ฅผ ์จ๊ฒจ๋๊ณ ์์ด์ ์คํด๊ฐ ์๊ธธ ์ ์์ต๋๋ค.
- ์ด๋ฅผ ์๋ก ๋ค๋ฉด, Storybook play๋ฅผ ์์ฑํ ๋, select tag๋ฅผ ์๋์ ๊ฐ์ด selector๋ก ์ฌ์ฉํ ์ ์๋ค๊ณ ์๊ฐํ ์ ์์ต๋๋ค.
await userEvent.selectOptions(canvas.getByLabelText('์์ด', { selector: 'select' }), 'level12', {
delay: 100,
});
- ํ์ง๋ง, ์ด๋ ๊ฒ ํ๋ฉด ์ค๋ฅ๊ฐ ๋ฐ์ํฉ๋๋ค. select๊ฐ ์๋ hidden input์ value๊ฐ ์๊ธฐ ๋๋ฌธ์, selector๋ฅผ input์ผ๋ก ์ค์ ํด์ผ ํฉ๋๋ค.
- ์ ๋ ์ด ์ฌํญ์ ์๊ณ ์์ด์ ๋์ฒ๊ฐ ๊ฐ๋ฅํ์ง๋ง, ๋ค๋ฅธ ์ฌ๋๋ค์ด ์ฌ์ฉํ ๋๋ ์ด ์ฌํญ์ ๋ชจ๋ฅด๊ณ ๋ด๋ถ ๊ตฌํ์ ์ฐพ์๋ด์ผ ํ ์๋ ์์ต๋๋ค. ์ด๋ฐ ์คํด๋ฅผ ์์ ๊ธฐ ์ํด hidden select๋ฅผ ๊ตฌํํ์์ต๋๋ค.
|
๐storybook: https://65ccb85d5afe55a024495bc0-jbitqugsgx.chromatic.com/ |
๐ ์์ ๋ด์ฉ
๐ค ๊ณ ๋ฏผ ํ๋ ๋ถ๋ถ
๐ ๋์์ด ํ์ํ ๋ถ๋ถ