Выполнение вызовов API из функционального компонента React

Я новичок в React и знаю о концепциях функциональных и классовых компонентов, а также о хуках, таких как useEffect и useState; следовательно, у меня возникли проблемы с применением этих знаний на практике, поскольку я работаю над следующим компонентом React, который выполняет простой вызов API-интерфейса Javascript, который теперь я хочу передать в реальный вызов API-интерфейса React, используя состояние и хуки.

Моя проблема заключается в следующем: я хочу отобразить объект-сотрудник, который возвращает API, начиная с имени и фамилии сотрудника, а затем дополнительной информации.

Запрос к API на простом Javascript проходит нормально и возвращает необходимые данные; следовательно, я не совсем уверен, что установить состояние по определению (0? false? что-то еще? И от чего это зависит, откуда я знаю?).

Вот код:

import React, {useEffect, useState} from 'react';
import { Link } from "react-router-dom";
import {
    Container,
    Row,
    Col,
    Card,
    CardBody,
    Table,
    Button, Alert, Modal, ModalHeader, ModalBody
} from "reactstrap";


const ContactsList = (props) => {

        let request = new XMLHttpRequest()

        // Open a new connection, using the GET request on the URL endpoint
        request.open('GET', 'https://somenet.net/employee', true)

        request.onload = function () {
            // Begin accessing JSON data here
            let data = JSON.parse(this.response)

           data.forEach((employee) => {
                // Log each movie's title
                console.log(employee.id, employee.firstname, employee.lastname, employee.performance_index, employee.min_customer_distance, employee.customer_distance_radius, employee.webfleet_obj_id, employee.default_employee_working_schedule_id)
            })
        }
        // Send request
        request.send()

        let employees = [


            {
                id: 1, img: "Null", name: "David McHenry", designation: "UI/UX Designer", email: "[email protected]", projects: "125",
                skills: [
                    { name: "Photoshop" },
                    { name: "illustrator" }
                ]
            }
        ]

       const users = [
            {
                id: 1, img: "Null", name: "David McHenry", designation: "UI/UX Designer", email: "[email protected]", projects: "125",
                skills: [
                    { name: "Photoshop" },
                    { name: "illustrator" }
                ]
            },
            {
                id: 2, img: avatar2, name: "Frank Kirk", designation: "Frontend Developer", email: "[email protected]", projects: "132",
                skills: [
                    { name: "Html" },
                    { name: "Css" },
                    { name: "2 + more" },
                ]
            },
            {
                id: 3, img: avatar3, name: "Rafael Morales", designation: "Backend Developer", email: "[email protected]", projects: "1112",
                skills: [
                    { name: "Php" },
                    { name: "Java" },
                    { name: "Python" },
                ]
            },
            {
                id: 4, img: "Null", name: "Mark Ellison", designation: "Full Stack Developer", email: "[email protected]", projects: "121",
                skills: [
                    { name: "Ruby" },
                    { name: "Php" },
                    { name: "2 + more" },
                ]
            },
            {
                id: 5, img: avatar4, name: "Minnie Walter", designation: "Frontend Developer", email: "[email protected]", projects: "145",
                skills: [
                    { name: "Html" },
                    { name: "Css" },
                    { name: "2 + more" },
                ]
            },
            {
                id: 6, img: avatar5, name: "Shirley Smith", designation: "UI/UX Designer", email: "[email protected]", projects: "136",
                skills: [
                    { name: "Photoshop" },
                    { name: "UI/UX Designer" }
                ]
            },
            {
                id: 7, img: "Null", name: "John Santiago", designation: "Full Stack Developer", email: "[email protected]", projects: "125",
                skills: [
                    { name: "Ruby" },
                    { name: "Php" },
                    { name: "2 + more" },
                ]
            },
            {
                id: 8, img: avatar7, name: "Colin Melton", designation: "Backend Developer", email: "[email protected]", projects: "136",
                skills: [
                    { name: "Php" },
                    { name: "Java" },
                    { name: "Python" },
                ]
            },
        ];

    const DefaultEvents = [{
        id: 1,
        title: 'Hey!',
        start: new Date().setDate(new Date().getDate() + 1),
        className: 'bg-warning text-white'
    },
        {
            id: 2,
            title: 'See John Deo',
            start: new Date(),
            end: new Date(),
            className: 'bg-success text-white'
        },
        {
            id: 3,
            title: 'Meet John Deo',
            start: new Date().setDate(new Date().getDate() + 8),
            className: 'bg-info text-white'
        },
        {
            id: 4,
            title: 'Buy a Theme',
            start: new Date().setDate(new Date().getDate() + 7),
            className: 'bg-primary text-white'
        }];

    const DefaultCategories = [
        {
            id: 1,
            title: 'New Theme Release',
            type: 'success'
        },
        {
            id: 2,
            title: 'My Event',
            type: 'info'
        },
        {
            id: 3,
            title: 'Meet Manager',
            type: 'warning'
        },
        {
            id: 4,
            title: 'Report Error',
            type: 'danger'
        },
    ];
    const event1= { id: 0, title: "", title_category: "", start: "", className: "", category: "", event_category: "" };
    const [calendarEvents, setCalendarEvents] = useState(DefaultEvents);
    const [categories, setCategories] = useState(DefaultCategories);
    const [modal, setModal] = useState(false);
    const [modal1, setModal1] = useState(false);
    const [modalcategory, setModalcategory] = useState(false);
    const [event, setEvent] = useState(event1);
    const [selectedDay, setSelectedDay] = useState(0);
    const title_category = false;

    const calendarComponentRef = React.createRef();
    

    useEffect(() => {
        new Draggable(document.getElementById("external-events"), {
            itemSelector: '.external-event',
        });
    });

    /**
     * Handling the modal state
     */
    function toggle() {
        setModal(!modal)
    }

    function toggle1() {
        setModal1(!modal1)
    }

    function togglecategory() {
        setModalcategory(!modalcategory)
    }

    /**
     * Handling date click on calendar
     */
    const handleDateClick = (arg) => {
        setSelectedDay(arg);
        toggle();
    }

    /**
     * Handling click on event on calendar
     */
    const handleEventClick = (arg) => {
        const eventNew = arg.event;

        const event_tmp = { id: eventNew.id, title: eventNew.title, title_category: eventNew.title_category, start: eventNew.start, className: eventNew.classNames, category: eventNew.classNames[0], event_category: eventNew.classNames[0] };

        setEvent(event_tmp);
        toggle1();
    }

    /**
     * Handling submit event on event form
     */
    const handleValidEventSubmit = (e, values) => {
        var newEvent = {};


        newEvent = {
            id: calendarEvents.length + 1,
            title: values['title'],
            start: selectedDay ? selectedDay.date : new Date(),
            className: values.category + ' text-white'
        };


        // save new event
        setCalendarEvents(calendarEvents.concat(newEvent));
        setSelectedDay(null);

        toggle();
    }

    const handleValidEventSubmitEvent = (e, values) => {
        var newEvent = {};
        newEvent = { id: event.id, title: values.title, classNames: values.category + ' text-white', start: event.start };
        //first, remove array item, which we want to edit
        let filteredArray = calendarEvents.filter(item => item.id + "" !== event.id + "");

        //then concat update item details
        let NewArray = filteredArray.concat(newEvent);

        //store to state
        setCalendarEvents(NewArray);
        setEvent(null);
        setSelectedDay(null);

        toggle1();
    }

    const handleValidEventSubmitcategory = (e, values) => {

        var newEvent = {};

        newEvent = {
            id: calendarEvents.length + 1,
            title: values['title_category'],
            type: values.event_category
        };
        // categories.concat(newEvent);
        setCategories(categories.concat(newEvent));

        togglecategory();
    }

    /**
     * On calendar drop event
     */
    const onDrop = (event) => {
        const draggedEl = event.draggedEl;

        var newEvent = {
            id: calendarEvents.length + 1,
            title: draggedEl.innerText,
            start: event.date,
            className: draggedEl.getAttribute('data-type') + ' text-white'
        };

        // save new event
        setCalendarEvents(calendarEvents.concat(newEvent));
    }


    return (
             <React.Fragment>
                <div className="page-content">
                    <Container fluid>

                        {/* Render Breadcrumbs */}
                        <Breadcrumbs title="Contacts" breadcrumbItem="Users List" />
                        <Card>
                            <CardBody>
                                <Row>
                                    <Col lg={3}>
                                        <Button color="primary" className="font-16 btn-block" onClick={() => togglecategory() }>
                                            <i className="mdi mdi-plus-circle-outline"></i> Create New Event
                                        </Button>

                                        <div id="external-events" className="mt-3">
                                            <p className="text-muted">Drag and drop your event or click in the calendar</p>

                                            {categories.map((category, i) => {
                                                return <Alert color={category.type}>{category.title} </Alert>
                                            })}
                                        </div>
                                        </Col>
                                    <Col className="col-lg-3">
                                        <div className="table-responsive">
                                            <Table className="table-centered table-nowrap table-hover">
                                                <thead className="thead-light">
                                                <tr>
                                                    <th scope="col" style={{ width: "70px" }}>#</th>
                                                    <th scope="col">Name</th>
                                                </tr>
                                                </thead>
                                                <tbody>
                                                {
                                                    employees.map((user, i) =>
                                                        <tr key={"_user_" + i} >
                                                            <td>
                                                                {
                                                                    user.img === "Null"
                                                                        ? <div className="avatar-xs">
                                                                                <span className="avatar-title rounded-circle">
                                                                                    {user.name.charAt(0)}
                                                                                </span>
                                                                        </div>
                                                                        : <div>
                                                                            <img className="rounded-circle avatar-xs" src={user.img} alt="" />
                                                                        </div>
                                                                }

                                                            </td>
                                                            <td>
                                                                <h5 className="font-size-14 mb-1"><Link to="#" className="text-dark">{user.name}</Link></h5>
                                                                <p className="text-muted mb-0">{user.designation}</p>
                                                            </td>
                                                        </tr>
                                                    )
                                                }
                                                </tbody>
                                            </Table>
                                        </div>

                                    </Col>
                                    <Col className="col-lg-6">
                                        {/* fullcalendar control */}
                                        <FullCalendar ref={calendarComponentRef} defaultView="dayGridMonth" plugins={[BootstrapTheme, dayGridPlugin, interactionPlugin]}
                                                      slotDuration={'00:15:00'}
                                                      minTime={'08:00:00'}
                                                      maxTime={'19:00:00'}
                                                      handleWindowResize={true}
                                                      themeSystem="bootstrap"
                                                      header={{
                                                          left: 'prev,next today',
                                                          center: 'title',
                                                          right: 'dayGridMonth,dayGridWeek,dayGridDay'
                                                      }}
                                                      events={calendarEvents}
                                                      editable={true}
                                                      droppable={true}
                                                      eventLimit={true}
                                                      selectable={true}
                                                      dateClick={handleDateClick}
                                                      eventClick={handleEventClick}
                                                      drop={onDrop}
                                                      id="calendar" />

                                        <button onClick={() => togglecategory() }
                                                className="btn btn-secondary float-right btn-lg waves-effect btn btn-secondary">
                                            Neuen Termin anlegen
                                        </button>
                                        {/* New event modal */}
                                        <Modal isOpen={modal} toggle={() => toggle()} className="">
                                            <ModalHeader toggle={() => toggle()} tag="h4">
                                                Add Event
                                            </ModalHeader>
                                            <ModalBody>
                                                <AvForm onValidSubmit={handleValidEventSubmit}>
                                                    <Row form>
                                                        <Col className="col-12">
                                                            <AvField name="title" label="Event Name" type="text" errorMessage="Invalid name" validate={{
                                                                required: { value: true }
                                                            }} value={event ? event.title : ''} />
                                                        </Col>
                                                        <Col className="col-12">
                                                            <AvField type="select" name="category" label="Select Category"
                                                                     value={event ? event.category : 'bg-primary'}>
                                                                <option value="bg-danger">Danger</option>
                                                                <option value="bg-success">Success</option>
                                                                <option value="bg-primary">Primary</option>
                                                                <option value="bg-info">Info</option>
                                                                <option value="bg-dark">Dark</option>
                                                                <option value="bg-warning">Warning</option>
                                                            </AvField>
                                                        </Col>
                                                    </Row>
                                                    <Row>
                                                        <Col>
                                                            <div className="text-right">
                                                                <button type="button" className="btn btn-light mr-2" onClick={() => toggle()}>Close</button>
                                                                <button type="submit" className="btn btn-success save-event">Save</button>
                                                            </div>
                                                        </Col>
                                                    </Row>
                                                </AvForm>
                                            </ModalBody>
                                        </Modal>

                                        {/* edit event modal */}
                                        <Modal isOpen={modal1} toggle={() => toggle1()} className="">
                                            <ModalHeader toggle={() => toggle1()} tag="h4">
                                                Edit Event
                                            </ModalHeader>
                                            <ModalBody>
                                                <AvForm onValidSubmit={handleValidEventSubmitEvent}>
                                                    <Row form>
                                                        <Col className="col-12">
                                                            <AvField name="title" label="Event Name" type="text" errorMessage="Invalid name" validate={{
                                                                required: { value: true }
                                                            }} value={event ? event.title : ''} />
                                                        </Col>
                                                        <Col className="col-12">
                                                            <AvField type="select" name="category" label="Select Category"
                                                                     value={event ? event.category : 'bg-primary'}>
                                                                <option value="bg-danger">Danger</option>
                                                                <option value="bg-success">Success</option>
                                                                <option value="bg-primary">Primary</option>
                                                                <option value="bg-info">Info</option>
                                                                <option value="bg-dark">Dark</option>
                                                                <option value="bg-warning">Warning</option>
                                                            </AvField>
                                                        </Col>
                                                    </Row>
                                                    <Row>
                                                        <Col>
                                                            <div className="text-right">
                                                                <button type="button" className="btn btn-light mr-2" onClick={() => toggle()}>Close</button>
                                                                <button type="submit" className="btn btn-success save-event">Save</button>
                                                            </div>
                                                        </Col>
                                                    </Row>
                                                </AvForm>
                                            </ModalBody>
                                        </Modal>

                                        <Modal isOpen={modalcategory} toggle={() => togglecategory()} className="">
                                            <ModalHeader toggle={() => togglecategory()} tag="h4">
                                                Add a category
                                            </ModalHeader>
                                            <ModalBody>
                                                <AvForm onValidSubmit={handleValidEventSubmitcategory}>
                                                    <Row form>
                                                        <Col className="col-12">
                                                            <AvField name="title_category" label="Category Name" type="text" errorMessage="Invalid name" validate={{
                                                                required: { value: true }
                                                            }} value={title_category ? event.title_category : ''} />
                                                        </Col>
                                                        <Col className="col-12">
                                                            <AvField type="select" name="event_category" label="Choose Category Color"
                                                                     value={event ? event.event_category : 'bg-primary'}>
                                                                <option value="bg-danger">Danger</option>
                                                                <option value="bg-success">Success</option>
                                                                <option value="bg-primary">Primary</option>
                                                                <option value="bg-info">Info</option>
                                                                <option value="bg-dark">Dark</option>
                                                                <option value="bg-warning">Warning</option>
                                                            </AvField>
                                                        </Col>
                                                    </Row>
                                                    <Row>
                                                        <Col>
                                                            <div className="text-right">
                                                                <button type="button" className="btn btn-light mr-2" onClick={() => togglecategory()}>Close</button>
                                                                <button type="submit" className="btn btn-success save-event">Save</button>
                                                            </div>
                                                        </Col>
                                                    </Row>
                                                </AvForm>
                                            </ModalBody>
                                        </Modal>
                                    </Col>
                                </Row>
                            </CardBody>
                        </Card>
                    </Container>
                </div>
            </React.Fragment>
          );
    }
        
