import { useActionState } from "react";
async function addToCart(itemId) {
'use server'
if (db.add(itemId)) {
return "success"| import { ReactNode, useLayoutEffect, useRef } from 'react'; | |
| const keyframes = ` | |
| @keyframes spinner { | |
| 0% { opacity: 1; } | |
| 100% { opacity: 0; } | |
| } | |
| `; | |
| export default function Spinner({ |
| [ | |
| { | |
| "key": "ctrl+k", | |
| "command": "editor.action.moveLinesUpAction", | |
| "when": "editorTextFocus && !editorReadonly" | |
| }, | |
| { | |
| "key": "alt+up", | |
| "command": "-editor.action.moveLinesUpAction", | |
| "when": "editorTextFocus && !editorReadonly" |
| { | |
| "diffEditor.renderSideBySide": true, | |
| "editor.fontSize": 14, | |
| "editor.lineHeight": 22, | |
| "editor.tabSize": 2, | |
| "editor.matchBrackets": "never", | |
| "editor.cursorBlinking": "solid", | |
| "editor.selectionHighlight": false, | |
| "editor.occurrencesHighlight": "off", | |
| "editor.scrollbar.horizontal": "hidden", |
| diff --git a/app/components/entry-form.tsx b/app/components/entry-form.tsx | |
| index 50e5aeb..84c64fc 100644 | |
| --- a/app/components/entry-form.tsx | |
| +++ b/app/components/entry-form.tsx | |
| @@ -1,6 +1,6 @@ | |
| -import { useFetcher } from "@remix-run/react"; | |
| +import { Form, useSubmit } from "@remix-run/react"; | |
| import { format } from "date-fns"; | |
| -import { useEffect, useRef } from "react"; | |
| +import { useRef } from "react"; |
| { | |
| "workbench.colorCustomizations": { | |
| "editor.background": "#0f172a", | |
| "menu.background": "#0f172a", | |
| "sideBar.background": "#0b111e", | |
| "banner.background": "#0f172a", | |
| "tab.inactiveBackground": "#0b111e", | |
| "tab.activeBackground": "#0f172a", | |
| "titleBar.activeBackground": "#0b111e", | |
| "editor.lineHighlightBorder": "#ff000000" |
| import { motion } from "framer-motion"; | |
| import { FormEvent, useState } from "react"; | |
| import { createGlobalState } from "react-hooks-global-state"; | |
| const { useGlobalState } = createGlobalState({ | |
| enabled: false, | |
| delay: 1000, | |
| }); |
| { | |
| "Use state": { | |
| "scope": "javascriptreact,typescriptreact", | |
| "prefix": "ush", | |
| "body": [ | |
| "let [${1}, set${1/(.*)/${1:/capitalize}/}] = useState($2);", | |
| ], | |
| "description": "useState()" | |
| } | |
| } |
| { | |
| "Range of numbers": { | |
| "scope": "javascriptreact,typescriptreact", | |
| "prefix": "range", | |
| "body": [ | |
| "{[...Array($1).keys()].map((i) => (", | |
| " <$2 key={i}>", | |
| " $3", | |
| " </$2>", | |
| "))}", |
Remix's useFetcher doesn't return a Promise for any of its methods (like fetcher.submit()) because Remix doesn't want you to explicitly await anything so they can handle things like cancellation for you. Instead, they recommend adding a useEffect and performing whatever logic you need to after the fetcher is in a particular state.
I found using an effect to run some logic after a submission to be too indirect, and there seem to be plenty of cases where you want to submit a form and then perform some other work on the client (sometimes async, like requesting the user's permission for their location), and I'd rather just do that after a submission in the event handler rather than an effect.
So here's a proof of concept hook that wraps Remix's useFetcher and returns a version of submit that is a promise, and resolves with the data from the action:
function useFetcherWithPromise() {
let resolveRef = useRef();
let promiseRef = useRef();