Sitemap
Yashraj

Yashraj

A passionate full-stack developer and designer, who wants to create something amazing and always trying to learn more and faster.

Stop Writing useState() 100 Times. This React UI Library Handles State Automatically

9 min readFeb 28, 2026

--

Press enter or click to view image in full size
react component code

If you’re still writing conditional rendering, icon imports, and animation classes for every button state in 2026, you’re wasting hours every single week.

I know because I did it for years. Every new project, same routine: write a button component, add useState for loading, write conditional rendering for icons, add animation classes, style each state differently, handle disabled logic, import spinner components...

Then copy-paste that exact visual rendering logic into the next project. And the next. And the next.

200+ hours of my life spent rewriting the same component visual logic.

That’s when I built Fysk.dev — a React UI library where components handle their own visual states automatically. You still control the logic (when states change), but Fysk handles all the visual rendering. No more conditional JSX. No more icon imports. No more animation classes.

Here’s how it saved me 30+ hours on my last two projects, and how it’ll do the same for you.

The Problem: React Components Are Dumb By Default

Let’s be honest. Most UI libraries give you beautiful, stateless shells. You still have to wire up all the logic yourself.

Here’s what building a simple button with loading state looks like in vanilla React:

// Standard React button - 40+ lines of repetitive code
import { useState } from 'react';
import { Button } from '@/components/ui/button'; // shadCN Button component

export function AsyncButton({ onClick, children }) {
const [isLoading, setIsLoading] = useState(false);
const [isSuccess, setIsSuccess] = useState(false);
const [isError, setIsError] = useState(false);

const handleClick = async () => {
setIsLoading(true);
setIsSuccess(false);
setIsError(false);

try {
await onClick();
setIsSuccess(true);
setTimeout(() => setIsSuccess(false), 2000);
} catch (error) {
setIsError(true);
setTimeout(() => setIsError(false), 2000);
} finally {
setIsLoading(false);
}
};

return (
<Button
onClick={handleClick}
disabled={isLoading}
className={`
${isLoading ? 'opacity-50 cursor-not-allowed' : ''}
${isSuccess ? 'bg-green-500' : ''}
${isError ? 'bg-red-500' : ''}
`}
>
{isLoading && <Spinner />}
{isSuccess && <CheckIcon />}
{isError && <XIcon />}
{!isLoading && !isSuccess && !isError && children}
</Button>
);
}

That’s ONE button. Now multiply that by every form, every input, every toggle, every tab component you build.

You’re writing the same state management logic hundreds of times across your career.

The Solution: Components That Think For Themselves

Here’s the exact same functionality in Fysk:

// Fysk button - Simple state prop handles everything
import { Button } from '@/components/ui/button'; // Fysk button component

export function AsyncButton({ onClick, children }) {
const [buttonState, setButtonState] = useState('idle');

const handleSave = async () => {
setButtonState('loading');
try {
await saveData();
setButtonState('success');
} catch (error) {
setButtonState('error');
}
};

return (
<Button
state={buttonState}
loadingText="Saving..."
successText="Saved!"
errorText="Failed!"
onClick={handleSave}
>
Save Changes
</Button>
);
}

Wait, that still has useState! True, but here’s what you DON’T have to write:

  • ❌ No loading spinner component
  • ❌ No disabled state logic
  • ❌ No success/error icon handling
  • ❌ No conditional rendering logic
  • ❌ No timeout to reset states
  • ❌ No className conditionals for styling

The button handles all visual feedback automatically. You just pass state="loading" and it knows exactly what to show.

Even simpler for static states:

// When you just need to show a specific state (demos, examples, UI kits)
<Button state="loading" loadingText="Saving...">Save</Button>

<Button state="success" successText="Saved!">Save</Button>

<Button state="error" errorText="Failed!">Save</Button>

Perfect for Storybook, documentation, or UI showcases where you want to demonstrate each state.

Real Example: I Built a Waitlist Page in 30 Minutes

I recently launched CreatorJot and needed a waitlist landing page fast.

