I am testing custom written context for my application. I have a interview-evaluations context file. I am trying to the fetchInterviewData functionality for this context provider.
/* eslint-disable prefer-destructuring */
/* eslint-disable react-hooks/exhaustive-deps */
import React, { useState, useCallback, useRef } from "react";
import PropTypes from "prop-types";
import { useAxios } from "../../hooks/useAxios";
import { useAlertContext } from "../alert";
export const InterviewEvaluationContext = React.createContext();
export const InterviewEvaluationContextProvider = ({ children }) => {
const [interviewData, setInterviewData] = useState({});
const { setAlert } = useAlertContext();
const { get, post } = useAxios();
const interviewRef = useRef();
const { current = {} } = interviewRef;
const { id: interviewId, posId: positionId, senId: seniorityId } = current;
const fetchInterviewData = useCallback(
async (id, posId, senId) => {
interviewRef.current = { id, posId, senId };
try {
const response = await get(`interview-evaluations/${id}`, {
positionId: posId,
seniorityId: senId,
});
setInterviewData({ ...response.data });
} catch (error) {
//
interviewRef.current = { id, posId, senId };
setAlert("Something went wrong", "error");
}
},
[get]
);
const providerValue = {
fetchInterviewData,
interviewData,
};
return (
<InterviewEvaluationContext.Provider value={providerValue}>
{children}
</InterviewEvaluationContext.Provider>
);
};
InterviewEvaluationContextProvider.propTypes = {
children: PropTypes.node.isRequired,
};
export const useInterviewEvaluationContext = () => {
const context = React.useContext(InterviewEvaluationContext);
if (context === undefined) {
throw new Error(
"useInterviewEvaluationContext() should be used inside InterviewEvaluationContextProvider"
);
}
return context;
};
The test file I'm writing looks like this
import { act, render } from "@testing-library/react";
import { MemoryRouter as Router, useLocation } from "react-router-dom";
import { useEffect, useRef } from "react";
import {
render as RenderWithWrapper,
screen,
waitFor,
} from "../../test-utils/testing-library-utils";
import { useInterviewEvaluationContext } from "./index";
jest.mock("react-router-dom", () => ({
...jest.requireActual("react-router-dom"),
useLocation: () => {
return {
pathname: "/feedback",
hash: "",
search: "",
state: {
interviewInfo: {
candidateName: "XYZ",
id: 121212312,
positionId: 94,
seniorityId: 6,
},
},
};
},
}));
describe("Testing Interview Evaluation Context", () => {
test("it should throw an error if InterviewEvaluationContext is not used in a provider", () => {
const MockComponent = () => {
const { interviewData } = useInterviewEvaluationContext();
return <>{JSON.stringify(interviewData)}</>;
};
expect(() => render(<MockComponent />)).toThrow(
"useInterviewEvaluationContext() should be used inside InterviewEvaluationContextProvider"
);
});
const MockComponent = () => {
const { interviewData, fetchInterviewData } =
useInterviewEvaluationContext();
const { state: { interviewInfo = {} } = {} } = useLocation();
const { id, positionId, seniorityId } = interviewInfo;
useEffect(() => {
fetchInterviewData(id, positionId, seniorityId);
}, [fetchInterviewData, id, positionId, seniorityId]);
return <div data-testid="container">{JSON.stringify(interviewData)}</div>;
};
test("it should show interviewData as expected", async () => {
RenderWithWrapper(
<Router>
<MockComponent />
</Router>
);
const container = await screen.findByTestId("container");
screen.debug(container);
expect(container.textContent).toBe("{}");
});
});
But whenever I am running this test suite I am getting an error as below on setInterviewData() line
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.
First of all, What you saw is a warning, not error. if your assertion corrects, your test will pass even if you see
can't perform a React state update
because setState works as asynchronously, you can't guarantee it actives before component unmounted.
there are few workaround for this,
for example you can use useRef as a closure to reference whether component is unmounted or not.
however,
can't perform a React state update .... memory leak
will not be showed from react18.as using above approach in every component is quiet cumbersome, It would be better write custom hook for using data fetching, or just migrate to react18.