Skip to content

Queries

useQuery and useMutation manage interacting with remote APIs. Their design is inspired by react-query.

Accepts the following options:

  • queryFn — an async function that returns the remote data
  • onStart — runs when the query begins
  • onSuccess — runs after a successful fetch; receives the data
  • onError — runs after a failed fetch; receives the error
  • onEnd — runs when the query ends, regardless of outcome
  • enabled — boolean (default true); whether the query runs immediately

Returns a query object with a refetch method. Arguments to refetch are passed on to queryFn.

function App() {
const refs = useRefProxy();
const [, setIsLoading] = usePropertyBoolean(refs.button, 'disabled', true, false);
const [, setParagraph] = useTextContent(refs.paragraph);
const { refetch } = useQuery({
queryFn: async () => {
const response = await fetch(...);
return await response.json();
},
onStart: () => {
setParagraph('');
setIsLoading(true);
},
onEnd: () => setIsLoading(false),
onSuccess: ({ message }) => setParagraph(message),
onError: () => setParagraph('Something went wrong'),
});
return (
<>
<p ref={refs.paragraph} />
<button onClick={refetch} ref={refs.button}>Refetch</button>
</>
);
}

Accepts the following options:

  • mutationFn — an async function that performs the mutation
  • onStart — runs when the mutation begins
  • onSuccess — runs after a successful mutation; receives the response
  • onError — runs after a failed mutation; receives the error
  • onEnd — runs when the mutation ends, regardless of outcome

Returns a mutation object with a mutate method. Arguments to mutate are passed on to mutationFn.

function App() {
const [input, button, paragraph] = useRefs();
const [getInput, setInput] = useTextInput(input);
const [, setIsLoading] = usePropertyBoolean(button, 'disabled', true, false);
const [, setParagraph] = useTextContent(paragraph);
const { mutate } = useMutation({
mutationFn: () => fetch(...),
onStart: () => setIsLoading(true),
onEnd: () => setIsLoading(false),
onSuccess: () => setParagraph('Saved'),
onError: () => setParagraph('Something went wrong'),
});
async function handleSubmit(event) {
event.preventDefault();
await mutate(getInput());
setInput('');
}
return (
<>
<form onSubmit={handleSubmit}>
<input ref={input} />
<button ref={button}>Save</button>
</form>
<p ref={paragraph} />
</>
);
}
Query + mutation against a fake API
Source
import './examples.css';
import {
  useForm,
  useMutation,
  usePropertyBoolean,
  useQuery,
  useRefProxy,
  useStyleBoolean,
} from 'domstatejsx';

const fakeApi = {
  messages: ['a@b.c', 'd@e.f'],
  get: async () => {
    await new Promise((resolve) => setTimeout(resolve, 400));
    return fakeApi.messages;
  },
  post: async (newMessage) => {
    await new Promise((resolve) => setTimeout(resolve, 400));
    if (newMessage.indexOf('@') === -1) {
      throw new Error('Invalid email address');
    }
    fakeApi.messages.push(newMessage);
  },
};

export default function App() {
  const refs = useRefProxy();

  const [, setQueryIsLoading] = useStyleBoolean(
    refs.loading,
    'display',
    null,
    'none',
  );
  const [, setFormIsLoading] = usePropertyBoolean(
    refs.submit,
    'disabled',
    true,
    false,
  );

  const { refetch } = useQuery({
    onStart: () => setQueryIsLoading(true),
    queryFn: fakeApi.get,
    onSuccess: (messages) => {
      refs.ul.current.replaceChildren(...messages.map((message) => <li>{message}</li>));
    },
    onEnd: () => setQueryIsLoading(false),
  });

  const { mutate } = useMutation({
    mutationFn: fakeApi.post,
    onSuccess: () => {
      reset();
      refs.ul.current.replaceChildren();
      refetch();
    },
  });

  const { registerForm, register, registerError, reset } = useForm({
    onStart: async () => {
      setFormIsLoading(true);
    },
    onSubmit: async (data) => {
      await mutate(data.message);
    },
    onEnd: async () => {
      setFormIsLoading(false);
    },
  });

  return (
    <>
      <p class="dx-loading" style={{ display: 'none' }} ref={refs.loading}>
        Loading...
      </p>
      <ul class="dx-list" ref={refs.ul} />
      <p>
        <button
          class="dx-btn"
          onClick={() => {
            refs.ul.current.replaceChildren();
            refetch();
          }}
        >
          Refetch
        </button>
      </p>
      <form {...registerForm()}>
        <p>
          Message:{' '}
          <input class="dx-input" {...register('message', { required: true })} />
        </p>
        <p class="dx-red" style={{ display: 'none' }} {...registerError()} />
        <p class="dx-red" style={{ display: 'none' }} {...registerError('message')} />
        <p>
          <button class="dx-btn" ref={refs.submit}>
            Submit
          </button>
        </p>
      </form>
    </>
  );
}