Skip to main content

The jods Journey: From Name-Squatting to Full Framework Integration

· 4 min read
Clay Stewart
Creator of jods

What started as a simple npm name-squatting exercise has evolved into something far more meaningful in just under two weeks. Here's the story of how jods came to be.

The Beginning: A Short Name on npm

About two weeks ago, I decided to claim a short, memorable name on npm — @jods (JavaScript Object Dynamics System). Initially, after playing around with BullMQ, I thought it might become a job queuing system (jods = jobs, get it?). But I quickly pivoted when I had a loose idea about creating a state management library instead. At that point, I just wanted to secure a name that was easy to remember and type.

Creating a Simple API

After securing the name, I started to flesh out what this library could be. I knew I wanted something with:

  • 🌟 A simple, intuitive API
  • 🎯 Zero dependencies
  • 🔌 Easy integration with any framework
  • 📸 JSON snapshot capability out of the box

I quickly realized that with a proxy-based approach, I could create something that felt natural to use — just plain JavaScript objects that magically become reactive.

React and Beyond

Once I had the core reactivity system working, I moved on to integrating with React. This is where things got interesting. I found that with the proxy approach, integrating with React was almost trivial:

// Create a store
const user = store({
firstName: "Burt",
lastName: "Macklin",
mood: "curious",
});

// In your React component
function Profile() {
const userData = useJods(user);

return (
<div>
<h1>Hello, {userData.firstName}</h1>
<p>Mood: {userData.mood}</p>

{/* Direct state mutations - no actions, reducers, or dispatchers needed */}
<button onClick={() => (user.mood = "sneaky")}>Go Undercover</button>
<button onClick={() => (user.firstName = "Burt Macklin")}>
Use FBI Name
</button>
</div>
);
}

The simplicity compared to some other libraries surprised me 🤔. No need for selectors, actions, or reducers — just directly use the object. That "aha moment" when I realized I could just write user.mood = "sneaky" and have the UI update automatically was when I knew this approach had merit. 🙌

The Signal Revolution

As the project progressed, I couldn't ignore the growing popularity of signals in frameworks like Preact, Solid, and Angular. The proposed TC39 signals spec caught my attention, and I even opened an issue to track our potential adoption of native signals.

So one morning, I created a branch to rewrite jods with signals as the foundation. This enabled more fine-grained reactivity — only updating components when the specific properties they use change, rather than the entire store. The community showed interest too, with discussions emerging around signal integration strategies.

Enter Remix: Rethinking the Model Layer

With the core reactivity model working well, I decided to tackle one of my favorite meta-frameworks: Remix. Remix is incredibly powerful, but there's sometimes boilerplate when setting up loaders, actions, and form handling.

I realized jods could serve as a bridge — creating a more model-centric approach where your data models define their:

  • 📝 Schema validation
  • 🔄 Server-side loaders
  • 📋 Form handlers
  • ⚡️ Client-side reactivity

The result is what we now have in jods/remix — a streamlined approach that reduces boilerplate and brings a more familiar model-view pattern to Remix development:

// Define your model once
export const user = defineStore({
name: "user",
schema: z.object({
name: z.string(),
email: z.string().email(),
}),
defaults: {
name: "Guest",
email: "",
},
handlers: {
async updateProfile({ current, form }) {
// Update logic
},
},
loader: async ({ request }) => {
// Load user data
},
});

// Export ready-to-use loaders and actions
export const loader = withJods([user]);
export const action = user.action;

Then in your components, simply use:

function ProfilePage() {
const userData = useJodsStore(user);
const form = useJodsForm(user.actions.updateProfile);

return (
<form {...form.props}>
<input name="name" defaultValue={userData.name} />
<button type="submit">Update</button>
</form>
);
}

The Road Ahead

While I'm thrilled with how far jods has come in such a short time, there's still much more to explore:

  • 💾 Enhanced persistence options (#5)
  • 🔄 Remote syncing capabilities (#7)
  • 📡 Streaming updates from server sources (#9)
  • ⏰ Time-travel debugging
  • 🛠️ Developer tools

Final Thoughts

What started as a simple name registration has evolved into a project I'm genuinely excited about. jods reflects my belief that state management should be as simple and intuitive as possible, while still being powerful enough for real-world applications.

I never expected to build all this in less than two weeks, but it's amazing what you can accomplish when you're passionate about solving a problem you care about.

I'd love to hear your thoughts and see what you build with jods!

clamstew