使用 React Hooks 在不同的页面中获取请求

Fetch Requests in different pages with React Hooks

我正在使用 React Hooks 构建一个网站,我有两个不同的页面(Workshops.js 和 Shows.js)从同一个 API 获取数据,只是参数不同(?type=0 和?type=1)。 获取数据后,我将映射结果(如果那里有一个可重用的组件会很好......请参阅下面代码中的注释)。当用户点击节目或研讨会时,他将被重定向到同一页面。

现在代码可以正常工作了。 有没有更优雅的方法来避免重复相同的代码? ...类似 Angular?

中的服务

谢谢!

这里是Workshop.js。

import React, { useState, useEffect } from 'react';
import { Link } from 'react-router-dom'
import api from '../../maps/Api' 

const Workshops = () => {
    const [ workshops, setWorkshop ] = useState([])
    const [ isLoading, setIsLoading ] = useState(false)
    const [ error, setError ] = useState(null)
    const GET_URL = api.get.workshops /* http://someapi/workshops?type=0 */

    useEffect(() => {
        setIsLoading(true)
        fetch(GET_URL, {headers: {
            "Accept": "application/json",
            "Access-Control-Allow-Origin": "*"
        }})
        .then(res => {
            return (res.ok) ?  res.json() : new Error("Mistake!")
        })
        .then(workshops => {
            if(workshops.upcoming) {
                setWorkshop(workshops.upcoming);
            }
            setIsLoading(false);
        })
        .catch(error => {
            setError(error)
        })
    }, [GET_URL])

    if ( error ){ return <p>{ error.message }</p> }
    if ( isLoading ){
        return <p>Loading workshops...</p>
    }

    return(

        <main>
            <div className='content'>
                <div className='contentCol'>
                    <ul id='workshopBox'>
                    {
                        workshops.map( (workshop, i) => (
                            <li> // FROM HERE...
                                <div
                                    className='workshop-active'>
                                    <h2>{ workshop.title }</h2>
                                    <p>{ workshop.description }</p>
                                    <p>{ workshop.place }</p>
                                    <p>{ (new Date(workshop.date).toLocaleDateString("it-IT", {
                                            weekday: 'long',
                                            year: 'numeric',
                                            month: 'long',
                                            day: 'numeric'
                                          }))}</p>
                                    <p>{ (new Date(workshop.date).toLocaleTimeString("it-IT", {
                                            hour: '2-digit',
                                            minute: '2-digit',
                                            hour12: true
                                          }))}</p>
                                    <p> Full price { workshop.price_full + ', 00'} &euro; </p>
                                    <p> Early bird price { workshop.price_earlybirds + ', 00'} &euro; </p>
                                    <p>
                                    <Link to={`/workshops/${ workshop.id}`}>
                                        <button>Details</button>
                                    </Link>
                                    </p>
                                    <br/>
                                </div>
                            </li> //..to HERE I WOULD LIKE TO USE A REUSABLE COMPONENT
                            ))
                    }
                    </ul>
                </div>
            </div>
        </main>
        )
    }

export default Workshops

这里是 Shows.js

import React, { useState, useEffect } from 'react';
//import { Link } from 'react-router-dom'
import api from '../maps/Api'

const Spettacoli = () => {
    const [ shows, setShows ] = useState([])
    const [ isLoading, setIsLoading ] = useState(false)
    const [ error, setError ] = useState(null)
    const GET_URL = api.get.shows /* http://someapi/workshops?type=1 */

    useEffect(() => {
        setIsLoading(true)
        fetch(GET_URL, {headers: {
            "Accept": "application/json",
            "Access-Control-Allow-Origin": "*"
        }})
        .then(res => {
            return (res.ok) ?  res.json() : new Error("Mistake!")
        })
        .then(shows => {
            setShows(shows)
            setIsLoading(false)
        })
        .catch(error => {
            setError(error)
        })
    }, [GET_URL])


    return(
        <main>
            <div className='content'>
                <div className='contentCol'>
                    /* SAME INTERFACE AS WORKSHOP */

                </div>
            </div>
        </main>
        )
}

export default Shows

因此您可以创建 custom hook:

function useMyDataFetch(GET_URL) {
    const [ data, setData ] = useState([])
    const [ isLoading, setIsLoading ] = useState(true)
    const [ error, setError ] = useState(null)

    useEffect(() => {
        let hasBeenAborted = false; // added
        setIsLoading(true)
        fetch(GET_URL, {headers: {
            "Accept": "application/json",
            "Access-Control-Allow-Origin": "*"
        }})
        .then(res => {
            return (res.ok) ?  res.json() : new Error("Mistake!")
        })
        .then(data => {
            if (hasBeenAborted) return; // added
            if(data.upcoming) {
                setData(data.upcoming);
            }
            setIsLoading(false);
        })
        .catch(error => {
            if (hasBeenAborted) return; // added
            setIsLoading(false); // added
            setError(error)
        });
        return () => { hasBeenAborted = true; } // added
    }, [GET_URL]);

    return { data, error, isLoading };
}

并在您的组件中使用它。

注意我用 // added 标记的行。 hasBeenAborted 允许我们在 GET_URL 由于任何原因对同一组件进行更新时做出反应。 Cleanup in useEffect 非常重要,因此我们避免了竞争条件。

我们可以使用 AbortController 而不是 hasBeenAborted 标志,但是这样我们仍然会落入 catch 分支并且需要额外的 if 来区分请求是否已被取消或实际上失败了。所以对我来说只是品味问题。

至于你的组件,他们将使用这样的钩子:

const Workshops = () => {
    const {isLoading, error, data: workshops} = useMyDataFetch(api.get.workshops);

    if ( error ){ return <p>{ error.message }</p> }
    if ( isLoading ){
        return <p>Loading workshops...</p>
    }

    return(
      // the same here
    );

}

export default Workshops