[갓생팟 스터디] Jotai(util 함수)

수연·2024년 1월 16일

study

목록 보기
3/8
post-thumbnail

util - Storage

atomWithStorage

로컬 스토리지, 세션 스토리지에 저장하는 값을 아톰으로 관리할 수 있어요.

따로 정의하지 않으면 로컬 스토리지를 사용해요.

import { useAtom } from 'jotai';
import { atomWithStorage } from 'jotai/utils';

// 처음 값은 스토리지에 굳이 저장 X
const darkModeAtom = atomWithStorage('darkMode', false);

const Page = () => {
  const [darkMode, setDarkMode] = useAtom(darkModeAtom);

  return (
    <>
      <h1>Welcome to {darkMode ? 'dark' : 'light'} mode!</h1>
      <button onClick={() => setDarkMode(!darkMode)}>toggle theme</button>
    </>
  )
}
const atom = atomWithStorage(key, initialValue, storage, options);
  • key: string 상태를 localStorage, sessionStorage 등과 동기화하기 위해 사용하는 유니크한 문자열
  • initialValue: string 아톰의 초기값
  • storage? 다음 메서드들로 이뤄진 객체
    • getItem(key, initailValue) 스토리지에서 값을 읽어오고, 없으면 initialValue 의 값을 반환.
    • setItem(key, value) 스토리지에 값을 저장.
    • removeItem(key) 스토리지에서 값을 삭제.
    • subscribe(key, callback, initialValue) 외부의 스토리지 업데이트를 구독하는 메서드
  • options? 초기화 시 값을 storage 에서 가져올지 결정하는 boolean 값
    • getOnInnit 초기화 시 storage 에서 아이템을 가져올지를 결정하는 boolean 값

⚠️ 스토리지로 관리하는 아톰이 있는 경우


  • 아톰이 페이지 스타일에 관여하는 경우 (className, 스타일 prop…), 그리고 SSR 을 하는 경우 다음 요소를 주의해야해요.
  • 서버에서 먼저 아톰을 initialValue 로 생성한 뒤, 클라이언트 측에서 로컬 스토리지에 있는 값을 가져오게 돼요.
  • 이때 사용자 스토리지에 있는 값 ≠ initialValue 이므로 사용자가 원하는 것과 다른 레이아웃이 보여질 수 있어요.

해결방법: rehydration 직후 ClientOnly 와 같은 래핑 컴포넌트를 만들어서 스토리지 값에 종속된 아톰과 관련있는 컴포넌트를 관리 → 클라이언트 컴포넌트로 리렌더링하는 걸 권장해요.

스토리지에서 값 삭제하기

스토리지에서 값을 삭제하고 싶은 경우 RESET 심볼을 write 함수에 쓸 수 있어요.

import { useAtom } from 'jotai'
import { atomWithStorage, RESET } from 'jotai/utils'

const textAtom = atomWithStorage('text', 'hello');

const TextBox = () => {
  const [text, setText] = useAtom(textAtom);

  return (
    <>
      <input value={text} onChange={(e) => setText(e.target.value)} />
      <button onClick={() => setText(RESET)}>Reset (to 'hello')</button>
    </>
  )
}
  • 처음엔 storage 값이 없다가, input 에 값을 입력하면 storage 에 현재 값을 저장해요.
  • Reset 버튼을 누르면 storage 에 있는 값은 삭제되고, text 는 hello 로 초기화 돼요.

util - Async

asyc Atom 이 suspense 나 에러를 반환하기 원하지 않는 경우 loadable 유틸 함수를 사용할 수 있어요.

loadable

loadable 유틸 함수는 다음 객체를 반환해요.

{
    state: 'loading' | 'hasData' | 'hasError',
    data?: any,
    error?: any,
}

이렇게 반환 받은 값으로react-query 처럼 로딩이나 에러를 처리할 수 있어요.

import { loadable } from "jotai/utils"

const asyncAtom = atom(async (get) => ...);
const loadableAtom = loadable(asyncAtom);

