AddressComponent
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.
- Tag your work
- Create the components folder and put in it a file called Address.js. This file should be based on App.js
- Remove code from Address.js that does not have to do with addresses
- Remove code from App.js that has to do with addresses
- import your Address component into index.js. Use it in ReactDOM.render.
- Create address-list.js with sample addresses in it
- In App.js pass the addresses from address-list to Address as props
- Consume props in Address
- Switch from record 0 to record 1 of the address list.
- Get tests working
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:
- Create an empty src/components directory.
- Create an an empty src/components/Address.js. (Now components is no longer empty.)
Define Address
Block copy the contents of App.js into Address.js.
- Rename the class from App to Address. At the bottom of the file, export Address rather than App.
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.
- Inside Address.js Remove references state.file and getFile from the constructor and render methods and from anywhere else you find them. Leave the tests alone. We are just editing Address.js in this step.
- Remove the header section from render.
- Remove the default paragraph about editing App.js generated by create-react-app: This is the text that reads: “To get started, edit…”.
- Remove imports that are no longer needed such as logo.svg.
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.
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 our Address component,
- Add a DIV to our render method
- Display the Address component by adding its tag to the DIV
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 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:
- Instantiates Address
- Sets the props for the Address component.
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.
- Create an empty file called components/Header.js
- Copy the contents of App.js into it.
- Remove everything but the render method and the Header:
- Remove the header from App.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.
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:
- src/App.test.js: All tests related to the App component
- src/Address.test.js: All tests related to the Address component
This will require a bit of cut and pasting, but it should not be overly difficult.
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.
- Folder: weekXX-YYYY
Your tests might look a bit like this:
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:
<p className="App-intro">
tollfree: {this.state.tollfree}
</p>
Then Enzyme can produce output like this to show what your JSX translates to at runtime:
<p className="App-intro">
tollfree:
unknown
</p>
The enzyme code that generates this output might look a bit like this:
const welcome = <p className="App-intro">tollfree: unknown</p>;
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.
<p className="App-intro">
tollfree:
unknown
</p>
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: