Reputation: 1678
I'm writing a logging framework for a react js app for performance monitoring. Basically there are two methods provided by sdk to create a time event and and to stop measuring and push timing details to where ever it wants. Since I need to capture the data fetching part too I thought of storing the created timing event in redux store so later can stop the timer when data is fetched. In order to decouple view components from logging as much as possible I'm trying to write two custom hooks for following purposes.
Following are the hooks I have created.
export const useStartRenderLogger = (
component: string,
key: string,
eventParams?: any
): void => {
const dispatch = useDispatch();
const existingTimer = useGetTimerByKey(key); <= Selector which return timer for key
const metrics = useMetrics(component); <= sdk hook to create a metric instance
if (existingTimer) return;
const timer = metrics.start(key, {
...eventParams,
stage: TimeLogStages.Render,
});
dispatch(TimeLogActions.pushLogger(key, timer)); <= dispatch passing timing log and key so it can stored on state
};
export const useStopRenderLogger = (
key: string,
pageRendered: boolean,
params?: any
): void => {
const timer = useGetTimerByKey(key);
const dispatch = useDispatch();
if (!pageRendered || isUndefined(timer)) return;
timer.stop(params);
dispatch(TimeLogActions.popLogger(key));
};
I call first one at the beginning of the component and the other right before the render passing boolean to indicate whether data has been fetched.
The problem is useStartRenderLogger
throws a error related to react rules of hooks.
index.js:1 Warning: React has detected a change in the order of Hooks called by MyComponent. This will lead to bugs and errors if not fixed. For more information, read the Rules of Hooks
It seems the dispatch is casing the issue. I guess pushing event into state make useGetTimerByKey
selector to update and cause the error. If that line is commented then page renders without errors.
Is my assumption about why error get thrown is correct?
Can I make useGetTimeByKey
selector not to update when state change?
Upvotes: 0
Views: 543
Reputation: 42228
return
Before a HookThe problem is these two lines:
if (existingTimer) return;
const metrics = useMetrics(component);
You must call the same hooks in the same order on every render. Calling a hook cannot be conditional. So you cannot have a return
above a hook call in your code, as that means useMetrics
is only sometimes called.
If you want to have some conditional logic, you can move that to inside the useMetrics
hook.
It's ok to make pure functions conditional. Calling dispatch(action)
can be conditional -- but calling useDispatch
cannot. So this code is ok:
export const useStartRenderLogger = (
component: string,
key: string,
eventParams?: any
): void => {
// call all hooks first
const dispatch = useDispatch();
const existingTimer = useGetTimerByKey(key);
const metrics = useMetrics(component);
// only return after all hooks are called
if (existingTimer) return;
// conditionally executed
const timer = metrics.start(key, {
...eventParams,
stage: TimeLogStages.Render,
});
dispatch(TimeLogActions.pushLogger(key, timer));
};
Upvotes: 1