0

Goal:
For React TS.
The page List1 and List2 should use the same method named useFetch (retrieve data by using api link) by using generic approach by sending the interface (named Client and TheComputer) to the useFetch.

Each interface has different datamember.
You should enable to use useFetch by many and different interface's name.

In other words, UseFetch should be a independent tool that can be used by different interface by sending api link and interface asa parameter.

Problem:
You are enable to use react js to achieve it (without using syntax interface) but not for React TS.

I have problem to make useFetch as a independent component with react TS. How should it be solved?

Other info:
*It is achieved for ReactJS but not for ReactTS.
*Somehow it doesn't work in my local computer probably due to strictly linting and TS error.
You need to use interface to order to retrieve data and then display it.
*Newbie in ReactTS

Thank you!


Stackblitz:

JS
https://stackblitz.com/edit/react-mjvs38?

TS
https://stackblitz.com/edit/react-ts-7oeqen?


index.tsx

import React, { Component } from 'react';
import { render } from 'react-dom';
import {
  BrowserRouter as Router,
  Link,
  Route,
  Routes,
  useParams,
} from 'react-router-dom';
import './style.css';
import useFetch1 from './useFetchTS1';
import useFetch2 from './useFetchTS2';

function App() {
  return (
    <div>
      <h1>Home</h1>
    </div>
  );
}

function List1() {
  const { data, loading, error } = useFetch1('https://api.github.com/users');

  if (loading) {
    return <div>Loading</div>;
  }

  return (
    <div>
      {data.map((item) => (
        <div>
          <img src={item.avatar_url} />
          <div>{item.id}</div>
        </div>
      ))}
      ;
    </div>
  );
}

function List2() {
  const { data, loading, error } = useFetch2(
    'https://jsonplaceholder.typicode.com/todos'
  );

  if (loading) {
    return <div>Loading</div>;
  }

  return (
    <div>
      {data.map((item) => (
        <div>
          <div>
            Id: {item.id} Title: {item.title}
          </div>
        </div>
      ))}
      ;
    </div>
  );
}

render(
  <Router>
    <div>
      <header>
        <Link to="/">Home</Link>
        <br />
        <Link to="/list1">List1</Link>
        <br />
        <Link to="/list2">List2</Link>
        <br />
        <hr />
      </header>
      <Routes>
        <Route path="/" element={<App />} exact></Route>
        <Route path="/list1" element={<List1 />} exact></Route>
        <Route path="/list2" element={<List2 />}></Route>
      </Routes>
    </div>
  </Router>,
  document.getElementById('root')
);

useFetchTS1.tsx

import { useState, useEffect } from 'react';

interface Client {
  id: number;
  avatar_url: string;
}

export default function useFetch1(url) {
  const [data, setData] = useState<Client[]>([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    async function init() {
      //debugger;
      try {
        const response = await fetch(url);

        if (response.ok) {
          const json = await response.json();
          setData(json);
        } else {
          throw Response;
        }
      } catch (e) {
        setError(e);
      } finally {
        setLoading(false);
      }
    }

    init();
  }, [url]);

  return { data, error, loading };
}

useFetchTS2.tsx

import { useState, useEffect } from 'react';

interface TheComputer {
  id: number;
  title: string;
}

export default function useFetch2(url) {
  const [data, setData] = useState<TheComputer[]>([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    async function init() {
      //debugger;
      try {
        const response = await fetch(url);

        if (response.ok) {
          const json = await response.json();
          setData(json);
        } else {
          throw Response;
        }
      } catch (e) {
        setError(e);
      } finally {
        setLoading(false);
      }
    }

    init();
  }, [url]);

  return { data, error, loading };
}
HelloWorld1
  • 13,688
  • 28
  • 82
  • 145

2 Answers2

1

There is a design that used to be called the Service Agent pattern that may work well for you:

  • Use React in the standard way, with useEffect etc
  • Views simply get type safe data and update their model
  • Views know nothing about APIs and just ask the agent class
  • The agent class can express the API interface
  • A lower level fetch class can do plumbing in a shared way

For sonething to compare against, see if any of my code is useful:

In these examples:

  • CompaniesContainer is the view class
  • ApiFetch sends and receives any type of API payload, and does common tasks such as refreshing OAuth tokens
  • ApiClient ensures that views use only type safe requests and responses

You can adapt some of this into a React hook if you prefer. Personally though I prefer to limit React syntax to view logic, and use plain Typescript classes in other places. I can then use equivalent classes in other types of UI, such as mobile apps.

Gary Archer
  • 22,534
  • 2
  • 12
  • 24
0

So I believe we can get the useFetch hook to be generic for you if we change it to the following:

import { useEffect, useState } from 'react';

export default function useFetch1<TData = any>(url) {
  const [data, setData] = useState<TData[]>([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    async function init() {
      //debugger;
      try {
        const response = await fetch(url);

        if (response.ok) {
          const json = await response.json();
          setData(json);
        } else {
          throw Response;
        }
      } catch (e) {
        setError(e);
      } finally {
        setLoading(false);
      }
    }

    init();
  }, [url]);

  return { data, error, loading };

We use a generic <TData = any> in the function definition and TData[] in the useState hook for data.

Then in your index.tsx file you can define the interfaces there, and pass them to the generic useFetch1 hook like this:

useFetch1<Client>('https://api.github.com/users');

and

useFetch1<TheComputer>('https://jsonplaceholder.typicode.com/todos');

This lets you have the useFetch hook be generic, and still get the data returned to be the correct Interface/Type.

Your updated index.tsx file would look like this:

import './style.css';

import {
  Link,
  Route,
  BrowserRouter as Router,
  Routes,
  useParams,
} from 'react-router-dom';
import React, { Component } from 'react';

import { render } from 'react-dom';
import useFetch1 from './useFetchTS1';

interface Client {
  id: number;
  avatar_url: string;
}

interface TheComputer {
  id: number;
  title: string;
}

function App() {
  return (
    <div>
      <h1>Home</h1>
    </div>
  );
}

function List1() {
  const { data, loading, error } = useFetch1<Client>('https://api.github.com/users');

  if (loading) {
    return <div>Loading</div>;
  }

  return (
    <div>
      {data.map((item) => (
        <div>
          <img src={item.avatar_url} />
          <div>{item.id}</div>
        </div>
      ))}
      ;
    </div>
  );
}

function List2() {
  const { data, loading, error } = useFetch1<TheComputer>(
    'https://jsonplaceholder.typicode.com/todos'
  );

  if (loading) {
    return <div>Loading</div>;
  }

  return (
    <div>
      {data.map((item) => (
        <div>
          <div>
            Id: {item.id} Title: {item.title}
          </div>
        </div>
      ))}
      ;
    </div>
  );
}

render(
  <Router>
    <div>
      <header>
        <Link to="/">Home</Link>
        <br />
        <Link to="/list1">List1</Link>
        <br />
        <Link to="/list2">List2</Link>
        <br />
        <hr />
      </header>
      <Routes>
        <Route path="/" element={<App />} exact></Route>
        <Route path="/list1" element={<List1 />} exact></Route>
        <Route path="/list2" element={<List2 />}></Route>
      </Routes>
    </div>
  </Router>,
  document.getElementById('root')
);

This utilizes generic types: https://www.typescriptlang.org/docs/handbook/2/generics.html

I updated the stackblitz too and seems to be working: https://stackblitz.com/edit/react-ts-zasl3r?file=useFetchTS1.tsx

Daniel Wise
  • 58
  • 1
  • 7