React Redux: Difference between revisions
No edit summary |
|||
(83 intermediate revisions by the same user not shown) | |||
Line 1: | Line 1: | ||
=Pure Functions= | = Redux Flow = | ||
== Initial Steps for Redux == | |||
You need 9 steps to create a redux app | |||
* Create Action | |||
* Create Reducer | |||
* Create Root reducer | |||
* Configure Store | |||
* Instantiate Store | |||
* Connect Application to redux store | |||
* Connect Component to redux | |||
* Pass props via connect | |||
* Dispatch action | |||
== Create Action == | |||
Pretty simple create function which returns an action. Don't forget to export | |||
<syntaxhighlight lang="javascript"> | |||
export function createCourse(course) { | |||
return {type: "CREATE_COURSE", course} | |||
} | |||
</syntaxhighlight> | |||
== Create Reducer == | |||
=== Simple Example === | |||
Note the reducer takes a state and an action. In this case state is an array. Also note the return is a new copy of state with the passed in course appended. | |||
<syntaxhighlight lang="javascript"> | |||
export default function courseReducer(state = [], action) { | |||
switch (action.type) { | |||
case "CREATE_COURSE": | |||
return [...state, { ...action.course }]; | |||
default: | |||
return state; | |||
} | |||
} | |||
</syntaxhighlight> | |||
=== Rules === | |||
Reducer must not | |||
* Mutate arguments | |||
* Perform side effects | |||
* Call non-pure functions | |||
=== Pure Functions === | |||
Reducers must use pure functions where the outcome is always predictable. i.e. you cannot call mutable function. e.g. | |||
<syntaxhighlight lang="javascript"> | <syntaxhighlight lang="javascript"> | ||
Line 7: | Line 48: | ||
return a * b; | return a * b; | ||
} | } | ||
</syntaxhighlight> | |||
* No side effects | |||
* Also yields same result | |||
=== Immutability === | |||
==== Way to be immutable ==== | |||
ES6 allows us to copy objects using Object.assign e.g. | |||
<syntaxhighlight lang="javascript"> | |||
var state = {color:'red', name: 'Adam', point:5} | |||
var state2 = Object.assign({}, state, {point:50}) | |||
</syntaxhighlight> | |||
You can use map or filter, reduce, concat or spread as these all create a new object | |||
==== Tools to enforce immutability ==== | |||
* Using trust with your team - relies on good code reviews | |||
* Use redux-immutable-state-invarient a package to detect problems (not for prod) | |||
* Enforce by using products like Immer, Immutable.js etc | |||
== Create root Reducer == | |||
This is used to combine reducers. In this example there is only one but there will be more. | |||
<syntaxhighlight lang="javascript"> | |||
import { combineReducers } from "redux"; | |||
import courses from "./courseReducer"; | |||
const rootReducer = combineReducers({ | |||
courses, | |||
}); | |||
export default rootReducer; | |||
</syntaxhighlight> | |||
== Configure Store == | |||
=== Simple Store === | |||
The simplest way to do this is | |||
<syntaxhighlight lang="javascript"> | |||
import { createStore } from "redux"; | |||
import rootDeducer from "./reducers"; | |||
export default function configureStore(initialState) { | |||
return createStore(rootDeducer, initialState); | |||
} | |||
</syntaxhighlight> | |||
=== Store with redux devtools === | |||
However here is a useful alternative which allows the use of redux devtools | |||
<syntaxhighlight lang="javascript"> | |||
import { createStore, applyMiddleware, compose } from "redux"; | |||
import rootDeducer from "./reducers"; | |||
import reduxImmutableStateInvariant from "redux-immutable-state-invariant"; | |||
export default function configureStore(initialState) { | |||
const composeEnhancers = | |||
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; | |||
return createStore( | |||
rootDeducer, | |||
initialState, | |||
composeEnhancers(applyMiddleware(reduxImmutableStateInvariant())) | |||
); | |||
} | |||
</syntaxhighlight> | |||
=== Store with logging === | |||
This will show action prev state and next state | |||
<syntaxhighlight lang="javascript"> | |||
import {applyMiddleware,createStore } from 'redux'; | |||
import logger from 'redux-logger'; | |||
var store = createStore( | |||
amount, | |||
applyMidddleware(logger) | |||
); | |||
</syntaxhighlight> | |||
=== Combining Middleware === | |||
Note the middleware can be combined by comma separated values. | |||
<syntaxhighlight lang="javascript"> | |||
createStore( | |||
rootDeducer, | |||
initialState, | |||
composeEnhancers(applyMiddleware(reduxImmutableStateInvariant(), logger)) | |||
</syntaxhighlight> | |||
== Connect Application to redux store == | |||
Create a redux store and pass it via a provider by surrounding the App with the redux provider | |||
<syntaxhighlight lang="jsx"> | |||
import React from "react"; | |||
import { render } from "react-dom"; | |||
import {Provider} from "react-redux" | |||
import configureStore from "./redux/createStore" | |||
const store = configureStore(); | |||
render( | |||
<Provider store={store}> | |||
<App /> | |||
</Provider> | |||
document.getElementById("app") | |||
); | |||
</syntaxhighlight> | |||
== Connect Component to redux == | |||
To connect a component to redux you need to call the connect function which takes two functions mapStateToProps and optionally mapDispatchToProps and pass your page as an argument | |||
<syntaxhighlight lang="javascript"> | |||
export default connect(mapStateToProps, mapDispatchToProps)(CoursesPage) | |||
</syntaxhighlight> | |||
=== mapStateToProps === | |||
This is when you pass a mapping of your state to your properties and your own props which might not be in state. | |||
<syntaxhighlight lang="javascript"> | |||
function mapStateToProps(state, ownProps) { | |||
return { | |||
courses: state.courses | |||
} | |||
} | |||
</syntaxhighlight> | |||
=== mapDispatchToProps === | |||
==== Null argument ==== | |||
<syntaxhighlight lang="javascript"> | |||
export default connect(mapStateToProps)(CoursePages) | |||
// Usage | |||
this.props.dispatch(courseActions.createCourses(this.state.course)) | |||
</syntaxhighlight> | |||
==== Wrap dispatch ==== | |||
Manually wrap each create with dispatch | |||
<syntaxhighlight lang="javascript"> | |||
function mapDispatchToProps(dispatch) { | |||
return { | |||
createCourse: course => dispatch(courseActions.createCourse(course)) | |||
} | |||
} | |||
// In the component | |||
this.props.createCourse(course) | |||
</syntaxhighlight> | |||
==== Using bindActionsCreator ==== | |||
<syntaxhighlight lang="javascript"> | |||
import {bindActionCreators} from "redux" | |||
... | |||
function mapDispatchToProps(dispatch ) { | |||
return { | |||
actions: bindActionCreators(courseActions, dispatch) | |||
} | |||
} | |||
// usage | |||
this.props.actions.createCourse(this.state.course) | |||
</syntaxhighlight> | |||
==== Using object ==== | |||
change the mapDispatchToProps as an object | |||
<syntaxhighlight lang="javascript"> | |||
const mapDispatchToProps = { | |||
createCourse: courseActions.createCourse, | |||
}; | |||
// usage | |||
this.props.createCourse(this.state.course); | |||
</syntaxhighlight> | |||
== Add Feature == | |||
So once initial setup is completion going forward some steps are repeated for each feature | |||
* Create Action | |||
* Enhance Reducer | |||
* Connect component | |||
* Dispatch action | |||
=Setting types using keyMirror= | |||
Just a nice little tip. keyMirror stops you having to type more for const e.g. | |||
<syntaxhighlight lang="javascript"> | |||
export var ActionType = { | |||
const THIS_IS_A_CONST = "THIS_IS_A_CONST"; | |||
}; | |||
</syntaxhighlight> | |||
becomes | |||
<syntaxhighlight lang="javascript"> | |||
export var ActionType = keyMirror ({ | |||
THIS_IS_A_CONST = null | |||
}); | |||
</syntaxhighlight> | |||
=Setting Initial State= | |||
People found it useful to have a initialState.js to allow people to see the shape of the store for all reducers. | |||
<syntaxhighlight lang="javascript"> | |||
export default { | |||
authors: [], | |||
courses: [], | |||
}; | |||
</syntaxhighlight> | |||
= Example on Git hub = | |||
You can find an example at [[https://gitlab.com/reactexamples1/redux-fundamentals/-/blob/master/after/]] | |||
= Legacy Redux = | |||
Note ... is new syntax for spread | |||
Stores often are stored in stores/configurationStore.js at the same level as compontents | |||
<syntaxhighlight lang="javascript"> | |||
import {createStore} from 'redux' | |||
var defaultState = { | |||
originalAmount: 0.00 | |||
}; | |||
function amount(state = defaultState, action) { | |||
if(action == = 'CHANGE_ORIGIN_AMOUNT') { | |||
return { | |||
...state, | |||
originalAmount: action.data | |||
} | |||
// return Object.assign({},state, {originalAmout: action.data}) | |||
} | |||
} | |||
var store = createStore(); | |||
store.subscribe(function() { | |||
console.log('state', store.getState()); | |||
}) | |||
store.dispatch({type:'CHANGE_ORIGIN_AMOUNT', data: 30.00}); | |||
store.dispatch({type:'', data: 30.00}); | |||
store.dispatch({type:'', data: 30.00}); | |||
</syntaxhighlight> | |||
=Legacy Redux with subscribing = | |||
Show us subscribing to the store to make sure updates passed down to the components. | |||
<syntaxhighlight lang="jsx"> | |||
class MainConponent extends React.Component { | |||
componentDidMount() { | |||
store.subscribe( () => { | |||
this.setState({}); | |||
} ) | |||
} | |||
render() { | |||
return ( | |||
<div> | |||
<Conversion originalAmount={store.getState().originalAmount} /> | |||
</div> ) | |||
} | |||
} | |||
</syntaxhighlight> | </syntaxhighlight> |
Latest revision as of 01:46, 12 July 2021
Redux Flow
Initial Steps for Redux
You need 9 steps to create a redux app
* Create Action * Create Reducer * Create Root reducer * Configure Store * Instantiate Store * Connect Application to redux store * Connect Component to redux * Pass props via connect * Dispatch action
Create Action
Pretty simple create function which returns an action. Don't forget to export
export function createCourse(course) {
return {type: "CREATE_COURSE", course}
}
Create Reducer
Simple Example
Note the reducer takes a state and an action. In this case state is an array. Also note the return is a new copy of state with the passed in course appended.
export default function courseReducer(state = [], action) {
switch (action.type) {
case "CREATE_COURSE":
return [...state, { ...action.course }];
default:
return state;
}
}
Rules
Reducer must not
- Mutate arguments
- Perform side effects
- Call non-pure functions
Pure Functions
Reducers must use pure functions where the outcome is always predictable. i.e. you cannot call mutable function. e.g.
function multiply(a, b)
{
return a * b;
}
- No side effects
- Also yields same result
Immutability
Way to be immutable
ES6 allows us to copy objects using Object.assign e.g.
var state = {color:'red', name: 'Adam', point:5}
var state2 = Object.assign({}, state, {point:50})
You can use map or filter, reduce, concat or spread as these all create a new object
Tools to enforce immutability
- Using trust with your team - relies on good code reviews
- Use redux-immutable-state-invarient a package to detect problems (not for prod)
- Enforce by using products like Immer, Immutable.js etc
Create root Reducer
This is used to combine reducers. In this example there is only one but there will be more.
import { combineReducers } from "redux";
import courses from "./courseReducer";
const rootReducer = combineReducers({
courses,
});
export default rootReducer;
Configure Store
Simple Store
The simplest way to do this is
import { createStore } from "redux";
import rootDeducer from "./reducers";
export default function configureStore(initialState) {
return createStore(rootDeducer, initialState);
}
Store with redux devtools
However here is a useful alternative which allows the use of redux devtools
import { createStore, applyMiddleware, compose } from "redux";
import rootDeducer from "./reducers";
import reduxImmutableStateInvariant from "redux-immutable-state-invariant";
export default function configureStore(initialState) {
const composeEnhancers =
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
return createStore(
rootDeducer,
initialState,
composeEnhancers(applyMiddleware(reduxImmutableStateInvariant()))
);
}
Store with logging
This will show action prev state and next state
import {applyMiddleware,createStore } from 'redux';
import logger from 'redux-logger';
var store = createStore(
amount,
applyMidddleware(logger)
);
Combining Middleware
Note the middleware can be combined by comma separated values.
createStore(
rootDeducer,
initialState,
composeEnhancers(applyMiddleware(reduxImmutableStateInvariant(), logger))
Connect Application to redux store
Create a redux store and pass it via a provider by surrounding the App with the redux provider
import React from "react";
import { render } from "react-dom";
import {Provider} from "react-redux"
import configureStore from "./redux/createStore"
const store = configureStore();
render(
<Provider store={store}>
<App />
</Provider>
document.getElementById("app")
);
Connect Component to redux
To connect a component to redux you need to call the connect function which takes two functions mapStateToProps and optionally mapDispatchToProps and pass your page as an argument
export default connect(mapStateToProps, mapDispatchToProps)(CoursesPage)
mapStateToProps
This is when you pass a mapping of your state to your properties and your own props which might not be in state.
function mapStateToProps(state, ownProps) {
return {
courses: state.courses
}
}
mapDispatchToProps
Null argument
export default connect(mapStateToProps)(CoursePages)
// Usage
this.props.dispatch(courseActions.createCourses(this.state.course))
Wrap dispatch
Manually wrap each create with dispatch
function mapDispatchToProps(dispatch) {
return {
createCourse: course => dispatch(courseActions.createCourse(course))
}
}
// In the component
this.props.createCourse(course)
Using bindActionsCreator
import {bindActionCreators} from "redux"
...
function mapDispatchToProps(dispatch ) {
return {
actions: bindActionCreators(courseActions, dispatch)
}
}
// usage
this.props.actions.createCourse(this.state.course)
Using object
change the mapDispatchToProps as an object
const mapDispatchToProps = {
createCourse: courseActions.createCourse,
};
// usage
this.props.createCourse(this.state.course);
Add Feature
So once initial setup is completion going forward some steps are repeated for each feature
* Create Action * Enhance Reducer * Connect component * Dispatch action
Setting types using keyMirror
Just a nice little tip. keyMirror stops you having to type more for const e.g.
export var ActionType = {
const THIS_IS_A_CONST = "THIS_IS_A_CONST";
};
becomes
export var ActionType = keyMirror ({
THIS_IS_A_CONST = null
});
Setting Initial State
People found it useful to have a initialState.js to allow people to see the shape of the store for all reducers.
export default {
authors: [],
courses: [],
};
Example on Git hub
You can find an example at [[1]]
Legacy Redux
Note ... is new syntax for spread
Stores often are stored in stores/configurationStore.js at the same level as compontents
import {createStore} from 'redux'
var defaultState = {
originalAmount: 0.00
};
function amount(state = defaultState, action) {
if(action == = 'CHANGE_ORIGIN_AMOUNT') {
return {
...state,
originalAmount: action.data
}
// return Object.assign({},state, {originalAmout: action.data})
}
}
var store = createStore();
store.subscribe(function() {
console.log('state', store.getState());
})
store.dispatch({type:'CHANGE_ORIGIN_AMOUNT', data: 30.00});
store.dispatch({type:'', data: 30.00});
store.dispatch({type:'', data: 30.00});
Legacy Redux with subscribing
Show us subscribing to the store to make sure updates passed down to the components.
class MainConponent extends React.Component {
componentDidMount() {
store.subscribe( () => {
this.setState({});
} )
}
render() {
return (
<div>
<Conversion originalAmount={store.getState().originalAmount} />
</div> )
}
}