Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"extends": "airbnb",
"env": {
"browser": true,
"jest": true
},
"plugins": [
"react",
"jsx-a11y",
"import"
],
"rules": {
"arrow-parens": ["error", "always"],
"react/forbid-prop-types": 0,
"react/jsx-filename-extension": 0
}
}
106 changes: 89 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,94 @@
# Create react redux app
This project was bootstrapped with [Create React App](https://github.com/facebookincubator/create-react-app).
This project was bootstrapped with [Create React App](https://github.com/facebookincubator/create-react-app).<br>
The project includes extra redux packages and improved file structure.

## Table of Contents
- [Dependencies that was added to app created with create-react-app](#dependencies-that-was-added-to-the-app)
- [Quick start](#quick-start)
- [Improved folder Structure](#improved-folder-structure)
- [Available Scripts](#available-scripts)
- [Table of Contents for react-scripts](
https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md)

## Dependencies that was added to the app
- redux
- react-redux
- redux-saga
- immutable
- react-router-dom
- react-router-redux

devDependencies:
- eslint (based on Airbnb rules)
- pre-commit

## Quick start
**1.** Clone project
```bash
# with SSH
git clone git@github.com:YUzhva/create-react-redux-app.git

# with HTTPS
git clone https://github.com/YUzhva/create-react-redux-app.git
```

**2.** Rename project
```bash
# command for Mac/Linux
mv create-react-redux-app NEW_PROJECT_NAME

# command for Windows
rename create-react-redux-app NEW_PROJECT_NAME
```

**3.** Go inside project folder `cd NEW_PROJECT_NAME` and edit project name inside `package.json`
```javascript
// change
{
"name": "create-react-redux-app",
}

// to the
{
"name": "NEW_PROJECT_NAME",
}
```

## Folder Structure
**4.** Delete .git folder
```bash
# command for Mac/Linux
rm -rf .git

After creation, your project should look like this:
# command for Windows
rmdir .git
```

**5.** Initialize new git
```bash
git init
git add .
git commit -m "[initial commit] NEW_PROJECT_NAME"
```

:beer: Have fun :beer: (=

## Improved folder Structure

```
my-app/
README.md
node_modules/
package.json
public/
index.html
favicon.ico
src/
App.css
App.js
App.test.js
index.css
index.js
logo.svg
- src
- components // reusable react components without redux
* ContainerName
tests
index.js // entry point for component

- containers // react components with redux and redux-saga data fetching
* ContainerName
tests
index.js // entry point for container
constants/actions/reducer/sagas/selectors.js // place container required files in root

- global-reducer.js // connect other containers reducers here
- global-sagas.js // connect other containers sagas here
```

For the project to build, **these files must exist with exact filenames**:
Expand Down Expand Up @@ -55,6 +124,9 @@ You will also see any lint errors in the console.
Launches the test runner in the interactive watch mode.<br>
See the section about [running tests](#running-tests) for more information.

### `npm run lint`
Lints your JavaScript.

### `npm run build`

Builds the app for production to the `build` folder.<br>
Expand Down
29 changes: 26 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,39 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"history": "^4.6.1",
"immutable": "^3.8.1",
"prop-types": "^15.5.10",
"react": "^15.5.4",
"react-dom": "^15.5.4"
"react-dom": "^15.5.4",
"react-redux": "^5.0.4",
"react-router-dom": "^4.1.1",
"react-router-redux": "^5.0.0-alpha.6",
"redux": "^3.6.0",
"redux-saga": "^0.15.3"
},
"devDependencies": {
"eslint": "^3.19.0",
"eslint-config-airbnb": "^14.1.0",
"eslint-plugin-import": "^2.2.0",
"eslint-plugin-jsx-a11y": "4.0.0",
"eslint-plugin-react": "^7.0.0",
"lint-staged": "^3.4.1",
"pre-commit": "^1.2.2",
"react-scripts": "0.9.5"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
}
"eject": "react-scripts eject",
"lint": "npm run lint:js",
"lint:staged": "lint-staged",
"lint:js": "eslint --ext .js src/"
},
"pre-commit": "lint:staged",
"lint-staged": {
"*.js": "lint:js"
},
"proxy": "https://httpbin.org"
}
21 changes: 0 additions & 21 deletions src/App.js

This file was deleted.

Empty file added src/components/.keep
Empty file.
19 changes: 19 additions & 0 deletions src/containers/App/actions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import {
GET_API_DATA,
GET_API_DATA_LOADED,
GET_API_DATA_ERROR,
} from './constants';

export const getAPIData = () => ({
type: GET_API_DATA,
});

export const getAPIDataLoaded = (data) => ({
type: GET_API_DATA_LOADED,
data,
});

export const getAPIDataError = (error) => ({
type: GET_API_DATA_ERROR,
error,
});
3 changes: 3 additions & 0 deletions src/containers/App/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const GET_API_DATA = 'containers/App/GET_API_DATA';
export const GET_API_DATA_LOADED = 'containers/App/GET_API_DATA_LOADED';
export const GET_API_DATA_ERROR = 'containers/App/GET_API_DATA_ERROR';
File renamed without changes
51 changes: 51 additions & 0 deletions src/containers/App/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';

import { getAPIData } from './actions';
import { selectApiData } from './selectors';

import logo from './images/logo.svg';

class App extends Component {
componentWillMount() {
this.props.actions.getAPIData();
}

render() {
return (
<div className="app">
<div className="app-header">
<img src={logo} className="app-logo" alt="logo" />
<h2>Welcome to React</h2>
</div>
<p className="app-intro">
To get started, edit <code>src/App.js</code> and save to reload.
</p>
<p className="app-intro">
Your IP is: {this.props.apiData && this.props.apiData.origin.split(', ')[1]}
</p>
</div>
);
}
}

App.defaultProps = {
apiData: {},
};

App.propTypes = {
actions: PropTypes.object.isRequired,
apiData: PropTypes.object,
};

const mapStateToProps = (state) => ({
apiData: selectApiData(state),
});

const mapDispatchToProps = (dispatch) => ({
actions: bindActionCreators({ getAPIData }, dispatch),
});

export default connect(mapStateToProps, mapDispatchToProps)(App);
38 changes: 38 additions & 0 deletions src/containers/App/reducer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { fromJS } from 'immutable';

import {
GET_API_DATA,
GET_API_DATA_LOADED,
GET_API_DATA_ERROR,
} from './constants';

const initialState = fromJS({
apiData: null,
apiDataLoading: null,
apiDataLoaded: null,
apiDataError: null,
});

const appReducer = (state = initialState, action) => {
switch (action.type) {
case GET_API_DATA:
return state
.set('apiDataLoading', true)
.set('apiDataError', null);
case GET_API_DATA_LOADED:
return state
.set('apiData', action.data)
.set('apiDataLoading', false)
.set('apiDataLoaded', true)
.set('apiDataError', null);
case GET_API_DATA_ERROR:
return state
.set('apiDataLoading', false)
.set('apiDataLoaded', false)
.set('apiDataError', action.error);
default:
return state;
}
};

export default appReducer;
38 changes: 38 additions & 0 deletions src/containers/App/sagas.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { takeLatest, call, put } from 'redux-saga/effects';

import { getAPIDataLoaded, getAPIDataError } from './actions';

import {
GET_API_DATA,
} from './constants';

/**
Data downloading using pure JS fetch
@type: JS object
{ result: resultObj, error: errorObj }
**/
const fetchData = (url, options) => {
const fetchRequest = new Request(url, options);

return fetch(fetchRequest)
.then((response) => (
response.json().then((result) => ({ result }))
))
.catch((error) => ({ error }));
};

function* getApiData() {
const { result, error } = yield call(fetchData, '/get', { method: 'get' });

if (error) {
yield put(getAPIDataError(error));
}

yield put(getAPIDataLoaded(result));
}

function* apiData() {
yield takeLatest(GET_API_DATA, getApiData);
}

export default apiData;
4 changes: 4 additions & 0 deletions src/containers/App/selectors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const selectAppContainer = (state) => state.containers.appReducer;

// Need to use .get, beucase reducer defaulState was created by using ImmutableJS
export const selectApiData = (state) => selectAppContainer(state).get('apiData');
12 changes: 6 additions & 6 deletions src/App.css → src/containers/App/styles.css
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
.App {
.app {
text-align: center;
}

.App-logo {
animation: App-logo-spin infinite 20s linear;
.app-logo {
animation: app-logo-spin infinite 20s linear;
height: 80px;
}

.App-header {
.app-header {
background-color: #222;
height: 150px;
padding: 20px;
color: white;
}

.App-intro {
.app-intro {
font-size: large;
}

@keyframes App-logo-spin {
@keyframes app-logo-spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
Loading