{"_id":"@albatro33/sy-funcs","name":"@albatro33/sy-funcs","dist-tags":{"latest":"1.1.0"},"versions":{"1.1.0":{"name":"@albatro33/sy-funcs","version":"1.1.0","description":"커링 기반 React 상태 업데이트 + Validation 훅 라이브러리. immutable.js 스타일의 편리함을 순수 JS 또는 Immer로 제공합니다.","main":"./dist/index.js","module":"./dist/index.mjs","types":"./dist/index.d.ts","exports":{".":{"import":{"types":"./dist/index.d.mts","default":"./dist/index.mjs"},"require":{"types":"./dist/index.d.ts","default":"./dist/index.js"}}},"scripts":{"build":"tsup","dev":"tsup --watch","typecheck":"tsc --noEmit","prepublishOnly":"npm run build","playground":"cd playground && npm run dev"},"keywords":["react","hooks","state-management","currying","immutable","validation","form","useChange","useUpdate","setIn","immer","typescript","nested-objects","deep-update","form-validation"],"author":{"name":"Albatro33","email":"cokujin518@gmail.com"},"license":"MIT","repository":{"type":"git","url":"git+https://github.com/albatro33/sy-funcs.git"},"homepage":"https://github.com/albatro33/sy-funcs#readme","bugs":{"url":"https://github.com/albatro33/sy-funcs/issues"},"peerDependencies":{"react":">=17.0.0","immer":">=10.0.0"},"peerDependenciesMeta":{"immer":{"optional":true}},"devDependencies":{"@types/node":"^22.10.2","@types/react":"^19.0.1","immer":"^11.1.0","react":"^18.3.1","tsup":"^8.3.5","typescript":"^5.7.2"},"_id":"@albatro33/sy-funcs@1.1.0","gitHead":"1cf23919269cd68bb4fab7817e73711431c3435c","_nodeVersion":"22.17.1","_npmVersion":"10.9.2","dist":{"integrity":"sha512-C21pbiH8URYiqq7nV07sZBDTwlRN8tCp4IXhS7XTMgmAEk2EuMmR29jtM21sNKZGkFkHf9qhObY3DPAIaijvdg==","shasum":"b9b35f848829c7a89d2ed4e58b3056daf9504a98","tarball":"https://registry.npmjs.org/@albatro33/sy-funcs/-/sy-funcs-1.1.0.tgz","fileCount":12,"unpackedSize":155849,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDPUSoEA4XKJtlrFgwSAdUyJ3kHRg0Sk2tTZ+Ccq/kp7AIgU0ea8j4GrvT4O79pjeromADHT2Igl8Vd1zYIRtg6WlY="}]},"_npmUser":{"name":"albatro33","email":"cokujin518@gmail.com"},"directories":{},"maintainers":[{"name":"albatro33","email":"cokujin518@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/sy-funcs_1.1.0_1768201705948_0.6100170080633232"},"_hasShrinkwrap":false}},"time":{"created":"2026-01-12T07:08:25.842Z","1.1.0":"2026-01-12T07:08:26.096Z","modified":"2026-01-12T07:08:26.390Z"},"maintainers":[{"name":"albatro33","email":"cokujin518@gmail.com"}],"description":"커링 기반 React 상태 업데이트 + Validation 훅 라이브러리. immutable.js 스타일의 편리함을 순수 JS 또는 Immer로 제공합니다.","homepage":"https://github.com/albatro33/sy-funcs#readme","keywords":["react","hooks","state-management","currying","immutable","validation","form","useChange","useUpdate","setIn","immer","typescript","nested-objects","deep-update","form-validation"],"repository":{"type":"git","url":"git+https://github.com/albatro33/sy-funcs.git"},"author":{"name":"Albatro33","email":"cokujin518@gmail.com"},"bugs":{"url":"https://github.com/albatro33/sy-funcs/issues"},"license":"MIT","readme":"# sy-funcs\r\n\r\n> 커링 기반 React 상태 업데이트 + Validation 훅 라이브러리\r\n\r\nReact에서 깊은 객체/배열의 상태를 쉽게 업데이트할 수 있는 커링 기반 훅 라이브러리입니다.\r\n\r\nimmutable.js의 `setIn`과 같은 편리함을 순수 JavaScript 또는 Immer로 제공합니다.\r\n\r\n[![npm version](https://img.shields.io/npm/v/sy-funcs)](https://www.npmjs.com/package/sy-funcs)\r\n[![bundle size](https://img.shields.io/bundlephobia/minzip/sy-funcs)](https://bundlephobia.com/package/sy-funcs)\r\n[![license](https://img.shields.io/npm/l/sy-funcs)](https://github.com/yourusername/my-hooks-lib/blob/main/LICENSE)\r\n\r\n## 설치\r\n\r\n```bash\r\nnpm install sy-funcs\r\n# or\r\nyarn add sy-funcs\r\n```\r\n\r\n> **참고**: Immer 버전의 훅을 사용하려면 `immer`를 별도로 설치해야 합니다.\r\n\r\n```bash\r\nnpm install immer\r\n```\r\n\r\n## 주요 기능\r\n\r\n- ✅ **커링 문법**: 함수 재사용성이 뛰어난 커링 패턴\r\n- ✅ **깊은 경로 지원**: 중첩된 객체/배열을 쉽게 업데이트\r\n- ✅ **순수 JS & Immer**: 순수 JavaScript 또는 Immer 중 선택 가능\r\n- ✅ **이벤트 자동 처리**: React 이벤트 객체 자동 변환\r\n- ✅ **Validation 내장**: 경로 기반 폼 검증 지원\r\n- ✅ **TypeScript 지원**: 완전한 타입 정의 제공\r\n\r\n## 사용법\r\n\r\n### 기본 예제\r\n\r\n```tsx\r\nimport { useState } from 'react';\r\nimport { useChange } from 'sy-funcs';\r\n\r\nfunction UserForm() {\r\n  const [state, setState] = useState({\r\n    user: {\r\n      name: '',\r\n      profile: {\r\n        age: 0,\r\n        email: ''\r\n      }\r\n    }\r\n  });\r\n\r\n  const handleChange = useChange(setState);\r\n\r\n  return (\r\n    <div>\r\n      <input \r\n        value={state.user.name}\r\n        onChange={handleChange(['user', 'name'])}\r\n      />\r\n      \r\n      <input \r\n        type=\"number\"\r\n        value={state.user.profile.age}\r\n        onChange={handleChange(['user', 'profile', 'age'])}\r\n      />\r\n      \r\n      <input \r\n        value={state.user.profile.email}\r\n        onChange={handleChange(['user', 'profile', 'email'])}\r\n      />\r\n    </div>\r\n  );\r\n}\r\n```\r\n\r\n### 배열 업데이트\r\n\r\n```tsx\r\nimport { useState } from 'react';\r\nimport { useChange, useUpdate } from 'sy-funcs';\r\n\r\nfunction TodoList() {\r\n  const [state, setState] = useState({\r\n    todos: [\r\n      { id: 1, text: '할일 1', done: false },\r\n      { id: 2, text: '할일 2', done: false }\r\n    ]\r\n  });\r\n\r\n  const handleChange = useChange(setState);\r\n  const handleUpdate = useUpdate(setState);\r\n\r\n  return (\r\n    <div>\r\n      {state.todos.map((todo, index) => (\r\n        <div key={todo.id}>\r\n          <input\r\n            type=\"checkbox\"\r\n            checked={todo.done}\r\n            onChange={handleChange(['todos', index, 'done'])}\r\n          />\r\n          <input\r\n            value={todo.text}\r\n            onChange={handleChange(['todos', index, 'text'])}\r\n          />\r\n        </div>\r\n      ))}\r\n      \r\n      <button\r\n        onClick={handleUpdate(['todos'])((prev) => [\r\n          ...prev,\r\n          { id: Date.now(), text: '새 할일', done: false }\r\n        ])}\r\n      >\r\n        추가\r\n      </button>\r\n    </div>\r\n  );\r\n}\r\n```\r\n\r\n### Immer 사용\r\n\r\n```tsx\r\nimport { useState } from 'react';\r\nimport { useChangeImmer } from 'sy-funcs';\r\n\r\nfunction App() {\r\n  const [state, setState] = useState({\r\n    users: [\r\n      { name: 'John', age: 25 },\r\n      { name: 'Jane', age: 30 }\r\n    ]\r\n  });\r\n\r\n  // Immer를 사용하면 내부적으로 더 효율적으로 업데이트됩니다\r\n  const handleChange = useChangeImmer(setState);\r\n\r\n  return (\r\n    <input\r\n      value={state.users[0].name}\r\n      onChange={handleChange(['users', 0, 'name'])}\r\n    />\r\n  );\r\n}\r\n```\r\n\r\n### 직접 값 전달\r\n\r\n```tsx\r\nfunction App() {\r\n  const [state, setState] = useState({ count: 0 });\r\n  const handleChange = useChange(setState);\r\n\r\n  return (\r\n    <button onClick={() => handleChange(['count'])(10)}>\r\n      Set to 10\r\n    </button>\r\n  );\r\n}\r\n```\r\n\r\n### 값 업데이트 (이전 값 기반)\r\n\r\n```tsx\r\nimport { useState } from 'react';\r\nimport { useUpdate } from 'sy-funcs';\r\n\r\nfunction Counter() {\r\n  const [state, setState] = useState({ count: 0 });\r\n  const handleUpdate = useUpdate(setState);\r\n\r\n  return (\r\n    <div>\r\n      <button onClick={() => handleUpdate(['count'])((n) => n + 1)}>\r\n        증가\r\n      </button>\r\n      <button onClick={() => handleUpdate(['count'])((n) => n - 1)}>\r\n        감소\r\n      </button>\r\n      <p>Count: {state.count}</p>\r\n    </div>\r\n  );\r\n}\r\n```\r\n\r\n### Validation + Auto Focus\r\n\r\n```tsx\r\nimport { useState } from 'react';\r\nimport { useValidatedChange, validators } from 'sy-funcs';\r\n\r\nfunction SignupForm() {\r\n  const [state, setState] = useState({ email: '', password: '' });\r\n\r\n  const { \r\n    handleChange, \r\n    handleBlur, \r\n    getError, \r\n    fieldRef,  // ref 함수\r\n    validateAll,\r\n    isValid \r\n  } = useValidatedChange(\r\n    state,\r\n    setState,\r\n    {\r\n      validation: {\r\n        rules: {\r\n          'email': [validators.required(), validators.email()],\r\n          'password': [validators.required(), validators.minLength(8)],\r\n        }\r\n      },\r\n      validateOn: 'blur',\r\n      autoFocus: true,  // 자동 focus 활성화\r\n    }\r\n  );\r\n\r\n  const handleSubmit = () => {\r\n    if (!validateAll?.()) {\r\n      // 자동으로 첫 번째 에러 필드에 focus!\r\n      return;\r\n    }\r\n    alert('가입 완료!');\r\n  };\r\n\r\n  return (\r\n    <form>\r\n      <input\r\n        ref={fieldRef?.(['email'])}  {/* ref 추가 */}\r\n        value={state.email}\r\n        onChange={handleChange(['email'])}\r\n        onBlur={handleBlur?.(['email'])}\r\n      />\r\n      {getError?.(['email']) && <span>{getError(['email'])}</span>}\r\n      \r\n      <input\r\n        ref={fieldRef?.(['password'])}  {/* ref 추가 */}\r\n        type=\"password\"\r\n        value={state.password}\r\n        onChange={handleChange(['password'])}\r\n        onBlur={handleBlur?.(['password'])}\r\n      />\r\n      {getError?.(['password']) && <span>{getError(['password'])}</span>}\r\n      \r\n      <button onClick={handleSubmit} disabled={!isValid}>가입</button>\r\n    </form>\r\n  );\r\n}\r\n```\r\n\r\n## API\r\n\r\n### Hooks\r\n\r\n#### `useChange(setter, options?)`\r\n\r\n순수 JavaScript로 깊은 경로의 값을 업데이트하는 커링 훅\r\n\r\n**파라미터:**\r\n- `setter`: setState 함수\r\n- `options.valueExtractor`: 커스텀 값 추출 함수\r\n- `options.setInOptions.createMissing`: 중간 경로 자동 생성 여부 (기본: true)\r\n\r\n**반환값:** `(path: Path) => (eventOrValue: any) => void`\r\n\r\n#### `useChangeImmer(setter, options?)`\r\n\r\nImmer를 사용하여 깊은 경로의 값을 업데이트하는 커링 훅\r\n\r\n#### `useUpdate(setter, options?)`\r\n\r\n순수 JavaScript로 이전 값을 기반으로 업데이트하는 커링 훅\r\n\r\n**반환값:** `(path: Path) => (updater: (prev) => newValue) => void`\r\n\r\n#### `useUpdateImmer(setter, options?)`\r\n\r\nImmer를 사용하여 이전 값을 기반으로 업데이트하는 커링 훅\r\n\r\n### Utility Functions\r\n\r\n#### `setIn(obj, path, value, options?)`\r\n\r\n순수 JavaScript로 깊은 경로에 값을 설정 (불변성 유지)\r\n\r\n```ts\r\nconst obj = { user: { name: 'John' } };\r\nconst newObj = setIn(obj, ['user', 'name'], 'Jane');\r\n// { user: { name: 'Jane' } }\r\n```\r\n\r\n#### `setInImmer(obj, path, value, options?)`\r\n\r\nImmer를 사용하여 깊은 경로에 값을 설정\r\n\r\n#### `getIn(obj, path, fallback?)`\r\n\r\n깊은 경로에서 값을 가져오기\r\n\r\n```ts\r\nconst obj = { user: { name: 'John' } };\r\ngetIn(obj, ['user', 'name']); // 'John'\r\ngetIn(obj, ['user', 'age'], 0); // 0 (fallback)\r\n```\r\n\r\n#### `updateIn(obj, path, updater, defaultValue?, options?)`\r\n\r\n순수 JavaScript로 깊은 경로의 값을 업데이트\r\n\r\n```ts\r\nconst obj = { count: 5 };\r\nconst newObj = updateIn(obj, ['count'], (n) => n + 1);\r\n// { count: 6 }\r\n```\r\n\r\n#### `updateInImmer(obj, path, updater, defaultValue?, options?)`\r\n\r\nImmer를 사용하여 깊은 경로의 값을 업데이트\r\n\r\n## 타입\r\n\r\n```ts\r\ntype PathKey = string | number;\r\ntype Path = readonly PathKey[];\r\n\r\ntype ValueExtractor = (eventOrValue: any) => any;\r\n\r\ninterface SetInOptions {\r\n  createMissing?: boolean;\r\n}\r\n\r\ntype Setter<T> = (updater: T | ((prev: T) => T)) => void;\r\n```\r\n\r\n## 왜 sy-funcs인가?\r\n\r\n### Before (without sy-funcs)\r\n\r\n```tsx\r\n// 😰 복잡하고 반복적인 코드\r\nconst handleNameChange = (e) => {\r\n  setState(prev => ({\r\n    ...prev,\r\n    user: {\r\n      ...prev.user,\r\n      profile: {\r\n        ...prev.user.profile,\r\n        name: e.target.value\r\n      }\r\n    }\r\n  }));\r\n};\r\n\r\nconst handleAgeChange = (e) => {\r\n  setState(prev => ({\r\n    ...prev,\r\n    user: {\r\n      ...prev.user,\r\n      profile: {\r\n        ...prev.user.profile,\r\n        age: Number(e.target.value)\r\n      }\r\n    }\r\n  }));\r\n};\r\n```\r\n\r\n### After (with sy-funcs)\r\n\r\n```tsx\r\n// 😍 간결하고 재사용 가능한 코드\r\nconst handleChange = useChange(setState);\r\n\r\n<input onChange={handleChange(['user', 'profile', 'name'])} />\r\n<input onChange={handleChange(['user', 'profile', 'age'])} />\r\n```\r\n\r\n## 비교\r\n\r\n| 기능 | sy-funcs | immer + useState | 순수 JS |\r\n|------|----------|------------------|---------|\r\n| 커링 문법 | ✅ | ❌ | ❌ |\r\n| 코드 간결성 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐ |\r\n| 함수 재사용성 | ⭐⭐⭐⭐⭐ | ⭐⭐ | ⭐ |\r\n| 깊은 경로 업데이트 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐ |\r\n| 이벤트 자동 처리 | ✅ | ❌ | ❌ |\r\n\r\n## 라이선스\r\n\r\nMIT\r\n\r\n## 기여\r\n\r\n이슈와 PR은 언제나 환영합니다!\r\n","readmeFilename":"README.md","_rev":"1-47e6db46a1dbaa79b786cb8f02d3e2f8"}