React Redux Sample
Description
React application bootstrapped with Create React App for using with REST API
and Redux for state managing.
Both components and redux-specific code (reducers, actions, action types) splitted by feature-first pattern Re-Ducks.
File structure
src/
├── state/ => represents redux
├── views/ => all react components
└── utilities/ => global constants and helper functions
Redux
State folder contains usual store.js
and folder ducks
, where one 'duck' equals one feature with one reducer. One duck contains actions.js
, redurers.js
, types.js
and optional utils.js
.
ducks/
├── duck/
| ├── actions.js
| ├── reducers.js
| ├── types.js
| ├── utils.js
| └── index.js
└── index.js
Since index.js
of each duck have default export as this feature's reducer, index of ducks
folder represents root reducer
. So adding a new, changing or deleting existing features in redux being not so painful - all files, related to one feature concentrated in one folder.
It also prevents merge conflicts in situations, when several people working around different features need to touch same files, as
types
,actions
, etc.
// ducks/index.js
export { reducer as form } from "redux-form"
export { default as user } from "./user"
export { default as profile } from "./profile"
/* ... */
// store.js
import * as reducers from "./ducks"
export default createStore(
combineReducers(reducers),
reduxDevTools,
applyMiddleware(...middlewares you use)
)
Index of ducks/ folder = old root reducer with x2 less more code
One more thing about reducers
There is a helper function, called createReducer
, used to create reducers, not using basic switch-case template.
const someReducer = createReducer(initialState)({
[types.YOUR_ACTION_TYPE]: (state, action) => {
const some_var = "";
return {
...state,
some_prop: action.payload
};
},
[types.SOME_ANOTHER_TYPE]: (state, { payload: { data } }) => ({
...state,
data,
loading: false
}),
[types.MAY_BE_YOU_WANT_RESET]: (state, action) => ({
...initialState
})
});
Its very useful, for example, if you need to scope out part of reducer to use variables with same name in several case
statements.
Tip:
switch-case
template still can be useful when several types causes same reaction.
About actions
To handle asynchronous actions we usually using redux-thunk middleware and always using action creators.
const someAction = payload => ({
type: types.SOME_YOUR_TYPE,
payload
});
const someFetchAction = payload => (dispatch, getState) => {
dispatch(setLoading(payload.id));
fetch(GET, `/api_endpoint?some_parameter=${payload.id}`)
.then(response => {
if (getState().yourReducer.currentLoading === payload.id) {
dispatch(setLoaded(response));
}
})
.catch(error => {
dispatch(setFail(error));
console.error(error);
});
};
React
views/
├── routes/ => base router
├── components/ => feature-first components
├── pages/ => layouts, related to routes
├── styled/ => StyledComponents
└── UI/ => reusable components
We splitting components to two parts - Container and Component.
Container
file concentrates in itself all logic and HOCs of this feature.
Component
itself usually a plain stateless component.
// FeatureContainer.js
import Feature from './Feature.jsx'
const withConnect = connect(...)
const withForm = reduxForm({
...
})
const enhance = compose(
withConnect,
withForm,
anyOtherListedHOC
)
export default enhance(Feature)
// Feature.jsx
const Feature = ({props you needed}) => (
/* some jsx code here */
)
export default Feature
License
react-app-best-practice is Copyright © 2015-2019 Codica. It is released under the MIT License.
About Codica
We love open source software! See our other projects or hire us to design, develop, and grow your product.