javascript - React loader 在“挂起”的 axios 请求期间不会保持可见
问题描述
我正在使用反应上下文和 axios 拦截器来隐藏/显示加载微调器。虽然在触发请求和响应时它会正确隐藏和显示,但我的加载微调器仅在网络请求开始时显示。请求触发,然后进入“待处理”状态。在“待定”状态期间,响应拦截器被触发并隐藏加载微调器。
如何确保加载微调器在待处理请求期间保持可见?
我尝试添加一些控制台日志以在返回请求、响应和错误(包括计数)时触发,并且它显示(对于两个请求)成功地从 0 - 1 - 2 - 1 - 0 开始,但在 chrome devtools即使所有请求都已返回,网络选项卡仍显示为待处理。
编辑:以为我在重构后让它工作了,但这是不行的。添加了更新的代码
import React, { useReducer, useRef, useEffect, useCallback } from "react";
import { api } from "api/api";
import LoadingReducer from "reducer/LoadingReducer";
const LoadingContext = React.createContext();
export const LoadingProvider = ({ children }) => {
const [loader, dispatch] = useReducer(LoadingReducer, {
loading: false,
count: 0,
});
const loaderKeepAlive = useRef(null),
showLoader = useRef(null);
const showLoading = useCallback(() => {
dispatch({
type: "SHOW_LOADING",
});
}, [dispatch]);
const hideLoading = useCallback(() => {
loaderKeepAlive.current = setTimeout(() => {
dispatch({
type: "HIDE_LOADING",
});
}, 3000);
return clearTimeout(loaderKeepAlive.current);
}, [dispatch]);
const requestHandler = useCallback(
(request) => {
dispatch({ type: "SET_COUNT", count: 1 });
return Promise.resolve({ ...request });
},
[dispatch]
);
const errorHandler = useCallback(
(error) => {
dispatch({ type: "SET_COUNT", count: -1 });
return Promise.reject({ ...error });
},
[dispatch]
);
const successHandler = useCallback(
(response) => {
dispatch({ type: "SET_COUNT", count: -1 });
return Promise.resolve({ ...response });
},
[dispatch]
);
useEffect(() => {
if (loader.count === 0) {
hideLoading();
clearTimeout(showLoader.current);
} else {
showLoader.current = setTimeout(() => {
showLoading();
}, 1000);
}
}, [showLoader, showLoading, hideLoading, loader.count]);
useEffect(() => {
if (!api.interceptors.request.handlers[0]) {
api.interceptors.request.use(
(request) => requestHandler(request),
(error) => errorHandler(error)
);
}
if (!api.interceptors.response.handlers[0]) {
api.interceptors.response.use(
(response) => successHandler(response),
(error) => errorHandler(error)
);
}
return () => {
clearTimeout(showLoader.current);
};
}, [errorHandler, requestHandler, successHandler, showLoader]);
return (
<LoadingContext.Provider
value={{
loader,
}}
>
{children}
</LoadingContext.Provider>
);
};
export default LoadingContext;
解决方案
我认为更标准的方法是仅利用loading
状态来有条件地渲染 Spinner 以及将其从 DOM 中删除的承诺的结果(参见下面的演示)。通常,拦截器用于从 API 响应返回错误,因为 axios 默认为状态错误(例如,404 - not found
)。
例如,自定义 axios 拦截器来显示 API 错误:
import get from "lodash.get";
import axios from "axios";
const { baseURL } = process.env;
export const app = axios.create({
baseURL
});
app.interceptors.response.use(
response => response,
error => {
const err = get(error, ["response", "data", "err"]);
return Promise.reject(err || error.message);
}
);
export default app;
演示
代码
应用程序.js
import React, { useEffect, useCallback, useState } from "react";
import fakeApi from "./api";
import { useAppContext } from "./AppContext";
import Spinner from "./Spinner";
const App = () => {
const { isLoading, error, dispatch } = useAppContext();
const [data, setData] = useState({});
const fetchData = useCallback(async () => {
try {
// this example uses a fake api
// if you want to trigger an error, then pass a status code other than 200
const res = await fakeApi.get(200);
setData(res.data);
dispatch({ type: "loaded" });
} catch (error) {
dispatch({ type: "error", payload: error.toString() });
}
}, [dispatch]);
const reloadData = useCallback(() => {
dispatch({ type: "reset" });
fetchData();
}, [dispatch, fetchData]);
useEffect(() => {
fetchData();
// optionally reset context state on unmount
return () => {
dispatch({ type: "reset" });
};
}, [dispatch, fetchData]);
if (isLoading) return <Spinner />;
if (error) return <p style={{ color: "red" }}>{error}</p>;
return (
<div style={{ textAlign: "center" }}>
<pre
style={{
background: "#ebebeb",
margin: "0 auto 20px",
textAlign: "left",
width: 600
}}
>
<code>{JSON.stringify(data, null, 4)}</code>
</pre>
<button type="button" onClick={reloadData}>
Reload
</button>
</div>
);
};
export default App;
AppContext.js
import React, { createContext, useContext, useReducer } from "react";
const AppContext = createContext();
const initialReducerState = {
isLoading: true,
error: ""
};
const handleLoading = (state, { type, payload }) => {
switch (type) {
case "loaded":
return { isLoading: false, error: "" };
case "error":
return { isLoading: false, error: payload };
case "reset":
return initialReducerState;
default:
return state;
}
};
export const AppContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(handleLoading, initialReducerState);
return (
<AppContext.Provider
value={{
...state,
dispatch
}}
>
{children}
</AppContext.Provider>
);
};
export const useAppContext = () => useContext(AppContext);
export default AppContextProvider;
Spinner.js
import React from "react";
const Spinner = () => <div className="loader">Loading...</div>;
export default Spinner;
fakeApi.js
const data = [{ id: "1", name: "Bob" }];
export const fakeApi = {
get: (status) =>
new Promise((resolve, reject) => {
setTimeout(() => {
status === 200
? resolve({ data })
: reject(new Error("Unable to locate data."));
}, 2000);
})
};
export default fakeApi;
index.js
import React from "react";
import ReactDOM from "react-dom";
import AppContextProvider from "./AppContext";
import App from "./App";
import "./styles.css";
const rootElement = document.getElementById("root");
ReactDOM.render(
<React.StrictMode>
<AppContextProvider>
<App />
</AppContextProvider>
</React.StrictMode>,
rootElement
);
推荐阅读
- python - 用于将数组/向量发送到 C++ 脚本的 Cython 示例
- python - 如何更改新类中方法的内容
- javascript - 需要使用 jQuery 使用 Wordpress 自定义字段填充 Contact Form 7 表单字段
- laravel - 具有相同范围的 Laravel Passport refresh_token
- pyspark - 如何从 .txt.bz2 文件创建 Spark DataFrame
- windows - Azure 函数应用 UI 中未显示的函数
- json - 接受任何 JSON 数据到 .NET Core Web API 控制器(无模型)
- unity3d - 同一角色多次旋转
- html - 如何在下拉菜单后添加换行符
- html - 输入字段接受任何字符,忽略正则表达式