Declarative rendering of react-query state via switch-query…
    Neura MarketNeura Market/DeepSeek
    ChatGPTChatGPTClaudeClaudeGeminiGeminiCursorCursorGrokGrokPerplexityPerplexityDeepSeekDeepSeek
    CoPilotCoPilotStable DiffusionStable DiffusionMidjourneyMidjourney
    View All Directories
    OverviewRulesPromptsMCPsAgentsGamesBlogVideosGuidesCoursesCommunityTrending
    DeepSeekBlogDeclarative rendering of react-query state via switch-query
    Back to Blog
    Declarative rendering of react-query state via switch-query
    react

    Declarative rendering of react-query state via switch-query

    Mikhail Panichev February 9, 2026
    0 views

    switch-query is a tiny npm package that simplifies rendering the state of @tanstack/react-query


    title: Declarative rendering of react-query state via switch-query published: true description: switch-query is a tiny npm package that simplifies rendering the state of @tanstack/react-query tags: react, reactquery cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hjagxgiuxa00z49mux5q.png

    Russian version

    switch-query is a tiny npm package that simplifies displaying the state from @tanstack/react-query. It exports a single component, SwitchQuery, along with its types. Despite its simplicity, the package streamlines code structure and enables more thoughtful design UI/UX.

    Request states

    In general, to provide users with the most accurate information retrieved from the server, the following scenarios must be considered:

    • Success — the request was successful, and data is displayed.
    • Failure — the request resulted in an error.
    • Loading — the request is in progress, and data is not yet available.
    • Empty state — the response was successful, but there is no data.

    @tanstack/react-query implements types narrowing. This allows you to explicitly separate request states using strict typing.

    Let’s take an example from the official website:

    import { useQuery } from '@tanstack/react-query';
    
    function Todos() {
        const { data, isPending, error } = useQuery({
            queryKey: ['todos'],
            queryFn: () => fetch('/api/todos').then((r) => r.json()),
        });
    
        if (isPending) {
            return <span>Loading...</span>;
        }
    
        if (error) {
            return <span>Oops!</span>;
        }
    
        return (
            <ul>
                {data.map((t) => (
                    <li key={t.id}>{t.title}</li>
                ))}
            </ul>
        );
    }
    
    export default Todos;
    

    In my opinion, this is one of the library’s key advantages: it encourages developers to create more thoughtful user interfaces.

    However, when using if and return statements, it becomes inconvenient to add elements common to each state. For example, let’s add a container and a create button.

    {% details Example of modified code %}

    import { useQuery } from '@tanstack/react-query';
    
    function Todos() {
        const { data, isPending, error } = useQuery({
            queryKey: ['todos'],
            queryFn: () => fetch('/api/todos').then((r) => r.json()),
        });
    
        if (isPending) {
            return (
                <div className="container">
                    <span>Loading...</span>
                    <button>add</button>
                </div>
            );
        }
    
        if (error) {
            return (
                <div className="container">
                    <span>Oops!</span>
                    <button>add</button>
                </div>
            );
        }
    
        return (
            <div className="container">
                <ul>
                    {data.map((t) => (
                        <li key={t.id}>{t.title}</li>
                    ))}
                </ul>
                <button>add</button>
            </div>
        );
    }
    
    export default Todos;
    

    An alternative using conditions within JSX solves the code duplication issue but is less explicit and leaves room for errors, such as displaying multiple states simultaneously:

    import { useQuery } from '@tanstack/react-query';
    
    function Todos() {
        const { data, isPending, error, isSuccess } = useQuery({
            queryKey: ['todos'],
            queryFn: () => fetch('/api/todos').then((r) => r.json()),
        });
    
        return (
            <div className="container">
                {isPending && <span>Loading...</span>}
    
                {error && <span>Oops!</span>}
    
                {isSuccess && (
                    <ul>
                        {data.map((t) => (
                            <li key={t.id}>{t.title}</li>
                        ))}
                    </ul>
                )}
                <button>add</button>
            </div>
        );
    }
    
    export default Todos;
    

    {% enddetails %}

    With switch-query, this task is easily solved by encapsulating checks within a nested JSX element:

    import { useQuery } from '@tanstack/react-query';
    import { SwitchQuery } from 'switch-query';
    
    function Todos() {
        const query = useQuery({
            queryKey: ['todos'],
            queryFn: () => fetch('/api/todos').then((r) => r.json()),
        });
    
        return (
            <div className="container">
                <SwitchQuery
                    query={query}
                    pending={<span>Loading...</span>}
                    error={<span>Oops!</span>}
                    success={({ data }) => (
                        <ul>
                            {data.map((t) => (
                                <li key={t.id}>{t.title}</li>
                            ))}
                        </ul>
                    )}
                />
                <button>add</button>
            </div>
        );
    }
    
    export default Todos;
    

    This approach is more convenient in the JSX context. There’s no need to excessively decompose components or use nested ternary operators.

    Multiple queries in a single component

    Rendering via JSX allows working with multiple queries in a single component.

    Suppose each item references a tag dictionary via tagId, and we need to display the tag title.

    In this case, you can nest <SwitchQuery /> elements within each other. The order in which responses are received doesn’t matter, as all states are accounted for at every level.

    import { useQuery } from '@tanstack/react-query';
    import { SwitchQuery } from 'switch-query';
    
    function Todos() {
        const todos = useQuery({
            queryKey: ['todos'],
            queryFn: () => fetch('/api/todos').then((r) => r.json()),
        });
        const tags = useQuery({
            queryKey: ['tags'],
            queryFn: () => fetch('/api/tags').then((r) => r.json()),
        });
    
        return (
            <div className="container">
                <SwitchQuery
                    query={todos}
                    pending={<span>Loading...</span>}
                    error={<span>Oops!</span>}
                    success={({ data }) => (
                        <ul>
                            {data.map((t) => (
                                <li key={t.id}>
                                    {t.title}
                                    <SwitchQuery
                                        query={tags}
                                        success={({ data }) => data.title}
                                        pending="loading..."
                                        error={`#${t.tagId}`}
                                    />
                                </li>
                            ))}
                        </ul>
                    )}
                />
                <button>add</button>
    
                <div>
                    <h2>Tags:</h2>
                    <SwitchQuery
                        query={tags}
                        success={({ data }) => (
                            <ul>
                                {Object.keys(data).map((id) => (
                                    <li key={id}>{data.title}</li>
                                ))}
                            </ul>
                        )}
                        pending="loading..."
                        error={({ error, refetch }) => (
                            <>
                                <div>{error.message}</div>
                                <button onClick={() => refetch()}>retry</button>
                            </>
                        )}
                    />
                </div>
            </div>
        );
    }
    
    export default Todos;
    

    Implementing such a view without switch-query would require creating additional components.

    Empty states

    For good design, it’s also important to explicitly inform the user when there is no data.

    In switch-query empty state case is moved to additional props:

    <SwitchQuery
        query={query}
        success={({ data }) => (
            <ul>
                {data.map((t) => (
                    <li key={t.id}>{t.title}</li>
                ))}
            </ul>
        )}
        checkIsEmpty={(data) => !data.length}
        empty={<div>Nothing to do</div>}
    />
    

    Handling all possible states is brought to a more declarative style.

    Conclusion

    One might argue that all the examples above are flawed and it would be better to move everything into separate components. This is a perfectly valid stance and you could even use useSuspenseQuery.

    However, in this case, implementation of a single interface block would be spread across multiple components and files, which would also need to be named.

    I believe that @tanstack/react-query blurs established patterns by making the request an integral part of the views, which aligns with the final result. The library’s declarative API is perfectly complemented by the declarative nature of JSX.

    Star on GitHub

    Tags

    reactreactquery

    Comments

    More Blog

    View all
    Five Gemma-4 models, one accelerator: what porting E2B 31B to AWS Inferentia2 taught megemma

    Five Gemma-4 models, one accelerator: what porting E2B 31B to AWS Inferentia2 taught me

    I ported the whole Gemma-4 family — E2B, E4B, 12B, 31B, and the 26B-A4B MoE — to run on...

    X
    xbill
    Hey DEV, I'm Tobore. Let's actually connect.community

    Hey DEV, I'm Tobore. Let's actually connect.

    Hey DEV, I'm Tobore. Let's actually connect. I've been on here for a while now, mostly writing and...

    L
    Laurina Ayarah
    I burned through thousands of AI tokens. Then a friend did it for freeai

    I burned through thousands of AI tokens. Then a friend did it for free

    (yep, kinda clickbait, just for the funsies 😊) At the beginning of the year, I relaunched my...

    P
    Paulo Henrique
    Claude might be saturating your machineai

    Claude might be saturating your machine

    My laptop was sitting idle with the fan at full tilt. Nothing was running that I knew of. The culprit...

    S
    Sidhant Panda
    Automated GitHub Code Reviews Using Google Geminigithubactions

    Automated GitHub Code Reviews Using Google Gemini

    I Built a Thing! TL;DR — Google Gemini-based Pull Request reviews and Issue Triaging for...

    D
    Darren "Dazbo" Lester
    What is an "agentic harness," actually?ai

    What is an "agentic harness," actually?

    I've been hearing the word "harness" thrown around a lot lately. I assumed it just meant "the IDE" or...

    T
    Tilde A. Thurium

    Stay up to date

    Get the latest DeepSeek prompts, rules, and resources delivered to your inbox weekly.

    Neura Market LogoNeura Market

    Discover the best AI prompts, plugins, and resources for DeepSeek and more.

    Content Types

    • Rules
    • Prompts
    • MCPs
    • Agents
    • Guides

    Platforms

    • ChatGPT Directory
    • Claude Directory
    • Gemini Directory
    • Cursor Directory
    • Grok Directory
    • Perplexity Directory
    • DeepSeek Directory
    • CoPilot Directory
    • Stable Diffusion Directory
    • Midjourney Directory
    • All Directories

    Resources

    • Blog
    • Documentation
    • Help Center
    • Marketplace

    Legal

    • Privacy Policy
    • Terms of Service

    © 2026 Neura Market. All rights reserved.

    |

    Not affiliated with any AI platform vendors.

    Neura Market

    Custom AI Systems & Services

    Our team of experienced AI builders will help build custom AI systems, workflows, and solutions for your business.

    Request custom work