Congratulations!

[Valid RSS] This is a valid RSS feed.

Recommendations

This feed is valid, but interoperability with the widest range of feed readers could be improved by implementing the following recommendations.

Source: https://www.peterbe.com/rss.xml

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Peterbe.com</title><link>http://www.peterbe.com/rss.xml</link><description>Stuff in Peter's head</description><atom:link href="http://www.peterbe.com/rss.xml" rel="self"/><language>en-us</language><lastBuildDate>Mon, 18 Nov 2024 16:20:15 +0000</lastBuildDate><item><title>An ideal pattern to combine React Router with TanStack Query</title><link>http://www.peterbe.com/plog/ideal-pattern-react-router-with-tanstackreact-query</link><description>If you use loaders in React Router and have them start XHR requests on the same QueryClient you use in your components, you can start API requests before you render the React page. This makes the page feel faster to load because you can have the back-end data ready for your eyes to feast on sooner.</description><pubDate>Mon, 18 Nov 2024 16:20:15 +0000</pubDate><guid>http://www.peterbe.com/plog/ideal-pattern-react-router-with-tanstackreact-query</guid></item><item><title>brotli_static in Nginx</title><link>http://www.peterbe.com/plog/brotli_static-in-nginx</link><description>`apt install libnginx-mod-http-brotli-static`</description><pubDate>Fri, 08 Nov 2024 17:06:28 +0000</pubDate><guid>http://www.peterbe.com/plog/brotli_static-in-nginx</guid></item><item><title>Object.keys to return the known strings of an object in TypeScript</title><link>http://www.peterbe.com/plog/object.keys-known-strings-object-ts</link><description>Use `keyof typeof myObject` to get the keys as an "list of strings" that are the keys of an object.</description><pubDate>Fri, 25 Oct 2024 13:07:32 +0000</pubDate><guid>http://www.peterbe.com/plog/object.keys-known-strings-object-ts</guid></item><item><title>How I make my Vite dev server experience faster</title><link>http://www.peterbe.com/plog/vite-dev-server-experience-faster</link><description>Use `server.warmup.clientFiles` to make Vite get to work on transforms as soon as possible, rather than on-demand. See what's going on using `vite --debug transform`</description><pubDate>Tue, 22 Oct 2024 14:14:10 +0000</pubDate><guid>http://www.peterbe.com/plog/vite-dev-server-experience-faster</guid></item><item><title>How to extend a function in TypeScript without repeating the signature types</title><link>http://www.peterbe.com/plog/extend-function-typescript-same-signature-types</link><description>Use the `Parameters&lt;typeof someFunction&gt;` to be able to extend or control the signature of a function you don't want to spell out.</description><pubDate>Wed, 16 Oct 2024 12:28:18 +0000</pubDate><guid>http://www.peterbe.com/plog/extend-function-typescript-same-signature-types</guid></item><item><title>Trying out the new Bun "Compile to bytecode"</title><link>http://www.peterbe.com/plog/trying-bun-compile-to-bytecode</link><description>Bun 1.1.30 added a `--bytecode` option to `bun build` and it does appear to make the executable faster</description><pubDate>Tue, 15 Oct 2024 11:49:16 +0000</pubDate><guid>http://www.peterbe.com/plog/trying-bun-compile-to-bytecode</guid></item><item><title>The performance benefits of code-split an SPA</title><link>http://www.peterbe.com/plog/performance-benefits-of-code-split-an-spa</link><description>Code-splitting can be great for web performance, but it's most likely about the underlying heavy libraries rather than your own code *using* those libraries.</description><pubDate>Sat, 12 Oct 2024 21:01:30 +0000</pubDate><guid>http://www.peterbe.com/plog/performance-benefits-of-code-split-an-spa</guid></item><item><title>Rate my golf swing (October 2024)</title><link>http://www.peterbe.com/plog/rate-my-golf-swing-october-2024</link><description>Video of my swing as of October 2024.</description><pubDate>Fri, 11 Oct 2024 20:10:35 +0000</pubDate><guid>http://www.peterbe.com/plog/rate-my-golf-swing-october-2024</guid></item><item><title>The 3 queries I use with pg_stat_statements to analyze slow PostgreSQL queries</title><link>http://www.peterbe.com/plog/3-queries-with-pg_stat_statements</link><description>3 primary SQL statements you need when using pg_stat_statements</description><pubDate>Mon, 30 Sep 2024 17:03:10 +0000</pubDate><guid>http://www.peterbe.com/plog/3-queries-with-pg_stat_statements</guid></item><item><title>How to handle success and failure in @tanstack/react-query useQuery hook</title><link>http://www.peterbe.com/plog/how-to-handle-success-and-failure-usequery-hook</link><description>&lt;p&gt;What &lt;a href="https://tanstack.com/query/latest/docs/framework/react/overview"&gt;&lt;code&gt;@tanstack/react-query&lt;/code&gt; is&lt;/a&gt; is a fancy way of fetching data, on the client, in a React app.&lt;/p&gt;
  3. &lt;p&gt;Simplified primer by example; instead of...&lt;/p&gt;
  4. &lt;pre&gt;&lt;code class="hljs"&gt;
  5. &lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title function_"&gt;MyComponent&lt;/span&gt;(&lt;span class="hljs-params"&gt;&lt;/span&gt;) {
  6.  &lt;span class="hljs-keyword"&gt;const&lt;/span&gt; [userInfo, setUserInfo] = &lt;span class="hljs-title function_"&gt;useState&lt;/span&gt;(&lt;span class="hljs-literal"&gt;null&lt;/span&gt;)
  7.  &lt;span class="hljs-title function_"&gt;useEffect&lt;/span&gt;(&lt;span class="hljs-function"&gt;() =&amp;gt;&lt;/span&gt; {
  8.    &lt;span class="hljs-title function_"&gt;fetch&lt;/span&gt;(&lt;span class="hljs-string"&gt;&amp;#x27;/api/user/info&amp;#x27;&lt;/span&gt;)
  9.    .&lt;span class="hljs-title function_"&gt;then&lt;/span&gt;(&lt;span class="hljs-function"&gt;&lt;span class="hljs-params"&gt;response&lt;/span&gt; =&amp;gt;&lt;/span&gt; response.&lt;span class="hljs-title function_"&gt;json&lt;/span&gt;())
  10.    .&lt;span class="hljs-title function_"&gt;then&lt;/span&gt;(&lt;span class="hljs-function"&gt;&lt;span class="hljs-params"&gt;data&lt;/span&gt; =&amp;gt;&lt;/span&gt; {
  11.      &lt;span class="hljs-title function_"&gt;setUserInfo&lt;/span&gt;(data)
  12.    })
  13.  }, [])
  14.  
  15.  &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; &lt;span class="language-xml"&gt;&lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;div&lt;/span&gt;&amp;gt;&lt;/span&gt;Username: {userInfo ? userInfo.user_name : &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;em&lt;/span&gt;&amp;gt;&lt;/span&gt;not yet known&lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;em&lt;/span&gt;&amp;gt;&lt;/span&gt;}&lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;div&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
  16. }
  17. &lt;/code&gt;&lt;/pre&gt;
  18.  
  19. &lt;!--split--&gt;
  20.  
  21. &lt;p&gt;you now do this:&lt;/p&gt;
  22. &lt;pre&gt;&lt;code class="hljs"&gt;
  23. &lt;span class="hljs-keyword"&gt;import&lt;/span&gt; { useQuery } &lt;span class="hljs-keyword"&gt;from&lt;/span&gt; &lt;span class="hljs-string"&gt;&amp;#x27;@tanstack/react-query&amp;#x27;&lt;/span&gt;
  24.  
  25. &lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title function_"&gt;MyComponent&lt;/span&gt;(&lt;span class="hljs-params"&gt;&lt;/span&gt;) {
  26.  &lt;span class="hljs-keyword"&gt;const&lt;/span&gt; {data} = &lt;span class="hljs-title function_"&gt;useQuery&lt;/span&gt;({
  27.    &lt;span class="hljs-attr"&gt;queryKey&lt;/span&gt;: [&lt;span class="hljs-string"&gt;&amp;#x27;userinfo&amp;#x27;&lt;/span&gt;],
  28.    &lt;span class="hljs-attr"&gt;queryFn&lt;/span&gt;: &lt;span class="hljs-keyword"&gt;async&lt;/span&gt; () {
  29.      &lt;span class="hljs-keyword"&gt;const&lt;/span&gt; response = &lt;span class="hljs-keyword"&gt;await&lt;/span&gt; &lt;span class="hljs-title function_"&gt;fetch&lt;/span&gt;(&lt;span class="hljs-string"&gt;&amp;#x27;/api/user/info&amp;#x27;&lt;/span&gt;)
  30.      &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; response.&lt;span class="hljs-title function_"&gt;json&lt;/span&gt;()
  31.    }
  32.  })
  33.  
  34.  &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; &lt;span class="language-xml"&gt;&lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;div&lt;/span&gt;&amp;gt;&lt;/span&gt;Username: {data ? data.user_name : &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;em&lt;/span&gt;&amp;gt;&lt;/span&gt;not yet known&lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;em&lt;/span&gt;&amp;gt;&lt;/span&gt;}&lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;div&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
  35. }
  36. &lt;/code&gt;&lt;/pre&gt;
  37.  
  38. &lt;h2&gt;That's a decent start, but...&lt;/h2&gt;
  39. &lt;p&gt;Error handling is a thing. Several things can go wrong:&lt;/p&gt;
  40. &lt;ol&gt;
  41. &lt;li&gt;Complete network failure during the &lt;code&gt;fetch(...)&lt;/code&gt;  &lt;/li&gt;
  42. &lt;li&gt;Server being (temporarily) down  &lt;/li&gt;
  43. &lt;li&gt;Not authorized  &lt;/li&gt;
  44. &lt;li&gt;Backend URL not found  &lt;/li&gt;
  45. &lt;li&gt;Backend URL found but wrong parameters&lt;/li&gt;
  46. &lt;/ol&gt;
  47. &lt;p&gt;None of the code solutions above deal with these things. At least not all of them.&lt;/p&gt;
  48. &lt;p&gt;By default, &lt;code&gt;useQuery&lt;/code&gt; will retry if any error thrown inside that &lt;code&gt;queryFn&lt;/code&gt; call.&lt;/p&gt;
  49. &lt;blockquote&gt;
  50. &lt;p&gt;&lt;em&gt;Queries that fail are silently &lt;/em&gt;&lt;em&gt;retried 3 times&lt;/em&gt;&lt;em&gt;, with exponential backoff delay before capturing and displaying an error to the UI.&lt;/em&gt;&lt;/p&gt;
  51. &lt;/blockquote&gt;
  52. &lt;p&gt;From the &lt;a href="https://tanstack.com/query/latest/docs/framework/react/guides/important-defaults"&gt;documentation about important defaults&lt;/a&gt;&lt;/p&gt;
  53. &lt;p&gt;For example, if the server responds with a &lt;code&gt;403&lt;/code&gt; the response body might not be of content-type JSON. So that &lt;code&gt;response.json()&lt;/code&gt; might fail and throw and then &lt;code&gt;useQuery&lt;/code&gt; will retry. You might be tempted to do this:&lt;/p&gt;
  54. &lt;pre&gt;&lt;code class="hljs"&gt;
  55.    queryFn: async () {
  56.      const response = await fetch(&amp;quot;/api/user/info&amp;quot;)
  57. &lt;span class="hljs-addition"&gt;+     if (!response.ok) {&lt;/span&gt;
  58. &lt;span class="hljs-addition"&gt;+        throw new Error(`Fetching data failed with a ${response.status} from the server`)&lt;/span&gt;
  59. &lt;span class="hljs-addition"&gt;+     }&lt;/span&gt;
  60.      return response.json()
  61.    }
  62. &lt;/code&gt;&lt;/pre&gt;
  63.  
  64. &lt;p&gt;The problem with this is that &lt;code&gt;useQuery&lt;/code&gt; still thinks it's an error and that it should retry. Sometimes it's the right thing to do, sometimes pointless.&lt;/p&gt;
  65. &lt;h2&gt;About retries&lt;/h2&gt;
  66. &lt;p&gt;The default implementation in @tanstack/react-query can be seen here: &lt;a href="https://github.com/TanStack/query/blob/acb5d37f5c81753243978ff2f4b016c1009f3813/packages/query-core/src/retryer.ts#L164-L206"&gt;&lt;code&gt;packages/query-core/src/retryer.ts&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
  67. &lt;p&gt;In a gross simplification, it works like this:&lt;/p&gt;
  68. &lt;pre&gt;&lt;code class="hljs"&gt;
  69. &lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title function_"&gt;run&lt;/span&gt;(&lt;span class="hljs-params"&gt;&lt;/span&gt;) {
  70.  &lt;span class="hljs-keyword"&gt;const&lt;/span&gt; promise = config.&lt;span class="hljs-title function_"&gt;fn&lt;/span&gt;()
  71.  &lt;span class="hljs-title class_"&gt;Promise&lt;/span&gt;.&lt;span class="hljs-title function_"&gt;resolve&lt;/span&gt;(promise)
  72.  .&lt;span class="hljs-title function_"&gt;then&lt;/span&gt;(resolve)
  73.  .&lt;span class="hljs-title function_"&gt;catch&lt;/span&gt;(&lt;span class="hljs-function"&gt;(&lt;span class="hljs-params"&gt;error&lt;/span&gt;) =&amp;gt;&lt;/span&gt; {
  74.  
  75.    &lt;span class="hljs-keyword"&gt;if&lt;/span&gt; (&lt;span class="hljs-title function_"&gt;shouldRetry&lt;/span&gt;(config)) {
  76.      &lt;span class="hljs-keyword"&gt;await&lt;/span&gt; &lt;span class="hljs-title function_"&gt;sleep&lt;/span&gt;(config.&lt;span class="hljs-title function_"&gt;sleepTime&lt;/span&gt;())
  77.      &lt;span class="hljs-title function_"&gt;run&lt;/span&gt;()
  78.    } &lt;span class="hljs-keyword"&gt;else&lt;/span&gt; {
  79.      &lt;span class="hljs-title function_"&gt;reject&lt;/span&gt;(error)
  80.    }
  81.  
  82.  })
  83.  
  84. &lt;/code&gt;&lt;/pre&gt;
  85.  
  86. &lt;p&gt;I'm not being accurate here but the point is that it's quite simple. The config has stuff like a count of how many times it's retried previously, dynamically whether it should retry, and how long it should sleep.&lt;br /&gt;
  87. The point is that it &lt;strong&gt;doesn't care what the nature of the error was&lt;/strong&gt;. It doesn't test if the error was of type &lt;code&gt;Response&lt;/code&gt; or if &lt;code&gt;error.message === "ECONNRESET"&lt;/code&gt; or something like that.&lt;/p&gt;
  88. &lt;p&gt;So in a sense, it's a "dumping ground" for any error thrown. So if you look into the response, within your query function,  and don't like the response, if you throw a new error, it will retry. And that might not be smart.&lt;/p&gt;
  89. &lt;p&gt;In simple terms; you &lt;strong&gt;&lt;em&gt;should retry&lt;/em&gt; if retrying is likely to yield a different result&lt;/strong&gt;. For example, if the server responded with a &lt;code&gt;503 Service Unavailable&lt;/code&gt; it's quite possible that if you just try again, a little later, it'll work.&lt;/p&gt;
  90. &lt;p&gt;What is wrong is if you get something like a &lt;code&gt;400 Bad Request&lt;/code&gt; response. Then, trying again won't work.&lt;br /&gt;
  91. Another thing that is wrong is if your own code throws an error within. For example, ...&lt;/p&gt;
  92. &lt;pre&gt;&lt;code class="hljs"&gt;
  93.    &lt;span class="hljs-attr"&gt;queryFn&lt;/span&gt;: &lt;span class="hljs-keyword"&gt;async&lt;/span&gt; () {
  94.      &lt;span class="hljs-keyword"&gt;const&lt;/span&gt; response = &lt;span class="hljs-keyword"&gt;await&lt;/span&gt; &lt;span class="hljs-title function_"&gt;fetch&lt;/span&gt;(&lt;span class="hljs-string"&gt;&amp;#x27;/api/user/info&amp;#x27;&lt;/span&gt;)
  95.      &lt;span class="hljs-keyword"&gt;const&lt;/span&gt; userInfo = response.&lt;span class="hljs-title function_"&gt;json&lt;/span&gt;()
  96.      &lt;span class="hljs-keyword"&gt;await&lt;/span&gt; &lt;span class="hljs-title function_"&gt;doSomethingComplexThatMightFail&lt;/span&gt;(userInfo)
  97.      &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; userInfo
  98.    }
  99. &lt;/code&gt;&lt;/pre&gt;
  100.  
  101. &lt;h2&gt;So, what's the harm?&lt;/h2&gt;
  102. &lt;p&gt;Suppose that you have something basic like this:&lt;/p&gt;
  103. &lt;pre&gt;&lt;code class="hljs"&gt;
  104.    &lt;span class="hljs-attr"&gt;queryFn&lt;/span&gt;: &lt;span class="hljs-keyword"&gt;async&lt;/span&gt; () {
  105.      &lt;span class="hljs-keyword"&gt;const&lt;/span&gt; response = &lt;span class="hljs-keyword"&gt;await&lt;/span&gt; &lt;span class="hljs-title function_"&gt;fetch&lt;/span&gt;(&lt;span class="hljs-string"&gt;&amp;quot;/api/user/info&amp;quot;&lt;/span&gt;)
  106.      &lt;span class="hljs-keyword"&gt;if&lt;/span&gt; (!response.&lt;span class="hljs-property"&gt;ok&lt;/span&gt;) {
  107.        &lt;span class="hljs-keyword"&gt;throw&lt;/span&gt; &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; &lt;span class="hljs-title class_"&gt;Error&lt;/span&gt;(&lt;span class="hljs-string"&gt;`Fetching data failed with a &lt;span class="hljs-subst"&gt;${response.status}&lt;/span&gt; from the server`&lt;/span&gt;)
  108.      }
  109.      &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; response.&lt;span class="hljs-title function_"&gt;json&lt;/span&gt;()
  110.    }
  111. &lt;/code&gt;&lt;/pre&gt;
  112.  
  113. &lt;p&gt;and you use it like this:&lt;/p&gt;
  114. &lt;pre&gt;&lt;code class="hljs"&gt;
  115. &lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title function_"&gt;MyComponent&lt;/span&gt;(&lt;span class="hljs-params"&gt;&lt;/span&gt;) {
  116.  &lt;span class="hljs-keyword"&gt;const&lt;/span&gt; {data, error} = &lt;span class="hljs-title function_"&gt;useQuery&lt;/span&gt;(...)
  117.  
  118.  &lt;span class="hljs-keyword"&gt;if&lt;/span&gt; (error) {
  119.    &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; &lt;span class="language-xml"&gt;&lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;div&lt;/span&gt;&amp;gt;&lt;/span&gt;An error happened. Reload the page mayhaps?&lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;div&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
  120.  }
  121.  &lt;span class="hljs-keyword"&gt;if&lt;/span&gt; (!data) {
  122.    &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; &lt;span class="language-xml"&gt;&lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;div&lt;/span&gt;&amp;gt;&lt;/span&gt;Loading...&lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;div&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
  123.  }
  124.  &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; &lt;span class="language-xml"&gt;&lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;AboutUser&lt;/span&gt; &lt;span class="hljs-attr"&gt;info&lt;/span&gt;=&lt;span class="hljs-string"&gt;{data.userInfo}/&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
  125. }
  126. &lt;/code&gt;&lt;/pre&gt;
  127.  
  128. &lt;p&gt;then, I guess if it's fine to &lt;em&gt;not&lt;/em&gt; be particularly "refined" about the error itself. It failed, refreshing the page might just work.&lt;/p&gt;
  129. &lt;h2&gt;If not an error, then what?&lt;/h2&gt;
  130. &lt;p&gt;The pattern I prefer, is to, if there is a problem with the response, to return it keyed as an error. Let's use TypeScript this time:&lt;/p&gt;
  131. &lt;pre&gt;&lt;code class="hljs"&gt;
  132. &lt;span class="hljs-comment"&gt;// THIS IS THE NAIVE APPROACH&lt;/span&gt;
  133.  
  134. &lt;span class="hljs-keyword"&gt;type&lt;/span&gt; &lt;span class="hljs-title class_"&gt;ServerResponse&lt;/span&gt; = {
  135.  &lt;span class="hljs-attr"&gt;user&lt;/span&gt;: {
  136.    &lt;span class="hljs-attr"&gt;first_name&lt;/span&gt;: &lt;span class="hljs-built_in"&gt;string&lt;/span&gt;
  137.    &lt;span class="hljs-attr"&gt;last_name&lt;/span&gt;: &lt;span class="hljs-built_in"&gt;string&lt;/span&gt;
  138.  }
  139. }
  140.  
  141. ...
  142.  
  143. &lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title function_"&gt;MyComponent&lt;/span&gt;(&lt;span class="hljs-params"&gt;&lt;/span&gt;) {
  144.  &lt;span class="hljs-keyword"&gt;const&lt;/span&gt; {data, error, isPending} = &lt;span class="hljs-title function_"&gt;useQuery&lt;/span&gt;({
  145.    &lt;span class="hljs-attr"&gt;queryKey&lt;/span&gt;: [&lt;span class="hljs-string"&gt;&amp;#x27;userinfo&amp;#x27;&lt;/span&gt;],
  146.    &lt;span class="hljs-attr"&gt;queryFn&lt;/span&gt;: &lt;span class="hljs-keyword"&gt;async&lt;/span&gt; () {
  147.      &lt;span class="hljs-keyword"&gt;const&lt;/span&gt; response = &lt;span class="hljs-keyword"&gt;await&lt;/span&gt; &lt;span class="hljs-title function_"&gt;fetch&lt;/span&gt;(&lt;span class="hljs-string"&gt;&amp;#x27;/api/user/info&amp;#x27;&lt;/span&gt;)
  148.      &lt;span class="hljs-keyword"&gt;if&lt;/span&gt; (!response.&lt;span class="hljs-property"&gt;ok&lt;/span&gt;) {
  149.         &lt;span class="hljs-keyword"&gt;throw&lt;/span&gt; &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; &lt;span class="hljs-title class_"&gt;Error&lt;/span&gt;(&lt;span class="hljs-string"&gt;`Bad response &lt;span class="hljs-subst"&gt;${response.status}&lt;/span&gt;`&lt;/span&gt;)
  150.      }
  151.      &lt;span class="hljs-keyword"&gt;const&lt;/span&gt; user = &lt;span class="hljs-keyword"&gt;await&lt;/span&gt; response.&lt;span class="hljs-title function_"&gt;json&lt;/span&gt;()
  152.      &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; user
  153.    }
  154.  })
  155.  
  156.  &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; &lt;span class="language-xml"&gt;&lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;div&lt;/span&gt;&amp;gt;&lt;/span&gt;Username: {userInfo ? userInfo.user_name : &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;em&lt;/span&gt;&amp;gt;&lt;/span&gt;not yet known&lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;em&lt;/span&gt;&amp;gt;&lt;/span&gt;}&lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;div&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
  157. }
  158. &lt;/code&gt;&lt;/pre&gt;
  159.  
  160. &lt;p&gt;A better approach is to allow &lt;code&gt;queryFn&lt;/code&gt; to return what it would 99% of the time, but also return an error, like this:&lt;/p&gt;
  161. &lt;pre&gt;&lt;code class="hljs"&gt;
  162. &lt;span class="hljs-comment"&gt;// THIS IS THE MORE REFINED APPROACH&lt;/span&gt;
  163.  
  164. &lt;span class="hljs-keyword"&gt;type&lt;/span&gt; &lt;span class="hljs-title class_"&gt;ServerResponse&lt;/span&gt; = {
  165.  user?: {
  166.    &lt;span class="hljs-attr"&gt;first_name&lt;/span&gt;: &lt;span class="hljs-built_in"&gt;string&lt;/span&gt;
  167.    &lt;span class="hljs-attr"&gt;last_name&lt;/span&gt;: &lt;span class="hljs-built_in"&gt;string&lt;/span&gt;
  168.  }
  169.  errorCode?: &lt;span class="hljs-built_in"&gt;number&lt;/span&gt;
  170. }
  171.  
  172. ...
  173.  
  174. &lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title function_"&gt;MyComponent&lt;/span&gt;(&lt;span class="hljs-params"&gt;&lt;/span&gt;) {
  175.  &lt;span class="hljs-keyword"&gt;const&lt;/span&gt; {data, error, isPending} = &lt;span class="hljs-title function_"&gt;useQuery&lt;/span&gt;({
  176.    &lt;span class="hljs-attr"&gt;queryKey&lt;/span&gt;: [&lt;span class="hljs-string"&gt;&amp;#x27;userinfo&amp;#x27;&lt;/span&gt;],
  177.    &lt;span class="hljs-attr"&gt;queryFn&lt;/span&gt;: &lt;span class="hljs-keyword"&gt;async&lt;/span&gt; () {
  178.      &lt;span class="hljs-keyword"&gt;const&lt;/span&gt; response = &lt;span class="hljs-keyword"&gt;await&lt;/span&gt; &lt;span class="hljs-title function_"&gt;fetch&lt;/span&gt;(&lt;span class="hljs-string"&gt;&amp;#x27;/api/user/info&amp;#x27;&lt;/span&gt;)
  179.      &lt;span class="hljs-keyword"&gt;if&lt;/span&gt; (response.&lt;span class="hljs-property"&gt;status&lt;/span&gt; &amp;gt;= &lt;span class="hljs-number"&gt;500&lt;/span&gt;) {
  180.         &lt;span class="hljs-comment"&gt;// This will trigger useQuery to retry&lt;/span&gt;
  181.         &lt;span class="hljs-keyword"&gt;throw&lt;/span&gt; &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; &lt;span class="hljs-title class_"&gt;Error&lt;/span&gt;(&lt;span class="hljs-string"&gt;`Bad response &lt;span class="hljs-subst"&gt;${response.status}&lt;/span&gt;`&lt;/span&gt;)
  182.      }
  183.      &lt;span class="hljs-keyword"&gt;if&lt;/span&gt; (response.&lt;span class="hljs-property"&gt;status&lt;/span&gt; &amp;gt;= &lt;span class="hljs-number"&gt;400&lt;/span&gt;) {
  184.         &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; {&lt;span class="hljs-attr"&gt;errorCode&lt;/span&gt;: response.&lt;span class="hljs-property"&gt;status&lt;/span&gt;}
  185.      }
  186.      &lt;span class="hljs-keyword"&gt;const&lt;/span&gt; user = &lt;span class="hljs-keyword"&gt;await&lt;/span&gt; response.&lt;span class="hljs-title function_"&gt;json&lt;/span&gt;()
  187.      &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; {user}
  188.    }
  189.  })
  190.  
  191.  
  192.  &lt;span class="hljs-keyword"&gt;if&lt;/span&gt; (errorCode) {
  193.     &lt;span class="hljs-keyword"&gt;if&lt;/span&gt; (errorCode === &lt;span class="hljs-number"&gt;403&lt;/span&gt;) {
  194.        &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; &lt;span class="language-xml"&gt;&lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;p&lt;/span&gt;&amp;gt;&lt;/span&gt;You&amp;#x27;re not authorized. &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;a&lt;/span&gt; &lt;span class="hljs-attr"&gt;href&lt;/span&gt;=&lt;span class="hljs-string"&gt;&amp;quot;/login&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;Log in here&lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;a&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;p&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
  195.     }
  196.     &lt;span class="hljs-keyword"&gt;throw&lt;/span&gt; &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; &lt;span class="hljs-title class_"&gt;Error&lt;/span&gt;(&lt;span class="hljs-string"&gt;`Unexpected response from the API (&lt;span class="hljs-subst"&gt;${errorCode}&lt;/span&gt;)`&lt;/span&gt;)
  197.  }
  198.  &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; &lt;span class="language-xml"&gt;&lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;div&lt;/span&gt;&amp;gt;&lt;/span&gt;
  199.     Username: {userInfo ? userInfo.user_name : &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;em&lt;/span&gt;&amp;gt;&lt;/span&gt;not yet known&lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;em&lt;/span&gt;&amp;gt;&lt;/span&gt;}
  200.  &lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;div&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
  201. }
  202. &lt;/code&gt;&lt;/pre&gt;
  203.  
  204. &lt;p&gt;It's just an example, but the point is; that you treat "problems" as valid results. That way you avoid throwing errors inside the query function, which will trigger nice retries.&lt;br /&gt;
  205. And in this example, it can potentially throw an error in the rendering phase, outside the hook, which means it needs your attention (and does not deserve a retry)&lt;/p&gt;
  206. &lt;p&gt;What's &lt;strong&gt;counter-intuitive&lt;/strong&gt; about this is that your backend probably doesn't return the error optionally with the data. Your backend probably looks like this:&lt;/p&gt;
  207. &lt;pre&gt;&lt;code class="hljs"&gt;
  208. &lt;span class="hljs-comment"&gt;# Example, Python, backend JSON endpoint &lt;/span&gt;
  209.  
  210. &lt;span class="hljs-keyword"&gt;def&lt;/span&gt; &lt;span class="hljs-title function_"&gt;user_info_view&lt;/span&gt;(&lt;span class="hljs-params"&gt;request&lt;/span&gt;):
  211.    &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; JsonResponse({
  212.        &lt;span class="hljs-string"&gt;&amp;quot;first_name&amp;quot;&lt;/span&gt;: request.user.first,
  213.        &lt;span class="hljs-string"&gt;&amp;quot;last_name&amp;quot;&lt;/span&gt;: request.user.last
  214.    })
  215. &lt;/code&gt;&lt;/pre&gt;
  216.  
  217. &lt;p&gt;So, if that's how the backend responds, it'd be tempting to &lt;em&gt;model&lt;/em&gt; the data fetched to that exact shape, but as per my example, you re-wrap it under a new key.&lt;/p&gt;
  218. &lt;h2&gt;Conclusion&lt;/h2&gt;
  219. &lt;p&gt;The shape of the data ultimately coming from within a &lt;code&gt;useQuery&lt;/code&gt; function doesn't have to map one-to-one to how the server sends it. The advantage is that what you get back into the rendering process of your component is that there's a chance of capturing other types of errors that aren't retriable.&lt;/p&gt;</description><pubDate>Mon, 16 Sep 2024 10:27:38 +0000</pubDate><guid>http://www.peterbe.com/plog/how-to-handle-success-and-failure-usequery-hook</guid></item></channel></rss>

If you would like to create a banner that links to this page (i.e. this validation result), do the following:

  1. Download the "valid RSS" banner.

  2. Upload the image to your own server. (This step is important. Please do not link directly to the image on this server.)

  3. Add this HTML to your page (change the image src attribute if necessary):

If you would like to create a text link instead, here is the URL you can use:

http://www.feedvalidator.org/check.cgi?url=https%3A//www.peterbe.com/rss.xml

Copyright © 2002-9 Sam Ruby, Mark Pilgrim, Joseph Walton, and Phil Ringnalda