Address Component

Elvenware Logo

TOC

Address Component

The goal is to extend our week02-react-jest project to support new React components with props.

NOTE: In many cases, my assignments are not simply cut and paste exercises. In my code I might write something like etc... or and so on... or You write the code. In these cases I expect you to complete the code as an exercise. I usually create sections like these by cutting and pasting working code into assignment, and then delete the parts I want readers to complete. Though I try to avoid it, there are places where I leave something out without explicitly making clear that you need to write some code on your own. In any case, you are responsible for creating assignments that compile cleanly and perform specific actions outlined in these assignments.

Goals

Here are the core goals of this assignment.

Use Git to Tag Your Project

Since we are often working on a single project that is implemented in multiple phases, I suggest creating a git tag marking the current status of your project:

$ git tag -a v3.0.0 -m "Start Week03"
$ git push origin v3.0.0
$ git tag -n1

The first command creates a tag that has a message associated with it. The message works much like the message in a commit.

The second command pushes the tag from your local machine to the cloud.

The last command lists your tags and their message on one line. If you have only a single tag, it is not particularly useful, but once you have multiple tags you will see how helpful this can be. Increase the value of the number after -n? to see more information about your tag. You can read about tags here:

Create Address Component

In Webstorm:

Define Address

Block copy the contents of App.js into Address.js.

The result, should include code that looks like this:

imports ...

class Address extends Component { ... }

export default Address;

Get rid of anything that is not directly associated with the idea of defining an address.

Clean Up App.js

In App.js do the mirror image of what you did in Address.js: remove all references to an address from the constructor and render methods, etc.

Preserve the header section for now, but remove the default text that begins "To get started, edit...".

Move App.js into the components directory.

Components Dir in Webstorm

This display in Webstorm can be a bit confusing. Note that App.css, App.test.js, etc are in src, not in src/components. The only files in the src/components directory at this stage are Address.js and App.js.

Add Address to our Main File

The next step is to display our new Address component. There are several ways to achieve this goal. One of the simplest is to render it in index.js. To do this, we will need to:

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App'; <=== MODIFY THIS LINE ==<
import Address from './components/Address'
import './index.css';

ReactDOM.render(
    <div>
        <App />
        <Address/>
    </div>,
    document.getElementById('root')
);

There is one further change you will need to make this file. I don't spell it explicitly, but I give you a strong hint about where to make the change. It is up to your modify the line and get your code running without error.

Clean up Relative Paths

Other changes, such as correcting the paths to App.css and logo.svg in the files moved to the components directory are left as an exercise. If you don't yet understand relative paths, read about them here:

NOTE: Remember to open up the Browser debugger (Developer Tools), usually with F12, to check for any errors.

Here is a sample error you might get if you do not have the relative paths set up correctly:

./src/components/Address.js
Module not found: Cannot resolve './App.css' in '/home/charlie/Git/prog272-calvert-2018/AddressComponent/src/components'

Use an HR element at the bottom of the render method for App.js to help separate the two components.

Address Program at Runtime

Address List

Let's create a simple file called src/address-list.js that contains two addresses:

const unknown = 'unknown';
const addresses = [
    {
        firstName: unknown,
        lastName: unknown,
        address: unknown,
        city: unknown,
        state: unknown,
        zip: unknown,
        phone: unknown,
        fax: unknown,
        tollfree: unknown
    },
    {
        firstName: 'Patty',
        lastName: 'Murray',
        address: '154 Russell Senate Office Building',
        city: 'Washington',
        state: 'D.C.',
        zip: '20510',
        phone: '(202) 224-2621',
        fax: '(202) 224-0238',
        tollfree: '(866) 481-9186'
    }

];

export default addresses;

Pass addresses to Address

Use props to pass address list to Address. First link in both our Address component and the address-list:

import Address from './components/Address'
import addresses from './address-list';

Now pass the address-list to the Address component:

<Address addressList={addresses} />

Altogether, it looks like this:

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import Address from './components/Address'
import addresses from './address-list';
import './index.css';

ReactDOM.render(
    <div>
        <App />
        <Address addressList={addresses} />
    </div>,
    document.getElementById('root')
);

Think of it this way. In index.js, at this stage, we are instantiating two components called App and Address and we are setting the props for the Address component:

<div>
    <App/>  <== This instantiates our App component
    <Address addressList={addressList}/>
</div>

Focus on this one line from the code fragment show above:

<Address addressList={addressList}/>

It does two things:

That means that our Address component can access the values from address-list.js files as props:

constructor(props) {
    super(props);
    console.log(this.props.addressList)
}    

Consume props

We don't want the Address component to be responsible for updating the address-list. It is an anti-pattern for a component to update it's own properties.

Address does not own the addressList. Right now, it is owned by index.js and so only index.js should change it. AddressShow only consumes address_list as props. We will set things up so the Address component can register changes to its state, but ultimately it will pass the changes back up the line and let some other component handle updating the address-list.

In the Address component, we need to consume the address-list passed in props. Let's just copy it into our state:

constructor(props) {
    super(props);

    console.log('ADDRESS PROPS', typeof this.props);
    const address = this.props.addressList[0];
    this.state = {
        firstName: address.firstName,
        lastName: address.lastName,
        address: address.address,
        city: address.city,
        state: address.state,
        zip: address.zip,
        phone: address.phone,
        fax: address.fax,
        tollfree: address.tollfree,
        website: address.website
    }

}

We grab the first item in the address-list array and use it to initialize our state.

Display State

We don't need to change our render method, but we do need to change what we display as our current state when the user clicks our button. Since we are only part way to our solution, we will simply get the second item in address-list, and display it to the user.

Note that the constructor gets the first item, this method gets the second item. This helps you see how the system works, but does not fully explain how our code will work in the long run.

