npm package: @albatro33/sy-funcs
Description: 커링 기반 React 상태 업데이트 + Validation 훅 라이브러리. immutable.js 스타일의 편리함을 순수 JS 또는 Immer로 제공합니다.
Author: Albatro33
Latest version: 1.1.0

# sy-funcs

> 커링 기반 React 상태 업데이트 + Validation 훅 라이브러리

React에서 깊은 객체/배열의 상태를 쉽게 업데이트할 수 있는 커링 기반 훅 라이브러리입니다.

immutable.js의 `setIn`과 같은 편리함을 순수 JavaScript 또는 Immer로 제공합니다.

[![npm version](https://img.shields.io/npm/v/sy-funcs)](https://www.npmjs.com/package/sy-funcs)
[![bundle size](https://img.shields.io/bundlephobia/minzip/sy-funcs)](https://bundlephobia.com/package/sy-funcs)
[![license](https://img.shields.io/npm/l/sy-funcs)](https://github.com/yourusername/my-hooks-lib/blob/main/LICENSE)

## 설치

```bash
npm install sy-funcs
# or
yarn add sy-funcs
```

> **참고**: Immer 버전의 훅을 사용하려면 `immer`를 별도로 설치해야 합니다.

```bash
npm install immer
```

## 주요 기능

- ✅ **커링 문법**: 함수 재사용성이 뛰어난 커링 패턴
- ✅ **깊은 경로 지원**: 중첩된 객체/배열을 쉽게 업데이트
- ✅ **순수 JS & Immer**: 순수 JavaScript 또는 Immer 중 선택 가능
- ✅ **이벤트 자동 처리**: React 이벤트 객체 자동 변환
- ✅ **Validation 내장**: 경로 기반 폼 검증 지원
- ✅ **TypeScript 지원**: 완전한 타입 정의 제공

## 사용법

### 기본 예제

```tsx
import { useState } from 'react';
import { useChange } from 'sy-funcs';

function UserForm() {
  const [state, setState] = useState({
    user: {
      name: '',
      profile: {
        age: 0,
        email: ''
      }
    }
  });

  const handleChange = useChange(setState);

  return (
    <div>
      <input 
        value={state.user.name}
        onChange={handleChange(['user', 'name'])}
      />
      
      <input 
        type="number"
        value={state.user.profile.age}
        onChange={handleChange(['user', 'profile', 'age'])}
      />
      
      <input 
        value={state.user.profile.email}
        onChange={handleChange(['user', 'profile', 'email'])}
      />
    </div>
  );
}
```

### 배열 업데이트

```tsx
import { useState } from 'react';
import { useChange, useUpdate } from 'sy-funcs';

function TodoList() {
  const [state, setState] = useState({
    todos: [
      { id: 1, text: '할일 1', done: false },
      { id: 2, text: '할일 2', done: false }
    ]
  });

  const handleChange = useChange(setState);
  const handleUpdate = useUpdate(setState);

  return (
    <div>
      {state.todos.map((todo, index) => (
        <div key={todo.id}>
          <input
            type="checkbox"
            checked={todo.done}
            onChange={handleChange(['todos', index, 'done'])}
          />
          <input
            value={todo.text}
            onChange={handleChange(['todos', index, 'text'])}
          />
        </div>
      ))}
      
      <button
        onClick={handleUpdate(['todos'])((prev) => [
          ...prev,
          { id: Date.now(), text: '새 할일', done: false }
        ])}
      >
        추가
      </button>
    </div>
  );
}
```

### Immer 사용

```tsx
import { useState } from 'react';
import { useChangeImmer } from 'sy-funcs';

function App() {
  const [state, setState] = useState({
    users: [
      { name: 'John', age: 25 },
      { name: 'Jane', age: 30 }
    ]
  });

  // Immer를 사용하면 내부적으로 더 효율적으로 업데이트됩니다
  const handleChange = useChangeImmer(setState);

  return (
    <input
      value={state.users[0].name}
      onChange={handleChange(['users', 0, 'name'])}
    />
  );
}
```

### 직접 값 전달

```tsx
function App() {
  const [state, setState] = useState({ count: 0 });
  const handleChange = useChange(setState);

  return (
    <button onClick={() => handleChange(['count'])(10)}>
      Set to 10
    </button>
  );
}
```

### 값 업데이트 (이전 값 기반)

```tsx
import { useState } from 'react';
import { useUpdate } from 'sy-funcs';

function Counter() {
  const [state, setState] = useState({ count: 0 });
  const handleUpdate = useUpdate(setState);

  return (
    <div>
      <button onClick={() => handleUpdate(['count'])((n) => n + 1)}>
        증가
      </button>
      <button onClick={() => handleUpdate(['count'])((n) => n - 1)}>
        감소
      </button>
      <p>Count: {state.count}</p>
    </div>
  );
}
```

### Validation + Auto Focus

```tsx
import { useState } from 'react';
import { useValidatedChange, validators } from 'sy-funcs';

function SignupForm() {
  const [state, setState] = useState({ email: '', password: '' });

  const { 
    handleChange, 
    handleBlur, 
    getError, 
    fieldRef,  // ref 함수
    validateAll,
    isValid 
  } = useValidatedChange(
    state,
    setState,
    {
      validation: {
        rules: {
          'email': [validators.required(), validators.email()],
          'password': [validators.required(), validators.minLength(8)],
        }
      },
      validateOn: 'blur',
      autoFocus: true,  // 자동 focus 활성화
    }
  );

  const handleSubmit = () => {
    if (!validateAll?.()) {
      // 자동으로 첫 번째 에러 필드에 focus!
      return;
    }
    alert('가입 완료!');
  };

  return (
    <form>
      <input
        ref={fieldRef?.(['email'])}  {/* ref 추가 */}
        value={state.email}
        onChange={handleChange(['email'])}
        onBlur={handleBlur?.(['email'])}
      />
      {getError?.(['email']) && <span>{getError(['email'])}</span>}
      
      <input
        ref={fieldRef?.(['password'])}  {/* ref 추가 */}
        type="password"
        value={state.password}
        onChange={handleChange(['password'])}
        onBlur={handleBlur?.(['password'])}
      />
      {getError?.(['password']) && <span>{getError(['password'])}</span>}
      
      <button onClick={handleSubmit} disabled={!isValid}>가입</button>
    </form>
  );
}
```

## API

### Hooks

#### `useChange(setter, options?)`

순수 JavaScript로 깊은 경로의 값을 업데이트하는 커링 훅

**파라미터:**
- `setter`: setState 함수
- `options.valueExtractor`: 커스텀 값 추출 함수
- `options.setInOptions.createMissing`: 중간 경로 자동 생성 여부 (기본: true)

**반환값:** `(path: Path) => (eventOrValue: any) => void`

#### `useChangeImmer(setter, options?)`

Immer를 사용하여 깊은 경로의 값을 업데이트하는 커링 훅

#### `useUpdate(setter, options?)`

순수 JavaScript로 이전 값을 기반으로 업데이트하는 커링 훅

**반환값:** `(path: Path) => (updater: (prev) => newValue) => void`

#### `useUpdateImmer(setter, options?)`

Immer를 사용하여 이전 값을 기반으로 업데이트하는 커링 훅

### Utility Functions

#### `setIn(obj, path, value, options?)`

순수 JavaScript로 깊은 경로에 값을 설정 (불변성 유지)

```ts
const obj = { user: { name: 'John' } };
const newObj = setIn(obj, ['user', 'name'], 'Jane');
// { user: { name: 'Jane' } }
```

#### `setInImmer(obj, path, value, options?)`

Immer를 사용하여 깊은 경로에 값을 설정

#### `getIn(obj, path, fallback?)`

깊은 경로에서 값을 가져오기

```ts
const obj = { user: { name: 'John' } };
getIn(obj, ['user', 'name']); // 'John'
getIn(obj, ['user', 'age'], 0); // 0 (fallback)
```

#### `updateIn(obj, path, updater, defaultValue?, options?)`

순수 JavaScript로 깊은 경로의 값을 업데이트

```ts
const obj = { count: 5 };
const newObj = updateIn(obj, ['count'], (n) => n + 1);
// { count: 6 }
```

#### `updateInImmer(obj, path, updater, defaultValue?, options?)`

Immer를 사용하여 깊은 경로의 값을 업데이트

## 타입

```ts
type PathKey = string | number;
type Path = readonly PathKey[];

type ValueExtractor = (eventOrValue: any) => any;

interface SetInOptions {
  createMissing?: boolean;
}

type Setter<T> = (updater: T | ((prev: T) => T)) => void;
```

## 왜 sy-funcs인가?

### Before (without sy-funcs)

```tsx
// 😰 복잡하고 반복적인 코드
const handleNameChange = (e) => {
  setState(prev => ({
    ...prev,
    user: {
      ...prev.user,
      profile: {
        ...prev.user.profile,
        name: e.target.value
      }
    }
  }));
};

const handleAgeChange = (e) => {
  setState(prev => ({
    ...prev,
    user: {
      ...prev.user,
      profile: {
        ...prev.user.profile,
        age: Number(e.target.value)
      }
    }
  }));
};
```

### After (with sy-funcs)

```tsx
// 😍 간결하고 재사용 가능한 코드
const handleChange = useChange(setState);

<input onChange={handleChange(['user', 'profile', 'name'])} />
<input onChange={handleChange(['user', 'profile', 'age'])} />
```

## 비교

| 기능 | sy-funcs | immer + useState | 순수 JS |
|------|----------|------------------|---------|
| 커링 문법 | ✅ | ❌ | ❌ |
| 코드 간결성 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐ |
| 함수 재사용성 | ⭐⭐⭐⭐⭐ | ⭐⭐ | ⭐ |
| 깊은 경로 업데이트 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐ |
| 이벤트 자동 처리 | ✅ | ❌ | ❌ |

## 라이선스

MIT

## 기여

이슈와 PR은 언제나 환영합니다!