export default ContactsList;

Будем очень признательны за любые подсказки или помощь - заранее спасибо!


person Die Webagenten    schedule 03.09.2020    source источник
comment
Было бы легче ответить на ваш вопрос, если бы он содержал код, который важен только для вашей проблемы.   -  person Christian    schedule 03.09.2020
comment
Это много нерелевантного кода. Скорее всего, вы получите лучшую обратную связь, если сузите предоставленный код до только соответствующих частей. Людям не нужно видеть вашу модальную реализацию, например, чтобы оставить отзыв о том, как правильно реализовать выборку данных.   -  person Patrick Roberts    schedule 03.09.2020
comment
Попробуйте react-query.tanstack.com/docs/examples/simple   -  person Ramesh    schedule 03.09.2020
comment
Может быть неактуальная тема - попробуйте сконцентрироваться и научитесь составлять приложение. В этом примере все находится в одном компоненте, что может быть не очень хорошо. Их можно разделить на отдельные компоненты.   -  person Ramesh    schedule 03.09.2020


Ответы (1)


Пара вещей:

  • Вы не должны получать данные в самой функции - это будет запускать выборку каждый раз, когда компонент отрисовывается. Как правило, вы хотите начать выборку при первой визуализации компонента:
useEffect(() => {
  fetchData().then(response => {
    const employees = JSON.parse(response)
    setEmployees(employees)
  })
}, [])

