DEV Community

Soatra
Soatra

Posted on

React Use Current โ€” A Tiny React Hook for Synchronous Reactive State

๐Ÿš€ React Use Current โ€” Reactive + Synchronous State in React

React gives us two main primitives for storing state:

โŒ useState

  • Reactive (re-renders UI)
  • Not synchronous (updates batch, no immediate .value)
  • Forces immutability
  • Requires creating new references (setState({ ... }))

โŒ useRef

  • Synchronous (ref.current = x)
  • Not reactive (no re-renders when changed)
  • No tracking
  • No deep mutation detection

โœ”๏ธ useCurrent (built on VRef)

  • Reactive (mutations trigger re-render)
  • Synchronous (updated instantly like refs)
  • Mutable (no cloning, no setState)
  • Deep tracking (powered by VRef)
  • Fast & GC-safe thanks to VRef

This hook gives React something it never had out-of-the-box:

Direct mutation that triggers re-renders โ€” immediately and safely.


๐Ÿ” What Is React Use Current?

react-use-current is a tiny hook built on top of VRef (vref).

It gives React the ability to:

  • Mutate values naturally (user.age += 1)
  • Track changes deeply
  • Trigger re-renders synchronously
  • Work with objects, arrays, nested data

No need for setState(), shallow copying, or immutable patterns.

npm install react-use-current
Enter fullscreen mode Exit fullscreen mode

๐Ÿงช Quick Example

import useCurrent from "react-use-current";

export default function Counter() {
  const count = useCurrent(0);

  return (
    <button onClick={() => (count.value += 1)}>
      Count: {count.value}
    </button>
  );
}
Enter fullscreen mode Exit fullscreen mode

โœ” updates instantly

โœ” mutates directly

โœ” re-renders correctly


๐Ÿง  Why Does This Hook Exist?

React developers often hit these problems:

โŒ "I need immediate state updates"

useState doesn't update until next render.

useCurrent updates instantly.

โŒ "I want to mutate state directly (nested objects)"

useState forces immutability โ†’ slow & annoying.

useCurrent allows:

user.address.city = "Phnom Penh";
Enter fullscreen mode Exit fullscreen mode

โŒ "useRef doesn't re-render when changed"

ref.current++ won't update UI.

useCurrent will.


๐Ÿ”Ž Comparison Table

Feature useState useRef useCurrent
Reactive โœ”๏ธ โŒ โœ”๏ธ
Synchronous โŒ โœ”๏ธ โœ”๏ธ
Deep mutation โŒ โœ”๏ธ โœ”๏ธ
Triggers re-render โœ”๏ธ โŒ โœ”๏ธ
Requires immutable updates โœ”๏ธ โŒ โŒ
Built on VRef โŒ โŒ โœ”๏ธ

๐Ÿ— How It Works (In Short)

  • useCurrent() internally wraps your initial value using VRef's reactive system.
  • Any mutation (obj.key = value) triggers VRef's change event.
  • useCurrent() listens to that event and updates an internal state token, which triggers a React re-render.
  • Works deeply โ€” arrays, objects, nested structures.

๐Ÿงฉ Real Example โ€” Objects, Deep Mutation, and Tracking

import { useEffect, useMemo } from "react";
import useCurrent, { track } from "react-use-current";

export default function UserCard() {
  const { value: user } = useCurrent({
    name: "John",
    age: 25,
  });

  // Run whenever user changes (deep)
  useEffect(() => {
    console.log("User mutated:", user);
  }, [track(user)]);

  // Memo recomputes when specific field changes
  const isAdult = useMemo(() => user.age >= 18, [track(user.age)]);
  // track for primitive is optional

  return (
    <div>
      <p>{user.name} โ€” {user.age}</p>
      <button onClick={() => (user.age += 1)}>Increase Age</button>
      <button onClick={() => (user.name = "Doe")}>
        Change Name
      </button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ”ง Auto-Tracking Helpers

These come from react-use-current (optional but powerful)


๐ŸŸฆ useApply

A version of useEffect that auto-tracks dependencies.

import { useApply } from "react-use-current";

useApply(() => {
  console.log("User changed:", user);
}, [user]);
Enter fullscreen mode Exit fullscreen mode

Equivalent to:

useEffect(() => {}, [track(user)]);
Enter fullscreen mode Exit fullscreen mode

๐ŸŸง useCompute

A version of useMemo that auto-tracks dependencies.

import { useCompute } from "react-use-current";

const isAdult = useCompute(() => {
  return user.age >= 18;
}, [user.age]);
Enter fullscreen mode Exit fullscreen mode

Equivalent to:

useMemo(() => ..., [track(user.age)]);
Enter fullscreen mode Exit fullscreen mode

๐Ÿงฌ Understanding the track() Function

track() converts reactive values into new value that React can detect in dependency arrays.

const updatedAt = track(user);  // reactive object
Enter fullscreen mode Exit fullscreen mode

Tracking is required for:

  • useEffect
  • useMemo
  • dependency change detection

This mechanism is identical to VRef's change reporters โ€” but adapted for React.


๐Ÿ“ฆ Real Use Cases

โœ” Complex forms

Directly mutate nested form fields without cloning.

โœ” Synchronous logic (counters, timers, scrolling)

Get immediate updatesโ€”no batching.

โœ” Local reactive stores

Like lightweight MobX-style state.

โœ” Avoid expensive cloning in large objects

Mutate in place, but still reactive.


๐ŸŽฎ Live Playground

Play with react-use-current:

๐Ÿ‘‰ Open Live Playground


๐Ÿ“ฆ Try It

๐ŸŸข npm: https://www.npmjs.com/package/react-use-current

๐Ÿ™ GitHub: https://github.com/JohnSoatra/react-use-current

Reference:

๐ŸŒ Docs: https://doc.soatra.com/vref (VRef base)


๐Ÿ’ฌ What do you think?

What do you think?

Would this help in forms, real-time state, animations, or nested data?

Top comments (0)