So I’m having this project and it kills me with the way the instructions unfold step by step.
At first they asked me to create a BookingForm.js component that includes a form, for my app.
They asked me to use "useEffect" hook to manage the state in the component, and so did I.
Right after, they asked me to lift the state of specific element to another component, and pass the state down as props, to my BookingForm.js component.
And since I’m inexperienced with hooks and useReducer, I’m struggling so much to find the solution.
I feel I could make it work on my own, if it wasn’t for this shift from useState to useReducer, because I don’t know what to keep and what to change now.
This is my Main.js component that imports the BookingForm.js component and renders it.
import React, {useReducer} from 'react';
import ReactLogo from '../bruchetta.svg';
import image1 from '../greeksalad.jpg';
import image3 from '../lemondessert.jpg';
import image4 from '../restauranfood.jpg';
import image5 from '../customer1.jpg';
import image6 from '../customer2.jpg';
import image7 from '../customer3.jpg';
import image8 from '../customer4.jpg';
import image9 from '../restaurant.jpg';
import image10 from '../mario-and-adrian.jpg';
import { faStar } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Link } from 'react-router-dom';
import BookingForm from './BookingForm';
function Main() {
function initializeTimes() {
return [
'17:00',
'18:00',
'19:00',
'20:00',
'21:00',
'22:00'
];
}
const [availableTimes, dispatch] = useReducer(updateTimes, initializeTimes());
const showBookingForm = false;
function updateTimes(state, action) {
switch (action.type) {
case 'update_times':
return action.availableTimes;
default:
return state;
}
}
return (
<div>
{showBookingForm && (<BookingForm availableTimes={availableTimes} dispatch={dispatch}/>)}
...rest of my Main code...
</div>
);
}
export default Main;
And this is my BookingForm.js component:
import React, { useState } from 'react';
function BookingForm({ availableTimes = [], dispatch }) {
const [date, setDate] = useState('');
const [time, setTime] = useState('17:00');
const [guests, setGuests] = useState(1);
const [occasion, setOccasion] = useState('Birthday');
const handleFormSubmit = (event) => {
event.preventDefault();
// Handle form submission logic here
// e.g., display form data or make an API call
console.log('Form submitted:', { date, time, guests, occasion });
};
const handleDateChange = (e) => {
const selectedDate = e.target.value;
setDate(selectedDate);
dispatch({type: "update_times"}); // Dispatch the selected date to update available times
};
return (
<form style={{ display: 'grid', maxWidth: '200px', gap: '20px' }} onSubmit={handleFormSubmit}>
<label htmlFor="res-date">Choose date</label>
<input type="date" id="res-date" value={date} onChange={handleDateChange} />
<label htmlFor="res-time">Choose time</label>
<select id="res-time" value={time} onChange={(e) => setTime(e.target.value)}>
{availableTimes.map((availableTime) => (
<option key={availableTime} value={availableTime}>
{availableTime}
</option>
))}
</select>
<label htmlFor="guests">Number of guests</label>
<input type="number" placeholder="1" min="1" max="10" id="guests" value={guests} onChange={(e) => setGuests(e.target.value)} />
<label htmlFor="occasion">Occasion</label>
<select id="occasion" value={occasion} onChange={(e) => setOccasion(e.target.value)}>
<option value="Birthday">Birthday</option>
<option value="Anniversary">Anniversary</option>
</select>
<input type="submit" value="Make Your reservation" />
</form>
);
}
export default BookingForm;
At first when the website loads, I initialize the ‘showBookingForm’ = ‘false’ in Main.js, so that this component will not be displayed, and will only appear when the user clicks on any of the respective links.
When that happens, the rest of the Main component disappears properly.
But when I’m in my BookingForm, and the user tries to pick a date, it throws either of the errors :
"dispatch is not a function / initializeTimes is not a function"
with whatever attempt I tried.
What I want is for these instructions to be met by, but I can’t:
"Step 1: Life state up to the Main component
As you added the table booking state to the BookingForm component in the previous exercise, in this exercise, you need to lift the state up to the Main component. This is the preferred approach in this case, as your app is relatively simple.
Move the availableTimes useState hook from the BookingForm component into the Main component
Pass down the state and state changing functions from the Main component to the BookingForm component using props in order to make state work across different components.
Step 2: Update BookingForm to display available times based on the selected date
The next step is to prepare the available times to be updated based on the date the user has selected. To do this, you will change the availableTimes state to a reducer.
In the Main component, create a function named updateTimes which will handle the state change. This function will change the availableTimes based on the selected date. For now, the function can return the same available times regardless of the date.
Next, create a function called initializeTimes which will create the initial state for the availableTimes.
Then, change availableTimes to a reducer using the useReducer function and provide the two previous functions as parameters.
Update the BookingForm component to dispatch the state change when the date form field is changed.
Tip: Include the newly selected date in the dispatch parameter."
2
Answers
I am not sure this solution will work for you or not, as I don’t have the full code.
But, you can try with the following code.
Update the
Main.js
file like this, Pass handleDateChange function as props to update the selected date.And, Update the
BookingForm.js
like this.Now, when the user selects a date in the BookingForm component, the handleDateChange function will be called which is placed in the Main component. The Main component will handle the state update and update the availableTimes accordingly.
By the sounds of the project your clients/supervisers are trying to introduce new concepts to you one stage at a time which is a good technique. There are a couple of things I don’t necessarily agree with (I’ll explain later) but here’s an example of how you might approach this.
It’s important that you move the reducer logic outside of the component. First it maintains its own logic, and can be easily moved. More importantly it gets recreated every time the component is rendered which is not what you want. So you can move it outside the main component itself or, perhaps for better encapsulation, move it to its own file for export, and you can then import it into the component file. Here I’ve just moved it outside the component function.
I’m a little unsure about using
initialTimes
to populate the state because the time select only allows you to select one time. Far better to pass down a list of the available times to the component along with the state and dispatch.On that last note – and I don’t want to get ahead of your learning here – the next steps might be use
useContext
and bind the reducer to that. You won’t have to pass down the state and dispatch function to the component because the component will have access to the context itself. But I’ll leave that one alone for this exercise.The initial state will look like this for your initial form values to work.
Note: you could set a initial date with
Intl.DateTimeFormat()
so that the date input would properly render the right date but I’ve left that for you to consider.The reducer will have a series of cases that match how you want to update the state with the payload from the dispatcher. I won’t replicate it here – you can see it in the code.
For convenience I’ve added a data attribute to each form input/select that you can use in the
handleChange
function. The function destructures the data attribute value, and the input value, and then uses those in the dispatch:dispatch({ type: dispatchType, payload: value });
. I’ve done this so you don’t need to create separate functions for each input.I’ve included a
useEffect
so you can see the changes to state as you make the separate input selections. You should remove that from your final code.There are a few bits and pieces I would change, and you may have to alter some of this to match the spec you’ve been given, but this should give you a good overview for now about how to proceed.