3

I've followed the Recoil docs to create the attached which works but with the following warning

Warning: Can't perform a React state update on a component that hasn't mounted yet. This indicates that you have a side-effect in your render function that asynchronously later calls tries to update the component. Move this work to useEffect instead

I can't see any information about this in the docs. Can someone please explain what I need to change?

Thank you!

selector and component screen shot

Code selector.js…

import { gql } from "@apollo/client";
import { atom, selector } from "recoil";
import client from "../apollo-client";

export const stateQuery = selector({
  key: "siteState",
  get: async ({ get }) => {
    const response = await client.query({
      query: gql`
        {
          siteSettings {
            siteSettings {
              contactEmail
              internationalTelephone
            }
          }
        }
      `,
    });
    return {
      contactEmail: response.data.siteSettings.siteSettings.contactEmail,
      internationalTelephone:
        response.data.siteSettings.siteSettings.internationalTelephone,
    };
  },
});

Code ExampleBlock.js…

import React, { useState, useEffect } from "react";
import { useRecoilValue } from "recoil";
import { stateQuery } from "@recoil/selector";
import tw from "twin.macro";

export default function ArticleQuoteBlock({ data: { text, name } }) {
  const { contactEmail, internationalTelephone } = useRecoilValue(stateQuery);

  return (
    <section tw="bg-white quote md:quote-md">
      <div tw="bg-pink quote-inner md:quote-inner-md">
        <h1 tw="quote-text md:quote-text-md">{contactEmail}</h1>
        <h1 tw="quote-text md:quote-text-md">{internationalTelephone}</h1>

        <h1 tw="quote-text md:quote-text-md"></h1>
        {text && (
          <p tw="quote-text md:quote-text-md indent md:indent-md">{text}</p>
        )}
        {name && <p tw="name md:name-md">{name}</p>}
      </div>
    </section>
  );
}
steven
  • 51
  • 3

1 Answers1

1

I believe, your ExampleBlock.js tries to mount contactEmail and internationalTelephone before their values are available. You can utilize useState and useEffect hooks to solve the issue. useState can be used to trigger rendering when the data is updated. useEffect can be used to wait for component to mount. Since you have an asynchronous call, you should define an asynchronous function inside of the useEffect, then trigger it, so that you can wait for the result and update your states accordingly.

Simplest useState usage:

const [yourState, setYourState] = useState('initialValue'). Initial value can be anything i.e. int, string, array etc.

Simplest useEffect usage:

useEffect(()=> { some_func() },[some_arg]). When some_arg is empty, some_func() is triggered only once.

In your case, below code snippet might solve the issue.

export default function ArticleQuoteBlock({ data: { text, name } }) {
    const [contactEmailInfo, setContactEmailInfo] = useState();
    const [internationalTelephoneInfo, setInternationalTelephoneInfo] = useState();
    useEffect(() => {
        async function fetchData() {
            let contactEmail;
            let internationalTelephone;
            ({ contactEmail, internationalTelephone } = await useRecoilValue(stateQuery))
            setContactEmailInfo(contactEmail);
            setInternationalTelephoneInfo(internationalTelephone);
        }
        fetchData()
    }, []);

    return (
        <section tw="bg-white quote md:quote-md">
            <div tw="bg-pink quote-inner md:quote-inner-md">
                <h1 tw="quote-text md:quote-text-md">{contactEmailInfo}</h1>
                <h1 tw="quote-text md:quote-text-md">{internationalTelephoneInfo}</h1>
                <h1 tw="quote-text md:quote-text-md"></h1>
                {text && <p tw="quote-text md:quote-text-md indent md:indent-md">{text}</p>}
                {name && <p tw="name md:name-md">{name}</p>}
            </div>
        </section>
    );
}
ilketorun
  • 324
  • 3
  • 14
  • 1
    Thank you for explaining! I have tried this now but it results in: **Error: Invalid hook call. Hooks can only be called inside of the body of a function component** useEffect works if I hard code those variables and comment out `({ contactEmail, internationalTelephone } = await useRecoilValue(stateQuery))` but otherwise I get this error. – steven Aug 06 '22 at 13:29
  • Okey, let's try this instead of previous definition: ```const ArticleQuoteBlock = ({ data: { text, name } }) => { // ... } export default ArticleQuoteBlock;``` – ilketorun Aug 06 '22 at 14:08
  • Hi, unfortunately still the same. – steven Aug 06 '22 at 18:08
  • Maybe it's erroring because it doesn't like the `useRecoilValue` hook being inside useEffect? – steven Aug 06 '22 at 18:19
  • 1
    Ah it looks like Suspense works! https://reactjs.org/blog/2022/03/29/react-v18.html – steven Aug 06 '22 at 18:37
  • Ah, no that only work on localhost, otherwise it results in 504: GATEWAY_TIMEOUT – steven Aug 06 '22 at 18:48
  • @steven Hi, again. Yes, if it is a hook, it will not work inside of a useEffect. I thought useRecoilValue is just a random function. My bad. I am glad Suspense solved your problem. – ilketorun Aug 06 '22 at 18:49
  • Hi, no … it doesn't work on the live site :( – steven Aug 06 '22 at 19:00
  • what about just changing the line: `const { contactEmail, internationalTelephone } = useRecoilValue(stateQuery);` to ```let contactEmail; let internationalTelephone; ({ contactEmail, internationalTelephone } = await useRecoilValue(stateQuery))``` – ilketorun Aug 06 '22 at 19:16
  • No it won't run like that. But thanks for all of these suggestions! – steven Aug 07 '22 at 07:16