#javascript #reactjs #networking #relayjs
#javascript #reactjs #сеть #relayjs
Вопрос:
В настоящее время я создаю веб-приложение Relay / React. Это было удивительно просто, за одним исключением. Я не смог понять, как я могу получить глобальное уведомление, когда какой-либо из моих компонентов выполняет сетевые запросы. Я собираюсь добавить счетчик в начало своего приложения, когда когда-либо будет сетевая активность, потому что некоторые из моих мутаций загружаются долго.
Это была моя попытка решить эту проблему, но она работает только при загрузке новой страницы маршрута.
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>
В идеале я мог бы получить некоторый обратный вызов, который я мог бы передать своему компоненту приложения, который отображает верхние и нижние колонтитулы всех моих страниц. Любая помощь в этом была бы весьма признательна. Некоторое время я рыскал по всему Интернету, пытаясь найти хорошее решение этой проблемы.
Ответ №1:
Вы также можете попробовать добавить пользовательский сетевой уровень ретрансляции, который отображает компонент индикатора загрузки при каждом запросе. Я думаю, что основная проблема, которую следует учитывать при «глобальном» индикаторе загрузки, касается не только дизайна индикатора, но и его влияния на пользовательский интерфейс в глобальном масштабе. Будет ли это блокировать пользовательский интерфейс?, только одну его часть?, будет ли он смещать другие элементы вверх / вниз? и т.д.
Тем временем вы можете сделать что-то вроде:
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 amp;amp; location.state.nextPathname) {
router.replace(location.state.nextPathname)
} else {
router.replace('/')
}
},
onFailure: transaction => {
this.setState({hasError: true});
this.setState({isLoading: false});
}
}
);
}
Приведенный выше фрагмент взят отсюда. Вы можете увидеть остальную логику в этом репозитории.
Ответ №2:
Вы можете создать пользовательский компонент счетчика в 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.
например
.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;
Ответ №3:
В итоге я расширил сетевой уровень Relay по умолчанию, чтобы я мог создавать некоторые события потока, которые прослушивает мой основной корневой компонент. Это позволяет мне иметь всю мою загрузку и обработку сообщений об ошибках в одном месте, если я того пожелаю.
Вот мое окончательное решение. Не уверен, что это самое чистое, что может быть, но работает довольно хорошо.
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:nn${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, и я слушаю эти события в моем основном компоненте оболочки.
надеюсь, это поможет кому-то еще. Я, конечно, потратил на это слишком много времени.
Также спасибо всем, кто помог мне прийти к этому решению.