3

I am new to React and I know about the concepts of Functional and Class components aswell as Hooks such as useEffect and useState; hence I am having troubles turning this knowledge into practice, as I am working on the following React component that makes a plain Javascript API Call which I now want to transfer into a "real" React API Call,using the State and Hooks.

My problem is the following: I want to render the employee-object that the API is returning, starting with the employee firstname and surname, then additional information.

The request to the API in plain Javascript goes well and returns the needed data; hence I am not really sure on what to set the state to by definition (0?false?something else?And on what does that depend on, how do I know?).

Here´s the code:

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: "david@skote.com", projects: "125",
                skills: [
                    { name: "Photoshop" },
                    { name: "illustrator" }
                ]
            }
        ]

       const users = [
            {
                id: 1, img: "Null", name: "David McHenry", designation: "UI/UX Designer", email: "david@skote.com", projects: "125",
                skills: [
                    { name: "Photoshop" },
                    { name: "illustrator" }
                ]
            },
            {
                id: 2, img: avatar2, name: "Frank Kirk", designation: "Frontend Developer", email: "frank@skote.com", projects: "132",
                skills: [
                    { name: "Html" },
                    { name: "Css" },
                    { name: "2 + more" },
                ]
            },
            {
                id: 3, img: avatar3, name: "Rafael Morales", designation: "Backend Developer", email: "Rafael@skote.com", projects: "1112",
                skills: [
                    { name: "Php" },
                    { name: "Java" },
                    { name: "Python" },
                ]
            },
            {
                id: 4, img: "Null", name: "Mark Ellison", designation: "Full Stack Developer", email: "mark@skote.com", projects: "121",
                skills: [
                    { name: "Ruby" },
                    { name: "Php" },
                    { name: "2 + more" },
                ]
            },
            {
                id: 5, img: avatar4, name: "Minnie Walter", designation: "Frontend Developer", email: "minnie@skote.com", projects: "145",
                skills: [
                    { name: "Html" },
                    { name: "Css" },
                    { name: "2 + more" },
                ]
            },
            {
                id: 6, img: avatar5, name: "Shirley Smith", designation: "UI/UX Designer", email: "shirley@skote.com", projects: "136",
                skills: [
                    { name: "Photoshop" },
                    { name: "UI/UX Designer" }
                ]
            },
            {
                id: 7, img: "Null", name: "John Santiago", designation: "Full Stack Developer", email: "john@skote.com", projects: "125",
                skills: [
                    { name: "Ruby" },
                    { name: "Php" },
                    { name: "2 + more" },
                ]
            },
            {
                id: 8, img: avatar7, name: "Colin Melton", designation: "Backend Developer", email: "colin@skote.com", 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;

Any hints or help would be very much appreciated - thank you in advance!

DWA
  • 530
  • 1
  • 5
  • 29
  • 3
    It would be easier to answer your question if it contained the code that is essential to your problem only. – Christian Sep 03 '20 at 07:47
  • 3
    That's a lot of irrelevant code. You'll likely get better feedback if you narrow down the provided code to only relevant parts. People don't need to see your modal implementation, for example, in order to give feedback about how to properly implement your data fetching. – Patrick Roberts Sep 03 '20 at 07:48
  • 1
    Try https://react-query.tanstack.com/docs/examples/simple – Ramesh Sep 03 '20 at 07:48
  • 2
    May be irrelevant topic - Try to focus and learn how to compose an application. In this example, all the stuff is in one single component which may not be good. They can be split into their own component. – Ramesh Sep 03 '20 at 07:52

1 Answers1

10

A couple things:

  • You shouldn't fetch data in the function itself - this will trigger a fetch each time the component renders. Typically, you want to start the fetch when the component rendered for the first time:
useEffect(() => {
  fetchData().then(response => {
    const employees = JSON.parse(response)
    setEmployees(employees)
  })
}, [])

See also this question: ReactJS: how to call useEffect hook only once to fetch API data

Now that we have a proper way to fetch data, we get to the other question - what to set the state to by definition. In other words, how to initialize the data. People may tell you that it can be anything, but really, if you're using an array (you're iterating of the employees object so I'm assuming it is an array), it's just good to initialize it with an empty array. This way the type doesn't change, and the JSX down below doesn't need additional conditional logic to handle different types of employees.

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

I usually start with the useState calls first, and then call useEffect. Not sure if it breaks if you swap them around, actually. So the total code would be:


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>
  )
}

On a more general note:

  • Your component is quite big. The beauty of React is that you can divide it into subcomponent. This is not only good practice for yourself, but also easier for other StackOverflow users to understand your question. Only keep in the component what really needs to be there.
Sventies
  • 2,314
  • 1
  • 28
  • 44
  • 1
    `setState()` doesn't necessarily trigger a re-render if the reference hasn't changed. – Patrick Roberts Sep 03 '20 at 08:20
  • I thought that was what React.memo() was for... Obviously still struggling to understand the inner workings of React. – Sventies Sep 03 '20 at 08:21
  • 1
    `memo()` only determines whether props changes trigger a re-render, whereas `setState()` is a different mechanism that doesn't use props. In any case, your answer is correct now, and I've already reflected that with an upvote – Patrick Roberts Sep 03 '20 at 08:23
  • @Sventies: Thanks a lot for your explanations! But the fetchData Function is still missing right? How might that look like? I implemented it this way, but that gives me errors: async function fetchData() { const res = await fetch("https://some.net/employee'"); res .json() .then(res => setEmployees(res)) .catch(err => setErrors(err)); } – DWA Sep 03 '20 at 08:56
  • @Sventies: The error is the following: Unhandled Rejection (SyntaxError): Unexpected token u in JSON at position 0 – DWA Sep 03 '20 at 08:58
  • 1
    @DieWebagenten That can happen if the API doesn't actually return json. If it returns undefined, a JSON parser may interpret the "u" of undefined as the first character, while in JSON it should be a " – Sventies Sep 03 '20 at 18:25