npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@albatro33/sy-funcs

v1.1.0

Published

커링 기반 React 상태 업데이트 + Validation 훅 라이브러리. immutable.js 스타일의 편리함을 순수 JS 또는 Immer로 제공합니다.

Readme

sy-funcs

커링 기반 React 상태 업데이트 + Validation 훅 라이브러리

React에서 깊은 객체/배열의 상태를 쉽게 업데이트할 수 있는 커링 기반 훅 라이브러리입니다.

immutable.js의 setIn과 같은 편리함을 순수 JavaScript 또는 Immer로 제공합니다.

npm version bundle size license

설치

npm install sy-funcs
# or
yarn add sy-funcs

참고: Immer 버전의 훅을 사용하려면 immer를 별도로 설치해야 합니다.

npm install immer

주요 기능

  • 커링 문법: 함수 재사용성이 뛰어난 커링 패턴
  • 깊은 경로 지원: 중첩된 객체/배열을 쉽게 업데이트
  • 순수 JS & Immer: 순수 JavaScript 또는 Immer 중 선택 가능
  • 이벤트 자동 처리: React 이벤트 객체 자동 변환
  • Validation 내장: 경로 기반 폼 검증 지원
  • TypeScript 지원: 완전한 타입 정의 제공

사용법

기본 예제

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>
  );
}

배열 업데이트

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 사용

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'])}
    />
  );
}

직접 값 전달

function App() {
  const [state, setState] = useState({ count: 0 });
  const handleChange = useChange(setState);

  return (
    <button onClick={() => handleChange(['count'])(10)}>
      Set to 10
    </button>
  );
}

값 업데이트 (이전 값 기반)

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

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로 깊은 경로에 값을 설정 (불변성 유지)

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?)

깊은 경로에서 값을 가져오기

const obj = { user: { name: 'John' } };
getIn(obj, ['user', 'name']); // 'John'
getIn(obj, ['user', 'age'], 0); // 0 (fallback)

updateIn(obj, path, updater, defaultValue?, options?)

순수 JavaScript로 깊은 경로의 값을 업데이트

const obj = { count: 5 };
const newObj = updateIn(obj, ['count'], (n) => n + 1);
// { count: 6 }

updateInImmer(obj, path, updater, defaultValue?, options?)

Immer를 사용하여 깊은 경로의 값을 업데이트

타입

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)

// 😰 복잡하고 반복적인 코드
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)

// 😍 간결하고 재사용 가능한 코드
const handleChange = useChange(setState);

<input onChange={handleChange(['user', 'profile', 'name'])} />
<input onChange={handleChange(['user', 'profile', 'age'])} />

비교

| 기능 | sy-funcs | immer + useState | 순수 JS | |------|----------|------------------|---------| | 커링 문법 | ✅ | ❌ | ❌ | | 코드 간결성 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐ | | 함수 재사용성 | ⭐⭐⭐⭐⭐ | ⭐⭐ | ⭐ | | 깊은 경로 업데이트 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐ | | 이벤트 자동 처리 | ✅ | ❌ | ❌ |

라이선스

MIT

기여

이슈와 PR은 언제나 환영합니다!