【问题标题】:React JS proper way to set a loader UIReact JS 设置加载器 UI 的正确方法
【发布时间】:2020-04-18 09:01:56
【问题描述】:

我试图在 Code Sandbox 中复制我的本地项目,但我发现了我的错误。我在这些项目中尝试做的是在从 Express + GraphQL 服务器获取一些数据时显示加载器微调器,但是加载数据时加载器不会隐藏。

这是我获取数据的代码:

import React, { useEffect, useContext, useState } from "react";
import GlobalContext from "../../context/Global/context";
import gql from "graphql-tag";
import { useLazyQuery } from "@apollo/react-hooks";

const GET_USERS = gql`
  query {
    users {
      id
      name
      address {
        street
      }
    }
  }
`;

export default props => {
  const globalContext = useContext(GlobalContext);
  const [getUsers, { called, loading, data: users }] = useLazyQuery(GET_USERS);

  useEffect(() => {
    getUsers();
  }, []);

  useEffect(() => {
    console.log(globalContext.loading)
    if (globalContext.loading && users.length) {
      globalContext.setLoading(false);
    }
  }, [globalContext, users]);

  if (called && loading) {
    globalContext.setLoading(true);
  }

  if (!users) {
    return <p>There are no users</p>;
  }
  console.log(users)

  return (
    <table>
      <thead>
        <tr>
          <th>ID</th>
          <th>Name</th>
          <th>Street</th>
        </tr>
      </thead>
      <tbody>
        {users.map(user => {
          return (
            <tr>
              <td>{user.id}</td>
              <td>{user.name}</td>
              <td>{user.address.street}</td>
            </tr>
          );
        })}
      </tbody>
    </table>
  );
};

这是我设置加载器的代码:

import React, { useContext, useEffect } from "react";
import { ApolloClient } from "apollo-boost";
import { ApolloProvider } from "@apollo/react-hooks";
import { HttpLink } from "apollo-link-http";
import { InMemoryCache } from "apollo-cache-inmemory";
import Users from "./components/Users";
import GlobalContext from "./context/Global/context";
import Loader from "react-loader-spinner";

const cache = new InMemoryCache();
const link = new HttpLink({
  uri: "https://73rcp.sse.codesandbox.io/"
});

const client = new ApolloClient({
  link,
  cache
});

export default () => {
  const globalContext = useContext(GlobalContext);

  if (globalContext.loading) {
    return (
      <div
        style={{
          width: "100vw",
          height: "100vh",
          display: "flex",
          justifyContent: "center",
          alignItems: "center"
        }}
      >
        <Loader type="Puff" />
      </div>
    );
  }

  return (
    <ApolloProvider client={client}>
      <Users />
    </ApolloProvider>
  );
};

This is the frontend code

This is the backend code

我总是有同样的问题,不知道我做错了什么,但我想这是因为当我设置 globalContext.setLoading(true) 时,组件重新渲染并且第一个组件 App.js 加载之前比 用户组件。

如果这是否是错误,那么在从任何地方获取任何数据时设置加载器微调器的正确方法是什么?提前致谢。

【问题讨论】:

    标签: javascript reactjs express graphql


    【解决方案1】:

    所以经过一些调试,主要问题是Users 组件在从服务器接收数据之前被卸载。另外,我遇​​到了一些命名冲突。

    // App.js
    export default () => {
      const globalContext = useContext(GlobalContext);
      // This is making the Users component unmount which won't allow to change the
      // global loading state once the data is received, hence the perpetual loading.
      if (globalContext.loading) {
        return (
          <div
            style={{
              width: "100vw",
              height: "100vh",
              display: "flex",
              justifyContent: "center",
              alignItems: "center"
            }}
          >
            <Loader type="Puff" />
          </div>
        );
      }
    
      return (
        <ApolloProvider client={client}>
          <Users />
        </ApolloProvider>
      );
    };
    

    一个简单的解决方法是同时渲染 Loader 和 Users 组件,但是,前者将被绝对定位,因此用户无法与 Users 组件交互。

    export default () => {
      const globalContext = useContext(GlobalContext);
    
      return (
        <ApolloProvider client={client}>
          <Users />
          {globalContext.loading && (
            <div
              style={{
                width: "100vw",
                height: "100vh",
                display: "flex",
                position: "absolute",
                top: 0,
                backgroundColor: "white",
                justifyContent: "center",
                alignItems: "center"
              }}
            >
              <Loader type="Puff" />
            </div> 
          )}
        </ApolloProvider>
      );
    };
    

    Users 组件存在一些问题:

    // Users component
    export default props => {
      const globalContext = useContext(GlobalContext);
      // data: users will not return users object, instead it is only renaming it. 
      // if we were to destructure it like data: { users }, an error would be thrown since 
      // data.users is undefined. 
      const [getUsers, { called, loading, data: users }] = useLazyQuery(GET_USERS);
    
      useEffect(() => {
        getUsers();
      }, [getUsers]);
    
      useEffect(() => {
        // data does not contain a length property since it is an object. Again an 
        // error would be thrown. 
        if (globalContext.loading && users.length) {
          globalContext.setLoading(false);
        }
      }, [globalContext, users]);
    
      if (called && loading) {
        globalContext.setLoading(true);
      }
    
      if (!users) {
        return <p>There are no users</p>;
      }
    
      return (
        <table>
          <thead>
            <tr>
              <th>ID</th>
              <th>Name</th>
              <th>Street</th>
            </tr>
          </thead>
          <tbody>
            {/* Since data is not an array, map would be undefined (error again)*/}
            {users.map(user => {
              return (
                <tr>
                  <td>{user.id}</td>
                  <td>{user.name}</td>
                  <td>{user.address.street}</td>
                </tr>
              );
            })}
          </tbody>
        </table>
      );
    };
    

    所以要解决这个问题,我们有:

    export default props => {
      const globalContext = useContext(GlobalContext);
      const [getUsers, { called, loading, data }] = useLazyQuery(GET_USERS);
    
      useEffect(() => {
        getUsers();
      }, [getUsers]);
    
      useEffect(() => {
        if (globalContext.loading && data) {
          globalContext.setLoading(false);
        }
      }, [globalContext, data]);
    
      if (called && loading) {
        globalContext.setLoading(true);
      }
    
      if (!data) {
        return <p>There are no users</p>;
      }
    
      return (
        <table>
          <thead>
            <tr>
              <th>ID</th>
              <th>Name</th>
              <th>Street</th>
            </tr>
          </thead>
          <tbody>
            {data &&
              data.users.map(user => {
                return (
                  <tr>
                    <td>{user.id}</td>
                    <td>{user.name}</td>
                    <td>{user.address.street}</td>
                  </tr>
                );
              })}
          </tbody>
        </table>
      );
    };
    

    所有更改都在这个sandbox中。

    【讨论】:

    • 谢谢,在组件的渲染中插入加载器就成功了。
    【解决方案2】:

    在获取数据或从/向服务器发送一些数据时,有多种方法可以在 React 应用程序中实现加载程序。

    最好的方法是创建一个高阶组件来加载包装功能组件或基于布尔标志的加载器。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-03-16
      • 2020-07-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-05-11
      • 2013-09-22
      相关资源
      最近更新 更多