Home > Back-end >  Invalid Hook Call when trying to create document on Firestore with React Redux Toolkit Query
Invalid Hook Call when trying to create document on Firestore with React Redux Toolkit Query

Time:12-22

I'm trying to make Redux Toolkit Query work with Firestore but I'm new to it, so I don't know if I'm really understanding what's going on. I was able to fetch items from Firestore with it so far, but when I tried to create a form so I could create new documents, I received the Invalid hook call error:

Uncaught Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.
    at Object.throwInvalidHookError (react-dom.development.js:16227:1)
    at useContext (react.development.js:1618:1)
    at useReduxContext (useReduxContext.js:21:1)
    at useStore (useStore.js:17:1)
    at useDispatch (useDispatch.js:14:1)
    at useQuerySubscription (buildHooks.ts:689:1)
    at Object.useQuery (buildHooks.ts:962:1)
    at onSubmit (NewItemForm.tsx:23:1)
    at HTMLUnknownElement.callCallback (react-dom.development.js:4164:1)
    at Object.invokeGuardedCallbackDev (react-dom.development.js:4213:1)
throwInvalidHookError @ react-dom.development.js:16227
useContext @ react.development.js:1618
useReduxContext @ useReduxContext.js:21
useStore @ useStore.js:17
useDispatch @ useDispatch.js:14
useQuerySubscription @ buildHooks.ts:689
useQuery @ buildHooks.ts:962
onSubmit @ NewItemForm.tsx:23
callCallback @ react-dom.development.js:4164
invokeGuardedCallbackDev @ react-dom.development.js:4213
invokeGuardedCallback @ react-dom.development.js:4277
invokeGuardedCallbackAndCatchFirstError @ react-dom.development.js:4291
executeDispatch @ react-dom.development.js:9041
processDispatchQueueItemsInOrder @ react-dom.development.js:9073
processDispatchQueue @ react-dom.development.js:9086
dispatchEventsForPlugins @ react-dom.development.js:9097
(anonymous) @ react-dom.development.js:9288
batchedUpdates$1 @ react-dom.development.js:26140
batchedUpdates @ react-dom.development.js:3991
dispatchEventForPluginEventSystem @ react-dom.development.js:9287
dispatchEventWithEnableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay @ react-dom.development.js:6465
dispatchEvent @ react-dom.development.js:6457
dispatchDiscreteEvent @ react-dom.development.js:6430

Here is a simplified version of my form:

export const NewItemForm = () => {
  const [title, setTitle] = useState("");

  const changeTitle = (e: React.ChangeEvent<HTMLInputElement>) => {
    setTitle(e.target.value);
  };

  const onSubmit = (e: React.ChangeEvent<HTMLFormElement>) => {
    e.preventDefault();

    const { data } = itemsApi.endpoints.createDoc.useQuery(title);
  };

  return (
    <form onSubmit={onSubmit}>
      <input
        type="text"
        value={title}
        onChange={changeTitle}
      />

      <input type="submit" value="Create Item" />
    </form>
  );
};

And here is a simplified version my Redux Toolkit Query Firestore API:

export const itemsApi = createApi({
  reducerPath: "itemsApi",
  baseQuery: fakeBaseQuery(),
  endpoints: (builder) => ({
    createDoc: builder.query<{ title: string }, string>({
      async queryFn(title) {
        try {
          const ref = await addDoc(collection(db, "items"), { title });
          const snap = await getDoc(ref);

          return { data: snap.data()! as { title: string } };
        } catch (error) {
          return { error };
        }
      },
    }),
  }),
});

Do I need to use builder.mutation perhaps?

Is there a more appropriate way of doing this? I feel like I'm hacking Redux Toolkit Query to fit Firestore...

CodePudding user response:

You cannot call a hook from inside a callback function, as this is a violation of the rules of hooks.

You need to move your hook to the top-level of the component.

You do not need to switch from a query to a mutation in order to defer the execution of the API call to the onSubmit handler, as you can by use the "lazy" version of the query hook. Like a mutation, the useLazyQuery hook returns a tuple with the trigger function and the state.

However createDoc should be a mutation. Generally speaking, queries should be read-only operations which do not change data on your server, while mutations are for create, update, and delete operations.

The component code is basically the same for both:

export const NewItemForm = () => {
  const [title, setTitle] = useState("");

  const [trigger, result] = itemsApi.endpoints.createDoc.useLazyQuery(); 

  const changeTitle = (e: React.ChangeEvent<HTMLInputElement>) => {
    setTitle(e.target.value);
  };

  const onSubmit = (e: React.ChangeEvent<HTMLFormElement>) => {
    e.preventDefault();
    trigger(title);
  };
export const NewItemForm = () => {
  const [title, setTitle] = useState("");

  const [trigger, result] = itemsApi.endpoints.createDoc.useMutation(); 

  const changeTitle = (e: React.ChangeEvent<HTMLInputElement>) => {
    setTitle(e.target.value);
  };

  const onSubmit = (e: React.ChangeEvent<HTMLFormElement>) => {
    e.preventDefault();
    trigger(title);
  };
  • Related