Skip to content

Commit

Permalink
Published Episode 10
Browse files Browse the repository at this point in the history
  • Loading branch information
cassiozen committed May 13, 2017
1 parent b464979 commit 1e96b72
Show file tree
Hide file tree
Showing 27 changed files with 510 additions and 0 deletions.
31 changes: 31 additions & 0 deletions episode10/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Redux Thunk Tricks

ReactCasts, episode 10.

Redux Thunk is the most used library for side effects and asyncronous calls in Redux - and that's for a reason. But despite being so used, there are a few useful thunk tricks that aren't yet commonplace, so in this episode I'll share some pieces of thunk knowledge that might help you build better applications.

Screencast video:
https://youtu.be/xihoZZU0gao

# Outline

- The first tip is very simple: You can return values from thunks, and this can be useful, for example, for asynchronous orchestration.

- The second tip is about thunk's getState, and how it can be a bad idea to rely on this mechanism for accessing data.

- The third tip is about using thunk withExtraArguments to make your thunks easy to test and run on multiple environments.


# Build & Run Instructions

1. To build and run the code in this directory, ensure you have [npm](https://www.npmjs.com) installed

2. Install
```
npm install
```

3. Start the application
```
npm start
```
22 changes: 22 additions & 0 deletions episode10/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"name": "bookstore",
"version": "0.1.0",
"private": true,
"dependencies": {
"lodash.intersection": "^4.4.0",
"react": "^15.5.4",
"react-dom": "^15.5.4",
"react-redux": "^5.0.4",
"redux": "^3.6.0",
"semantic-ui-react": "^0.68.2"
},
"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"
}
}
Binary file added episode10/public/books/gameofthrones.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added episode10/public/books/harrypotter.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added episode10/public/books/lotr.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added episode10/public/books/murderorient.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added episode10/public/books/neuromancer.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added episode10/public/books/readyp1.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added episode10/public/books/sherlockholmes.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added episode10/public/favicon.ico
Binary file not shown.
32 changes: 32 additions & 0 deletions episode10/public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<!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">
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.2/semantic.min.css"></link>
<!--
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>
41 changes: 41 additions & 0 deletions episode10/src/App.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React, { Component } from 'react';
import { Dropdown, Segment } from 'semantic-ui-react'
import Auth from './components/Auth';
import BookDetails from './components/BookDetails';

class App extends Component {
state = {
selectedId: null
}
render() {
return (
<div>
<Auth />
{ this.state.selectedId ?
<BookDetails id={this.state.selectedId} />
:
<Segment>
<Dropdown
placeholder='Select a Book'
options={[
{ text: 'Harry Potter', value: 1 },
{ text: 'Lord of the Rings', value: 2 },
{ text: 'Game of Thrones', value: 3 },
{ text: 'Sherlock Holmes', value: 4 },
{ text: 'Murder in the Orient Express', value: 5 },
{ text: 'Neuromancer', value: 6 },
{ text: 'Ready Player One', value: 7 },
]}
onChange={(e, selected)=> this.setState({selectedId: selected.value})}
fluid
search
selection
/>
</Segment>
}
</div>
);
}
}

export default App;
45 changes: 45 additions & 0 deletions episode10/src/actions/book.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import {
BOOK_REQUESTING, BOOK_SUCCESS, BOOK_FAILURE, SIMILAR_REQUESTING, SIMILAR_SUCCESS, SIMILAR_FAILURE
} from '../constants';

const entryLoading = id => ({ type: BOOK_REQUESTING, payload: id });
const entryLoaded = book => ({ type: BOOK_SUCCESS, payload: book });
const entryLoadError = () => ({ type: BOOK_FAILURE });

export const requestBook = id => (
(dispatch, getState, api) => {
dispatch(entryLoading(id));
return api.fetchBook(id)
.then(book => {
dispatch( entryLoaded(book) );
return book;
})
.catch(err => {
dispatch( entryLoadError() );
});
}
);

const similarEntriesLoading = tags => ({ type: SIMILAR_REQUESTING, payload: tags });
const similarEntriesLoaded = books => ({ type: SIMILAR_SUCCESS, payload: books });
const similarEntriesLoadError = () => ({ type: SIMILAR_FAILURE });

export const requestBooksByTags = (id, tags) => (
(dispatch, getState, api) => {
dispatch(similarEntriesLoading(tags));
api.fetchBooksByTags(tags)
.then(books => {
dispatch( similarEntriesLoaded(books.filter(p => p.id !== id)) );
})
.catch(err => {
dispatch( similarEntriesLoadError() );
});
}
);


export const requestBookAndSimilars = (id) => (
dispatch => {
dispatch(requestBook(id)).then(book => dispatch(requestBooksByTags(id, book.tags)));
}
)
13 changes: 13 additions & 0 deletions episode10/src/actions/cart.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { ADDED_TO_CART } from '../constants';