With vanilla React + shadcn/ui, here’s what I’d have spent time on:

  • Form validation state (10 min)
  • Input error handling (15 min)
  • Submit button loading states (10 min)
  • Success/error toast notifications (15 min)
  • Input field focus/blur animations (10 min)
  • Overall form state coordination (20 min)

Total: 80+ minutes just wiring up state logic before even thinking about design.

With Fysk, I did this:

// with Fysk components
import { Input, Button, Form } from '@/components/ui';

export function WaitlistForm() {
const [isLoading, setLoading] = useState<boolean>(fasle);
const [errMsg, setErrorMsg] = useState<string|null>(null);

const onSubmit = async (data) => {
try{
setErrorMsg(null);
setLoading(true);
await fetch('/api/waitlist', {
method: 'POST',
body: JSON.stringify(data)
});

} catch(err){
toast.error(err);
setErrorMsg(err);
} finally{
setLoading(false);
}
};

return (
<Form onSubmit={onSubmit}>
<Input
state={isLoading ? 'loading' : errMsg ? 'error' : 'idle'} // idle is default state
name="email"
type="email"
placeholder="Enter your email"
/>
<Button state={isLoading ? 'loading' : errMsg ? 'error' : 'idle'} type="submit">Join Waitlist</Button>
</Form>
);
}

Time spent: 5 minutes.

The Input automatically:

  • Shows validation errors
  • Displays valid state with checkmark
  • Handles opacity down/up transitions

The Button automatically:

  • Goes into loading state on submit
  • Shows success when API call succeeds
  • Shows error if API call fails
  • Re-enables after completion

The Form automatically:

  • Prevents double submissions
  • Handles form-level state coordination
  • Integrates with React Hook Form + Zod

Result: Full waitlist page in 30 minutes instead of 2 hours. That’s a 75% time savings on state management alone.

Fysk vs. shadcn/ui: What’s Different?

“Wait, isn’t Fysk just shadcn/ui with extra steps?”

No. Here’s the key difference:

shadcn/ui (Manual Everything)

// You handle ALL the logic manually
const [loading, setLoading] = useState(false);
const [success, setSuccess] = useState(false);
const [error, setError] = useState(false);

<Button
disabled={loading}
className={`${loading ? 'opacity-50' : ''} ${success ? 'bg-green-500' : ''}`}
onClick={async () => {
setLoading(true);
try {
await action();
setSuccess(true);
setTimeout(() => setSuccess(false), 2000);
} catch (e) {
setError(true);
setTimeout(() => setError(false), 2000);
}
setLoading(false);
}}
>
{loading && <Spinner />}
{success && <CheckIcon />}
{error && <XIcon />}
{!loading && !success && !error && 'Submit'}
</Button>

Fysk (Intelligent State Handling)

// Fysk handles the visual logic for you
const [state, setState] = useState('idle');

<Button
state={state}
loadingText="Submitting..."
successText="Done!"
errorText="Failed"
onClick={async () => {
setState('loading');
try {
await action();
setState('success');
} catch (e) {
setState('error');
}
}}
>
Submit
</Button>

Fysk is built ON TOP of shadcn/ui and Radix primitives. We kept everything developers love about shadcn (copy-paste freedom, full code ownership, Tailwind styling) and added automatic state management.

The comparison:

ShadCN UI and Fysk UI comparison

Think of Fysk as “shadcn/ui with intelligent visual states.”

More Components That Handle Their Own State

Input Component

// Fysk Input handles validation visually
const [email, setEmail] = useState('');
const [emailState, setEmailState] = useState('idle');

<Input
value={email}
onChange={(e) => {
setEmail(e.target.value);
setEmailState(e.target.value.includes('@') ? 'valid' : 'error');
}}
variant="glass" // fysk support glass variant to make your UI looks more elegant
state={emailState}
errorText="Invalid email"
placeholder="Email address"
/>

No need to write:

  • Smooth animation on error/loading/success
  • Checkmark icon on valid
  • Error message styling
  • Input border color changes

Switch Component

// Clean toggle with built-in state
const [enabled, setEnabled] = useState(false);

<Switch
checked={enabled}
onCheckedChange={setEnabled}
/>

