useLayoutEffect()没有触发,无法从后端加载数据并将其设置为表格的状态。

huangapple go评论68阅读模式
英文:

useLayoutEffect() not firing to load data from backend and set it to the state for a table

问题

在一个组件中,我使用useLayoutEffect()从后端加载数据并将其放入状态/上下文中。然后MyTable从上下文中加载数据以进行渲染。这个钩子应该在浏览器有机会绘制之前触发。但是注意到组件渲染发生在没有触发这个钩子的情况下,所以DataTable会抱怨上下文为空。

useGetSetting的定义:

export function useGetSetting(
    id: number
): useGetSettingResponse {
    console.log("1");
    const [state, dispatch] = React.useReducer(settingsReducer, settingsInitialState);
    console.log("2");

    React.useLayoutEffect(() => {
        console.log("3");

        const settings = getSettings(id)
        console.log(settings);
        dispatch({
            settings: settings,
        });

    }, [id, dispatch]);

    console.log("4");
    return { state, dispatch };
}

1、2、4会在浏览器中记录。3从未记录。

MyTable的定义:

export const MyTable = ({ id }: MyTableProps) => {
    // 在此处从上下文中加载设置
    const settings = useSettingContext();

    return (
        <>
            <BaseTable
                id="settingsTable"
                data={settings}
                rowRenderer={(data: Settings, i: number) => (
                    <BaseTable role="row" aria-rowindex={i + 1}>
                        {MyTableDefinition.map((field) => (
                            <BaseTableCell key={field.key} role="cell" className={styles.tableCell}>
                                <div>
                                    {field.formatter(data, id)}
                                </div>
                            </BaseTableCell>
                        ))}
                    </BaseTableRow>
                )}
                role="table"
            />

        </>
    );
};

最后,useSettingsContext的定义:

export const useSettingsContext = () => {
    const context = React.useContext(SettingsContext);
    if (context === undefined) {
        throw new Error(
            '没有设置,' // 在浏览器中看到这个错误
        );
    }
    return context.state.settings;
};
英文:

In a component, I use useLayoutEffect() to load data from the backend and put it in state/context. Then MyTable is loading the data from context to render.
This hook should fire before the browser has had a chance to paint. But noticing component rendering happens without this hook firing so DataTable complains of empty context.


export function MySettings() {
    const id = useSelector(getId);
    //call to backend to set data to state
    const { state, dispatch } = useGetSetting(id);
    //state.setting is empty here
    console.log({ state });
    return (
        &lt;div&gt;
            &lt;section&gt;
                &lt;header&gt;
                    &lt;MyTable /&gt;
                &lt;/header&gt;
            &lt;/section&gt;
        &lt;/div&gt;
    );
}

useGetSetting definition:

export function useGetSetting(
    id: number
): useGetSettingResponse {
    console.log(&quot;1&quot;);
    const [state, dispatch] = React.useReducer(settingsReducer, settingsInitialState);
    console.log(&quot;2&quot;);

    React.useLayoutEffect(() =&gt; {
        console.log(&quot;3&quot;);

        const settings = getSettings(id)
        console.log(settings);
        dispatch({
            settings: settings,
        });

    }, [id, dispatch]);

    console.log(&quot;4&quot;);
    return { state, dispatch };
}

1 ,2, 4 are logged in browser. 3 is never logged.
MyTable definition:

export const MyTable = ({ id }: MyTableProps) =&gt; {
    //loading settings from context here
    const settings = useSettingContext();

    return (
        &lt;&gt;
            &lt;BaseTable
                id=&quot;settingsTable&quot;
                data={settings}
                rowRenderer={(data: Settings, i: number) =&gt; (
                    &lt;BaseTable role=&quot;row&quot; aria-rowindex={i + 1}&gt;
                        {MyTableDefinition.map((field) =&gt; (
                            &lt;BaseTableCell key={field.key} role=&quot;cell&quot; className={styles.tableCell}&gt;
                                &lt;div&gt;
                                    {field.formatter(data, id)}
                                &lt;/div&gt;
                            &lt;/BaseTableCell&gt;
                        ))}
                    &lt;/BaseTableRow&gt;
                )}
                role=&quot;table&quot;
            /&gt;

        &lt;/&gt;
    );
};

