1

I have a simple useEffect hook in my Task:

const TaskAD = ({ match }: TaskADProps) => {
  const { taskName } = match.params;
  const [task, setTask] = useState<TaskData | null>(null);
  const [loading, setLoading] = useState(true);

  const authCommunicator = authRequest();

  useEffect(() => {
    const getTask = async () => {
      const taskData = await authCommunicator
        .get(`/task/${taskName}`)
        .then((response) => response.data);
      setTask(taskData);
      setLoading(false);
    };
    getTask();
  }, []);

  if (loading || task == null) {
    return <Spinner centered />;
  }

  const updateDescription = async (content: string): Promise<boolean> => {
    const r = await authCommunicator
      .patch(`/task/${task.name}/`, {
        description: content,
      })
      .then((response) => {
        console.log("Setting Task data!");
        setTask(response.data);
        return true;
      })
      .catch(() => false);
    return r;
  };

  return (
    <ProjectEntity name={taskName}>
      <Space direction="vertical" size="small" style={{ width: "100%" }}>
        <StatusRow status="Open" />
        <TaskDetails task={task} />
        <Description content={task.description} onSubmit={updateDescription} />
        <Title level={2}>Subtasks:</Title>
        <Table dataSource={dataSource} columns={columns} />
      </Space>
    </ProjectEntity>
  );
};

Task object contains a description. The description is another component with a text area. The idea is: when a user changes the description in the child component, the child component has a function (passed via props) to update the description.

So I pass updateDescription to my child component (Description) via props. Both useEffect and updateDescription are in my Task component, the Description component is basically stateless. What happens:

  • user updates a description
  • child component calls the function, it updates a record in my DB
  • it gets the response from the API and calls setTask
  • task variable is passed to Description via props in Task's render, so they both get updated since the state of parent Task has changed
  • I see updated description

The only problem is that although it work, but when I do this, I can see this in console:

Setting Task data!
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.

(i've added the console.log just to see when it happens).

So I wanted to ask if this is a problem of me having async calls outside useEffect or maybe something else?

@Edit Description code (I removed all the unnecessary junk):

interface DescriptionProps {
  content: string;
  onSubmit?: (content: string) => Promise<boolean>;
  title?: string;
  rows?: number;
}

const Description = (props: DescriptionProps) => {
  const { content, onSubmit, title, rows } = props;
  const [descriptionContent, setDescriptionContent] = useState(content);
  const [expanded, setExpanded] = useState(true);
  const [editMode, setEditMode] = useState(false);
  const [descriptionChanged, setDescriptionChanged] = useState(false);
  const editable = onSubmit !== undefined;

  const resetDescription = () => {
    setDescriptionContent(content);
    setDescriptionChanged(false);
  };

  const changeDescription = (value: string) => {
    setDescriptionContent(value);
    setDescriptionChanged(true);
  };

  const descriptionTitle = (
    <>
      <S.DescriptionTitle>{title}</S.DescriptionTitle>
    </>
  );

  return (
    <Collapse
      defaultActiveKey={["desc"]}
      expandIcon={S.ExpandIcon}
      onChange={() => setExpanded(!expanded)}
    >
      <S.DescriptionHeader header={descriptionTitle} key="desc">
        <S.DescriptionContent
          onChange={(event): void => changeDescription(event.target.value)}
        />
        {descriptionChanged && onSubmit !== undefined ? (
          <S.DescriptionEditActions>
            <Space size="middle">
              <S.SaveIcon
                onClick={async () => {
                  setDescriptionChanged(!(await onSubmit(descriptionContent)));
                }}
              />
              <S.CancelIcon onClick={() => resetDescription()} />
            </Space>
          </S.DescriptionEditActions>
        ) : null}
      </S.DescriptionHeader>
    </Collapse>
  );
};

@Edit2

Funny thing, adding this to my Description solves the issue:

  useEffect(
    () => () => {
      setDescriptionContent("");
    },
    [content]
  );

Can anyone explain why?

dabljues
  • 1,663
  • 3
  • 14
  • 30
  • Does this solve you problem: https://stackoverflow.com/a/60907638/10763202 – Claire Lin Jun 14 '21 at 06:55
  • Yeah, I've googled that question before I made my own, doesn't help (tried both with `let Mounted` in `useEffect` and with `useRef` in my component). I think the answer you mentioned could work, but since my async call is not in `useEffect`, but in some other function, this cannot help me :( – dabljues Jun 14 '21 at 06:59
  • Can you share the part of the code where `Description` gets rendered? – Claire Lin Jun 14 '21 at 07:17
  • Done. Forgive me for making it so long, didn't know which parts could I exclude, since I don't really know what can affect the issue – dabljues Jun 14 '21 at 07:33
  • is `Description` wrapped in conditional rendering in any way in `Task`? – Claire Lin Jun 14 '21 at 07:45
  • Nope. I've updated my question with full `Task` snippet. Unless by me returning the spinner early counts as conditional (but it shouldn't ever happen, because the task will never be null after it's rendered and I can edit its description) – dabljues Jun 14 '21 at 07:57
  • @ClaireLin I've added a piece of code that actually solves the issue. Do you have any idea why that may be the answer? – dabljues Jun 14 '21 at 21:43

0 Answers0