如何在每个 Relay.js 网络请求上添加加载指示器
How to add a loading indicator on every Relay.js network request
我目前正在构建一个 Relay/React 网络应用程序。除了一个例外,它非常简单。当我的任何组件发出网络请求时,我无法弄清楚如何获得全局通知。当有网络 activity 时,我希望在我的应用程序顶部添加一个微调器,因为我的一些突变需要很长时间才能加载。
这是我尝试解决此问题的尝试,但它仅适用于新路由页面加载。
function renderer(info)
{
let {props, error, element} = info;
if (error) {
return (<ServerError errors={error}/>);
} else {
if (props) {
return React.cloneElement(element, props);
} else {
return (<Loading />);
}
}
}
ReactDOM.render(
<Router
history={browserHistory}
render={applyRouterMiddleware(useRelay)}
environment={Relay.Store}
>
<Route
path="/"
queries={ViewerQuery}
component={App}
>
<IndexRoute
queries={ViewerQuery}
component={Libraries}
render={renderer}
/>
<Route path="*" component={Error}/>
</Route>
</Router>
理想情况下,我可以获得一些回调,我可以将其传递给我的 App 组件,该组件呈现我所有的页面页眉和页脚。对此的任何帮助将不胜感激。我已经在互联网上搜索了一段时间,试图找到一个好的解决方案。
您可以在 React 中创建自定义微调器组件,并根据您的数据加载状态显示或隐藏微调器。
一个例子可以是 -
您的 Spinner 组件可能是这样的 -
let SpinMe
= (
<div className="spinner-container">
<div className="loader">
<svg className="circular">
<circle className = "path"
cx = "50"
cy = "50"
r = "20"
fill = "none"
strokeWidth = "3"
strokeMiterLimit = "10"
/>
</svg>
</div>
</div>
);
此微调器组件应比其他组件 z-index
稍高,以便在加载时用户无法单击其他组件或与其他组件交互。
还在样式中显示微调器的一些透明深色背景。
例如
.spinner-container {
position : absolute;
background-color : rgba(12, 12, 12, 0.61);
width : 100%;
min-height : 100%;
background-size : 100% 100%;
text-align : center;
z-index : 3;
top : 0;
left : 0;
}
现在您要使用微调器的另一个组件,并在此组件中发出网络请求。
import React from 'react';
class SpinTestComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoading:false
};
}
sendNetworkRequest(URL, dataToSend) {
return $.ajax({
type : 'POST',
url : URL,
data : JSON.stringify(dataToSend),
dataType : 'json'
});
}
componentDidMount() {
const URL = "test url";
const dataToSend = { param1:"val", param2:"val" };
this.setState({isLoading:true});
this.sendNetworkRequest(dataToSend)
.then(
() => {
// success response now remove spinner
this.setState({isLoading:false});
},
() => {
// error response again remove spinner and
// show error message to end user
this.setState({isLoading:false});
});
}
render() {
return (
<div>
{ this.state.isLoading ? <SpinMe/> : null }
<div>
<h1>
Remaining Component structure
or Jsx
</h1>
<p>
To be show after loading is done.
</p>
</div>
</div>
);
}
}
export default SpinTestComponent;
您也可以尝试添加自定义 Relay Network Layer 以在每次请求时呈现加载指示器组件。我认为 "global" 加载指标要考虑的主要问题不仅在于指标的设计,还在于其对全球 UI 的影响。它会阻挡 UI 吗?只是其中的一部分?它会取代其他元素 up/down 吗?等等
在此期间,您可以执行以下操作:
handleSubmit(event) {
event.preventDefault();
this.setState({isLoading: true});
this.props.relay.commitUpdate(
new LoginMutation({
email: this.refs.email.value,
password: this.refs.password.value
}), {
onSuccess: response => {
Auth.login(response.login.accessToken);
const { location, router } = this.props;
if (location.state && location.state.nextPathname) {
router.replace(location.state.nextPathname)
} else {
router.replace('/')
}
},
onFailure: transaction => {
this.setState({hasError: true});
this.setState({isLoading: false});
}
}
);
}
以上片段摘自here。您可以在该存储库中看到其余的逻辑。
我最终扩展了 Relay 的默认网络层,这样我就可以创建一些我的主要根组件监听的 Flux 事件。如果我愿意,这使我可以将所有加载和错误消息处理放在一个地方。
这是我的最终解决方案。不确定它是否是最干净的,但它工作得很好。
import Relay from "react-relay";
import RelayMutationRequest from "react-relay/lib/RelayMutationRequest";
import AppDispatcher from "./AppDispatcher";
export default class CustomNetworkLayer extends Relay.DefaultNetworkLayer {
constructor(uri, init)
{
super(uri, init);
}
networkLoadingEvent()
{
AppDispatcher.dispatch({
actionType: 'LOADING'
});
}
networkDoneLoadingEvent()
{
AppDispatcher.dispatch({
actionType: 'DONE_LOADING'
});
}
networkErrorEvent(error)
{
AppDispatcher.dispatch({
actionType: 'ERROR',
payload: error
});
}
sendQueries(requests)
{
this.networkLoadingEvent();
return Promise.all(requests.map(request => (
this._sendQuery(request).then(
result => result.json()
).then(payload =>
{
if (payload.hasOwnProperty('errors')) {
const error = CustomNetworkLayer.createRequestError(request, '200', payload);
this.networkErrorEvent(payload.errors[0].message);
request.reject(error);
} else {
if (!payload.hasOwnProperty('data')) {
const error = new Error(
'Server response was missing for query ' +
`\`${request.getDebugName()}\`.`
);
this.networkErrorEvent(error);
request.reject(error);
} else {
this.networkDoneLoadingEvent();
request.resolve({response: payload.data});
}
}
}).catch(
error =>
{
this.networkErrorEvent(error);
request.reject(error)
}
)
)));
}
sendMutation(request)
{
this.networkLoadingEvent();
return this._sendMutation(request).then(
result => result.json()
).then(payload =>
{
if (payload.hasOwnProperty('errors')) {
const error = CustomNetworkLayer.createRequestError(request, '200', payload);
this.networkErrorEvent(payload.errors[0].message);
request.reject(error);
} else {
this.networkDoneLoadingEvent();
request.resolve({response: payload.data});
}
}).catch(
error =>
{
this.networkErrorEvent(error);
request.reject(error)
}
);
}
/**
* Formats an error response from GraphQL server request.
*/
static formatRequestErrors(request, errors)
{
const CONTEXT_BEFORE = 20;
const CONTEXT_LENGTH = 60;
const queryLines = request.getQueryString().split('\n');
return errors.map(({locations, message}, ii) =>
{
const prefix = (ii + 1) + '. ';
const indent = ' '.repeat(prefix.length);
//custom errors thrown in graphql-server may not have locations
const locationMessage = locations ?
('\n' + locations.map(({column, line}) =>
{
const queryLine = queryLines[line - 1];
const offset = Math.min(column - 1, CONTEXT_BEFORE);
return [
queryLine.substr(column - 1 - offset, CONTEXT_LENGTH),
' '.repeat(Math.max(0, offset)) + '^^^',
].map(messageLine => indent + messageLine).join('\n');
}).join('\n')) :
'';
return prefix + message + locationMessage;
}).join('\n');
}
static createRequestError(request, responseStatus, payload)
{
const requestType =
request instanceof RelayMutationRequest ? 'mutation' : 'query';
const errorReason = typeof payload === 'object' ?
CustomNetworkLayer.formatRequestErrors(request, payload.errors) :
`Server response had an error status: ${responseStatus}`;
const error = new Error(
`Server request for ${requestType} \`${request.getDebugName()}\` ` +
`failed for the following reasons:\n\n${errorReason}`
);
error.source = payload;
error.status = responseStatus;
return error;
}
}
然后在我的 index.js 文件中我这样做:
Relay.injectNetworkLayer(new CustomNetworkLayer("/graphql",
{
fetchTimeout: 35000, // timeout after 35 seconds
retryDelays: [2000] // retry after 2 seconds
}));
快速说明:AppDispatcher 只是一个 flux js 调度程序,我正在我的主要包装器组件中监听这些事件。
希望这对其他人有所帮助。我当然在这上面花了太多时间。
也感谢所有帮助我得出这个解决方案的人。
我目前正在构建一个 Relay/React 网络应用程序。除了一个例外,它非常简单。当我的任何组件发出网络请求时,我无法弄清楚如何获得全局通知。当有网络 activity 时,我希望在我的应用程序顶部添加一个微调器,因为我的一些突变需要很长时间才能加载。
这是我尝试解决此问题的尝试,但它仅适用于新路由页面加载。
function renderer(info)
{
let {props, error, element} = info;
if (error) {
return (<ServerError errors={error}/>);
} else {
if (props) {
return React.cloneElement(element, props);
} else {
return (<Loading />);
}
}
}
ReactDOM.render(
<Router
history={browserHistory}
render={applyRouterMiddleware(useRelay)}
environment={Relay.Store}
>
<Route
path="/"
queries={ViewerQuery}
component={App}
>
<IndexRoute
queries={ViewerQuery}
component={Libraries}
render={renderer}
/>
<Route path="*" component={Error}/>
</Route>
</Router>
理想情况下,我可以获得一些回调,我可以将其传递给我的 App 组件,该组件呈现我所有的页面页眉和页脚。对此的任何帮助将不胜感激。我已经在互联网上搜索了一段时间,试图找到一个好的解决方案。
您可以在 React 中创建自定义微调器组件,并根据您的数据加载状态显示或隐藏微调器。
一个例子可以是 -
您的 Spinner 组件可能是这样的 -
let SpinMe
= (
<div className="spinner-container">
<div className="loader">
<svg className="circular">
<circle className = "path"
cx = "50"
cy = "50"
r = "20"
fill = "none"
strokeWidth = "3"
strokeMiterLimit = "10"
/>
</svg>
</div>
</div>
);
此微调器组件应比其他组件 z-index
稍高,以便在加载时用户无法单击其他组件或与其他组件交互。
还在样式中显示微调器的一些透明深色背景。
例如
.spinner-container {
position : absolute;
background-color : rgba(12, 12, 12, 0.61);
width : 100%;
min-height : 100%;
background-size : 100% 100%;
text-align : center;
z-index : 3;
top : 0;
left : 0;
}
现在您要使用微调器的另一个组件,并在此组件中发出网络请求。
import React from 'react';
class SpinTestComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoading:false
};
}
sendNetworkRequest(URL, dataToSend) {
return $.ajax({
type : 'POST',
url : URL,
data : JSON.stringify(dataToSend),
dataType : 'json'
});
}
componentDidMount() {
const URL = "test url";
const dataToSend = { param1:"val", param2:"val" };
this.setState({isLoading:true});
this.sendNetworkRequest(dataToSend)
.then(
() => {
// success response now remove spinner
this.setState({isLoading:false});
},
() => {
// error response again remove spinner and
// show error message to end user
this.setState({isLoading:false});
});
}
render() {
return (
<div>
{ this.state.isLoading ? <SpinMe/> : null }
<div>
<h1>
Remaining Component structure
or Jsx
</h1>
<p>
To be show after loading is done.
</p>
</div>
</div>
);
}
}
export default SpinTestComponent;
您也可以尝试添加自定义 Relay Network Layer 以在每次请求时呈现加载指示器组件。我认为 "global" 加载指标要考虑的主要问题不仅在于指标的设计,还在于其对全球 UI 的影响。它会阻挡 UI 吗?只是其中的一部分?它会取代其他元素 up/down 吗?等等
在此期间,您可以执行以下操作:
handleSubmit(event) {
event.preventDefault();
this.setState({isLoading: true});
this.props.relay.commitUpdate(
new LoginMutation({
email: this.refs.email.value,
password: this.refs.password.value
}), {
onSuccess: response => {
Auth.login(response.login.accessToken);
const { location, router } = this.props;
if (location.state && location.state.nextPathname) {
router.replace(location.state.nextPathname)
} else {
router.replace('/')
}
},
onFailure: transaction => {
this.setState({hasError: true});
this.setState({isLoading: false});
}
}
);
}
以上片段摘自here。您可以在该存储库中看到其余的逻辑。
我最终扩展了 Relay 的默认网络层,这样我就可以创建一些我的主要根组件监听的 Flux 事件。如果我愿意,这使我可以将所有加载和错误消息处理放在一个地方。
这是我的最终解决方案。不确定它是否是最干净的,但它工作得很好。
import Relay from "react-relay";
import RelayMutationRequest from "react-relay/lib/RelayMutationRequest";
import AppDispatcher from "./AppDispatcher";
export default class CustomNetworkLayer extends Relay.DefaultNetworkLayer {
constructor(uri, init)
{
super(uri, init);
}
networkLoadingEvent()
{
AppDispatcher.dispatch({
actionType: 'LOADING'
});
}
networkDoneLoadingEvent()
{
AppDispatcher.dispatch({
actionType: 'DONE_LOADING'
});
}
networkErrorEvent(error)
{
AppDispatcher.dispatch({
actionType: 'ERROR',
payload: error
});
}
sendQueries(requests)
{
this.networkLoadingEvent();
return Promise.all(requests.map(request => (
this._sendQuery(request).then(
result => result.json()
).then(payload =>
{
if (payload.hasOwnProperty('errors')) {
const error = CustomNetworkLayer.createRequestError(request, '200', payload);
this.networkErrorEvent(payload.errors[0].message);
request.reject(error);
} else {
if (!payload.hasOwnProperty('data')) {
const error = new Error(
'Server response was missing for query ' +
`\`${request.getDebugName()}\`.`
);
this.networkErrorEvent(error);
request.reject(error);
} else {
this.networkDoneLoadingEvent();
request.resolve({response: payload.data});
}
}
}).catch(
error =>
{
this.networkErrorEvent(error);
request.reject(error)
}
)
)));
}
sendMutation(request)
{
this.networkLoadingEvent();
return this._sendMutation(request).then(
result => result.json()
).then(payload =>
{
if (payload.hasOwnProperty('errors')) {
const error = CustomNetworkLayer.createRequestError(request, '200', payload);
this.networkErrorEvent(payload.errors[0].message);
request.reject(error);
} else {
this.networkDoneLoadingEvent();
request.resolve({response: payload.data});
}
}).catch(
error =>
{
this.networkErrorEvent(error);
request.reject(error)
}
);
}
/**
* Formats an error response from GraphQL server request.
*/
static formatRequestErrors(request, errors)
{
const CONTEXT_BEFORE = 20;
const CONTEXT_LENGTH = 60;
const queryLines = request.getQueryString().split('\n');
return errors.map(({locations, message}, ii) =>
{
const prefix = (ii + 1) + '. ';
const indent = ' '.repeat(prefix.length);
//custom errors thrown in graphql-server may not have locations
const locationMessage = locations ?
('\n' + locations.map(({column, line}) =>
{
const queryLine = queryLines[line - 1];
const offset = Math.min(column - 1, CONTEXT_BEFORE);
return [
queryLine.substr(column - 1 - offset, CONTEXT_LENGTH),
' '.repeat(Math.max(0, offset)) + '^^^',
].map(messageLine => indent + messageLine).join('\n');
}).join('\n')) :
'';
return prefix + message + locationMessage;
}).join('\n');
}
static createRequestError(request, responseStatus, payload)
{
const requestType =
request instanceof RelayMutationRequest ? 'mutation' : 'query';
const errorReason = typeof payload === 'object' ?
CustomNetworkLayer.formatRequestErrors(request, payload.errors) :
`Server response had an error status: ${responseStatus}`;
const error = new Error(
`Server request for ${requestType} \`${request.getDebugName()}\` ` +
`failed for the following reasons:\n\n${errorReason}`
);
error.source = payload;
error.status = responseStatus;
return error;
}
}
然后在我的 index.js 文件中我这样做:
Relay.injectNetworkLayer(new CustomNetworkLayer("/graphql",
{
fetchTimeout: 35000, // timeout after 35 seconds
retryDelays: [2000] // retry after 2 seconds
}));
快速说明:AppDispatcher 只是一个 flux js 调度程序,我正在我的主要包装器组件中监听这些事件。
希望这对其他人有所帮助。我当然在这上面花了太多时间。
也感谢所有帮助我得出这个解决方案的人。