const added = id => ({ type: ADDED_TO_CART, payload: id });

export const addToCart = (bookId) => (
(dispatch, getState, api) => {
const user = getState().user;
if(user){
api.addToCart(bookId)
.then(dispatch(added(bookId)));
}
}
);
21 changes: 21 additions & 0 deletions episode10/src/actions/user.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import {
LOG_USER, LOGGED_USER, USER_FAILURE
} from '../constants';

const userLoading = id => ({ type: LOG_USER, payload: id });
const userLoaded = user => ({ type: LOGGED_USER, payload: user });
const userLoadError = () => ({ type: USER_FAILURE });

export const getUser = () => (
(dispatch, getState, api) => {
dispatch(userLoading);
return api.getUser()
.then(user => {
dispatch( userLoaded(user) );
return user;
})
.catch(err => {
dispatch( userLoadError() );
});
}
);
87 changes: 87 additions & 0 deletions episode10/src/api/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import intersection from 'lodash.intersection';

const books = [
{
id: 1,
series: 'Harry Potter',
title: 'Harry Potter and the Philosopher\'s Stone',
author: 'J. K. Rowling',
image: `${process.env.PUBLIC_URL}/books/harrypotter.jpg`,
tags: ['fantasy', 'magic', 'puberty']
},
{
id: 2,
series: 'Lord of the Rings',
title: 'The fellowship of the Ring',
author: 'J. R. R. Tolkien',
image: `${process.env.PUBLIC_URL}/books/lotr.jpg`,
tags: ['fantasy', 'magic', 'jewelry']
},
{
id: 3,
series: 'Game of Thrones',
title: 'A Song of Ice and Fire',
author: 'George R. R. Martin',
image: `${process.env.PUBLIC_URL}/books/gameofthrones.jpg`,
tags: ['fantasy', 'killing everyone you will care about']
},
{
id: 4,
series: 'Sherlock Holmes',
title: 'The adventures of Sherlock Holmes',
author: 'Arthur Conan Doyle',
image: `${process.env.PUBLIC_URL}/books/sherlockholmes.jpg`,
tags: ['detective', 'crime', 'drug abuse']
},
{
id: 5,
title: 'Murder on the Orient Express',
author: 'Agatha Cristie',
image: `${process.env.PUBLIC_URL}/books/murderorient.jpg`,
tags: ['detective', 'crime', 'tourism']
},
{
id: 6,
title: 'Neuromancer',
author: 'William Gibson',
image: `${process.env.PUBLIC_URL}/books/neuromancer.jpg`,
tags: ['science fiction', 'matrix', 'cowboys']
},
{
id: 7,
title: 'Ready Player One',
author: 'Ernest Cline',
image: `${process.env.PUBLIC_URL}/books/readyp1.jpg`,
tags: ['science fiction', 'matrix', 'insert coin']
},
];

const fetchBook = id => {
const book = books.find(book => book.id === parseInt(id, 10));
return fakeRequest(book);
};

const fetchBooksByTags = tags => {
const similar = books.filter(p => intersection(p.tags, tags).length > 0)
return fakeRequest(similar);
};

const getUser = () => {
return fakeRequest({id: 1, name: 'cassiozen', token: 'k536kh36kh456h4536'});
};

const addToCart = (id) => {
return fakeRequest();
};

// Returns a promise for data that resolves after a random timeout (0 to 500 ms).
function fakeRequest(data) {
return new Promise(resolve => {
setTimeout(()=>{
resolve(data);
}, (Math.random() * 100));
})
};


export default { fetchBook, fetchBooksByTags, getUser, addToCart };
25 changes: 25 additions & 0 deletions episode10/src/components/Auth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { getUser } from '../actions/user';
import { Button } from 'semantic-ui-react'

class Auth extends Component {
render() {
const { user, getUser } = this.props
return (
<Button onClick={getUser}>
{ user? 'Logged' : 'Login' }
</Button>
)
}
}

const mapStateToProps = (state) => {
return { user: state.user };
}

const mapDispatchToProps = {
getUser
}

export default connect(mapStateToProps, mapDispatchToProps)(Auth);
28 changes: 28 additions & 0 deletions episode10/src/components/BookDetails.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
.cover {
display: block;
height: 305px;
float: left;
margin-right: 15px;
}

.segment::after {
content: '';
display: block;
clear: both;
}

.similar {
margin-top: 29px;
}

.similar_cover {
display: block;
height: 125px;
float: left;
margin-right: 15px;
}

.segment .ui.button {
display: block;
margin: 15px 0;
}
Loading

0 comments on commit 1e96b72

Please sign in to comment.