No need to write:

  • Toggle animation
  • Background color transitions
  • Disabled state styling

Tabs Component

// Manages active tab state internally
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui"

<Tabs defaultValue="account">
<TabsList>
<TabsTrigger value="account">Account</TabsTrigger>

// 👉 Shows an animated spinner and disables the tab during activity.
<TabsTrigger loading={true} value="password" loading>Password</TabsTrigger>

</TabsList>
<TabsContent value="account">Content Acc</TabsContent>
<TabsContent value="password">Content Pass</TabsContent>
</Tabs>

No useState. No activeTab variable. No setActiveTab handlers.

Get Yashraj’s stories in your inbox

Join Medium for free to get updates from this writer.

Components remember their own state. You just compose the UI.

Real Project Impact: PixEarn

PixEarn is my main app — think YouTube but for visuals. The entire form system (upload flows, profile settings, payment forms) is built with Fysk.

Before Fysk (estimated time):

  • User profile form: 4 hours (state management + validation)
  • Image upload component: 3 hours (loading, progress, error states)
  • Form: 5 hours (multi-step state, validation, loading)
  • Total: 12 hours of pure state wrangling

With Fysk (actual time):

  • User profile form: 45 minutes
  • Image upload component: 1 hour
  • Payment form: 1.5 hours
  • Total: 3.25 hours

Time saved: 8.75 hours on forms alone. That’s more than a full workday.

Multiply that across dozens of components and multiple projects? You save weeks.

The Architecture: How It Actually Works

Fysk isn’t magic. It’s intelligent component design.

Every Fysk component comes with:

  1. Built-in visual states: Components know how to render loading, success, error, and idle states
  2. Smart prop handling: Pass a state prop and the component handles all visual transitions
  3. Automatic animations: State changes trigger smooth, professional animations
  4. Consistent patterns: Same state handling pattern across all components

Example: How the Button handles states

// it contains more code check full component code on fysk.dev/docs

const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
(
{
className,
autoStateVariant = false,
variant = "default",
size = "md",
asChild = false,
state = "idle",
loadingText,
successText,
errorText,
icon,
iconLoading: propIconLoading,
iconSuccess: propIconSuccess,
iconError: propIconError,
iconPosition: propIconPosition,
children,
...props
},
ref
) => {
const config = useFyskConfig()
const { isEnabled, motion, AnimatePresence, transitions, variants } = useFyskAnimation()

const isLoading = state === "loading"
const isSuccess = state === "success"
const isError = state === "error"

const activeIconPosition = propIconPosition || config.iconPosition || "start"

const finalIconLoading = propIconLoading || config.icons?.loading
const finalIconSuccess = propIconSuccess || config.icons?.success
const finalIconError = propIconError || config.icons?.error

// Icon Size Detection
const isIconSize = size?.toString().startsWith("icon")

// Content Logic
// For icon sizes, we treat children as the icon if no icon prop is provided.
// We also don't render text in icon-sizes to maintain the square aspect ratio.
const idleIcon = isIconSize ? (icon || children) : icon
const idleText = isIconSize ? null : children

const activeIcon = isLoading ? finalIconLoading : isSuccess ? finalIconSuccess : isError ? finalIconError : idleIcon
const activeText = isLoading ? (loadingText || children) : isSuccess ? (successText || children) : isError ? (errorText || children) : idleText

const MotionButton = isEnabled && motion ? motion.button : "button"
const Comp = asChild ? Slot : (MotionButton as any)
const MotionSpan = isEnabled && motion ? motion.span : "span"

// Final variant based on the state
variant = autoStateVariant ? isSuccess ? "success-outline" : isError ? "destructive-outline" : variant : variant;

return (
<Comp
data-slot="button"
data-variant={variant}
data-size={size}
data-icon-position={activeIconPosition}
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
disabled={isLoading || props.disabled}
data-state={state}
{...(isEnabled && motion && !asChild ? {
layout: true,
transition: transitions?.layoutEnter
} : {})}
{...props}
>
{asChild ? children : (
<AnimatePresence {...(isEnabled && motion ? { mode: "wait", initial: false } : {})}>
<MotionSpan
key={state}
className={cn(
"inline-flex items-center justify-center gap-[inherit]",
activeIconPosition === "end" && "flex-row-reverse"
)}
{...(isEnabled && motion && variants ? {
layout: true,
...variants.fadeSlide,
transition: transitions?.content
} : {})}
>
{activeIcon && (
<MotionSpan
className={cn(
"inline-flex shrink-0 items-center justify-center",
// Auto-scale SVGs for standard sizes
!isIconSize && (
size === "xs" ? "[&_svg]:size-3" :
size === "sm" ? "[&_svg]:size-3.5" :
size === "xl" ? "[&_svg]:size-5" :
"[&_svg]:size-4"
)
)}
{...(isEnabled && motion && variants ? {
layout: true,
...variants.iconPop
} : {})}
>
{activeIcon}
</MotionSpan>
)}
{!isIconSize && activeText}
</MotionSpan>
</AnimatePresence>
)}
</Comp>
)
}
)

