├── .babelrc ├── .gitignore ├── README.md ├── index.html ├── package.json ├── src ├── actions │ └── index.js ├── components │ └── app.js ├── index.js └── reducers │ ├── index.js │ └── users.js ├── style └── style.css ├── test ├── components │ └── app_test.js └── test_helper.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["react", "es2015", "stage-1"] 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | node_modules 28 | 29 | # Optional npm cache directory 30 | .npm 31 | 32 | # Optional REPL history 33 | .node_repl_history 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ##Action Creator 2 | 3 | En primer lugar creamos la acción (en realidad es un action creator), que es donde se encuentra el código encargado de conectarse al API. Como vamos a realizar una llamada asíncrona, en lugar de devolver un simple objeto JSON, nuestro action creator va a tener que devolver una función. Esta función la va a tener que procesar un middleware, redux-thunk. Por lo tanto vamos a tener que intalar este middleware, y también vamos a instalar el módulo axios para realizar peticiones HTTP. El código de nuestro action creator quedaría definido de la siguiente forma: 4 | 5 | ```javascript 6 | import axios from 'axios' 7 | 8 | export const SHOW_USERS = 'SHOW_USERS' 9 | 10 | export function showUsers() { 11 | 12 | return (dispatch, getState) => { 13 | axios.get('http://jsonplaceholder.typicode.com/users') 14 | .then((response) => { 15 | dispatch( { type: SHOW_USERS, payload: response.data } ) 16 | }) 17 | } 18 | 19 | } 20 | ``` 21 | 22 | 23 | ##Reducer 24 | 25 | El siguiente fichero que vamos a crear es nuestro reducer. Este fichero no tiene nada especial. Se trata de un reducer estándar: 26 | 27 | ```javascript 28 | import { SHOW_USERS } from '../actions' 29 | 30 | const initialState = { 31 | list: [] 32 | } 33 | 34 | export function showUsers(state = initialState, action) { 35 | 36 | switch (action.type) { 37 | case SHOW_USERS: 38 | return Object.assign({}, state, {list: action.payload}) 39 | default: 40 | return state 41 | } 42 | 43 | } 44 | ``` 45 | 46 | ##Component 47 | Y por último, tenemos que modificar nuestro Componente para que se conecta a nuestro almacén de Redux y para que muestre el listado de usuarios en forma de tabla. El código del componente quedaría de la siguiente forma: 48 | 49 | ```javascript 50 | import React from 'react'; 51 | import { Component } from 'react'; 52 | import { connect } from 'react-redux' 53 | import { showUsers } from '../actions' 54 | import { Table } from 'react-bootstrap' 55 | 56 | class App extends Component { 57 | 58 | componentWillMount() { 59 | this.props.showUsers() 60 | } 61 | 62 | renderUsersList() { 63 | return this.props.users.map((user) => { 64 | return ( 65 | 66 | {user.id} 67 | {user.name} 68 | {user.email} 69 | 70 | ) 71 | }) 72 | } 73 | 74 | render() { 75 | return ( 76 |
77 |

Users List

