1

I made a server with following REST JSON API:

  • Get list of classrooms /api/v1/room
  • Get dates when certain classroom is booked /api/v1/room/[roomId]
  • Get list of teachers /api/v1/person
  • Book(Post) a date in shedule for certain classroom by certain teacher /api/v1/person/[personId]/room/[roomId]/date/[date]
  • Delete(Post) a booking of certain classroom on certain date /api/v1/room/[roomId]/date/[date]

Data about classrooms, schedule, and teachers taken from JSON files

const express = require('express');
const fs = require('fs');
const path = require('path');
const app = express();
const cors = require('cors');
const port = 3001;


function readJsonFile(filename) {
  try {
    const data = fs.readFileSync(path.join(__dirname, filename), 'utf8');
    return JSON.parse(data);
  } catch (err) {
    console.error(`Ошибка чтения файла ${filename}:`, err);
    return { reservation: {"person" : []} };
  }
}

const rooms = readJsonFile('room.json');
const persons = readJsonFile('person.json');
let bookings = readJsonFile('bookings.json');


app.use(cors({
  origin: `http://localhost:3000`
}))

// GET /api/v1/room
app.get('/api/v1/room', (req, res) => {
  res.json(rooms);
});

// GET /api/v1/room/[roomId]
app.get('/api/v1/room/:roomId', (req, res) => {
  const roomId = parseInt(req.params.roomId);
  const roomBookings = bookings.reservation.filter(booking => booking.roomId === roomId);
  res.json(roomBookings.map(booking => booking.date));
});

// GET /api/v1/person
app.get('/api/v1/person', (req, res) => {
  res.json(persons);
});

// POST /api/v1/person/[personId]/room/[roomId]/date/[date]
app.post('/api/v1/person/:personId/room/:roomId/date/:date', (req, res) => {
  const personId = parseInt(req.params.personId);
  const roomId = parseInt(req.params.roomId);
  const date = req.params.date;

  const person = persons.person.find(p => p.id === personId);
  const room = rooms.room.find(r => r.id === roomId);

  if (!person || !room) {
    return res.status(404).json({ error: 'Человек или комната не найдены' });
  }


  if (bookings.reservation.find(b => b.roomId === roomId && b.date === date)) {
    return res.status(400).json({ error: 'Комната уже забронирована на эту дату' });
  }

  bookings.reservation.push({ userId: personId, roomId, date });

  fs.writeFileSync(path.join(__dirname, 'bookings.json'), JSON.stringify(bookings, null, 2));

  res.status(201).json({ message: 'Бронь успешно создана' });
});

// POST /api/v1/room/[roomId]/date/[date]
app.post('/api/v1/room/:roomId/date/:date', (req, res) => {
  const roomId = parseInt(req.params.roomId);
  const date = req.params.date;

  bookings.reservation = bookings.reservation.filter(booking => !(booking.roomId === roomId && booking.date === date));

  fs.writeFileSync(path.join(__dirname, 'bookings.json'), JSON.stringify(bookings, null, 2));

  res.status(204).send();
});


app.listen(port, () => {
  console.log(`Сервер запущен на порту ${port}`);
});

The server is working. Then I tried to make web service for working with that API. The service must provide (a) the possibility of getting a list of all classrooms and bookings for certain classroom, and (b) the possibility of creating/deleting (by certain teacher) a certain booking.

import React, { useState, useEffect } from "react";
import "./App.css";
import Dropdown from "./Dropdown"; 
import { Calendar, momentLocalizer } from "react-big-calendar";
import moment from "moment";
import "react-big-calendar/lib/css/react-big-calendar.css";

const localizer = momentLocalizer(moment);

const API_URL = "http://localhost:3001/api/v1"; 

