React child component not re-rendering on parent force update

Viewed 36

I am facing a problem where calling forceUpdate on parent component doesn't re render the child component. I have attached the code snippets below.

PARENT COMPONENT

    const forceUpdate = React.useReducer(() => ({}))[1];

    const getAccountDetails = () => {
    const opts = {
        mailboxKey: details.getMailboxKey()
    };
    details.getAccountDetails(opts).then((response) => {
        if (response.resp.data.lastSyncTime && response.resp.data.lastSyncTime !== -1) {
            setLastSyncTime(response.resp.data.lastSyncTime);
            if (response?.resp?.data?.ssi) {
                details.set("ssi", response.resp.data.ssi);
            }
            if (response?.resp?.data?.fsi) {
                details.set("fsi", response.resp.data.fsi);
            }
        }
        details.set("isSyncCompleted", response.resp.data?.isSyncCompleted);
    }).catch((ex) => {
        succErrMsg(getI18N("ERROR"), {
            severity: "error",
            ...succErrMsgOption
        });
    });
    forceUpdate();
};

useEffect(() => {
    getAccountDetails();
}, []);

const renderStatustable = () => {
    const isSyncCompleted = details.getIsSyncCompleted();
    if (isSyncCompleted) {
        return <ImapSyncStatus {...{details}} />;
    }
    return <ImapFolderSyncStatus {...{details, getAccountDetails}} />;
};
return (showSyncStatusTable && renderStatustable())

CHILD COMPONENT ImapFolderSyncStatus.js

const [fsiData, setFsiData] = useState();

useEffect(() => {
    setFsiData(details.getFolderSyncStatusInfo());
}, []);

function renderFolderSyncStatusRows () {
    const rows = [];
    for (const key in fsiData) {
        if (Object.hasOwnProperty.call(fsiData, key)) {
            const element = fsiData[key];
            rows.push(
                <Tr>
                    <Td>
                        {element.folderName}
                    </Td>
                    <Td>
                        {element.ci.ZCount}
                    </Td>
                    <Td>
                        {element.ci.mailCount}
                    </Td>
                </Tr>
            );
        }
    }
    return rows;
}

    return (<>
<ButtonColumn align="right">
                            <Button variant="flat" variantColor="primary" size="custom" onClick={getAccountDetails}>
                                <Icon className="rotate-right" />
                                {getI18N("Refresh folder")}
                            </Button>
                        </ButtonColumn>{renderFolderSyncStatusRows()}
</>);

As u can see, I am calling the parent getAccountDetails function from child. The function gets called correctly and forceUpdate is also triggered. However if I add breakpoint on the line const [fsiData, setFsiData] = useState(); and inside the useEffect in child component, only the former breakpoint is triggered. I solved this by adding a key to the child component but I don't understand what exactly is going on.

1 Answers

Your child component is updating just fine. It's just that the data you're seeing won't because of your misuse of useEffect.

With a component like

function Component(...) {
  const [fsiData, setFsiData] = useState();

  useEffect(() => {
    setFsiData(details.getFolderSyncStatusInfo());
  }, []);
  // ... use fsiData for rendering...
}

the effect is only ever run when the component mounts and never again.

The props changing doesn't cause the component to (re)mount, so the effect is never run and you're stuck with the same data. (The component's key changing will cause a remount.)

Just do

function Component(...) {
  const fsiData = details.getFolderSyncStatusInfo();
}
Related