The key difference: You control WHEN states change (your logic), but Fysk controls HOW they look (visual feedback, animations, icons, styling).

You don’t write:

  • Conditional rendering for icons
  • Loading spinner components
  • Disabled state styling
  • Animation classes
  • Success/error visual feedback

You just pass state="loading" and Fysk does the rest.

When Should You Use Fysk?

Fysk is perfect for:

  • ✅ Rapid MVP development (founders building fast)
  • ✅ Internal dashboards (where speed > custom design)
  • ✅ Form-heavy applications (admin panels, SaaS tools)
  • ✅ Developers tired of useState spam (anyone who’s built 10+ React apps)
  • ✅ Teams wanting consistent state patterns (no more “how should we handle loading?” debates)

Fysk might NOT be for:

  • ❌ Highly custom UI/UX (where you need full control over every state transition)
  • ❌ Static marketing sites (overkill for mostly static content)
  • ❌ Teams using Vue/Svelte/Angular (React only, for now)

The Bottom Line

Every minute you spend writing useState for buttons, inputs, and forms is a minute not spent building features.

I built Fysk because I was tired of rewriting the same state logic in every project. Now:

  • PixEarn’s entire form system: Built in days, not weeks
  • CreatorJot’s waitlist page: 30 minutes from idea to deployed
  • Every new project: 50–70% less time on state management

The math is simple:

  • Average React project: 20–30 hours on state management
  • With Fysk: 5–8 hours on state management
  • Time saved: 15–25 hours per project

If you value your time at even $50/hour, that’s $750–1,250 saved per project.

And it’s free. And you own the code. And you can customize it however you want.

Try Fysk Right Now

Stop writing the same state logic over and over.

Get started in 2 minutes:

  1. Go to Docs: fysk.dev/doc website docs and explore the all the component, choose your component, copy the command like following one
    npx shadcn@latest add https://fysk.dev/r/button.jso
  2. Add component: now you can use the ShadCN CLI to add this component in your project, run the above command in your terminal (project root)

🎉 That’s it, you added your first Fysk component, for complete guide, check the following link

→ Start with the Quick Start Guide

Join the Fysk Community

⭐ Star Fysk on GitHub — Help other developers discover it

🐦 Follow @yrjdev on X — Daily React tips, Fysk updates, and builder content

📖 Explore All Components — See every stateful component available

About the Author

I’m Yashraj the creator of Fysk, PixEarn, STL (Structured Table Language), Regify and CreatorJot.
As a full-stack developer, I built 10+ React apps, and after facing the same issue to right state code again and again, I created Fysk to eliminate the repetitive state management code I was tired of writing. Based in India, working from home.

Follow for React tips & library updates: X/Twitter · GitHub · LinkedIn

Need Help Integrating Fysk Into Your Project?

Building a new product or revamping an existing one? Dojo Studio specializes in React development and can help you integrate Fysk into your application, set up component systems, and build production-ready UIs faster.

From complete web app builds to monthly maintenance, we create digital experiences optimized for speed and scale. Visit dojostudio.in to explore our development services.

--

--

Written by Yashraj

A passionate full-stack developer and designer, who wants to create something amazing and always trying to learn more and faster.