function App() {
  const [isShown, setIsShown] = useState(false);
  const [selectedRoomId, setSelectedRoomId] = useState(null);
  const [events, setEvents] = useState([]);
  const [isDeleting, setIsDeleting] = useState(false);
  const [selectedEvent, setSelectedEvent] = useState(null);
  const [roomData, setRoomData] = useState([]);
  const [bookingData, setBookingData] = useState([]);
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    fetchRooms().then(() => setIsLoading(false));
  }, []);

  useEffect(() => {
    fetchBookings();
  }, [selectedRoomId]);

  useEffect(() => {
    if (selectedRoomId) {
      const filteredBookings = bookingData.filter(
        (booking) => booking.roomId === selectedRoomId
      );
      const calendarEvents = filteredBookings.map((booking) => ({
        start: moment(booking.date, "DD.MM.YYYY").toDate(),
        end: moment(booking.date, "DD.MM.YYYY").add(1, "day").toDate(),
        title: `booked`,
        id: booking.userId,
        roomId: booking.roomId
      }));
      setEvents(calendarEvents);
    }
  }, [selectedRoomId, bookingData]);

  const handleClick = (event) => {
    setIsShown((current) => !current);
  };

  const handleRoomChange = (event) => {
    setSelectedRoomId(parseInt(event.target.value));
    if (roomData.length > 0) { 
      fetchBookings(); 
    }
  };

  const handleEventClick = (event) => {
    setSelectedEvent(event);
    setIsDeleting(true);
  };

  const handleDeleteConfirm = () => {
    deleteBooking(selectedEvent.id, selectedEvent.roomId, selectedEvent.start);
    setIsDeleting(false);
  };

  const handleDeleteCancel = () => {
    setIsDeleting(false);
  };

  const fetchRooms = async () => {
    try {
      const response = await fetch(`${API_URL}/room`);
      const data = await response.json();
      setRoomData(data);
    } catch (error) {
      console.error("Classrom info loading error:", error);
    }
  };

  const fetchBookings = async () => {
    try {
      const response = await fetch(`${API_URL}/room/${selectedRoomId}`);
      const data = await response.json();
      setBookingData(data);
    } catch (error) {
      console.error("Shedule info loading error:", error);
    }
  };


  const deleteBooking = async (userId, roomId, date) => {
    try {
      const response = await fetch(`${API_URL}/room/${roomId}/date/${date}`, {
        method: "POST",
      });

      if (response.ok) {
        fetchBookings();
      } else {
        console.error("Room booking deleting error.");
      }
    } catch (error) {
      console.error("Room booking deleting error:", error);
    }
  };

  return (
    <div className="App">
      <button className="myButton classListButton" onClick={handleClick}>
        Classroom list
      </button>
      {isShown && (
        <div className="classList">
          {isLoading ? (
            <p>Classroom info loading...</p>
          ) : (
            <Dropdown
              data={roomData}
              dataKey="room"
              onChange={handleRoomChange}
              selectedRoomId={selectedRoomId}
              isLoading={isLoading} 
            />
          )}
          <Calendar
            localizer={localizer}
            events={events}
            startAccessor="start"
            endAccessor="end"
            onSelectEvent={handleEventClick}
            className="cal"
          />
          {isDeleting && (
            <div className="delete-confirmation">
              <p>you sure you want to delete booking?</p>
              <button onClick={handleDeleteConfirm}>Yes</button>
              <button onClick={handleDeleteCancel}>No</button>
            </div>
          )}
        </div>
      )}
      <button className="myButton teacherListButton">Create booking</button>
    </div>
  );
}

export default App;

Dropdown is drop-down list

import React, { useState, useEffect } from 'react';

const Dropdown = ({ data, dataKey, onChange, selectedRoomId, isLoading }) => {
  const [options, setOptions] = useState([]);

  useEffect(() => {
    if (data && data.length > 0) {
      setOptions(data.map((item) => ({
        value: item.id,
        label: item[dataKey],
      })));
    }
  }, [data, dataKey]);

  return (
    <select value={selectedRoomId} onChange={onChange} disabled={isLoading}>
      <option value="">Choose classroom</option>
      {options.map((option) => (
        <option key={option.value} value={option.value}>
          {option.label}
        </option>
      ))}
    </select>
  );
};

export default Dropdown;

The interface consists out of two buttons, "Classroom list" and "create booking." When the first button is pressed, a drop-down list appears where you choose a classroom, and a react big calendar appears where all bookings for the chosen classroom are shown. You can press on a certain date and if there is booking, you will be offered to delete the booking. The "Create booking" button is just a button.

So here's the problem. When I press the first button, it shows a drop-down list and a calendar, but there are no classrooms in the drop-down list. The web console shows this error:

Warning: `value` prop on `select` should not be null. Consider using an empty string to clear the component or `undefined` for uncontrolled components.
    at select
    at Dropdown (http://localhost:3000/static/js/bundle.js:276:3)
    at div
    at div
    at App (http://localhost:3000/static/js/bundle.js:42:80)

I tried consulting a chat gpt, tried to find a problem myself, but it didn't get me far

2
  • If you are getting that error message, it means selectedRoomId was null. Try looking through the locations where you call setSelectedRoomId to see why it got set to null.
    – nullromo
    Commented Jul 9 at 22:17
  • What does the data from room.json look like? In the Dropdown component it's redundant to have the options state. Loop over data. It will remove one render cycle. Commented Jul 10 at 10:17

1 Answer 1

0

You initialized your selectedRoomId state variable to null.

const [selectedRoomId, setSelectedRoomId] = useState(null);

Then you passed that into the <Dropdown> component.

<Dropdown
    // ...
    selectedRoomId={selectedRoomId}
    // ...
/>

Then you passed that variable into the value prop of the <select>.

<select value={selectedRoomId} />

But it was null, and the <select> element does not want null for its value prop.

You can fix this by changing the assignment.

<select value={selectedRoomId ?? ""} />

This way, if selectedRoomId is null, it will set the value of the <select> element to "" instead.

2
  • thanks a lot for help, but there is another problem now. I pass into Dropdown element a list of date before i got it. because of that drop-down list shows nothing and i have no idea how to fix that
    – Liron
    Commented Jul 9 at 22:34
  • Sorry, I'm not sure what you mean. I don't see any dates passed into the <Dropdown> component. Perhaps you can provide more details by editing the question.
    – nullromo
    Commented Jul 10 at 23:50

Not the answer you're looking for? Browse other questions tagged or ask your own question.