78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | { this.renderUsersList() } 88 | 89 |
IdNameEmail
90 |
91 | ); 92 | } 93 | } 94 | 95 | function mapStateToProps(state) { 96 | return { 97 | users: state.user.list 98 | } 99 | } 100 | 101 | export default connect(mapStateToProps, { showUsers })(App) 102 | ``` 103 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redux-simple-starter", 3 | "version": "1.0.0", 4 | "description": "Simple starter package for Redux with React and Babel support", 5 | "main": "index.js", 6 | "repository": "git@github.com:StephenGrider/ReduxSimpleStarter.git", 7 | "scripts": { 8 | "start": "node ./node_modules/webpack-dev-server/bin/webpack-dev-server.js", 9 | "test": "mocha --compilers js:babel-core/register --require ./test/test_helper.js --recursive ./test", 10 | "test:watch": "npm run test -- --watch" 11 | }, 12 | "author": "", 13 | "license": "ISC", 14 | "devDependencies": { 15 | "babel-core": "^6.2.1", 16 | "babel-loader": "^6.2.0", 17 | "babel-preset-es2015": "^6.1.18", 18 | "babel-preset-react": "^6.1.18", 19 | "chai": "^3.5.0", 20 | "chai-jquery": "^2.0.0", 21 | "jquery": "^2.2.1", 22 | "jsdom": "^8.1.0", 23 | "mocha": "^2.4.5", 24 | "react-addons-test-utils": "^0.14.7", 25 | "webpack": "^1.12.9", 26 | "webpack-dev-server": "^1.14.0" 27 | }, 28 | "dependencies": { 29 | "axios": "^0.11.0", 30 | "babel-preset-stage-1": "^6.1.18", 31 | "lodash": "^3.10.1", 32 | "react": "^0.14.3", 33 | "react-bootstrap": "^0.29.3", 34 | "react-dom": "^0.14.3", 35 | "react-redux": "^4.0.0", 36 | "react-router": "^2.0.1", 37 | "redux": "^3.0.4", 38 | "redux-thunk": "^2.0.1" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/actions/index.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | export const SHOW_USERS = 'SHOW_USERS' 4 | 5 | export function showUsers() { 6 | 7 | return (dispatch, getState) => { 8 | axios.get('http://jsonplaceholder.typicode.com/users') 9 | .then((response) => { 10 | dispatch( { type: SHOW_USERS, payload: response.data } ) 11 | }) 12 | } 13 | 14 | } 15 | 16 | -------------------------------------------------------------------------------- /src/components/app.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Component } from 'react'; 3 | import { connect } from 'react-redux' 4 | import { showUsers } from '../actions' 5 | import { Table } from 'react-bootstrap' 6 | 7 | class App extends Component { 8 | 9 | componentWillMount() { 10 | this.props.showUsers() 11 | } 12 | 13 | renderUsersList() { 14 | return this.props.users.map((user) => { 15 | return ( 16 | 17 | {user.id} 18 | {user.name} 19 | {user.email} 20 | 21 | ) 22 | }) 23 | } 24 | 25 | render() { 26 | return ( 27 |
28 |

Users List

29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | { this.renderUsersList() } 39 | 40 |
IdNameEmail
41 |
42 | ); 43 | } 44 | } 45 | 46 | function mapStateToProps(state) { 47 | return { 48 | users: state.user.list 49 | } 50 | } 51 | 52 | export default connect(mapStateToProps, { showUsers })(App) -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { Provider } from 'react-redux'; 4 | import { createStore, applyMiddleware } from 'redux'; 5 | 6 | import App from './components/app'; 7 | import reducers from './reducers'; 8 | import thunk from 'redux-thunk' 9 | 10 | 11 | const createStoreWithMiddleware = applyMiddleware(thunk)(createStore); 12 | 13 | ReactDOM.render( 14 | 15 | 16 | 17 | , document.querySelector('.container')); 18 | -------------------------------------------------------------------------------- /src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { showUsers } from './users' 3 | 4 | const rootReducer = combineReducers({ 5 | user: showUsers 6 | }); 7 | 8 | export default rootReducer; 9 | -------------------------------------------------------------------------------- /src/reducers/users.js: -------------------------------------------------------------------------------- 1 | import { SHOW_USERS } from '../actions' 2 | 3 | const initialState = { 4 | list: [] 5 | } 6 | 7 | export function showUsers(state = initialState, action) { 8 | 9 | switch (action.type) { 10 | case SHOW_USERS: 11 | return Object.assign({}, state, {list: action.payload}) 12 | default: 13 | return state 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /style/style.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jlmonteagudo/react-redux-api-rest-example/5663ad9d51b98555d7d313a123f3c51c4cf626c2/style/style.css -------------------------------------------------------------------------------- /test/components/app_test.js: -------------------------------------------------------------------------------- 1 | import { renderComponent , expect } from '../test_helper'; 2 | import App from '../../src/components/app'; 3 | 4 | describe('App' , () => { 5 | let component; 6 | 7 | beforeEach(() => { 8 | component = renderComponent(App); 9 | }); 10 | 11 | it('renders something', () => { 12 | expect(component).to.exist; 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /test/test_helper.js: -------------------------------------------------------------------------------- 1 | import _$ from 'jquery'; 2 | import React from 'react'; 3 | import ReactDOM from 'react-dom'; 4 | import TestUtils from 'react-addons-test-utils'; 5 | import jsdom from 'jsdom'; 6 | import chai, { expect } from 'chai'; 7 | import chaiJquery from 'chai-jquery'; 8 | import { Provider } from 'react-redux'; 9 | import { createStore } from 'redux'; 10 | import reducers from '../src/reducers'; 11 | 12 | global.document = jsdom.jsdom(''); 13 | global.window = global.document.defaultView; 14 | const $ = _$(window); 15 | 16 | chaiJquery(chai, chai.util, $); 17 | 18 | function renderComponent(ComponentClass, props = {}, state = {}) { 19 | const componentInstance = TestUtils.renderIntoDocument( 20 | 21 | 22 | 23 | ); 24 | 25 | return $(ReactDOM.findDOMNode(componentInstance)); 26 | } 27 | 28 | $.fn.simulate = function(eventName, value) { 29 | if (value) { 30 | this.val(value); 31 | } 32 | TestUtils.Simulate[eventName](this[0]); 33 | }; 34 | 35 | export {renderComponent, expect}; 36 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | entry: [ 3 | './src/index.js' 4 | ], 5 | output: { 6 | path: __dirname, 7 | publicPath: '/', 8 | filename: 'bundle.js' 9 | }, 10 | module: { 11 | loaders: [{ 12 | exclude: /node_modules/, 13 | loader: 'babel' 14 | }] 15 | }, 16 | resolve: { 17 | extensions: ['', '.js', '.jsx'] 18 | }, 19 | devServer: { 20 | historyApiFallback: true, 21 | contentBase: './' 22 | } 23 | }; 24 | --------------------------------------------------------------------------------