React 测试库测试仍然出现 "React state update on an unmounted component" 错误
Still getting "React state update on an unmounted component" error with React Testing Library tests
我的测试代码出现了很多错误,因此采取了几个步骤来改进。本文帮助:https://binarapps.com/blog/clean-up-request-in-useeffect-react-hook/woodb
我通过引入 AbortController 改进了我怀疑是问题的 useEffect
:
useEffect(() => {
if (companyId && companyId !== -1) {
const abortController = new AbortController();
requests.get(`${requests.API_ROOT()}account_management/roles?company_id=${companyId}`)
.then(response => {
dispatch({type: UPDATE_ROLES, payload: response.data.roles});
})
.catch(error => {
if (error.response) {
throw('Error fetching roles: ', error.response.status);
}
});
return () => {
abortController.abort();
};
}
}, [companyId, dispatch]);
我也这样重构了我的测试代码:
it('Should hide modal if Close is pressed', async () => {
await act(async () => {
let { getByTestId, getByText, queryByTestId, queryByText } = await renderDom(<AddUsersLauncher />);
fireEvent.click(queryByText(/^Add Users/i));
fireEvent.click(getByText(/^Close/i));
expect(queryByTestId('add-users-modal')).toBeNull();
});
});
注:renderDom
功能与之前相同:
const _initialState = {
session: {
user: {},
isLoading: false,
error: false,
},
};
function renderDom(component, initialState = _initialState) {
const store = configureStore(initialState);
return {
...render(
<Provider store={store}>
<SessionProvider>
<ThemeProvider theme={theme}>
<SystemMessages />
<CustomComponent />
{component}
</ThemeProvider>
</SessionProvider>
</Provider>),
store
};
}
根据 oemera 的问题,这是异步调用:
export const get = async function(url: string, _options: any) {
const options = _options || await _default_options();
return axios.get(url, options);
};
经过这次重构,errors/warnings 已经减少了,但仍然出现了一个:
> Add Users component tests
> ✓ Should display the Add Users modal (119ms)
> ✓ Should cancel w/o confirmation modal if no data is entered (34ms)
> ✓ Should hide modal if Close is pressed (32ms)
> ✓ Should hide modal if Close is pressed (29ms)
>
> console.error
> node_modules/react-dom/cjs/react-dom.development.js:558
> 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.
> in UsersProvider (at AddUsersModal.js:10)
> in div (at AddUsersModal.js:9)
> in AddUsersModal (at AddUsersLauncher.js:16)
>
> Test Suites: 1 passed, 1 total Tests: 4 passed, 4 total
> Snapshots: 0 total Time: 4.999s Ran all test suites matching
> /AddUsers.test/i.
我已经检查了提到的所有代码 AddUsers...
文件,但那里没有 useEffect
个实例。
有什么建议可以消除此警告吗?
当您使用 axios
而不是 fetch
时,中止控制器将无法工作。这就是你在 axios 中的做法:
import React, { Component } from 'react';
import axios from 'axios';
class Example extends Component {
signal = axios.CancelToken.source();
state = {
isLoading: false,
user: {},
}
componentDidMount() {
this.onLoadUser();
}
componentWillUnmount() {
this.signal.cancel('Api is being canceled');
}
onLoadUser = async () => {
try {
this.setState({ isLoading: true });
const response = await axios.get('https://randomuser.me/api/', {
cancelToken: this.signal.token,
})
this.setState({ user: response.data, isLoading: true });
} catch (err) {
if (axios.isCancel(err)) {
console.log('Error: ', err.message); // => prints: Api is being canceled
}
else {
this.setState({ isLoading: false });
}
}
}
render() {
return (
<div>
<pre>{JSON.stringify(this.state.user, null, 2)}</pre>
</div>
)
}
}
来源: https://gist.github.com/adeelibr/d8f3f8859e2929f3f1adb80992f1dc09
请注意您是如何传递 cancelToken
而不是整个信号的。您还可以通过 axios.CancelToken.source()
获得信号,然后调用 signal.cancel
或 abortController.abort
所以对于你的例子,这应该像这样工作或者至少引导你朝着正确的方向前进:
useEffect(() => {
if (companyId && companyId !== -1) {
const signal = axios.CancelToken.source();
requests.get(`${requests.API_ROOT()}account_management/roles?company_id=${companyId}`,
{ cancelToken: signal.token})
.then(response => {
dispatch({type: UPDATE_ROLES,
payload: response.data.roles}); })
.catch(error => {
if (error.response) {
throw('Error fetching roles: ' error.response.status);
}
});
return () => { signal.cancel('Cancelling request...');};
} }, [companyId, dispatch]);
我的测试代码出现了很多错误,因此采取了几个步骤来改进。本文帮助:https://binarapps.com/blog/clean-up-request-in-useeffect-react-hook/woodb
我通过引入 AbortController 改进了我怀疑是问题的 useEffect
:
useEffect(() => {
if (companyId && companyId !== -1) {
const abortController = new AbortController();
requests.get(`${requests.API_ROOT()}account_management/roles?company_id=${companyId}`)
.then(response => {
dispatch({type: UPDATE_ROLES, payload: response.data.roles});
})
.catch(error => {
if (error.response) {
throw('Error fetching roles: ', error.response.status);
}
});
return () => {
abortController.abort();
};
}
}, [companyId, dispatch]);
我也这样重构了我的测试代码:
it('Should hide modal if Close is pressed', async () => {
await act(async () => {
let { getByTestId, getByText, queryByTestId, queryByText } = await renderDom(<AddUsersLauncher />);
fireEvent.click(queryByText(/^Add Users/i));
fireEvent.click(getByText(/^Close/i));
expect(queryByTestId('add-users-modal')).toBeNull();
});
});
注:renderDom
功能与之前相同:
const _initialState = {
session: {
user: {},
isLoading: false,
error: false,
},
};
function renderDom(component, initialState = _initialState) {
const store = configureStore(initialState);
return {
...render(
<Provider store={store}>
<SessionProvider>
<ThemeProvider theme={theme}>
<SystemMessages />
<CustomComponent />
{component}
</ThemeProvider>
</SessionProvider>
</Provider>),
store
};
}
根据 oemera 的问题,这是异步调用:
export const get = async function(url: string, _options: any) {
const options = _options || await _default_options();
return axios.get(url, options);
};
经过这次重构,errors/warnings 已经减少了,但仍然出现了一个:
> Add Users component tests
> ✓ Should display the Add Users modal (119ms)
> ✓ Should cancel w/o confirmation modal if no data is entered (34ms)
> ✓ Should hide modal if Close is pressed (32ms)
> ✓ Should hide modal if Close is pressed (29ms)
>
> console.error
> node_modules/react-dom/cjs/react-dom.development.js:558
> 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.
> in UsersProvider (at AddUsersModal.js:10)
> in div (at AddUsersModal.js:9)
> in AddUsersModal (at AddUsersLauncher.js:16)
>
> Test Suites: 1 passed, 1 total Tests: 4 passed, 4 total
> Snapshots: 0 total Time: 4.999s Ran all test suites matching
> /AddUsers.test/i.
我已经检查了提到的所有代码 AddUsers...
文件,但那里没有 useEffect
个实例。
有什么建议可以消除此警告吗?
当您使用 axios
而不是 fetch
时,中止控制器将无法工作。这就是你在 axios 中的做法:
import React, { Component } from 'react';
import axios from 'axios';
class Example extends Component {
signal = axios.CancelToken.source();
state = {
isLoading: false,
user: {},
}
componentDidMount() {
this.onLoadUser();
}
componentWillUnmount() {
this.signal.cancel('Api is being canceled');
}
onLoadUser = async () => {
try {
this.setState({ isLoading: true });
const response = await axios.get('https://randomuser.me/api/', {
cancelToken: this.signal.token,
})
this.setState({ user: response.data, isLoading: true });
} catch (err) {
if (axios.isCancel(err)) {
console.log('Error: ', err.message); // => prints: Api is being canceled
}
else {
this.setState({ isLoading: false });
}
}
}
render() {
return (
<div>
<pre>{JSON.stringify(this.state.user, null, 2)}</pre>
</div>
)
}
}
来源: https://gist.github.com/adeelibr/d8f3f8859e2929f3f1adb80992f1dc09
请注意您是如何传递 cancelToken
而不是整个信号的。您还可以通过 axios.CancelToken.source()
获得信号,然后调用 signal.cancel
或 abortController.abort
所以对于你的例子,这应该像这样工作或者至少引导你朝着正确的方向前进:
useEffect(() => {
if (companyId && companyId !== -1) {
const signal = axios.CancelToken.source();
requests.get(`${requests.API_ROOT()}account_management/roles?company_id=${companyId}`,
{ cancelToken: signal.token})
.then(response => {
dispatch({type: UPDATE_ROLES,
payload: response.data.roles}); })
.catch(error => {
if (error.response) {
throw('Error fetching roles: ' error.response.status);
}
});
return () => { signal.cancel('Cancelling request...');};
} }, [companyId, dispatch]);