Finally useSettingsContext definition:

export const useSettingsContext = () =&gt; {
    const context = React.useContext(SettingsContext);
    if (context === undefined) {
        throw new Error(
            &#39;No settings,&#39; //Seeing this error in browser
        );
    }
    return context.state.settings;
};

答案1

得分: 0

对于useEffectuseLayoutEffect,React会首先渲染您的组件。它通过组件树逐一调用每个组件,您可以在页面的相应部分返回您想要的内容。然后,它会找出与上一次渲染的差异,并更新DOM以反映这些更改。

只有在所有这些都完成之后,您的effect中的代码才会运行。如果无法渲染组件(因为发生错误),React将无法运行您的效果。在效果运行之前,您的组件必须能够渲染。

在加载数据方面,最常见的做法是在加载完成之前渲染一些占位内容。如果您不想渲染任何内容,可以从组件中返回null,如下所示:

const Example = () => {
  const [loading, setLoading] = useState(true);
  const [data, setData] = useState(null);
  useEffect(() => {
    // 插入加载数据的代码,然后设置加载和数据状态
  }, []);

  if (loading) {
    return null;
    // 或者
    // return <div>加载中...</div>;
  } else {
    // 返回真正的UI
  }
}

useLayoutEffect并不是经常需要的东西。它唯一改变的是,如果在effect内同步设置状态,然后React会同步进行另一个渲染。这主要用于您想要在页面上放置一些内容,然后测量它们,然后在用户看到闪烁的情况下更改页面上的内容的情况。它对加载数据没有帮助。


P.S,您的问题还涉及到上下文,而且上下文是为您提供了一个undefined。这可能表示存在问题,但是您的问题只显示了您如何消费上下文(即通过调用useContext(SettingsContext)),而没有显示您如何提供上下文(即通过渲染一个<SettingsContext.Provider value={/* something */}>)。我需要看到两方面才能确定问题所在。

英文:

For both useEffect and useLayoutEffect, react will start by rendering your component. It works its way through the tree of components, calling each one, and you returning what you want on that part of the page. Then it figures out what differences there are from the previous render, and updates the DOM with those changes.

Only after all of that is done does the code in your effect run. If you can't render the components (because an error was thrown), react won't be able to get to the point of running your effects. Your component must be capable of rendering before the effects run.

When it comes to loading data, the most common thing to do is to render some placeholder until the loading is complete. You can return null from a component if you don't want to render anything at all, as in:

const Example = () =&gt; {
  const [loading, setLoading] = useState(true);
  const [data, setData] = useState(null)
  useEffect(() =&gt; {
    // Insert code to do the loading, then set the loading and data states
  }, []

  if (loading) {
    return null
    // Or, 
    // return &lt;div&gt;Loading...&lt;/div&gt;
  } else {
    // return the real ui
  }
}

useLayoutEffect is not something you'll need very often. The only thing it changes is that if you synchronously set state during the effect, then react will synchronously do another render. This is mainly for cases where you want to, say, put some things on the page, then measure them, then change what's on the page without the user seeing a flicker. It won't help with loading data.


P.S, Your question also is making use of context, and that context is giving you an undefined. That may indicate a problem, but your question only showed how you're consuming the context (ie, by calling useContext(SettingsContext), not how you're providing the context (ie, by rendering a &lt;SettingsContext.Provider value={/* something */}&gt;). I'd need to see both sides of that to identify what the problem is.

huangapple
  • 本文由 发表于 2023年7月11日 10:03:16
  • 转载请务必保留本文链接:https://go.coder-hub.com/76658302.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定