Получать запросы на разных страницах с помощью React Hooks - PullRequest
1 голос
/ 13 октября 2019

Я создаю веб-сайт с использованием React Hooks, и у меня есть две разные страницы (Workshops.js и Shows.js), извлекающие данные из одного и того же API, только с разными параметрами (? Type = 0 и? Type = 1). Как только данные извлечены, я отображаю результаты (было бы неплохо иметь там компонент многократного использования .. см. Комментарии в коде ниже). Когда пользователь нажимает на шоу или семинар, он перенаправляется на ту же страницу.

Теперь код работает в единственном числе. Есть ли более элегантный способ избежать повторения одного и того же кода? ... что-то вроде Services in 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

1 Ответ

1 голос
/ 14 октября 2019

Таким образом, вы можете создать свой пользовательский хук :

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 был обновлен по какой-либо причине для того же компонента. Очистка в useEffect действительно важна, поэтому мы избегаем условий гонки.

Вместо флага hasBeenAborted мы могли бы использовать AbortController, но при этом мы все равно попали бы в ветку 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
...