1. How to Migrate to v2 from v1

How to Migrate to v2 from v1

Changes in v2

React 19 officially introduces the use hook to handle promises. Valtio v1 internally handled promises, which is no longer recommended. In Valtio v2, promises are not handled internally, and developers should explicitly use the use hook to manage promises.

Note: If you are still using React 18, you can use the use hook shim.

Valtio v2 also introduces two subtle changes in its design choices:

First, the behavior of proxy(obj) has changed. In v1, it was a pure function and deeply copied obj. In v2, it is an impure function and deeply modifies obj. Generally, reusing obj is not recommended. Unless you reuse obj, nothing will break.

Second, the behavior of useSnapshot() has been altered. Although it is a subtle change, it is less optimized to ensure compatibility with useMemo and the upcoming React compiler. The change may lead to extra re-renders in some edge cases, but it might not be noticeable.

Other notable changes to keep things updated and fresh include:

  • Removal of all deprecated features
  • Requirement of React version 18 and above
  • Requirement of TypeScript version 4.5 and above
  • The build target updated to ES2018

Migration for breaking changes

Resolving promises

// v1
import { proxy, useSnapshot } from 'valtio'

const state = proxy({ data: fetch(...).then((res) => res.json()) })

const Component = () => {
  const snap = useSnapshot(state)
  return <>{JSON.stringify(snap.data)}</>
}
// v2
import { use } from 'react'
import { proxy, useSnapshot } from 'valtio'

const state = proxy({ data: fetch(...).then((res) => res.json()) })

const Component = () => {
  const snap = useSnapshot(state)
  return <>{JSON.stringify(use(snap.data))}</>
  // If `data` is not an object, you can directly embed it in JSX.
  // return <>{snap.data}</>
}

Impure proxy(obj)

If you don't reuse the object you pass to the proxy, nothing will break.

import { proxy } from 'valtio'

// This works in both v1 and v2
const state = proxy({ count: 1, obj: { text: 'hi' } })

// This works in both v1 and v2
state.obj = { text: 'hello' }

That's the recommended way to use proxy.

For some reason, if you reuse the object, you need to use deepClone explicitly in v2 to keep the same behavior as v1.

// v1
import { proxy } from 'valtio'

const initialObj = { count: 1, obj: { text: 'hi' } }
const state = proxy(initialObj)
// and do something later with `initialObj`

const newObj = { text: 'hello' }
state.obj = newObj
// and do something later with `newObj`
// v2
import { proxy } from 'valtio'
import { deepClone } from 'valtio/utils'

const initialObj = { count: 1, obj: { text: 'hi' } }
const state = proxy(deepClone(initialObj))
// and do something later with `initialObj`

const newObj = { text: 'hello' }
state.obj = deepClone(newObj)
// and do something later with `newObj`

useLayoutEffect server warning

If you're using React 18 with SSR, add this conditional to prevent excessive warnings.

import { snapshot, useSnapshot as useSnapshotOrig } from 'valtio'

const isSSR = typeof window === 'undefined'
export const useSnapshot = isSSR ? (p) => snapshot(p) : useSnapshotOrig

// render with `useSnapshot` as usual

Links