├── .gitignore ├── tsconfig.json ├── src └── index.ts ├── package.json ├── README.md └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": true, 4 | "esModuleInterop": true, 5 | "lib": ["DOM", "ESNEXT"], 6 | "outDir": "dist" 7 | }, 8 | "files": ["src/index.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import type { DehydratedState } from "@tanstack/query-core"; 2 | 3 | import { useMatches } from "@remix-run/react"; 4 | import merge from "deepmerge"; 5 | 6 | const useDehydratedState = (): DehydratedState => { 7 | const matches = useMatches(); 8 | 9 | const dehydratedState = matches 10 | .map((match) => match.data?.dehydratedState) 11 | .filter(Boolean); 12 | 13 | return dehydratedState.length 14 | ? dehydratedState.reduce( 15 | (accumulator, currentValue) => merge(accumulator, currentValue), 16 | {} 17 | ) 18 | : undefined; 19 | }; 20 | 21 | export { useDehydratedState }; 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "use-dehydrated-state", 3 | "version": "0.1.0", 4 | "description": "A simple utility Hook for TanStack Query & Remix", 5 | "homepage": "https://github.com/maplegrove-io/use-dehydrated-state#readme", 6 | "bugs": { 7 | "url": "https://github.com/maplegrove-io/use-dehydrated-state/issues" 8 | }, 9 | "license": "MIT", 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/maplegrove-io/use-dehydrated-state" 13 | }, 14 | "author": "plpaquin", 15 | "files": [ 16 | "dist" 17 | ], 18 | "main": "dist/index.js", 19 | "scripts": { 20 | "build": "tsc" 21 | }, 22 | "dependencies": { 23 | "deepmerge": "^4.3.1" 24 | }, 25 | "devDependencies": { 26 | "@remix-run/react": "^1.19.3", 27 | "@remix-run/server-runtime": "^1.19.3", 28 | "@tanstack/query-core": "^4.33.0", 29 | "@types/react": "^18.2.21", 30 | "typescript": "^5.2.2" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # use-dehydrated-state 2 | 3 | `use-dehydrated-state` is a simple utility [Hook](https://react.dev/learn/reusing-logic-with-custom-hooks) for [TanStack Query](https://tanstack.com/query) & [Remix](https://remix.run). 4 | 5 | ## Installation 6 | 7 | ### NPM 8 | 9 | ```bash 10 | npm install use-dehydrated-state 11 | # or 12 | pnpm add use-dehydrated-state 13 | # or 14 | yarn add use-dehydrated-state 15 | ``` 16 | 17 | ## Usage 18 | 19 | To support caching queries on the server and set up hydration: 20 | 21 | - Create a new `QueryClient` instance **inside of your app, and on an instance ref (or in React state). This ensures that data is not shared between different users and requests, while still only creating the QueryClient once per component lifecycle.** 22 | - Wrap your app component with `` and pass it the client instance 23 | - Wrap your app component with `` and pass it the `dehydratedState` prop from `useDehydratedState()` 24 | 25 | ```tsx 26 | // root.tsx 27 | import { Outlet } from "@remix-run/react"; 28 | import { 29 | Hydrate, 30 | QueryClient, 31 | QueryClientProvider, 32 | } from "@tanstack/react-query"; 33 | 34 | import { useDehydratedState } from "use-dehydrated-state"; 35 | 36 | export default function MyApp() { 37 | const [queryClient] = React.useState(() => new QueryClient()); 38 | 39 | const dehydratedState = useDehydratedState(); 40 | 41 | return ( 42 | 43 | 44 | 45 | 46 | 47 | ); 48 | } 49 | ``` 50 | 51 | Now you are ready to prefetch some data in your [`loader`](https://remix.run/docs/en/route/loader). 52 | 53 | - Create a new `QueryClient` instance **for each page request. This ensures that data is not shared between users and requests.** 54 | - Prefetch the data using the clients `prefetchQuery` method and wait for it to complete 55 | - Use `dehydrate` to dehydrate the query cache and pass it to the page via the `dehydratedState` prop. This is the same prop that `useDehydratedState()` will pick up to cache in your `root.tsx` 56 | 57 | ```tsx 58 | // pages/invoices.tsx 59 | import { json } from "@remix-run/node"; 60 | import { dehydrate, QueryClient, useQuery } from "@tanstack/react-query"; 61 | 62 | export async function loader() { 63 | const queryClient = new QueryClient(); 64 | 65 | await queryClient.prefetchQuery(["invoices"], getInvoices); 66 | 67 | return json({ dehydratedState: dehydrate(queryClient) }); 68 | } 69 | 70 | export default function Invoices() { 71 | const { data } = useQuery({ queryKey: ["invoices"], queryFn: getInvoices }); 72 | 73 | // ... 74 | } 75 | ``` 76 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@remix-run/react@^1.19.3": 6 | version "1.19.3" 7 | resolved "https://registry.yarnpkg.com/@remix-run/react/-/react-1.19.3.tgz#00efcc583bf05b434566e56381d51df86575d8b0" 8 | integrity sha512-iP37MZ+oG1n4kv4rX77pKT/knra51lNwKo5tinPPF0SuNJhF3+XjWo5nwEjvisKTXLZ/OHeicinhgX2JHHdDvA== 9 | dependencies: 10 | "@remix-run/router" "1.7.2" 11 | react-router-dom "6.14.2" 12 | 13 | "@remix-run/router@1.7.2": 14 | version "1.7.2" 15 | resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.7.2.tgz#cba1cf0a04bc04cb66027c51fa600e9cbc388bc8" 16 | integrity sha512-7Lcn7IqGMV+vizMPoEl5F0XDshcdDYtMI6uJLQdQz5CfZAwy3vvGKYSUk789qndt5dEC4HfSjviSYlSoHGL2+A== 17 | 18 | "@remix-run/server-runtime@^1.19.3": 19 | version "1.19.3" 20 | resolved "https://registry.yarnpkg.com/@remix-run/server-runtime/-/server-runtime-1.19.3.tgz#206b55337c266c5bc254878f8ff3cd5677cc60fb" 21 | integrity sha512-KzQ+htUsKqpBgKE2tWo7kIIGy3MyHP58Io/itUPvV+weDjApwr9tQr9PZDPA3yAY6rAzLax7BU0NMSYCXWFY5A== 22 | dependencies: 23 | "@remix-run/router" "1.7.2" 24 | "@types/cookie" "^0.4.1" 25 | "@web3-storage/multipart-parser" "^1.0.0" 26 | cookie "^0.4.1" 27 | set-cookie-parser "^2.4.8" 28 | source-map "^0.7.3" 29 | 30 | "@tanstack/query-core@^4.33.0": 31 | version "4.33.0" 32 | resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-4.33.0.tgz#7756da9a75a424e521622b1d84eb55b7a2b33715" 33 | integrity sha512-qYu73ptvnzRh6se2nyBIDHGBQvPY1XXl3yR769B7B6mIDD7s+EZhdlWHQ67JI6UOTFRaI7wupnTnwJ3gE0Mr/g== 34 | 35 | "@types/cookie@^0.4.1": 36 | version "0.4.1" 37 | resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.4.1.tgz#bfd02c1f2224567676c1545199f87c3a861d878d" 38 | integrity sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q== 39 | 40 | "@types/prop-types@*": 41 | version "15.7.5" 42 | resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf" 43 | integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w== 44 | 45 | "@types/react@^18.2.21": 46 | version "18.2.21" 47 | resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.21.tgz#774c37fd01b522d0b91aed04811b58e4e0514ed9" 48 | integrity sha512-neFKG/sBAwGxHgXiIxnbm3/AAVQ/cMRS93hvBpg8xYRbeQSPVABp9U2bRnPf0iI4+Ucdv3plSxKK+3CW2ENJxA== 49 | dependencies: 50 | "@types/prop-types" "*" 51 | "@types/scheduler" "*" 52 | csstype "^3.0.2" 53 | 54 | "@types/scheduler@*": 55 | version "0.16.3" 56 | resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.3.tgz#cef09e3ec9af1d63d2a6cc5b383a737e24e6dcf5" 57 | integrity sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ== 58 | 59 | "@web3-storage/multipart-parser@^1.0.0": 60 | version "1.0.0" 61 | resolved "https://registry.yarnpkg.com/@web3-storage/multipart-parser/-/multipart-parser-1.0.0.tgz#6b69dc2a32a5b207ba43e556c25cc136a56659c4" 62 | integrity sha512-BEO6al7BYqcnfX15W2cnGR+Q566ACXAT9UQykORCWW80lmkpWsnEob6zJS1ZVBKsSJC8+7vJkHwlp+lXG1UCdw== 63 | 64 | cookie@^0.4.1: 65 | version "0.4.2" 66 | resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" 67 | integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== 68 | 69 | csstype@^3.0.2: 70 | version "3.1.2" 71 | resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b" 72 | integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ== 73 | 74 | deepmerge@^4.3.1: 75 | version "4.3.1" 76 | resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" 77 | integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== 78 | 79 | react-router-dom@6.14.2: 80 | version "6.14.2" 81 | resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.14.2.tgz#88f520118b91aa60233bd08dbd3fdcaea3a68488" 82 | integrity sha512-5pWX0jdKR48XFZBuJqHosX3AAHjRAzygouMTyimnBPOLdY3WjzUSKhus2FVMihUFWzeLebDgr4r8UeQFAct7Bg== 83 | dependencies: 84 | "@remix-run/router" "1.7.2" 85 | react-router "6.14.2" 86 | 87 | react-router@6.14.2: 88 | version "6.14.2" 89 | resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.14.2.tgz#1f60994d8c369de7b8ba7a78d8f7ec23df76b300" 90 | integrity sha512-09Zss2dE2z+T1D03IheqAFtK4UzQyX8nFPWx6jkwdYzGLXd5ie06A6ezS2fO6zJfEb/SpG6UocN2O1hfD+2urQ== 91 | dependencies: 92 | "@remix-run/router" "1.7.2" 93 | 94 | set-cookie-parser@^2.4.8: 95 | version "2.6.0" 96 | resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz#131921e50f62ff1a66a461d7d62d7b21d5d15a51" 97 | integrity sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ== 98 | 99 | source-map@^0.7.3: 100 | version "0.7.4" 101 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656" 102 | integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA== 103 | 104 | typescript@^5.2.2: 105 | version "5.2.2" 106 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.2.2.tgz#5ebb5e5a5b75f085f22bc3f8460fba308310fa78" 107 | integrity sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w== 108 | --------------------------------------------------------------------------------