{"id":19335183,"url":"https://github.com/orjdev/solidstart-data","last_synced_at":"2025-04-12T20:10:09.474Z","repository":{"id":259793467,"uuid":"877010868","full_name":"OrJDev/solidstart-data","owner":"OrJDev","description":"SolidStart Data API Guide","archived":false,"fork":false,"pushed_at":"2024-10-27T23:11:32.000Z","size":94,"stargazers_count":21,"open_issues_count":0,"forks_count":2,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-12T20:09:55.966Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/OrJDev.png","metadata":{"files":{"readme":"README.MD","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-10-22T23:47:28.000Z","updated_at":"2025-03-05T22:35:34.000Z","dependencies_parsed_at":"2024-10-28T02:34:15.941Z","dependency_job_id":"54fa7a26-4a35-4860-90bd-8931285fa058","html_url":"https://github.com/OrJDev/solidstart-data","commit_stats":null,"previous_names":["orjdev/solidstart-data"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/OrJDev%2Fsolidstart-data","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/OrJDev%2Fsolidstart-data/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/OrJDev%2Fsolidstart-data/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/OrJDev%2Fsolidstart-data/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/OrJDev","download_url":"https://codeload.github.com/OrJDev/solidstart-data/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248625493,"owners_count":21135513,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-10T03:05:54.263Z","updated_at":"2025-04-12T20:10:09.452Z","avatar_url":"https://github.com/OrJDev.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Data Loading\n\nSolidStart has built in methods to fetch and manage your data while also keeping your UI updated. In this guide we are going to talk and explain about how data should be loaded in SolidStart, how to trigger Suspense, create server actions \u0026 server data loaders. We are also going to learn about optimistic updates and all the existing methods.\n\n## Table Of Content\n\nFirst lets understand the SolidStart Data API. Please read each section carefully starting with Route Data.\n\n- [Route Data](#Route-Data)\n  - [Server Route Data](#Server-Route-Data)\n  - [createAsync \u0026 Suspense](#createAsync)\n    - [Server Async Data](#Server-Async-Data)\n  - [cache](#cache)\n    - [key](#key)\n    - [keyFor](#keyFor)\n  - [preload](#preload)\n    - [Preload Server Data](#preloading-server-data)\n- [Actions](#Actions)\n  - [SSR Note](#note-form--ssr)\n  - [Using A Form](#Using-A-Form)\n    - [.with](#with)\n  - [Using useAction](#Using-useAction)\n  - [Server Actions](#Server-Actions)\n  - [Revalidation](#Revalidating-Data--Preloaded-Data)\n    - [Using A Response](#returning-a-response-from-the-action)\n    - [Using The revalidate Function](#Using-The-revalidate-Function)\n    - [Prevent Default Revalidation](#Disable-Revalidation)\n  - [Optimistic Updates](#Optimistic-Updates)\n    - [useSubmission](#useSubmission)\n      - [filter](#Using-A-Filter)\n      - [Video Of Usage](#Video-Of-Usage)\n    - [useSubmissions](#useSubmissions)\n      - [filter](#Using-A-Filter1)\n      - [Video Of Usage](#Video-Of-Usage1)\n\nAfter learning the basics, ahead over to the [ToDo App](#todo-app) section to see how we can create a fully functioning app with Optimistic Updates \u0026 Server Actions in SolidStart.\n\n- [ToDo App](#todo-app)\n  - [Demo](#video)\n  - [Installation](#install)\n  - [Prefetching The ToDo List](#prefetching-the-todo-list)\n  - [Create ToDo](#create-todo-action)\n  - [Update Todo](#update-todo-status)\n    - [Optimistic Updates](#optimistic-updates-1)\n  - [Final Code](#results)\n\n# Route Data\n\nWhen wanting to preload data or run some logic like protected route or setting cookies / headers, we can use the `preload` function to define a route data function\nRoute data is an object that contains the [`preload`](#preload) function you can export from a route (a page: i.e routes/index.tsx) in order to load / preload data. This is also valid for server functions, so you could get a csrf cookie and set it before the page is sent to the client.\n\nWhen using routeData, you must provide a `preload` function and consume it by using `createAsync`, please read [createAsync](#createasync) and [preload](#preload), you can also use [cache](#cache) to cache your data functions.\n\n```ts\nimport { cache } from \"@solidjs/router\";\n\nconst myFn = cache(async () =\u003e {\n  return [1, 2, 3];\n}, \"myFn\");\n\nexport const route = {\n  preload: () =\u003e {\n    myFn();\n  },\n};\n```\n\n## preload\n\nThe preload function is called once per route, which is the first time the user comes to that route.\nUsing preload functions, fetching the data parallel to loading the route is possible to allow use of the data as soon as possible.\nThe preload function can be called when the Route is loaded or eagerly when links are hovered.\n\nOnce the preload function is set, we can use [createAsync](#createasync) to get the data (like in this example).\n\nIn this example, we are going to hit up the `/api/notes` endpoint and return the data we received.\n\n```tsx\nimport { Suspense, type VoidComponent } from \"solid-js\";\nimport { cache, createAsync } from \"@solidjs/router\";\nimport { isServer } from \"solid-js/web\";\n\nconst myFn = cache(async () =\u003e {\n  return await fetch(\n    isServer ? \"http://localhost:3000/api/notes\" : \"/api/notes\"\n  ).then((r) =\u003e r.json() as unknown as number[]);\n}, \"myFn\");\n\nexport const route = {\n  preload: () =\u003e {\n    myFn();\n  },\n};\n\nconst Home: VoidComponent = () =\u003e {\n  const data = createAsync(async () =\u003e myFn());\n  return (\n    \u003cSuspense\u003e\n      \u003cmain\u003e\n        \u003cpre\u003e{data()?.join(\",\")}\u003c/pre\u003e\n      \u003c/main\u003e\n    \u003c/Suspense\u003e\n  );\n};\n\nexport default Home;\n```\n\nIn the example above, the data returns instantly, but we can even make it more intrensting by adding a timeout so we can actually see the `Suspense` working.\n\n```ts\nconst myFn = cache(async () =\u003e {\n  await new Promise((resolve) =\u003e\n    setTimeout(() =\u003e {\n      resolve(undefined);\n    }, 3000)\n  );\n  return await fetch(\n    isServer ? \"http://localhost:3000/api/notes\" : \"/api/notes\"\n  ).then((r) =\u003e r.json() as unknown as number[]);\n}, \"myFn\");\n```\n\nThe `Home` page will only be rendered after 3 seconds using this Promise `setTimeout` with the Suspense.\n\n### Preloading Server Data\n\nYou can create a server function using the `\"use server\";` pragma, then call it (as you would call a regular function).\n\n```ts\nconst myFn = cache(async () =\u003e {\n  \"use server\";\n\n  return await fetch(\n    isServer ? \"http://localhost:3000/api/notes\" : \"/api/notes\"\n  ).then((r) =\u003e r.json() as unknown as number[]);\n}, \"myFn\");\n\nexport const route = {\n  preload: () =\u003e {\n    myFn();\n  },\n};\n```\n\n## Server Route Data\n\nUsing the `\"use server\";` pragma you can turn this function to a server function, the client use case stays exactly the same, we just add this line.\n\n```ts\nimport { cache } from \"@solidjs/router\";\n\nconst myFn = cache(async () =\u003e {\n  \"use server\";\n\n  return [1, 2, 3];\n}, \"myFn\");\n\nexport const route = {\n  preload: () =\u003e {\n    myFn();\n  },\n};\n```\n\n## createAsync\n\ncreateAsync is a function that transforms an async function into a signal that could be used to read the data returned from the promise \u0026 trigger Suspense/Transitions.\ncreateAsync is the main function you should be using when interacting with the data API.\n\n```tsx\nimport { myFnPromise } from \"@/somewhere\";\n\nconst Home: VoidComponent = () =\u003e {\n  const data = createAsync(async () =\u003e await myFnPromise());\n  return (\n    \u003cSuspense fallback=\"Loading...\"\u003e\n      \u003cmain\u003e\n        {/* Reading data() triggers the Suspense*/}\n        \u003cpre\u003e{data()}\u003c/pre\u003e\n      \u003c/main\u003e\n    \u003c/Suspense\u003e\n  );\n};\n```\n\nYou can also use the `createAsync` method with [preloaded](#preload) functions but you can also use a server function or any function you want.\n\n### Server Async Data\n\nYou can create a server function using the `\"use server\";` pragma, then call it (as you would call a regular function) using createAsync to render the data on the client side.\n\n```tsx\nimport { Suspense, type VoidComponent } from \"solid-js\";\nimport { cache, createAsync } from \"@solidjs/router\";\n\nconst myServerFn = cache(async () =\u003e {\n  \"use server\";\n\n  console.log(\"on server\");\n  return [1, 2, 3];\n}, \"myServerFn\");\n\nconst Home: VoidComponent = () =\u003e {\n  const data = createAsync(async () =\u003e myServerFn());\n  return (\n    \u003cSuspense\u003e\n      \u003cmain\u003e\n        \u003cpre\u003e{data()?.join(\",\")}\u003c/pre\u003e\n      \u003c/main\u003e\n    \u003c/Suspense\u003e\n  );\n};\n\nexport default Home;\n```\n\n## cache\n\ncache is a higher-order function designed to create a new function with the same signature as the function passed to it. When this newly created function is called for the first time with a specific set of arguments, the original function is run, and its return value is stored in a cache and returned to the caller of the created function. The next time the created function is called with the same arguments (as long as the cache is still valid), it will return the cached value instead of re-executing the original function.\n\nThis function is being used to cache returned data from a function and try to minimize the unnecessary function calls\n\nCached functions also provide utils that are useful when retrieving the keys used in cases involving [invalidation](#revalidating-data--preloaded-data): [.key](#key) and [.keyFor](#keyFor)\n\n```ts\nimport { cache } from \"@solidjs/router\";\n\nconst cachedFn = cache(fn, key);\n\n// ie\nconst myFn = cache(({ count }: { count: number }) =\u003e {\n  return count === 2 ? [4, 5, 6] : [1, 2, 3];\n}, \"myFn\");\n```\n\nIn the following example there are two examples functions. One is cached and one is not\n\n```ts\nimport { cache } from \"@solidjs/router\";\n\nconst nonCachedFn = async () =\u003e {\n  console.log(\"called\");\n  const data = await fetch(\"/api/notes\").then((d) =\u003e d.json());\n  return data;\n};\n\nconst cachedFn = cache(() =\u003e {\n  console.log(\"called\");\n  const data = await fetch(\"/api/notes\").then((d) =\u003e d.json());\n  return data;\n}, \"uniqueKey\");\n```\n\nWhen we call `cachedFn` the first time it will actually console log `called`, but when we call it the second time, it will not print anything, matter of fact it will not even make the request to `/api/notes`, that is because the data returned from it is cached, meaning Solid is smart enough to return the latest data received from this function instead of re-trigerring it.\n\n```ts\ncachedFn(); // prints called\ncachedFn(); // doesn't print anything but returns the data fetched from the previous call (line above)\n```\n\nIf we call `nonCachedFn` which is not cached, it will print called every call.\n\n```ts\nnonCachedFn(); // prints called\nnonCachedFn(); // prints called\nnonCachedFn(); // prints called\n```\n\n### Server functions with cache\n\nYou can create a server function using the `\"use server\";` pragma, then you call `cache` like you would with a regular function.\n\n```ts\nconst cachedFn = cache((name: string) =\u003e {\n  \"use server\";\n\n  console.log(\"called on server\", name);\n  const data = await fetch(\"http://localhost:3000/api/notes\").then((d) =\u003e\n    d.json()\n  );\n  return data;\n}, \"uniqueKey\");\n```\n\nCached functions provide utils that are useful when retrieving the keys used in cases involving invalidation:\n\n### .key\n\nGetting the key of a cached function, useful for revalidating all the data() calls for this function.\n\n```ts\nconst key = cachedFn.key; // \"uniqueKey\"\n```\n\nAfter getting the key you can invalidate all calls by doing:\n\n```ts\nimport { revalidate } from \"@solidjs/router\";\n\nrevalidate(cachedFn.key);\n```\n\nRead more about [Data Revalidation](#revalidating-data--preloaded-data)\n\n### .keyFor\n\nGetting a specific key from a cached function for a specific input, useful for revalidating the data() call for this specific input on this specific function.\n\n```ts\nconst key = cachedFn.keyFor(\"OrJDev\"); // [\"uniqueKey\", \"OrJDev\"]\n```\n\nAfter getting the key you can invalidate this specific call by doing:\n\n```ts\nimport { revalidate } from \"@solidjs/router\";\n\nrevalidate(cachedFn.keyFor(\"OrJDev\"));\n```\n\nRead more about [Data Revalidation](#revalidating-data--preloaded-data)\n\n# Actions\n\nAn action is a function you may use to mutate data, meaning when you want to send, update, delete or interact with a server / lib. This is considered to be a `mutation`.\nActions only work with the `POST` request method, this means forms require `method=\"post\"`\nBy default all cached functions will be revalidated (re-fetched), event if the action does or doesn't return response, you can change this behavior, read more [here](#disable-revalidation)\n\nTo create an action you simply call the `action` method from Solid Router.\n\n```ts\nimport { action, redirect } from \"@solidjs/router\";\n\nconst fn = action(myFunction, myUniqueName);\n\nconst callWithForm = action(async (formData: FormData) =\u003e {\n  const name = formData.get(\"name\")!;\n  console.log(`Hey ${name}`);\n  return redirect(\"/success\");\n}, \"myUniqueName\");\n\nconst callWithString = action(async (name: string) =\u003e {\n  console.log(`Hey ${name}`);\n  return redirect(\"/success\");\n}, \"myUniqueName\");\n\nconst callWithRevalidation = action(async () =\u003e {\n  return redirect(\"/some-page\", { revalidate: \"abcd\" });\n}, \"myUniqueName\");\n```\n\n## Note (Form + SSR)\n\nThis requires stable references because a string can only be serialized as an attribute, and it is crucial for consistency across SSR. where these references must align. The solution is to provide a unique name. Meaning, you always have to provide a second argument when you call `action`\n\n```ts\naction(fn, key); // cool\naction(fn); // not cool\n```\n\nYou can either use a Form to call an action or use the `useAction` hook from Solid Router.\n\n## Using A Form\n\n```tsx\nimport { type VoidComponent } from \"solid-js\";\nimport { action, redirect } from \"@solidjs/router\";\n\nconst callWithParams = action(async (formData: FormData) =\u003e {\n  const name = formData.get(\"name\")!;\n  console.log(`Hey ${name}`);\n  return redirect(\"/success\");\n}, \"myMutation\");\n\nconst Home: VoidComponent = () =\u003e {\n  return (\n    \u003cmain\u003e\n      \u003cform action={callWithParams} method=\"post\"\u003e\n        \u003cinput type=\"hidden\" name=\"name\" value={\"OrJDev\"} /\u003e\n        \u003cbutton type=\"submit\"\u003eCall\u003c/button\u003e\n      \u003c/form\u003e\n    \u003c/main\u003e\n  );\n};\n\nexport default Home;\n```\n\n### .with\n\nActions have a `with` method, this method can be used when typed data is required. This removes the need to use `FormData` or other additional hidden fields. The with method works similar to bind, which applies the arguments in order.\n\n```tsx\nimport { type VoidComponent } from \"solid-js\";\nimport { action, redirect } from \"@solidjs/router\";\n\nconst callWithParams = action(async (name: string) =\u003e {\n  console.log(`Hey ${name}`);\n  return redirect(\"/success\");\n}, \"myMutation\");\n\nconst Home: VoidComponent = () =\u003e {\n  return (\n    \u003cmain\u003e\n      \u003cform action={callWithParams.with(\"OrJDev\")} method=\"post\"\u003e\n        \u003cbutton type=\"submit\"\u003eCall\u003c/button\u003e\n      \u003c/form\u003e\n    \u003c/main\u003e\n  );\n};\n\nexport default Home;\n```\n\nAs you can see, no `FormData` was involved in this action.\n\n## Using useAction\n\nIf you don't want to use a form, you can use the `useAction` hook from Solid Router. This consumes the action and return a function you can call from anywhere, with any params you would like.\n\n```tsx\nimport { type VoidComponent } from \"solid-js\";\nimport { action, redirect, useAction } from \"@solidjs/router\";\n\nconst callWithParams = action(async (name: string) =\u003e {\n  console.log(`Hey ${name}`);\n  return redirect(\"/success\");\n}, \"myMutation\");\n\nconst Home: VoidComponent = () =\u003e {\n  const myAction = useAction(callWithParams);\n  return (\n    \u003cmain\u003e\n      \u003cbutton onClick={() =\u003e myAction(\"OrJDev\")}\u003eCall\u003c/button\u003e\n    \u003c/main\u003e\n  );\n};\n\nexport default Home;\n```\n\n## Server Actions\n\nYou can create a server function using the `\"use server\";` pragma, then you call `action` like you would with a regular function.\n\n```ts\nimport { action, redirect } from \"@solidjs/router\";\n\nconst callWithParams = action(async (name: string) =\u003e {\n  \"use server\";\n\n  // this is printed on the server\n  console.log(`Hey ${name}`);\n  return redirect(\"/success\");\n}, \"myMutation\");\n```\n\n## Disable Revalidation\n\nBy default all cached functions will be revalidated (re-fetched), event if the action does or doesn't return response. You can change this by returning a string as the `revalidate` key.\n\n```ts\nimport { action, redirect } from \"@solidjs/router\";\n\nconst callWithParams = action(async (name: string) =\u003e {\n  console.log(`Hey ${name}`);\n  return redirect(\"/success\", { revalidate: \"nothing\" });\n}, \"myMutation\");\n```\n\n## Revalidating Data / Preloaded Data\n\nAssuming we have a [cached](#cache) function called `myFn`, we can invalidate its data by either:\n\n### Returning A Response From The Action\n\nBy returning a response from the action, we can choose which keys to invalidate (if we want any).\n\n```ts\nimport { action, json, reload, redirect } from \"@solidjs/router\";\n\nconst mutateMyFn = action(async (formData: FormData) =\u003e {\n  const name = Number(formData.get(\"name\"));\n  await mutateData(name); // assuming we changed something\n  return json({ done: name }, { revalidate: [\"myFn\"] });\n\n  //or\n  return reload({\n    revalidate: [\"myFn\"],\n  });\n\n  //or\n  return redirect(\"/\", {\n    revalidate: [\"myFn\"],\n  });\n});\n```\n\nA key could also be:\n\n```ts\nreturn redirect(\"/\", {\n  revalidate: [\"getAllTodos\", getTodos.key, getTodoByID.keyFor(id)],\n});\n```\n\n### Using The revalidate Function\n\nAssuming we don't want to define the revalidation logic within the action itself, we can also invalidate it from anywhere we want, so we can use the `revalidate` function from Solid Router.\n\n```ts\nimport { revalidate } from \"@solidjs/router\";\nrevalidate([getTodos.key, getTodoByID.keyFor(id)]);\n```\n\n## Optimistic Updates\n\nOptimistic updates are crucial for the best dx, they are used to display the data as if it has already been received while its still fetching the actual data in the background. So the user thinks that our server is fast, while the action is still running.\n\nAssuming we have this action:\n\n```ts\nconst callWithParams = action(async (name: string) =\u003e {\n  console.log(`Hey ${name}`);\n  await new Promise((resolve) =\u003e\n    setTimeout(() =\u003e {\n      resolve(undefined);\n    }, 3000)\n  );\n  return `Hey ${name}`;\n}, \"myMutation\");\n```\n\nWhen using [action](#Actions) and wanting to implement optimistic updates, you must use one of the following:\n\n### useSubmission\n\nThis function takes two arguments:\n\n1.  action - The action we created before\n2.  filter - Optionally filter out this hook for certain inputs - `([name]) =\u003e name !== \"OrJDev\";`\n\nAfter that, it returns useful data \u0026 functions such as `clear`, `input`, `result`, `pending`, `error` and more.\n\nWe can use the input property to try and set the optimistic data.\n\n```tsx\nimport { Show, type VoidComponent } from \"solid-js\";\nimport { useSubmission } from \"@solidjs/router\";\n\nconst Home: VoidComponent = () =\u003e {\n  const submit = useSubmission(callWithParams);\n  return (\n    \u003cmain\u003e\n      \u003cform action={callWithParams.with(\"OrJDev\")} method=\"post\"\u003e\n        \u003cbutton disabled={submit.pending} type=\"submit\"\u003e\n          {submit.pending ? \"Calling\" : \"Call\"}\n        \u003c/button\u003e\n      \u003c/form\u003e\n      \u003cShow when={submit.error}\u003e\n        \u003cdiv\u003eError\u003c/div\u003e\n      \u003c/Show\u003e\n      \u003cShow when={submit.input?.[0]}\u003e\n        {(name) =\u003e \u003cdiv\u003eOptimistic {`Hey ${name()}`}\u003c/div\u003e}\n      \u003c/Show\u003e\n      \u003cShow when={submit.result}\u003e{(text) =\u003e \u003cdiv\u003eResult {text()}\u003c/div\u003e}\u003c/Show\u003e\n    \u003c/main\u003e\n  );\n};\n\nexport default Home;\n```\n\nSo in this example, using the `input` and the `result` property, the optimistic is going to be rendered instantly, while the result returned from the function is going to be rendered after 3 seconds!\n\nYou can also use the other methods to modify the state of this submission\n\n```tsx\nconst SomeUtils = () =\u003e {\n  return (\n    \u003cdiv\u003e\n      \u003cbutton onClick={() =\u003e submit.retry()}\u003eRetry\u003c/button\u003e\n      \u003cbutton onClick={() =\u003e submit.clear()}\u003eClear\u003c/button\u003e\n    \u003c/div\u003e\n  );\n};\n```\n\n#### Using A Filter\n\nIn this example, we are going to filter out the fake JDev out there, yack\n\n```tsx\nimport { Show, type VoidComponent } from \"solid-js\";\nimport { useSubmission } from \"@solidjs/router\";\n\nconst Home: VoidComponent = () =\u003e {\n  const submit = useSubmission(callWithParams, ([name]) =\u003e {\n    return name !== \"FakeJD\";\n  });\n  return (\n    \u003cmain\u003e\n      \u003cform action={callWithParams.with(\"OrJDev\")} method=\"post\"\u003e\n        \u003cbutton disabled={submit.pending} type=\"submit\"\u003e\n          {submit.pending ? \"Calling\" : \"Call\"}\n        \u003c/button\u003e\n      \u003c/form\u003e\n      \u003cform action={callWithParams.with(\"FakeJD\")} method=\"post\"\u003e\n        \u003cbutton disabled={submit.pending} type=\"submit\"\u003e\n          Call Fake JDev\n        \u003c/button\u003e\n      \u003c/form\u003e\n      \u003cShow when={submit.error}\u003e\n        \u003cdiv\u003eError\u003c/div\u003e\n      \u003c/Show\u003e\n      \u003cShow when={submit.input?.[0]}\u003e\n        {(name) =\u003e \u003cdiv\u003eOptimistic {`Hey ${name()}`}\u003c/div\u003e}\n      \u003c/Show\u003e\n      \u003cShow when={submit.result}\u003e{(text) =\u003e \u003cdiv\u003eResult {text()}\u003c/div\u003e}\u003c/Show\u003e\n    \u003c/main\u003e\n  );\n};\n\nexport default Home;\n```\n\nWhen calling this function with `FakeJD` as the name, all the proprties will remain null and will not be rendered (input,error, etc), if we call it with any other name, it will be rendered instantly.\n\n#### Video Of Usage\n\n[![Demo](https://github-production-user-asset-6210df.s3.amazonaws.com/91349014/379047774-ebc79b96-54aa-464f-844f-81c74c3e5b76.png?X-Amz-Algorithm=AWS4-HMAC-SHA256\u0026X-Amz-Credential=AKIAVCODYLSA53PQK4ZA%2F20241022%2Fus-east-1%2Fs3%2Faws4_request\u0026X-Amz-Date=20241022T233538Z\u0026X-Amz-Expires=300\u0026X-Amz-Signature=3aa100e2515e154097da15b37c7c97aeace4ed246d391ca9693bb162a21e5da3\u0026X-Amz-SignedHeaders=host)](https://github.com/user-attachments/assets/45973a6a-f841-4677-b0e0-c556a3ab353c)\n\n### useSubmissions\n\nThis function is similar to [usesSbmission](#usesubmission), except instead of returning one single object with the submission properties (input,result,etc), it returns an array of all the submissions for this action. So instead of:\n\n```ts\n{\n  input: any;\n  error: any;\n}\n```\n\nits:\n\n```ts\nArray\u003c{\n  input: any;\n  error: any;\n}\u003e;\n```\n\nThis function takes two arguments:\n\n1.  action - The action we created before\n2.  filter - Optionally filter out this hook for certain inputs - `([name]) =\u003e name !== \"OrJDev\";`\n\n```tsx\nimport { For, Show, type VoidComponent } from \"solid-js\";\nimport { useSubmissions } from \"@solidjs/router\";\n\nconst Home: VoidComponent = () =\u003e {\n  const submits = useSubmissions(callWithParams);\n  return (\n    \u003cmain\u003e\n      \u003cform action={callWithParams.with(\"OrJDev\")} method=\"post\"\u003e\n        \u003cbutton disabled={submits.pending} type=\"submit\"\u003e\n          {submits.pending ? \"Calling\" : \"Call\"}\n        \u003c/button\u003e\n      \u003c/form\u003e\n      \u003cFor each={[...submits.entries()]}\u003e\n        {([attempt, data]) =\u003e {\n          return (\n            \u003cdiv\u003e\n              \u003cShow when={data.error}\u003e\n                \u003cdiv\u003eError {attempt}\u003c/div\u003e\n              \u003c/Show\u003e\n              \u003cShow when={data.input?.[0]}\u003e\n                {(name) =\u003e (\n                  \u003cdiv\u003e\n                    Optimistic {attempt} {`Hey ${name()}`}\n                  \u003c/div\u003e\n                )}\n              \u003c/Show\u003e\n              \u003cShow when={data.result}\u003e\n                {(text) =\u003e (\n                  \u003cdiv\u003e\n                    Result {attempt} {text()}\n                  \u003c/div\u003e\n                )}\n              \u003c/Show\u003e\n            \u003c/div\u003e\n          );\n        }}\n      \u003c/For\u003e\n    \u003c/main\u003e\n  );\n};\n\nexport default Home;\n```\n\nSo in this example, using the `input` and the `result` property, the optimistic is going to be rendered instantly, while the result returned from the function is going to be rendered after 3 seconds!\n\n#### Using A Filter\n\nIn this example, we are going to filter out the fake JDev out there, yack\n\n```tsx\nimport { For, Show, type VoidComponent } from \"solid-js\";\nimport { useSubmissions } from \"@solidjs/router\";\n\nconst Home: VoidComponent = () =\u003e {\n  const submits = useSubmissions(callWithParams, ([name]) =\u003e {\n    return name !== \"FakeJD\";\n  });\n  return (\n    \u003cmain\u003e\n      \u003cform action={callWithParams.with(\"OrJDev\")} method=\"post\"\u003e\n        \u003cbutton disabled={submits.pending} type=\"submit\"\u003e\n          {submits.pending ? \"Calling\" : \"Call\"}\n        \u003c/button\u003e\n      \u003c/form\u003e\n      \u003cform action={callWithParams.with(\"FakeJD\")} method=\"post\"\u003e\n        \u003cbutton disabled={submits.pending} type=\"submit\"\u003e\n          Call Fake JDev\n        \u003c/button\u003e\n      \u003c/form\u003e\n\n      \u003cFor each={[...submits.entries()]}\u003e\n        {([attempt, data]) =\u003e {\n          return (\n            \u003cdiv\u003e\n              \u003cShow when={data.error}\u003e\n                \u003cdiv\u003eError {attempt}\u003c/div\u003e\n              \u003c/Show\u003e\n              \u003cShow when={data.input?.[0]}\u003e\n                {(name) =\u003e (\n                  \u003cdiv\u003e\n                    Optimistic {attempt} {`Hey ${name()}`}\n                  \u003c/div\u003e\n                )}\n              \u003c/Show\u003e\n              \u003cShow when={data.result}\u003e\n                {(text) =\u003e (\n                  \u003cdiv\u003e\n                    Result {attempt} {text()}\n                  \u003c/div\u003e\n                )}\n              \u003c/Show\u003e\n            \u003c/div\u003e\n          );\n        }}\n      \u003c/For\u003e\n    \u003c/main\u003e\n  );\n};\n\nexport default Home;\n```\n\nWhen calling this function with `FakeJD` as the name, all the properties will remain null and will not be rendered (input,error, etc), if we call it with any other name, it will be rendered instantly.\n\n#### Video Of Usage\n\n[![Demo](https://github-production-user-asset-6210df.s3.amazonaws.com/91349014/379047355-a06af32e-947c-4a74-a295-404057718f53.png?X-Amz-Algorithm=AWS4-HMAC-SHA256\u0026X-Amz-Credential=AKIAVCODYLSA53PQK4ZA%2F20241022%2Fus-east-1%2Fs3%2Faws4_request\u0026X-Amz-Date=20241022T233500Z\u0026X-Amz-Expires=300\u0026X-Amz-Signature=778c51ca9ffa73fd5457bbe0e613ad2fb5b9988215415a3c8107578914a93e16\u0026X-Amz-SignedHeaders=host)](https://github.com/user-attachments/assets/de2f78ef-29e3-4bf4-93c1-0b937692534c)\n\n# ToDo App\n\nLets use what we learned and build a fully functioning ToDo App in SolidStart.\n\n## Install\n\nFirst, lets clone the ToDo app template (which already includes the db configuration):\n\n```sh\ngit clone git@github.com:OrJDev/solidstart-data.git todo-app\n```\n\nAfter that intall the dependencies:\n\n```sh\npnpm install\n```\n\nAnd then create a `.env` file with:\n\n```\nDATABASE_URL=file:./db.sqlite\n```\n\n## Video\n\n[![Demo](https://github.com/user-attachments/assets/a4581cfb-83b5-4fd1-a3b9-c4424ae56cc3)](https://github.com/user-attachments/assets/da621f3f-222c-426d-93ed-cc7ac5523dd6)\n\nWe can start building the actual app.\n\n## Prefetching The ToDo List\n\nAhead over to `routes/index.tsx` and add the following preload method (this is going to pre-load the todo list and trigger Suspense)\n\nFirst lets create the `getTodos` function, since we use the db directly to fetch the todos, we need to mark this function as server function:\n\n```ts\nimport { db } from \"~/server/db\";\nimport { cache } from \"@solidjs/router\";\n\nconst getTodos = cache(async () =\u003e {\n  \"use server\";\n\n  return await db.todo.findMany();\n}, \"todos\");\n```\n\nAfter creating this function, we can use and export the `preload` method in order to load the todos list before the page is actually sent to the client:\n\n```ts\nexport const route = {\n  async preload() {\n    await getTodos();\n  },\n};\n```\n\nWe need to consume this function and trigger Suspense by using `createAsync`.\n\n```tsx\nimport { createAsync } from \"@solidjs/router\";\n\nconst Home: VoidComponent = () =\u003e {\n  const todos = createAsync(() =\u003e getTodos());\n  return (\n    \u003cmain class=\"flex min-h-screen flex-col gap-4 items-center py-12 bg-gradient-to-b from-[#026d56] to-[#152a2c]\"\u003e\n      \u003cdiv class=\"w-full flex gap-2 items-center justify-center flex-wrap\"\u003e\n        \u003cFor each={actualToDos()}\u003e\n          {(todo) =\u003e (\n            \u003cdiv class=\"flex flex-col gap-2 items-center border p-3 border-gray-500 rounded-lg\"\u003e\n              \u003cspan class=\"text-xl font-bold text-gray-300\"\u003e{todo.text}\u003c/span\u003e\n              \u003cinput type=\"checkbox\" checked={todo.completed} /\u003e\n            \u003c/div\u003e\n          )}\n        \u003c/For\u003e\n      \u003c/div\u003e\n    \u003c/main\u003e\n  );\n};\n```\n\nBy reading `todos()` we trigger suspense (as mentioned in the [createAsync](#createasync) guide).\n\nAfter that we can implement the `createToDo` action.\n\n## Create ToDo Action\n\nWe are going to create a server action that takes in a formData, it validates that the formData has `text`, after that it creates a new todo with the provided text. Once the todo is created, we are to going to invalidate the `getTodos` method we preloaded eariler, meaning we are going to refetch the todo list . We are also going to wait 2 seconds before revalidation so we can see the optimistic updates in action.\n\n```ts\nimport { revalidate, action } from \"@solidjs/router\";\n\nconst createToDo = action(async (formData: FormData) =\u003e {\n  \"use server\";\n\n  const text = formData.get(\"text\");\n  if (!text || typeof text !== \"string\") {\n    throw new Error(\"Missing Text\");\n  }\n  await db.todo.create({ data: { text } });\n  await new Promise((res) =\u003e\n    setTimeout(() =\u003e {\n      res(undefined);\n    }, 2000)\n  );\n  await revalidate(getTodos.key);\n}, \"createToDo\");\n```\n\nHaving the action ready, we can create a form that will trigger this action:\n\n```tsx\nimport { useSubmissions } from \"@solidjs/router\";\nimport { createSignal } from \"solid-js\";\n\nconst CreateToDo = () =\u003e {\n  const [text, setText] = createSignal(\"\");\n  const createSubmissions = useSubmissions(createToDo);\n  return (\n    \u003cform\n      action={createToDo}\n      method=\"post\"\n      class=\"flex flex-col gap-4 items-center text-white text-lg\"\n    \u003e\n      \u003cinput\n        type=\"text\"\n        placeholder=\"Text\"\n        class=\"outline-none border-gray-400 bg-inherit border border-solid rounded-lg p-3 text-white font-bold\"\n        name=\"text\"\n        value={text()}\n        onChange={(e) =\u003e setText(e.currentTarget.value)}\n      /\u003e\n      \u003cbutton\n        disabled={createSubmissions.pending}\n        type=\"submit\"\n        class=\"font-bold text-2xl text-orange-400\"\n      \u003e\n        Create ToDo\n      \u003c/button\u003e\n    \u003c/form\u003e\n  );\n};\n```\n\nThis form will be used to create new todos.\n\n## Update ToDo Status\n\nFirst lets create an action that takes in two args, one is the checked value (if the todo is completed or not) and the other is the id of the todo.\n\n```ts\nconst update = action(async (chcked: boolean, id: string) =\u003e {\n  \"use server\";\n\n  await db.todo.update({\n    where: { id },\n    data: {\n      completed: chcked,\n    },\n  });\n  await new Promise((res) =\u003e\n    setTimeout(() =\u003e {\n      res(undefined);\n    }, 2000)\n  );\n  await revalidate(getTodos.key);\n}, \"updateToDo\");\n```\n\nAfter creating this action, we can use it to update a todo. Since the todo completed value is stored within a `checkbox` we won't use a form but rather the `useAction` function, we will also be using the `useSubmissions` hook for optimistic updates \u0026 check if function is pending.\n\nFirst lets switch the way we render the current todo:\n\n```tsx\nimport { useAction } from \"@solidjs/router\";\n\nconst updateToDo = useAction(update);\n\n\u003cFor each={actualToDos()}\u003e\n  {(todo) =\u003e (\n    \u003cdiv class=\"flex flex-col gap-2 items-center border p-3 border-gray-500 rounded-lg\"\u003e\n      \u003cspan class=\"text-xl font-bold text-gray-300\"\u003e{todo.text}\u003c/span\u003e\n      \u003cinput\n        onClick={(e) =\u003e {\n          e.preventDefault();\n          updateToDo(e.currentTarget.checked, todo.id);\n        }}\n        type=\"checkbox\"\n        checked={todo.completed}\n        disabled={\n          !!updateSubmissions.find((e) =\u003e e.pending \u0026\u0026 e.input[1] === todo.id)\n        }\n      /\u003e\n    \u003c/div\u003e\n  )}\n\u003c/For\u003e;\n```\n\nWe are using `e.preventDefault()` so it will not display the checkmark until the server has completed, also we are using `disabled={...}` to check if we are already trying to update the current todo by filtering the id \u0026 pending status.\n\n### Optimistic Updates\n\nBecause we purposely added a timeout in the server to stimulate slow response, lets implement optimistic updates mechanism (predicting the response of the server) to make this faster. To achieve this, we are going to use the `useSubmissions` hook.\n\n```ts\nconst updateSubmissions = useSubmissions(update);\n```\n\nHvaing the submisions, we can create a new memo called `actualTodos` which will be a combination of the todos returned from the server \u0026 the optimistic todos.\n\n```ts\nimport { createMemo } from \"solid-js\";\n\nconst actualToDos = createMemo(() =\u003e {\n  const t = todos();\n  if (!updateSubmissions.pending) return t;\n  return t?.map((todo) =\u003e {\n    const exists = updateSubmissions.find(\n      (e) =\u003e e.pending \u0026\u0026 e.input[1] === todo.id\n    );\n    if (exists) {\n      return {\n        ...todo,\n        completed: exists.input[0],\n      };\n    }\n    return todo;\n  });\n});\n```\n\nWe are checking if there are any update todo submissions, if not return the todos returned from the server. If there are, we are going to map the todos returned from the server and compare it with the submissions. We will search if `updateSubmissions` has a todo with the id of the current todo, if so return the input of it (the completed boolean).\n\n## Results\n\nThis is what the code is expected to be:\n\n```tsx\nimport {\n  createMemo,\n  createSignal,\n  For,\n  Show,\n  type VoidComponent,\n} from \"solid-js\";\n\nimport { db } from \"~/server/db\";\nimport { cache, createAsync, useAction, useSubmissions } from \"@solidjs/router\";\n\nconst getTodos = cache(async () =\u003e {\n  \"use server\";\n\n  return await db.todo.findMany();\n}, \"todos\");\n\nimport { revalidate, action } from \"@solidjs/router\";\n\nconst createToDo = action(async (formData: FormData) =\u003e {\n  \"use server\";\n\n  const text = formData.get(\"text\");\n  if (!text || typeof text !== \"string\") {\n    throw new Error(\"Missing Text\");\n  }\n  await db.todo.create({ data: { text } });\n  await new Promise((res) =\u003e\n    setTimeout(() =\u003e {\n      res(undefined);\n    }, 2000)\n  );\n  await revalidate(getTodos.key);\n}, \"createToDo\");\n\nconst update = action(async (chcked: boolean, id: string) =\u003e {\n  \"use server\";\n\n  await db.todo.update({\n    where: { id },\n    data: {\n      completed: chcked,\n    },\n  });\n  await new Promise((res) =\u003e\n    setTimeout(() =\u003e {\n      res(undefined);\n    }, 2000)\n  );\n  await revalidate(getTodos.key);\n}, \"updateToDo\");\n\nexport const route = {\n  async preload() {\n    await getTodos();\n  },\n};\n\nconst Home: VoidComponent = () =\u003e {\n  const todos = createAsync(() =\u003e getTodos());\n  const [text, setText] = createSignal(\"\");\n  const createSubmissions = useSubmissions(createToDo);\n  const updateToDo = useAction(update);\n  const updateSubmissions = useSubmissions(update);\n\n  const actualToDos = createMemo(() =\u003e {\n    const t = todos();\n    if (!updateSubmissions.pending) return t;\n    return t?.map((todo) =\u003e {\n      const exists = updateSubmissions.find(\n        (e) =\u003e e.pending \u0026\u0026 e.input[1] === todo.id\n      );\n      if (exists) {\n        return {\n          ...todo,\n          completed: exists.input[0],\n        };\n      }\n      return todo;\n    });\n  });\n\n  return (\n    \u003cmain class=\"flex min-h-screen flex-col gap-4 items-center py-12 bg-gradient-to-b from-[#026d56] to-[#152a2c]\"\u003e\n      \u003cdiv class=\"w-full flex gap-2 items-center justify-center flex-wrap\"\u003e\n        \u003cFor each={actualToDos()}\u003e\n          {(todo) =\u003e (\n            \u003cdiv class=\"flex flex-col gap-2 items-center border p-3 border-gray-500 rounded-lg\"\u003e\n              \u003cspan class=\"text-xl font-bold text-gray-300\"\u003e{todo.text}\u003c/span\u003e\n              \u003cinput\n                onClick={(e) =\u003e {\n                  e.preventDefault();\n                  updateToDo(e.currentTarget.checked, todo.id);\n                }}\n                type=\"checkbox\"\n                checked={todo.completed}\n                disabled={\n                  !!updateSubmissions.find(\n                    (e) =\u003e e.pending \u0026\u0026 e.input[1] === todo.id\n                  )\n                }\n              /\u003e\n            \u003c/div\u003e\n          )}\n        \u003c/For\u003e\n      \u003c/div\u003e\n      \u003cform\n        action={createToDo}\n        method=\"post\"\n        class=\"flex flex-col gap-4 items-center text-white text-lg\"\n      \u003e\n        \u003cinput\n          type=\"text\"\n          placeholder=\"Text\"\n          class=\"outline-none border-gray-400 bg-inherit border border-solid rounded-lg p-3 text-white font-bold\"\n          name=\"text\"\n          value={text()}\n          onChange={(e) =\u003e setText(e.currentTarget.value)}\n        /\u003e\n        \u003cbutton\n          disabled={createSubmissions.pending}\n          type=\"submit\"\n          class=\"font-bold text-2xl text-orange-400\"\n        \u003e\n          Create ToDo\n        \u003c/button\u003e\n      \u003c/form\u003e\n    \u003c/main\u003e\n  );\n};\n\nexport default Home;\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Forjdev%2Fsolidstart-data","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Forjdev%2Fsolidstart-data","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Forjdev%2Fsolidstart-data/lists"}