См. Также этот вопрос: ReactJS: как вызвать ловушку useEffect только один раз для получения данных API

Теперь, когда у нас есть правильный способ получения данных, мы переходим к другому вопросу - what to set the state to by definition. Другими словами, как инициализировать данные. Люди могут сказать вам, что это может быть что угодно, но на самом деле, если вы используете массив (вы повторяете объект сотрудников, поэтому я предполагаю, что это массив), просто хорошо инициализировать его пустым массивом . Таким образом, тип не меняется, и нижеприведенный JSX не требует дополнительной условной логики для обработки различных типов сотрудников.

const [employees, setEmployees] = useState([])

Обычно я сначала начинаю с вызовов useState, а затем вызываю useEffect. Не уверен, что он сломается, если вы поменяете их местами. Итак, общий код будет:


const fetchData = async () => {
    const res = await fetch('https://swapi.dev/api/people/')
    const json = await res.json()
    return json.result
}

const ContactsList = props => {
  const [employees, setEmployees] = useState([])

  useEffect(() => {
    fetchData().then(employees => {
      setEmployees(employees)
    })
  }, [])

  return (
    <div>
      {employees.map(employee => <div key={employee.id}>{employee.name}</div>)}
    </div>
  )
}

В более общем плане:

  • Ваш компонент довольно большой. Прелесть React в том, что вы можете разделить его на подкомпоненты. Это хорошая практика не только для вас, но и для других пользователей StackOverflow, которые могут понять ваш вопрос. Сохраняйте в компоненте только то, что действительно должно быть там.
