官术网_书友最值得收藏!

Adding events to a functional component

We're about to add state to our component. The component will show a button for each appointment. When the button is clicked, the component stores the array index of the appointment that it refers to. To do that, we'll use the useState hook.

Hooks are a feature of React that manage various non-rendering related operations. The useState hook stores data across multiple renders of your function. The call to useState returns you both the current value in storage and a setter function that allows it to be set.

If you're new to hooks, check out the Further learning section at the end of this chapter. Alternatively, you could just follow along and see how much you can pick up just by reading the tests!

Let's start by asserting that each li element has a button element:

  1. Add the following test, just below the last one you added. The second expectation is a little peculiar in that it is checking the type of the button to be button. If you haven't seen this before, it's idiomatic when using button elements to define its role by setting the type attribute, as I'm doing here:
it('has a button element in each li', () => {
render(<AppointmentsDayView appointments={appointments} />);
expect(
container.querySelectorAll('li > button')
).toHaveLength(2);
expect(
container.querySelectorAll('li > button')[0].type
).toEqual('button');
});
We don't need to be pedantic about checking the content or placement of the button element within its parent. For example, this test would pass if we put an empty button child at the end of  li . But, thankfully, doing the right thing is just as simple as doing the wrong thing, so we can opt to do the right thing instead. All we need to do to make this pass is wrap the existing content in the new tag.
  1. Make this test pass by modifying the AppointmentsDayView return value, as shown:
<ol>
{appointments.map(appointment => (
<li key={appointment.startsAt}>
<button type="button">
{appointmentTimeOfDay(appointment.startsAt)}
</button>
</li>))}
</ol>;
  1. We can now test what happens when the button is clicked. Back in test/Appointment.test.js, add the following as the next test. This uses the ReactTestUtils.Simulate.click function to perform the click action:
it('renders another appointment when selected', () => {
render(<AppointmentsDayView appointments={appointments} />);
const button = container.querySelectorAll('button')[1];
ReactTestUtils.Simulate.click(button);
expect(container.textContent).toMatch('Jordan');
});
React components respond to what it calls synthetic events. React uses these to mask browser discrepancies in the DOM event model. That means we can't raise standard events that we'd fire through JSDOM. Instead, we use the ReactTestUtils.Simulate object to raise events.
  1. Include the following import at the top of test/Appointment.test.js:
import ReactTestUtils from 'react-dom/test-utils';
  1. Go ahead and run the test:
  ● AppointmentsDayView ? renders appointment when selected

expect(received).toMatch(expected)

Expected value to match:
"Jordan"
Received:
"12:0013:00Ashley"

We're getting all of the list content dumped out too, because we've used container.textContent in our expectation rather than something more specific.

At this stage, I'm not too bothered about where the customer name appears on screen. Testing container.textContent is like saying I want this text to appear somewhere, but I don't care where. Later on, we'll see techniques for expecting text in specific places.

There's a lot we now need to get in place in order to make the test pass: we need to introduce state and we need to add the handler. But, first, we'll need to modify our definition to use a block with a return statement:

  1. Set the last test to skip, using it.skip.
We never refactor on red. It's against the rules! But if you're on red, you can cheat a little by rewinding to green by skipping the test that you've just written.

It may seem a little pedantic to do that for the very tiny change we're about to make, but it's good practice.
  1. Wrap the constant definition in curly braces, and then return the existing value. Once you've made this change, run your tests and check you're all green:
export const AppointmentsDayView = ({ appointments }) => {
return (
<div id="appointmentsDayView">
<ol>
{appointments.map(appointment => (
<li key={appointment.startsAt}>
<button type="button">
{appointmentTimeOfDay(appointment)}
</button>
</li>))}
</ol>
<Appointment customer={appointments[0].customer} />
</div>
);
};
  1. Unskip the latest test by changing it.skip to it, and let's get to work on making it pass.
  2. Update the import at the top of the file to pull in the useState function:
import React, { useState } from 'react';
  1. Add the following line above the return statement:
const [selectedAppointment, setSelectedAppointment] = useState(
0
);
  1. We can now use this selectedAppointment rather than hard-coding an index selecting the right appointment. Change the return value to use this new state value when selecting an appointment:
<div id="appointmentsDayView">
...
<Appointment {...appointments[selectedAppointment]} />
</div>
  1. Then, change the map call to include an index in its arguments. Let's just name that i:
{appointments.map((appointment, i) => (
<li key={appointment.startsAt}>
<button type="button">
{appointmentTimeOfDay(appointment.startsAt)}
</button>
</li>
))}
  1. Now call setSelectedAppointment from within the onClick handler on the button element:
<button
type="button"
onClick={() => setSelectedAppointment(i)}>
  1. Run your tests, and you should find they're all green:
PASS test/Appointment.test.js
Appointment
? renders the customer first name (18ms)
? renders another customer first name (2ms)
AppointmentsDayView
? renders a div with the right id (7ms)
? renders multiple appointments in an ol element (16ms)
? renders each appointment in an li (4ms)
? initially shows a message saying there are no appointments today (6ms)
? selects the first element by default (2ms)
? has a button element in each li (2ms)
? renders another appointment when selected (3ms)

Our component is now complete and ready to be used in the rest of our application. That is, once we've built the rest of the application!

主站蜘蛛池模板: 南郑县| 瓦房店市| 咸丰县| 当涂县| 乡城县| 荔波县| 淳安县| 金华市| 手机| 独山县| 财经| 望谟县| 定南县| 稷山县| 尚义县| 揭西县| 广宗县| 东辽县| 麻栗坡县| 西充县| 墨脱县| 抚远县| 吉林市| 义乌市| 上饶市| 射洪县| 红河县| 宜兰市| 井陉县| 化德县| 洮南市| 买车| 凌云县| 东兴市| 漯河市| 新邵县| 濮阳市| 双鸭山市| 英吉沙县| 江达县| 金溪县|