Declarative rendering of react-query state via switch-query — DeepSeek Blog | Neura Market
    Neura MarketNeura Market/DeepSeek
    ChatGPTChatGPTClaudeClaudeGeminiGeminiCursorCursorGrokGrokPerplexityPerplexityDeepSeekDeepSeek
    CoPilotCoPilotStable DiffusionStable DiffusionMidjourneyMidjourney
    View All Directories
    OverviewRulesPromptsMCPsAgentsBlogVideosGuidesCoursesCommunityTrendingGenerate
    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](https://mpanichev.ru/articles/switch-query-package/) [`switch-query`](https://github.com/denisinvader/switch-query) is a tiny [npm package](https://www.npmjs.com/package/switch-query) that simplifies displaying the state from [`@tanstack/react-query`](https://tanstack.com/query/latest). 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](https://www.typescriptlang.org/docs/handbook/2/narrowing.html). This allows you to explicitly separate request states using strict typing. Let’s take an example from the official website: ```tsx 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 %} ```tsx 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: ```tsx 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: ```tsx 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. ```tsx 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: ```tsx <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](https://github.com/denisinvader/switch-query)

    Tags

    reactreactquery

    Comments

    More Blog

    View all
    How I'm using ASTs and Gemini to solve the "Codebase Onboarding" problem 🧠ai

    How I'm using ASTs and Gemini to solve the "Codebase Onboarding" problem 🧠

    Hi everyone! 👋 I’m Tara, a Senior Software Engineer and Consultant. Over the years, I've jumped...

    T
    tworrell
    Local AI Will Save Us All (The Math Says So, Trust Me)ai

    Local AI Will Save Us All (The Math Says So, Trust Me)

    Every few weeks a take goes viral in tech circles making the case for ditching cloud AI and running...

    S
    Sebastian Schürmann
    Lost in the AI Hype, I Started Smallai

    Lost in the AI Hype, I Started Small

    And it helped me get back into tech without drowning TL;DR at the end Coming back to...

    R
    Rohini Gaonkar
    Building a Replay-Tested Interactive Brokers Client in Gogo

    Building a Replay-Tested Interactive Brokers Client in Go

    I wanted an IBKR library that felt like Go and had testing I could trust. So I wrote one.

    T
    Thomas Marcelis
    Playwright in Pictures: Fully Parallel Modeplaywright

    Playwright in Pictures: Fully Parallel Mode

    Playwright’s fullyParallel mode is often treated as a simple performance switch. In practice, it...

    V
    Vitaliy Potapov
    Designing a CLI for Both Humans and Agentscli

    Designing a CLI for Both Humans and Agents

    Learn how Alpic designed its CLI for both human developers and AI agents — covering tradeoffs like polling, context windows, interactivity, and statelessness.

    J
    Julien Vallini

    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.