Migrate to React Zero-UI from existing state management solutions
Step-by-step guides for common migration scenarios.
Before (React useState):
import { useState } from "react";
function ThemeToggle() {
const [theme, setTheme] = useState("light");
return (
<div className={theme === "dark" ? "bg-gray-900" : "bg-white"}>
<button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>Toggle Theme</button>
</div>
);
}After (React Zero-UI):
import { useUI } from "@react-zero-ui/core";
function ThemeToggle() {
const [, setTheme] = useUI("theme", "light");
return (
<div className="theme-light:bg-white theme-dark:bg-gray-900">
<button onClick={() => setTheme((prev) => (prev === "light" ? "dark" : "light"))}>Toggle Theme</button>
</div>
);
}Key Changes:
- Replace
useStatewithuseUI - Replace conditional classNames with Tailwind variants
- State key becomes a data attribute (
data-theme) - Use functional updates for state transitions
Before:
function App() {
const [isModalOpen, setIsModalOpen] = useState(false);
return (
<>
<button onClick={() => setIsModalOpen(true)}>Open Modal</button>
{isModalOpen && (
<div className="fixed inset-0 bg-black/50">
<div className="bg-white p-6">
<button onClick={() => setIsModalOpen(false)}>Close</button>
</div>
</div>
)}
</>
);
}After:
function App() {
const [, setModal] = useUI("modal", "closed");
return (
<>
<button onClick={() => setModal("open")}>Open Modal</button>
<div className="modal-closed:hidden fixed inset-0 bg-black/50">
<div className="bg-white p-6">
<button onClick={() => setModal("closed")}>Close</button>
</div>
</div>
</>
);
}Before (Context API):
const ThemeContext = createContext();
function ThemeProvider({ children }) {
const [theme, setTheme] = useState("light");
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<div className={theme === "dark" ? "dark" : "light"}>{children}</div>
</ThemeContext.Provider>
);
}
function ThemeToggle() {
const { theme, setTheme } = useContext(ThemeContext);
return <button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>Current: {theme}</button>;
}After (React Zero-UI):
// No provider needed!
function App({ children }) {
return <div className="theme-light:bg-white theme-dark:bg-gray-900">{children}</div>;
}
function ThemeToggle() {
const [, setTheme] = useUI("theme", "light");
return (
<button onClick={() => setTheme((prev) => (prev === "light" ? "dark" : "light"))}>
<span className="theme-light:inline theme-dark:hidden">Light</span>
<span className="theme-dark:inline theme-light:hidden">Dark</span>
</button>
);
}Benefits:
- No more context providers
- No more prop drilling
- No re-renders when state changes
- State accessible anywhere via Tailwind classes
Before (Redux):
// store/themeSlice.ts
const themeSlice = createSlice({
name: "theme",
initialState: { value: "light" },
reducers: {
toggleTheme: (state) => {
state.value = state.value === "light" ? "dark" : "light";
},
},
});
// Component
function ThemeToggle() {
const theme = useSelector((state) => state.theme.value);
const dispatch = useDispatch();
return (
<div className={theme === "dark" ? "bg-gray-900" : "bg-white"}>
<button onClick={() => dispatch(toggleTheme())}>Toggle Theme</button>
</div>
);
}After (React Zero-UI):
// No store setup needed!
function ThemeToggle() {
const [, setTheme] = useUI("theme", "light");
return (
<div className="theme-light:bg-white theme-dark:bg-gray-900">
<button onClick={() => setTheme((prev) => (prev === "light" ? "dark" : "light"))}>Toggle Theme</button>
</div>
);
}Before (Zustand):
const useThemeStore = create((set) => ({ theme: "light", toggleTheme: () => set((state) => ({ theme: state.theme === "light" ? "dark" : "light" })) }));
function ThemeToggle() {
const { theme, toggleTheme } = useThemeStore();
return (
<div className={`${theme === "dark" ? "bg-gray-900" : "bg-white"}`}>
<button onClick={toggleTheme}>Current: {theme}</button>
</div>
);
}After (React Zero-UI):
function ThemeToggle() {
const [, setTheme] = useUI("theme", "light");
return (
<div className="theme-light:bg-white theme-dark:bg-gray-900">
<button onClick={() => setTheme((prev) => (prev === "light" ? "dark" : "light"))}>
<span className="theme-light:inline theme-dark:hidden">Light</span>
<span className="theme-dark:inline theme-light:hidden">Dark</span>
</button>
</div>
);
}Before (Styled Components):
const ThemeProvider = styled.div`
background: ${(props) => props.theme.bg};
color: ${(props) => props.theme.text};
`;
const theme = { light: { bg: "white", text: "black" }, dark: { bg: "black", text: "white" } };
function App() {
const [currentTheme, setCurrentTheme] = useState("light");
return (
<ThemeProvider theme={theme[currentTheme]}>
<button onClick={() => setCurrentTheme(currentTheme === "light" ? "dark" : "light")}>Toggle</button>
</ThemeProvider>
);
}After (React Zero-UI + Tailwind):
function App() {
const [, setTheme] = useUI("theme", "light");
return (
<div className="theme-light:bg-white theme-light:text-black theme-dark:bg-black theme-dark:text-white">
<button onClick={() => setTheme((prev) => (prev === "light" ? "dark" : "light"))}>Toggle</button>
</div>
);
}Before (Local component state):
function Sidebar() {
const [isOpen, setIsOpen] = useState(false);
return (
<div className={`${isOpen ? "w-64" : "w-16"} transition-all`}>
<button onClick={() => setIsOpen(!isOpen)}>Toggle</button>
</div>
);
}
function Header() {
// Can't access sidebar state!
return <div>Header content</div>;
}After (Global state, accessible everywhere):
function Sidebar() {
const [, setSidebar] = useUI("sidebar", "closed");
return (
<div className="sidebar-closed:w-16 sidebar-open:w-64 transition-all">
<button onClick={() => setSidebar((prev) => (prev === "closed" ? "open" : "closed"))}>Toggle</button>
</div>
);
}
function Header() {
// Can respond to sidebar state!
return <div className="sidebar-open:ml-64 sidebar-closed:ml-16 transition-all">Header content</div>;
}- List all
useStatehooks that control UI appearance - Identify global state (Context, Redux, Zustand)
- Find conditional className logic
- Note prop drilling for UI state
npx create-zero-uiOr manual setup:
- Install
@react-zero-ui/core - Configure PostCSS plugin
- Update Tailwind config
- Replace
useStatewithuseUI - Convert conditional classes to Tailwind variants
- Remove context providers for UI state
- Update component dependencies
- Verify all state changes work
- Check for any missing CSS variants
- Test SSR/hydration (no FOUC)
- Validate performance improvements
// Avoid conflicts with existing data attributes
const [, setState] = useUI("id", "default"); // conflicts with data-id
// Use descriptive, unique keys
const [, setState] = useUI("modal-state", "closed");// Ensure initial values match what CSS expects
const [, setTheme] = useUI("theme", "lite"); // typo!
// Match your Tailwind variants exactly
const [, setTheme] = useUI("theme", "light"); // matches theme-light:Imported variables can't be resolved statically — even if reassigned locally.
// This will fail at build time
import { THEME_KEY } from "./constants";
const localKey = THEME_KEY;
const [, setTheme] = useUI(localKey, "dark");// Inline the string directly or re-declare as a top-level const
const THEME_KEY = "theme";
const [, setTheme] = useUI(THEME_KEY, "dark");We're working on support for imported bindings once Next.js exposes a plugin API for Turbopack. Until then, stick with top-level
constliterals.
// Don't use returned value for logic
const [theme, setTheme] = useUI("theme", "light");
if (theme === "dark") {
/* Won't work! */
}
// Use CSS classes for visual state
<div className="theme-dark:hidden">Only visible in light mode</div>;| Aspect | Before (Traditional) | After (React Zero-UI) |
|---|---|---|
| Bundle Size | +5KB (Redux) / +2KB (Context) | +350 bytes |
| Re-renders | Every state change | Zero |
| Performance | Slower with scale | Constant fast |
| Setup | Complex (store, providers) | Simple (one hook) |
| Global Access | Prop drilling / Context | Tailwind variants anywhere |
| SSR | Hydration mismatches | Perfect SSR |
Your app should now be faster, simpler, and more maintainable.