skip to Main Content

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 (
        <div>
            <section>
                <header>
                    <MyTable />
                </header>
            </section>
        </div>
    );
}

useGetSetting definition:

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 are logged in browser. 3 is never logged.
MyTable definition:

export const MyTable = ({ id }: MyTableProps) => {
    //loading settings from context here
    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"
            />

        </>
    );
};

Finally useSettingsContext definition:

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

2

Answers


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

    This is not what the hook is supposed to do. React first reads the code and then decides to paint so useLayoutEffect() fires to guide it in doing so, you see just to paint, React when reads the code useLayoutEffect() didn’t fire yet so you don’t expect it to provide data it is usually used to manipulate dom elements.
    in your use case you just use useEffect and handle the first render when the data is not settled yet

    useLayoutEffect

    Login or Signup to reply.
  2. 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 = () => {
      const [loading, setLoading] = useState(true);
      const [data, setData] = useState(null)
      useEffect(() => {
        // Insert code to do the loading, then set the loading and data states
      }, []
    
      if (loading) {
        return null
        // Or, 
        // return <div>Loading...</div>
      } 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 <SettingsContext.Provider value={/* something */}>). I’d need to see both sides of that to identify what the problem is.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search