testing-library / testing-library/react-testing-library
renderHook: Context Provider, state change in child function act() error
Nobody has claimed this yet.
- Dominant language
- JavaScript
- Stars
- 19.7k
- Forks
- 1.2k
- Avg merge
- 3d 16h
- Merged PRs (30d)
- 1
Description
I have a custom hook that uses a Context Provider for global state management. I am passing a dispatch and data from the state taken from the context inside the hook to a function that will at the end of requesting data from an endpoint dispatch this data coursing a state update inside the context API Provider passed down to the custom hook.
The issue is when I test the custom hook with a wrapper of the Context Provider passed into renderHook() function, I get in the test run the dreaded "act()" error of:
Warning: An update to StoreProvider inside a test was not wrapped in act(...)
20 | .then((res) => {
> 22 | setLoading(false)
| ^
23 | dispatch({
24 | type: ActionTypesEnum.FETCH_USERS,
25 | payload: res.data
you can see the full explanation at: https://stackoverflow.com/questions/76157226/react-hooks-testing-library-context-provider-state-change-in-child-function-ac
The useUserSearch Hook:
const useUserSearch = (searchCriteria: string): SearchHookProps => {
// data from the context and stored in the state
const { dispatch, state } = useStateContext();
const { users } = state;
const [ userData, setUserData ] = useState<UserProps[]>([]);
const [ loading, setLoading ] = useState<boolean>(false);
const [ error, setError ] = useState<boolean>(false);
// connected to the pagination affected by the search
const [ currentPage, setCurrentPage ] = useState<number>(1)
const [ totalPages, setTotalPages ] = useState<number>(1)
const [ totalUserCount, setTotalUserCount ] = useState<number>(0)
const totalPerPage = 12
useEffect(() => {
if (users === null) FetchUsers(dispatch, searchCriteria, setLoading, setError);
}, [users]);
useEffect(() => {
if (searchCriteria.length > 0) FetchUsers(dispatch, searchCriteria, setLoading, setError);
}, [searchCriteria]);
return {
userData,
totalUserCount,
setCurrentPage,
currentPage,
totalPages,
loading,
error,
}
}
FetchUsers function:
const FetchUsers = (
dispatch: Dispatch<any>,
searchCriteria: string,
setLoading: Dispatch<SetStateAction<boolean>>,
setError: Dispatch<SetStateAction<boolean>>,) => {
const { global: { apiUrl } } = AppConfig
const search = `?q=${encodeURIComponent(searchCriteria)}`
setLoading(true)
axios.get(`${apiUrl}/users?q=${search}+in:login+type:user`)
.then((res) => {
setLoading(false)
dispatch({
type: ActionTypesEnum.FETCH_USERS,
payload: res.data
})
})
.catch(() => {
setLoading(false)
setError(true)
})
}
StoreProvider:
import React, { useContext, useReducer, useMemo, createContext } from 'react';
import Reducer from '../../Reducers';
import AppConfig from '../../Configs';
import { StateInterface, StoreInterface, ProviderInterface } from '../../Interfaces';
export const initialState: StateInterface = {
appConfig: AppConfig,
users: null,
};
export const StoreContext = createContext({
state: initialState,
} as StoreInterface);
export const useStateContext = () => useContext(StoreContext);
export const StoreProvider = ({ children }: ProviderInterface) => {
const [state, dispatch] = useReducer(Reducer, initialState);
const StoreProviderValue = useMemo(() => ({ state, dispatch }), [state, dispatch]);
return <StoreContext.Provider value={StoreProviderValue}>{children}</StoreContext.Provider>;
};
The Test spec file:
import React from 'react'
import { render, cleanup, waitFor, renderHook, act } from '@testing-library/react'
import axios from 'axios'
import usersMock from '../__mocks__/usersMock'
import { StoreProvider } from '../Providers'
import useUserSearch from './useUserSearch'
jest.mock('axios')
const mockedAxios = axios as jest.Mocked<typeof axios>;
describe('Test that <User />', () => {
const renderHookComponent = (searchCriteria: string) => {
const wrapper = ({ children }: any) => <StoreProvider>{children}</StoreProvider>
const { result, rerender, unmount } = renderHook(() => useUserSearch(searchCriteria), {
wrapper: wrapper as React.ComponentType<any>
})
return { result, rerender, unmount }
}
afterEach(() => {
cleanup()
})
it('renders the hook useUserSearch', async () => {
mockedAxios.get.mockResolvedValue(usersMock);
const { result } = renderHookComponent('test')
// await waitFor(() => {
// expect(result.current.userData.length).toBe(0)
// })
})
})
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the useUserSearch hook, its FetchUsers helper, StoreProvider, and the test spec shown in the issue. Run the hook test with the mocked axios response and trace the asynchronous state updates; the issue does not define a requested change or clear completion condition.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, react
- Domain
- frontend, testing-qa
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100