Commit 07f7fd91 authored by Kristian Freeman's avatar Kristian Freeman

2018-04-22T11:36:05

parent 8a460409
# See https://help.github.com/ignore-files/ for more about ignoring files.
# dependencies
/node_modules
# testing
/coverage
# production
/build
# misc
.DS_Store
.env
npm-debug.log*
yarn-debug.log*
yarn-error.log*
This diff is collapsed.
{
"name": "first",
"version": "0.1.0",
"private": true,
"dependencies": {
"enzyme": "^2.9.1",
"react": "^15.6.1",
"react-dom": "^15.6.1",
"react-router": "^4.2.0",
"react-router-dom": "^4.2.2",
"react-test-renderer": "^15.6.1"
},
"devDependencies": {
"react-scripts": "0.9.5"
},
"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">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<!--
Notice the use of %PUBLIC_URL% in the tag 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>
<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`.
To create a production bundle, use `npm run build`.
-->
</body>
</html>
import React from 'react';
import {BrowserRouter, Link, Route} from 'react-router-dom';
import ReactDOM from 'react-dom';
const Home = () => <h1>Welcome to my website</h1>;
const About = () => <h1>About this website</h1>;
const Help = () => <h1>Need help? Email us at email@provider.com</h1>;
// Two things need to be done in this component:
//
// First, you must create the <Link>s to the correct components:
// - Home, at the path "/"
// - About, at the path "/about"
// - Help, at the path "/help"
//
// Add each <Link> inside of a <li> component.
//
// Second, you need to define the <Route>s themselves, for each
// component above. To do this, use the <Route> component,
// passing a "path" string prop, and a component prop
// (e.g. component={MyComponent}).
//
// Additionally, the Home route definition should set the
// "exact" prop to true, in order to exactly match the root URL.
// To pass a "true" bool as a prop, you can use a short-hand -
// below, I'll set boolProp to true:
//
// <MyComponent boolProp />
//
class Application extends React.Component {
render() {
return <div />;
}
}
const root = document.getElementById('root');
// This application variable currently just contains
// <Application>. In order to use react-router, you should
// wrap it inside of a BrowserRouter component. For example:
//
// <ComponentA>
// <ComponentB>
// </ComponentA>
//
const application = <Application />;
if (root) {
ReactDOM.render(application, root);
}
export default application;
import React from 'react';
import ReactDOM from 'react-dom';
import { shallow } from 'enzyme';
import MyFirstComponent from './index';
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<MyFirstComponent />, div);
});
it('has initial state set', () => {
const wrapper = shallow(<MyFirstComponent />);
expect(wrapper.state('hello')).toEqual('world');
});
it('displays the text variable in the <span>', () => {
const wrapper = shallow(<MyFirstComponent />);
expect(wrapper.contains('Hello!')).toEqual(true);
});
it('clicks the button and updates state', () => {
const wrapper = shallow(<MyFirstComponent />);
const button = wrapper.find('button')
expect(button.length).toEqual(1);
button.simulate('click');
expect(wrapper.state('ping')).toEqual('pong');
});
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3">
<g fill="#61DAFB">
<path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/>
<circle cx="420.9" cy="296.5" r="45.7"/>
<path d="M520.5 78.1z"/>
</g>
</svg>
This diff is collapsed.
......@@ -5,74 +5,65 @@ import Header from './Header';
// Import the NewPost component from ./NewPost.
// Follow the structure of the other components in
// this section.
import NewPost from './NewPost';
import Post from './Post';
import Sidebar from './Sidebar';
export default class Blog extends React.Component {
constructor() {
super()
super();
this.state = {
blogTitle: "",
blogAuthor: "",
posts: []
}
blogTitle: '',
blogAuthor: '',
posts: [],
};
this.addPost = this.addPost.bind(this);
}
componentDidMount() {
const url = "https://bytesized-training-assets.herokuapp.com/blog.json"
fetch(url).then(resp => resp.json().then(json => {
this.setState({
blogTitle: json.blogTitle,
blogAuthor: json.blogAuthor,
posts: json.posts
})
}));
const url = 'https://bytesized-training-assets.herokuapp.com/blog.json';
fetch(url).then(resp =>
resp.json().then(json => {
this.setState({
blogTitle: json.blogTitle,
blogAuthor: json.blogAuthor,
posts: json.posts,
});
}),
);
}
addPost(newPost) {
const { posts } = this.state;
const newPosts = [].concat(newPost, posts.concat);
this.setState({ posts: newPosts })
const {posts} = this.state;
const newPosts = [].concat(newPost, posts);
this.setState({posts: newPosts});
}
render() {
const { blogTitle, blogAuthor, posts } = this.state;
const {blogTitle, blogAuthor, posts} = this.state;
/*
Replace the "Placeholder" span by rendering the NewPost component.
The NewPost component should also take a prop, `addPost`,
which is set to `this.addPost`.
*/
return (
<div>
<Header
blogTitle={blogTitle}
blogAuthor={blogAuthor}
/>
<div style={{width: "100%"}}>
{/*
Replace this comment by rendering the NewPost component.
The NewPost component should also take a prop, `addPost`,
which is set to `this.addPost`.
*/}
<Header blogTitle={blogTitle} blogAuthor={blogAuthor} />
<div style={{width: '100%'}}>
<span>Placeholder</span>
</div>
<div style={{display: "flex"}}>
<div style={{width: "75%"}}>
{posts.map((post, index) =>
<Post
key={index}
post={post}
/>
)}
<div style={{display: 'flex'}}>
<div style={{width: '75%'}}>
{posts.map((post, index) => <Post key={index} post={post} />)}
</div>
<div style={{height: "100%", width: "25%"}}>
<Sidebar
posts={posts}
/>
<div style={{height: '100%', width: '25%'}}>
<Sidebar posts={posts} />
</div>
</div>
</div>
)
);
}
}
......@@ -2,11 +2,11 @@ import React from 'react';
export default class NewPost extends React.Component {
constructor() {
super()
super();
this.state = {
title: "",
body: ""
}
title: '',
body: '',
};
this.addPost = this.addPost.bind(this);
this.changeTitle = this.changeTitle.bind(this);
......@@ -16,23 +16,23 @@ export default class NewPost extends React.Component {
addPost(evt) {
evt.preventDefault();
const { addPost } = this.props;
const {addPost} = this.props;
const { title, body } = this.state;
const {title, body} = this.state;
const newPost = {
comments: [],
postDate: new Date().toString(),
postTitle: title,
postContent: body
}
postContent: body,
};
addPost(newPost)
addPost(newPost);
this.setState({
title: "",
body: ""
})
title: '',
body: '',
});
}
changeTitle(evt) {
......@@ -54,39 +54,34 @@ export default class NewPost extends React.Component {
}
render() {
// This component has two state variables:
// - title
// - body
// Assign these to variables.
/* This component has two state variables:
* - title
* - body
* Assign these to variables.
*
* Add the onSubmit prop to form, which should be set
* to this.addPost.
*
* Two additional props should be provided to the
* "Post Title" input:
* - value, which is set to the title value from state
* - onChange, which is set to this.changeTitle
*
* Two additional props should be provided to the
* "Post Body" textarea:
* - value, which is set to the body value from state
* - onChange, which is set to this.changeBody
*/
return (
{/* Add the onSubmit prop to form, which should be set
to this.addPost. */}
<form style={{padding: "2em"}} onSubmit={this.addPost}>
<form style={{padding: '2em'}}>
<div>
<input
placeholder="Post Title"
{/*
Two additional props should be
provided here:
- value, which is set to the title value from state
- onChange, which is set to this.changeTitle
*/}
/>
<input placeholder="Post Title" />
</div>
<div>
<textarea
placeholder="Post Body"
{/*
Two additional props should be
provided here:
- value, which is set to the body value from state
- onChange, which is set to this.changeBody
*/}
/>
<textarea placeholder="Post Body" />
</div>
<button type="submit">Add new post</button>
</form>
)
);
}
}
......@@ -3,16 +3,17 @@ import Comment from './Comment';
export default class Post extends React.Component {
render() {
const { post } = this.props;
const { postTitle, postContent, comments } = post;
const {post} = this.props;
const {postTitle, postContent, comments} = post;
return (
<div style={{padding: "2em"}}>
<div style={{padding: '2em'}}>
<h1>{postTitle}</h1>
<p>{postContent}</p>
{comments.map((value, index) =>
<Comment key={index} comment={value} />)}
{comments &&
comments.map((value, index) => (
<Comment key={index} comment={value} />
))}
</div>
);
}
}
......@@ -7,7 +7,7 @@ import './index.css';
class NetworkedComponent extends React.Component {
constructor() {
super()
super();
// Set this component's state:
// 1. `loading` should be `false`
......@@ -23,14 +23,13 @@ class NetworkedComponent extends React.Component {
// this.setState({ key: 'value' })
// This does not need to be changed.
var url = "https://bytesized-training-assets.herokuapp.com/exercise.json"
var url = 'https://bytesized-training-assets.herokuapp.com/exercise.json';
// `fetch` makes a HTTP request to the url, then...
fetch(url).then(resp => {
// With the response, we try and turn it into
// a JavaScript object from JSON, using .json(), then...
resp.json().then(json => {
// With the variable `json` that is provided as
// the result of `resp.json()`, update the component state,
// using this.setState below:
......@@ -38,7 +37,6 @@ class NetworkedComponent extends React.Component {
// - `string` should be set to the value of `json.string`:
// this is the value of the key `string` in the returned
// JSON.
});
});
}
......@@ -53,7 +51,6 @@ class NetworkedComponent extends React.Component {
// Set these values to a variable name of
// your choice below.
// When you've replaced both instances of UPDATE_ME
// in this render function, you can delete its
// definition below.
......@@ -65,7 +62,7 @@ class NetworkedComponent extends React.Component {
// If we're still loading, we use
// this custom LoadingComponent.
// This does not need to be changed.
loadingOrString = <LoadingComponent />
loadingOrString = <LoadingComponent />;
} else {
// If we aren't loading, we should display
// the string value from state instead.
......@@ -73,15 +70,11 @@ class NetworkedComponent extends React.Component {
// from the network. Replace UPDATE_ME
// with the variable you defined to
// store this.state.string.
loadingOrString = <h3>{UPDATE_ME}</h3>
loadingOrString = <h3>{UPDATE_ME}</h3>;
}
// This does not need to be changed.
return (
<div>
{loadingOrString}
</div>
);
return <div>{loadingOrString}</div>;
}
}
......@@ -91,17 +84,19 @@ class LoadingComponent extends React.Component {
render() {
return (
<div className="spinner">
<div className="rect1"></div>
<div className="rect2"></div>
<div className="rect3"></div>
<div className="rect4"></div>
<div className="rect5"></div>
<div className="rect1" />
<div className="rect2" />
<div className="rect3" />
<div className="rect4" />
<div className="rect5" />
</div>
)
);
}
}
var root = document.getElementById("root");
if (root) { ReactDOM.render(<NetworkedComponent />, root); }
var root = document.getElementById('root');
if (root) {
ReactDOM.render(<NetworkedComponent />, root);
}
export default NetworkedComponent;
{
"presets": ["es2015"]
}
default:
node *.js
./node_modules/.bin/babel-node *.js
{
"devDependencies": {
"babel-cli": "^6.26.0",
"babel-preset-es2016": "^6.24.1",
"rimraf": "^2.6.2"
}
}
const assert = require("assert");
const assert = require('assert');
const initialState = {
items: []
items: [],
};
// No changes are needed above this line.
const ADD_ITEM = 'ADD_ITEM';
const addItem = (item) => { return { type: ADDITEM, item }}
const MY_CONSTANT = 'MY_CONSTANT';
const UPDATE_ITEM = 'ADD_ITEM';
const updateItem = (item, index) => { return { type: UDPATE_ITEM, item, index }}
// Define two action constants: ADD_ITEM
// and UPDATE_ITEM.
// Replace the string 'replace me' with your
// new action constant for this method.
const addItem = item => {
return {type: 'replace me', item};
};
// Replace the string 'replace me' with your
// new action constant for this method.
const updateItem = (item, index) => {
return {type: 'replace me', item, index};
};
function itemsReducer(state = [], action) {
switch (action.type) {
case ADDITEM:
// Replace the 'replace me' text with
// the correct action constant for
// this action.
case 'replace me':
// Adding an item
return [].concat(state, action.item);
case UPDATEITEM:
// Replace the 'replace me' text with
// the correct action constant for
// this action.
case 'replace me':
// Updating an item
const newItems = [].concat(state);
newItems[action.index] = action.item;
return newItems;
......@@ -27,10 +46,16 @@ function itemsReducer(state = [], action) {
function appReducer(state = initialState, action) {
switch (action.type) {
case ADDITEM:
case UPDATEITEM:
// Replace both 'replace me' texts with
// the two action constants you've defined
// in this exercise. Note that putting
// two case statements in a row means
// that the first statement will "pass through"
// and use the same functionality as the second.
case 'replace me':
case 'replace me':
return Object.assign({}, state, {
items: itemsReducer(state.items, action)
items: itemsReducer(state.items, action),
});
default:
return state;
......@@ -40,21 +65,43 @@ function appReducer(state = initialState, action) {
// No changes are needed below this line.
let itemsState = itemsReducer([], {});
let newItemState = itemsReducer(itemsState, { action: 'UNKNOWN' });
assert.notEqual(itemsState, undefined, "The itemsReducer isn't properly returning state - the new state is undefined");
let newItemState = itemsReducer(itemsState, {action: 'UNKNOWN'});
assert.notEqual(
itemsState,
undefined,
"The itemsReducer isn't properly returning state - the new state is undefined",
);
itemsState = itemsReducer([], {});
itemsState = itemsReducer(itemsState, addItem('New item'));
assert.deepEqual(itemsState, ['New item'], "The itemsReducer didn't create a new item, given the ADD_ITEM action");
assert.deepEqual(
itemsState,
['New item'],
"The itemsReducer didn't create a new item, given the ADD_ITEM action",
);
const updateAction = updateItem('Newer item', 0);
itemsState = itemsReducer(itemsState, updateAction);
assert.deepEqual(itemsState, ['Newer item'], `The itemsReducer didn't update an item, given the UPDATE_ITEM action. Got ${updateAction.type} instead`);
assert.deepEqual(
itemsState,
['Newer item'],
`The itemsReducer didn't update an item, given the UPDATE_ITEM action. Got ${
updateAction.type
} instead`,
);
let state = appReducer(initialState, {});
state = appReducer(state, addItem('New item'));
state = appReducer(state, updateItem('Newer item', 0));
assert.deepEqual(state.items, ['Newer item'], "The appReducer didn't update an item, given the UPDATE_ITEM action");
assert.notEqual(state, undefined, "The appReducer isn't properly returning state - the new state is undefined");
assert.deepEqual(
state.items,
['Newer item'],
"The appReducer didn't update an item, given the UPDATE_ITEM action",
);
assert.notEqual(
state,
undefined,
"The appReducer isn't properly returning state - the new state is undefined",
);
console.log("Passed! :)");
console.log('Passed! :)');
This diff is collapsed.
import React, { Component } from 'react';
import React, {Component} from 'react';
// Import the named function connect from
// 'react-redux'.
......
......@@ -5,7 +5,7 @@ import registerServiceWorker from './registerServiceWorker';
// Import the named class Provider from 'react-redux'
import { createStore } from 'redux';
import {createStore} from 'redux';
import reducer from './reducer';
// Define a store variable that is the result of calling the createStore
......@@ -18,9 +18,6 @@ import reducer from './reducer';
// Instead of directly rendering <App />, make <App /> the child element
// of a <Provider /> component, and render that using ReactDOM.render.
ReactDOM.render(
<App />
document.getElementById('root')
);
ReactDOM.render(<App />, document.getElementById('root'));
registerServiceWorker();
import React from 'react';
import ReactDOM from 'react-dom';
import reducers, { addItem } from './reducers';
import { createStore } from 'redux';
import reducers, {addItem} from './reducers';
import {createStore} from 'redux';
export const store = createStore(reducers);
......@@ -33,26 +33,21 @@ class ConnectedComponent extends React.Component {
appendItem() {
// Dispatch the addItem action from inside the store.
// addItem takes an argument - the item that you
// want to pass into the store.
// want to pass into the store. Try passing a string.
//
// To dispatch the action, call store.dispatch and
// pass an action as the argument.
}
render() {
const { items } = this.state;
const {items} = this.state;
return (
<div>
<ul>
{items.map((value, index) =>
<li key={index}>{value}</li>)}
</ul>
<ul>{items.map((value, index) => <li key={index}>{value}</li>)}</ul>
<button onClick={this.appendItem}>
Append item