person Sventies    schedule 03.09.2020
comment
setState() не обязательно запускает повторную визуализацию, если ссылка не изменилась. - person Patrick Roberts; 03.09.2020
comment
Я думал, что это то, для чего был React.memo () ... Очевидно, все еще изо всех сил пытался понять внутреннюю работу React. - person Sventies; 03.09.2020
comment
memo() только определяет, вызывают ли изменения реквизита повторный рендеринг, тогда как setState() - это другой механизм, который не использует реквизиты. В любом случае, теперь ваш ответ правильный, и я уже отразил это, проголосовав за - person Patrick Roberts; 03.09.2020
comment
@Sventies: Большое спасибо за ваши объяснения! Но функция fetchData все еще отсутствует, верно? Как это может выглядеть? Я реализовал это таким образом, но это дает мне ошибки: async function fetchData () {const res = await fetch (some.net / employee '); res .json () .then (res = ›setEmployees (res)) .catch (err =› setErrors (err)); } - person Die Webagenten; 03.09.2020
comment
@Sventies: ошибка заключается в следующем: необработанное отклонение (SyntaxError): неожиданный токен u в JSON в позиции 0 - person Die Webagenten; 03.09.2020
comment
@DieWebagenten Это может произойти, если API на самом деле не возвращает json. Если он возвращает undefined, парсер JSON может интерпретировать u of undefined как первый символ, тогда как в JSON это должен быть - person Sventies; 03.09.2020