const Component = () => {
  const [value] = useAtom(loadableAtom);

  if (value.state === 'hasError') {
		return <Text>{value.error}</Text>
	}
  if (value.state === 'loading') {
    return <Text>Loading...</Text>
  }

  return <Text>Value: {value.data}</Text>
}

unwrap

loadable 과 유사하지만 2가지가 달라요.

  • 다른점 1 - unwrap 은 fallback 값 줄 수 있어요.
  • 다른점 2 - loadable 은 에러 시 에러 값을 확인할 수 있지만, unwrap 은 에러 시 바로 throw 에러 객체를 던져요.

아래는 fallback 값을 주는 예시예요.

import { atom } from 'jotai';
import { unwrap } from 'jotai/utils';

const countAtom = atom(0);
const delayedCountAtom = atom(async (get) => {
  await new Promise((r) => setTimeout(r, 500))
  return get(countAtom)
});

// fallback X -> undefined 반환
const unwrapped1Atom = unwrap(delayedCountAtom);

// fallback O, 0 반환하고 업데이트가 일어나면 해당 데이터로 바꿈
const unwrapped2Atom = unwrap(delayedCountAtom, (prev) => prev ?? 0)

util - Resettable

atomWithReset

resettable 한 아톰을 만들기 위한 메서드예요.

import { atomWithReset } from 'jotai/utils';

const editableAtom = atomWithReset(false);

useResetAtom

resettable 한 아톰을 초기화시킬 수 있는 메서드예요.

import { useResetAtom } from 'jotai/utils';
import editableAtom from '~/store';

const EditPage = () => {
	const resetEditable = useResetAtom(editableAtom);

	useEffect(() => {
		return () => resetEditable();
	}, []);
}

util - Select

selectAtom

  • 객체 형태의 아톰에서 특정 프로퍼티만 구독이 가능해요. 해당 프로퍼티가 변경되어야 구독한 컴포넌트에서 업데이트가 일어나요.
  • pure Atom 이 아니기 때문에 불가피한 경우에만 사용해요.
const defaultPerson = {
  name: {
    first: 'Jane',
    last: 'Doe',
  },
  birth: {
    year: 2000,
    month: 'Jan',
    day: 1,
    time: {
      hour: 1,
      minute: 1,
    },
  },
};
const personAtom = atom(defaultPerson);

// person.name 추적
// person.name 객체 자체가 변경되면 업데이트
const nameAtom = selectAtom(personAtom, (person) => person.name);

// person.birth 추적
// year, month 등 하위 프로퍼티가 변경되면 업데이트
// birthAtom 자체가 변경되는 경우 업데이트 X 
const birthAtom = selectAtom(personAtom, (person) => person.birth, deepEquals);

util - Split

splitAtom

배열의 각 원소를 아톰으로 만들고 싶은 경우 사용해요.

const initialState = [
  {
    task: 'help the town',
    done: false,
  },
  {
    task: 'feed the dragon',
    done: false,
  },
]

// 먼저 아톰으로 만들어서 사용
const todosAtom = atom(todos);
const todoAtomsAtom = splitAtom(todosAtom);

const TodoList = () => {
	const [todoAtoms, dispatch] = useAtom(todoAtomsAtom);

	return (
	    <ul>
	      {todoAtoms.map((todoAtom, index) => (
	        <TodoItem todoAtom={todoAtom} />
	      ))}
	    </ul>
	  )
}
  • Jotai 1.6.4 버전부터 splitAtom 에서 dispatch 를 반환받아요.
  • remove insert move 와 같은 액션을 지정해서 사용할 수 있어요.
  • 각 dispatch 엔 필요한 인자값을 넣어줘야 해요.
    <TodoItem 
    	todoAtom={todoAtom}
    	remove={() => dispatch({ type: 'remove', atom: todoAtom })}
    	move={() => dispatch({ type: 'move', from: index, to: 5})}
    	insert={() => dispatch({ type: 'insert', index: 5 })}
    />
    • remove: 해당 아톰을 삭제해요.
    • move: 특정 위치에 있는 Atom 을 다른 위치로 이동시켜요.
    • insert: 해당 index 에 해당 아톰을 추가로 넣어요.

0개의 댓글