Как добавить индикатор загрузки для каждого Relay.js сетевой запрос

#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, и я слушаю эти события в моем основном компоненте оболочки.

надеюсь, это поможет кому-то еще. Я, конечно, потратил на это слишком много времени.

Также спасибо всем, кто помог мне прийти к этому решению.