简体   繁体   中英

How to call a react hook fetch request in a functional component to access data then pass to a class component to map?

After a huge amount of trial and error for a complex webGL project I have landed on a solution that will reduce the amount of re-engineering working, threejs code (from another developer) and, as this project is extremely time restrained, reduce the amount of time needed. It's also worth noting my experience of this is limited and I am the only developer left on the team.

The project current accepts a large array of random user data, which is exported from a js file and then consumed here...

import Users from "./data/data-users";

class UsersManager {
  constructor() {
    this.mapUserCountries = {};
  }

  init() {
    Users.forEach(user => {
      const c = user.country;

      if (!this.mapUserCountries[c])
        this.mapUserCountries[c] = { nbUsers: 0, users: [] };
      this.mapUserCountries[c].nbUsers++;
      this.mapUserCountries[c].users.push(user);
    });
  }

  getUsersPerCountry(country) {
    return this.mapUserCountries[country];
  }
}

export default new UsersManager();

Here is my fetch request..

import { useState, useEffect } from "react";

const FetchUsers = () => {
  const [hasError, setErrors] = useState(false);
  const [users, setUsers] = useState({});

  async function fetchData() {
    const res = await fetch(
      "https://swapi.co/api/planets/4/"
    );
    res
      .json()
      .then(res => setUsers(res))
      .catch(err => setErrors(err));
  }

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

  return JSON.stringify(users);
};

export default FetchUsers;

I have run into lots of issues as the UserManager is a class component and if I import my fetchUsers into this file, call it and save it to a variable like so const Users = fetchUsers(); it violates hooks.

I want to be able to return a function that will return my users from the database as an array.

That will then be able to be passed into the UserManager in the same way the hard coded data is and mapped over to be actioned by LOTS of other files.

I've mocked up a small codesandbox with what the flow would be ideally but I know I need a solution outside of hooks...

https://codesandbox.io/s/funny-borg-u2yl6

thanks

--- EDIT ---

import usersP from "./data/data-users";

class UsersManager {
  constructor() {
    this.mapUserCountries = {};
    this.state = {
      users: undefined
    };
  }

  init() {
    usersP.then(users => {
      this.setState({ users });
    });
    console.log(usersP);
    this.state.users.forEach(user => {
      const c = user.country;
      if (!this.mapUserCountries[c])
        this.mapUserCountries[c] = { nbUsers: 0, users: [] };
      this.mapUserCountries[c].nbUsers++;
      this.mapUserCountries[c].users.push(user);
    });
  }

  getUsersPerCountry(country) {
    return this.mapUserCountries[country];
  }
}

export default new UsersManager();
console.log (UsersManager.js:16 Uncaught TypeError: Cannot read property 'forEach' of undefined
    at UsersManager.init (UsersManager.js:16)
    at Loader.SceneApp.onLoadingComplete [as callback] (App.js:39)
    at Loader.onAssetLoaded (index.js:20)
    at index.js:36
    at three.module.js:36226
    at HTMLImageElement.onImageLoad)

There are a couple guidelines that will help separate functions that are Hooks and functions that are Components (these are true most of the time):

1 Component functions use pascal case (start with a capital letter) and always return JSX.

2 Custom Hooks functions conventionally begin with the word "use" and never return JSX.

In your case you probably want to make a custom Hooks function that must be called in a component;

function useUserData() {
  const [hasError, setErrors] = useState(false);
  const [users, setUsers] = useState({});

  const networkCall = useCallback(async fetchData = () => {
    const res = await fetch(
      "https://swapi.co/api/planets/4/"
    );
    res
      .json()
      .then(res => setUsers(res))
      .catch(err => setErrors(err));
  } , [])

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

  return {users, hasError};
}

Then call that custom hook in one of your components:

function App() {
  const {users, hasError} = useUserData();

    return (
      <div className="App">
        <h1>Hello CodeSandbox</h1>
        <div>{users}</div>
        <h2>Start editing to see some magic happen!</h2>
      </div>
    );
  }
}

If you then need to share that fetched data throughout your app, you can pass it down via props or the context API: https://reactjs.org/docs/context.html (post a message if you'd like an example of this).

I fixed your sandbox example .

You cannot load the users synchronously (using import ) as you need to make a http call to fetch the users so it's asynchronous .

As a result you can fetch the users inside the componentDidMount lifecycle method and use a state variable to store them once they are fetched

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM