Commit 62d16caf authored by Spencer Williams's avatar Spencer Williams

Initial commit

parents
node_modules
This source diff could not be displayed because it is too large. You can view the blob instead.
# Instructions
Let's do something a little bit more complicated. Instead of displaying a
list of users and their movies, this time you need to display a list of movies.
For each movie in the list, there are two options:
1. If the movie has been favorited, then display a list of all of the users who said that this movie was their favorite.
2. If the movie has *not* been favorited, display some text stating that no one favorited the movie.
As you go about tackling this project, try to make the app *modular* by breaking it into resusable React components.
## Example
```html
<h2>Forrest Gump</h2>
<p>Liked By:</p>
<ul>
<li>Nicholas Lain</li>
</ul>
<h2>Get Out</h2>
<p>Liked By:</p>
<ul>
<li>John Doe</li>
<li>Autumn Green</li>
</ul>
<h2>Autumn Green</h2>
<p>None of the current users liked this movie</p>
```
\ No newline at end of file
This diff is collapsed.
{
"name": "exercise2",
"version": "0.1.0",
"private": true,
"dependencies": {
"react": "^16.3.2",
"react-dom": "^16.3.2",
"react-scripts": "1.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<!--
manifest.json provides metadata used when your web app is added to the
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": "./index.html",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
File added
.App-logo {
animation: App-logo-spin infinite 20s linear;
height: 80px;
}
.App-header {
background-color: #222;
height: 150px;
padding: 20px;
text-align: center;
color: white;
}
.App-title {
font-size: 1.5em;
}
.App-intro {
font-size: large;
}
@keyframes App-logo-spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
/*
Display a list of movies where each movie contains a list of users that favorited it.
For detailed instructions, refer to instructions.md.
*/
const profiles = [
{
id: 1,
userID: '1',
favoriteMovieID: '1',
},
{
id: 2,
userID: '2',
favoriteMovieID: '1',
},
{
id: 3,
userID: '4',
favoriteMovieID: '5',
},
{
id: 4,
userID: '5',
favoriteMovieID: '2',
},
{
id: 5,
userID: '3',
favoriteMovieID: '5',
},
{
id: 6,
userID: '6',
favoriteMovieID: '4',
},
];
const users = {
1: {
id: 1,
name: 'Jane Jones',
userName: 'coder',
},
2: {
id: 2,
name: 'Matthew Johnson',
userName: 'mpage',
},
3: {
id: 3,
name: 'Autumn Green',
userName: 'user123',
},
4: {
id: 4,
name: 'John Doe',
userName: 'user123',
},
5: {
id: 5,
name: 'Lauren Carlson',
userName: 'user123',
},
6: {
id: 6,
name: 'Nicholas Lain',
userName: 'user123',
},
};
const movies = {
1: {
id: 1,
name: 'Planet Earth',
},
2: {
id: 2,
name: 'Selma',
},
3: {
id: 3,
name: 'Million Dollar Baby',
},
4: {
id: 4,
name: 'Forrest Gump',
},
5: {
id: 5,
name: 'Get Out',
},
};
class App extends Component {
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">ReactND - Coding Practice</h1>
</header>
<h2>How Popular is Your Favorite Movie?</h2>
<FavoriteMovies />
</div>
);
}
}
class FavoriteMovies extends Component {
render() {
let moviesClone = Object.assign({}, movies);
moviesClone.length = 6
let moviesArray = Array.from(moviesClone)
moviesArray.shift()
return (
<div className="favorite-movies">
{
moviesArray.map((movie) => {
let movieProfiles = profiles.filter((profile) => (
+profile.favoriteMovieID === +movie.id
));
let userList = [];
movieProfiles.forEach((movieProfile) => {
userList.push(users[movieProfile.userID]);
});
console.log('movieProfiles', movieProfiles);
return (
<MovieInfo key={movie.id} movieName={movie.name} users={userList} />
)
}
)
}
</div>
)
}
}
class MovieInfo extends Component {
render() {
return (
<div className='movie-info'>
<h2>{this.props.movieName}</h2>
<p>Liked By:</p>
<UserList users={this.props.users} />
</div>
)
}
}
class UserList extends Component {
render() {
let listContent = null;
if(this.props.users.length === 0){
listContent = <p>None of the current users liked this movie</p>;
} else {
listContent = <ul>
{
this.props.users.map((user) => (
<UserListItem key={user.id} user={user} />
))
}
</ul>
}
return (
listContent
)
}
}
class UserListItem extends Component {
render() {
return (
<li>
{this.props.user.name}
</li>
)
}
}
export default App;
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<App />, div);
ReactDOM.unmountComponentAtNode(div);
});
import React, { Component } from "react";
import logo from "../logo.svg";
import "../App.css";
import Dashboard from "./Dashboard";
const profiles = [
{
id: 1,
userID: "1",
favoriteMovieID: "1"
},
{
id: 2,
userID: "2",
favoriteMovieID: "1"
},
{
id: 3,
userID: "4",
favoriteMovieID: "5"
},
{
id: 4,
userID: "5",
favoriteMovieID: "2"
},
{
id: 5,
userID: "3",
favoriteMovieID: "5"
},
{
id: 6,
userID: "6",
favoriteMovieID: "4"
}
];
const users = {
1: {
id: 1,
name: "Jane Jones",
userName: "coder"
},
2: {
id: 2,
name: "Matthew Page",
userName: "mpage"
},
3: {
id: 3,
name: "Autumn Green",
userName: "user123"
},
4: {
id: 3,
name: "John Doe",
userName: "user123"
},
5: {
id: 5,
name: "Lauren Johnson",
userName: "user123"
},
6: {
id: 6,
name: "Nicholas Lain",
userName: "user123"
}
};
const movies = {
1: {
id: 1,
name: "Planet Earth 1"
},
2: {
id: 2,
name: "Selma"
},
3: {
id: 3,
name: "Million Dollar Baby"
},
4: {
id: 4,
name: "Forrest Gump"
},
5: {
id: 5,
name: "Get Out"
}
};
class App extends Component {
/*
The constructor is a "special method for creating and initializing an object."
(https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes). The
Component's constructor is the first thing that runs when the object is created.
*/
constructor(props) {
super(props);
this.usersByMovie = {};
/*
We can map the users by the movie they liked.
*/
profiles.forEach(profile => {
const movieID = profile.favoriteMovieID;
if (this.usersByMovie[movieID]) {
this.usersByMovie[movieID].push(profile.userID);
} else {
this.usersByMovie[movieID] = [profile.userID];
}
});
}
/*
The render method gets called automatically every time the value of the
component's props changes.
*/
render() {
return (
<div>
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">ReactND - Coding Practice</h1>
</header>
<h2>How Popular is Your Favorite Movie?</h2>
<Dashboard
usersByMovie={this.usersByMovie}
movies={movies}
users={users}
/>
</div>
);
}
}
export default App;
import React, { Component } from "react";
import MovieCard from "./MovieCard";
class Dashboard extends Component {
render() {
/*
Destructuring via ES6. We're getting the profiles, users, and movies properties
off of the pros passed into this presentational component. If you need a refresher on this syntax, check
out this course: https://www.udacity.com/course/es6-javascript-improved--ud356
*/
const { usersByMovie, users, movies } = this.props;
const movieCards = Object.keys(movies).map(id => (
<MovieCard
key={id}
users={users}
usersWhoLikedMovie={usersByMovie[id]}
movieInfo={movies[id]}
/>
));
/*
Return JSX
*/
return <ul>{movieCards}</ul>;
}
}
export default Dashboard;
import React, { Component } from "react";
import UserList from "./UserList";
class MovieCard extends Component {
render() {
/*
Destructuring via ES6. We're getting the profiles, users, and movies properties
off of the pros passed into this presentational component. If you need a
refresher on this syntax, check out this course:
https://www.udacity.com/course/es6-javascript-improved--ud356
*/
const { users, usersWhoLikedMovie, movieInfo } = this.props;
return (
<li key={movieInfo.id}>
<h2>{movieInfo.name}</h2>
<h3>Liked By:</h3>
<UserList usersWhoLikedMovie={usersWhoLikedMovie} users={users} />
</li>
);
}
}
export default MovieCard;
import React, { Component } from "react";
class UserList extends Component {
render() {
/*
Destructuring via ES6. We're getting the profiles, users, and movies properties
off of the pros passed into this presentational component. If you need a refresher on this syntax, check
out this course: https://www.udacity.com/course/es6-javascript-improved--ud356
*/
const { users, usersWhoLikedMovie } = this.props;
if (!usersWhoLikedMovie || usersWhoLikedMovie.length === 0) {
return <p>None of the current users liked this movie.</p>;
}
const listofItems = usersWhoLikedMovie.map(id => (
<li key={id}>
<p>{users[id].name}</p>
</li>
));
return <ul>{listofItems}</ul>;
}
}
export default UserList;
# Explanation
There are two ways to approach this app. The first is a bottom-up approach and
the second is a top-down approach.
`PossibleSolution1` and `PossibleSolution2` describe two possible results you
could have achieved with either approach.
## Bottom-Up Approach
The bottom-up approach involves two steps: putting all of our code into the
`App./js` file and, once the app renders what you want it to render, you break
that code up into different components. This is a possible approach to the first
step of the bottom-up strategy:
```js
class App extends Component {
constructor(props) {
super(props);
this.usersByMovie = {};
profiles.forEach(profile => {
const movieID = profile.favoriteMovieID;
if (this.usersByMovie[movieID]) {
this.usersByMovie[movieID].push(profile.userID);
} else {
this.usersByMovie[movieID] = [profile.userID];
}
});
}
render() {
return (
<div>
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">ReactND - Coding Practice</h1>
</header>
<h1>How Popular is Your Favorite Movie?</h1>
<ul>
{Object.keys(movies).map(key => {
const userIDs = this.usersByMovie[movies[key].id];
return (
<li key={movies[key].id}>
<h2>{movies[key].name}</h2>
<h3>Liked By:</h3>
{!userIDs ? (
<h4>None of the current users liked this movie</h4>
) : (
""
)}
<ul>
{userIDs &&
userIDs.map(userId => {
return (
<li key={userId}>
<p>{users[userId].name}</p>
</li>
);
})}
</ul>
</li>
);
})}
</ul>
</div>
);
}
}
export default App;
```
The reason we would need to do step 2 (break down that code into components)
instead of just keeping it as it is above is that this code above is hard to
read, harder to maintain, and impossible to reuse.
To identify the components, we would want to break up the code into sections. For
example, here's a section of the code that corresponds to a particular movie:
```js
<li key={movies[key].id}>
<h2>{movies[key].name}</h2>
<h3>Liked By:</h3>
{!userIDs ? <h4>None of the current users liked this movie</h4> : ""}
<ul>
{userIDs &&
userIDs.map(userId => {
return (
<li key={userId}>
<p>{users[userId].name}</p>
</li>
);
})}
</ul>
</li>
```
See how the `li` element represents a movie? We can replace that entire `li` chunk
with:
```js
<MovieCard
key={id}
users={users}
usersWhoLikedMovie={usersByMovie[id]}
movieInfo={movies[id]}
/>
```
Now, we can use the `MovieCard` class to render our dashboard:
```js
<ul>
{Object.keys(movies).map(id => (
<MovieCard
key={id}
users={users}
usersWhoLikedMovie={usersByMovie[id]}
movieInfo={movies[id]}
/>
))}
</ul>
```
The entire chunk above can go inside our Dashboard Component.
The list of users who liked a particular movie can either reside inside of the
MovieCard Component(see `PossibleSolution2`) or be its own Component
(see `PossibleSolution1`). In this application, it's a matter of preference
because we won't be reusing the UserList Component elsewhere and the code inside of
the MovieCard Component doesn't look bloated. However, if we were also required
to show a list of all users in our app, we would want to reuse the UserList
Component to do that.
## Top-Down Approach
Now, the top-down approach to making this app involves identifying each
component first and then starting to code. We recommend practicing
this approach because it will help you later in the course,
when you learn about state and your apps become more complex.
An intuitive way of breaking an app into components is to draw what you want
your resulting app to look like and then to physically draw boxes around each
piece of our application - take a look at this article to see an
example of that: https://brotzky.co/blog/react-thinking-in-components.
### How to Know What should be a Component:
Components are reusable chunks that can be nested inside of each other, like
Russian nesting dolls. Each component needs to follow the Single Responsibility
Principle - that is, do only 1 thing.
Oftentimes, the number of components an app should have is subjective, but it is
always the case that they should follow the Single Responsibility Principle,