setAddress = () => {
    const address = this.props.addressList[1];

    this.setState({
          firstName: address.firstName,
          lastName: address.lastName,
          address: address.address,
          city: address.city,
          state: address.state,
          zip: address.zip,
          phone: address.phone,
          fax: address.fax,
          tollfree: address.tollfree,
          website: address.website
      })    
};

Note that we are violating DRY. There are two chunks of code, one in the constructor, one here, that are identical. How can that be fixed?

Create Header Component

For extra credit, you can move the header element from App.js into a new file called components/Header.js.

Here is the complete declaration for the Header class.

import...

class Header extends Component {
    render() {
        return (
            <div className="App">
                <header className="App-header">
                    <img src={logo} className="App-logo" alt="logo"/>
                    <h2 className="App-title">Welcome to React</h2>
                </header>
            </div>
        );
    }
}

export...

Now add the Header into index.js. This will involve adding a new import and adding a new element into the DIV.

Header in components

Tests

As you refactor your components, your tests might need to change. For instance, if you move the H2 for your app into components/Header.js, you might need to change your tests. Consider this code:

import App from './App';

// Code omitted here

it.only('renders and reads H2 text', () => {
    const wrapper = shallow(<App />);
    const welcome = <h2>Welcome to React</h2>;
    expect(wrapper.contains(welcome)).toEqual(true);
});

It will likely end up like this:

import Header from './components/Header';

// Code omitted here

it.only('renders and reads H1 text', () => {
    const wrapper = shallow(<Header />);
    const welcome = <h2>Welcome to React</h2>;
    expect(wrapper.contains(welcome)).toEqual(true);
});

If you want to start to test the Address component, remember that it expects to be passed some props. You already saw how to do this in the main program. Simple import the address-list.js file, and then use it when you instantiate an instance of that component.

NOTE: I'm intentionally leaving out some detail here to make you did a bit of thinking on your own.

Refactor Tests

Just as you refactored your components into two modules called App.js and Address.js, you should also refactor your tests into:

This will require a bit of cut and pasting, but it should not be overly difficult.

Refactor tests Header, Address and App

Describe Tests

Make sure you uniquely describe each suite of tests:

describe('Jest Address Tests', function () { ... })

describe('Jest App Tests', function () { ... })

Here I describe one suite as being for the Address component and one for the App component.

Include a string that describes your individual test suites:

describe('Address tests', function () {

  // YOUR TESTS HERE

});

Your other module might use the string App tests.

Debug Jest Message

As we start writing more complex tests, we want to create some methods that will help us debug them. In particular, we want to use the Enzyme debug, find and childAt methods.

const getLast = (wrapper, element) => {
    const ninep = wrapper.find(element).last().debug();
    console.log(ninep);
};

const getFirst = (wrapper, element) => {
    const ninep = wrapper.find(element).first().debug();
    console.log(ninep);
};

const getChild = (wrapper, element, index) => {
    const lastParagraph = wrapper.find(element).childAt(index).debug();
    console.log(lastParagraph);
};

Call it like this:

it.only('renders and reads H2 text', () => {
    const wrapper = shallow(<App />);
    getFirst(wrapper, 'h1');
    const welcome = <h2>Welcome to React</h2>;
    expect(wrapper.contains(welcome)).toEqual(true);
});

Or like this, if you want something a bit more flexible. The important difference is that it has the quiet option, but not that this one goes after h2 instead of P:

   var quiet = false;

   const getLast = (wrapper, element) => {
     const lastParagraph = wrapper.find(element).last().debug();
     if (!quiet) {
       console.log(lastParagraph);
     }
 };

 const getFirst = (wrapper, element) => {
     const firstParagraph = wrapper.find(element).first().debug();
     if (!quiet) {
       console.log(firstParagraph);
     }
 };

 const getChild = (wrapper, element, index) => {
     const indexedParagraph = wrapper.find(element).childAt(index).debug();
     if (!quiet) {
        console.log(indexedParagraph);
     }
 };

Then change quiet to true to suppress the strings.

Turn it in

Make sure your code runs and tests work. Commit your work, push. Tell me where to look for program.

Your tests might look a bit like this:

Final Tests

Simple Test

Sometimes it seems all my tests are failing and I don't know if the problem is in the logic of my tests, or in the way I have set up my tests. In cases like that, I want to find a way to ensure that I have at least one test that will definitely pass. If that "unbreakable" test fails, then the problem is not the logic of any particular test, but in the setup or syntax of my tests. Here is my test that "cannot fail":

fit('proves we can run a test', () => {
  expect(true).toBe(true);
});

true should always be true, so if this test fails, or our tests won't run at all, the problem is not the logic of the test itself, but the way we have set up our test.

Note: I am using fit to ensure that only this one test in my current test module is run. After I can confirm that this test passes, then I might change fit to it or add fit to the next test in the module.

Enzyme Dreams

Enzyme can help you see the output generated by your JSX. Suppose you have this JSX in your React Component:

tollfree: {this.state.tollfree}

Then Enzyme can produce output like this to show what your JSX translates to at runtime:

tollfree: unknown

The enzyme code that generates this output might look a bit like this:

const welcome = 

tollfree: unknown

; const lastParagraph = wrapper.find('div').childAt(10).debug(); console.log(lastParagraph); expect(wrapper.contains(welcome)).toEqual(true);

The wrapper contains all the HTML from your project. See it like this:

console.log(wrapper.debug();

But the childAt code above finds one node, one paragraph element, in that DOM array of elements, and displays its content.

tollfree: unknown

Props Singe Node Error

This is a common error that you should know because it is so hard to diagnose if you get it. Please look here:

ENOSPC Error

This is a relatively rare error that you should know because it is so hard to diagnose if you get it.

Please look here: