React Redux: Difference between revisions

From bibbleWiki
Jump to navigation Jump to search
 
(64 intermediate revisions by the same user not shown)
Line 1: Line 1:
=Unidirectional=
= Redux Flow =
Data only flows one direction
== Initial Steps for Redux ==
=Pure Functions=
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


* Like static is c#/c++ only using inputs to produce outputs
== 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 15: Line 53:
* Also yields same result
* Also yields same result


=Immutability=
=== Immutability ===


== Way to be immutable
==== Way to be immutable ====
ES6 allows us to copy objects using Object.assign e.g.
ES6 allows us to copy objects using Object.assign e.g.


Line 27: Line 65:
You can use map or filter, reduce, concat or spread as these all create a new object
You can use map or filter, reduce, concat or spread as these all create a new object


== Tools to enforce immutability ==
==== Tools to enforce immutability ====
* Using trust with your team - relies on good code reviews  
* Using trust with your team - relies on good code reviews  
* Use redux-immutable-state-invarient a package to detect problems (not for prod)
* Use redux-immutable-state-invarient a package to detect problems (not for prod)
* Enforce by using products like Immer, Immutable.js etc
* Enforce by using products like Immer, Immutable.js etc


=Example=
== 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,
});


Note ... is new syntax for spread
export default rootReducer;
</syntaxhighlight>


Stores often are stored in stores/configurationStore.js at the same level as compontents
== Configure Store ==


=== Simple Store ===
The simplest way to do this is 
<syntaxhighlight lang="javascript">
<syntaxhighlight lang="javascript">
import { createStore } from "redux";
import rootDeducer from "./reducers";


import {createStore} from 'redux'
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


var defaultState = {
<syntaxhighlight lang="javascript">
     originalAmount: 0.00
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;


function amount(state = defaultState, action) {
  return createStore(
     if(action == = 'CHANGE_ORIGIN_AMOUNT') {
    rootDeducer,
        return {
     initialState,
            ...state,
    composeEnhancers(applyMiddleware(reduxImmutableStateInvariant()))
            originalAmount: action.data
  );
        }
        // return Object.assign({},state, {originalAmout: action.data})
    }
}
}
</syntaxhighlight>


var store = createStore();
=== 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>


store.subscribe(function() {
== Connect Application to redux store ==
    console.log('state', store.getState());
})


store.dispatch({type:'CHANGE_ORIGIN_AMOUNT', data: 30.00});
Create a redux store and pass it via a provider by surrounding the App with the redux provider
store.dispatch({type:'', data: 30.00});
<syntaxhighlight lang="jsx">
store.dispatch({type:'', data: 30.00});


</syntaxhighlight>
import React from "react";
import { render } from "react-dom";


=Example Component=
import {Provider} from "react-redux"
Show us subscribing to the store to make sure updates passed down to the components.
import configureStore from "./redux/createStore"
<syntaxhighlight lang="javascript">


class MainConponent extends React.Component {
const store = configureStore();


     componentDidMount() {
render(
         store.subscribe( () => {
     <Provider store={store}>
            this.setState({});
         <App />
        } )
    </Provider>
    }
  document.getElementById("app")
);


    render() {
        return (
          <div>
              <Conversion originalAmount={store.getState().originalAmount} />
          </div>        )
    }
}
</syntaxhighlight>
</syntaxhighlight>


=Reducer=
== Connect Component to redux ==
== Rules ==
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
Reducer must not
* Mutate arguments
* Perform side effects
* Call non-pure functions


Pure function are those where the answer is predictable and the same every time e.g.
<syntaxhighlight lang="javascript">
<syntaxhighlight lang="javascript">
function add(a, b) {
export default connect(mapStateToProps, mapDispatchToProps)(CoursesPage)
  return a + b;
}
</syntaxhighlight>
</syntaxhighlight>


== Example of reducer ==
=== mapStateToProps ===
Here is a reducer which is wrong because it changes the original state
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">
<syntaxhighlight lang="javascript">
function myReducer(stat, action) {
function mapStateToProps(state, ownProps) {
     switch(action.type) {
     return {
        case "INCREMENT_COUNT":
    courses: state.courses
          state.counter++
          return state
        default:
          return state
     }
     }
}
}
</syntaxhighlight>
</syntaxhighlight>


The above is wrong because the state is change. Here is the corrected version
=== mapDispatchToProps ===


==== Null argument ====
<syntaxhighlight lang="javascript">
<syntaxhighlight lang="javascript">
function myReducer(stat, action) {
export default connect(mapStateToProps)(CoursePages)
    switch(action.type) {
        case "INCREMENT_COUNT":
          return { ...state, counter: state.count + 1}
        default:
          return state
    }
}
</syntaxhighlight>
 
== Connecting App to Store ==
We do this using a provider at the app level.


<syntaxhighlight lang="javascript">
// Usage
<Provider store={this.props.store} >
this.props.dispatch(courseActions.createCourses(this.state.course))
  <App />
</Provider>
</syntaxhighlight>
</syntaxhighlight>


Connect connects container components
==== Wrap dispatch ====
Manually wrap each create with dispatch
<syntaxhighlight lang="javascript">
<syntaxhighlight lang="javascript">
export default connect(mapStateToProps, mapDispatchToProps)(DemoPage);
function mapDispatchToProps(dispatch) {
</syntaxhighlight>
    return {
        createCourse: course => dispatch(courseActions.createCourse(course))
    }
}


=Example Component with provider=
// In the component
This removes the need to subscribe and pass the original amount
this.props.createCourse(course)


</syntaxhighlight>


==== Using bindActionsCreator ====
<syntaxhighlight lang="javascript">
<syntaxhighlight lang="javascript">


class MainConponent extends React.Component {
import {bindActionCreators} from "redux"
 
...
    render() {
function mapDispatchToProps(dispatch ) {
 
  return {
        return (
       actions: bindActionCreators(courseActions, dispatch)
          <div>
  }
              <Conversion/>
          </div>       )
    }
}
}


ReactDOM.render(<Provide store={store}><MainComponent /></Provider>,getElementById('container'));
// usage
this.props.actions.createCourse(this.state.course)


</syntaxhighlight>
</syntaxhighlight>


We need to map the state back to the properties on initialisation of the component. We do this via connect
 
==== Using object ====
change the mapDispatchToProps as an object


<syntaxhighlight lang="javascript">
<syntaxhighlight lang="javascript">
const mapDispatchToProps = {
  createCourse: courseActions.createCourse,
};


export default connect(function mapStateToProps(state,props)) {
// usage
 
this.props.createCourse(this.state.course);
    return {
        originAmount : state.originalAmount
    }
}


</syntaxhighlight>
</syntaxhighlight>


=Adding logging to redux=
== Add Feature ==
This will show action prev state and next state
So once initial setup is completion going forward some steps are repeated for each feature
<syntaxhighlight lang="javascript">
* Create Action
import {applyMiddleware,createStore } from 'redux';
* Enhance Reducer
import logger from 'redux-logger';
* Connect component
 
* Dispatch action
var store = createStore(
    amount,
    applyMidddleware(logger)
);
</syntaxhighlight>


=Setting types using keyMirror=
=Setting types using keyMirror=
Line 204: Line 249:
     THIS_IS_A_CONST = null
     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>
</syntaxhighlight>


Line 209: Line 263:
You can find an example at [[https://gitlab.com/reactexamples1/redux-fundamentals/-/blob/master/after/]]
You can find an example at [[https://gitlab.com/reactexamples1/redux-fundamentals/-/blob/master/after/]]


= how it works =
= Legacy Redux =
 
Note ... is new syntax for spread


In the code handleOriginalAmount is fired by a change
Stores often are stored in stores/configurationStore.js at the same level as compontents


<syntaxhighlight lang="javascript">
<syntaxhighlight lang="javascript">
    this.props.dispatch(actions.changeOriginAmount(newAmount));
</syntaxhighlight>


In the store there is a reducer which has the actions
import {createStore} from 'redux'
 
var defaultState = {
    originalAmount: 0.00
};


<syntaxhighlight lang="javascript">
function amount(state = defaultState, action) {
function amount(state = defaultState, action) {
     switch (action.type) {
     if(action == = 'CHANGE_ORIGIN_AMOUNT') {
         case (types.CHANGE_ORIGIN_AMOUNT):
         return {
            return {
            ...state,
                ...state,
            originalAmount: action.data
                originAmount: action.data.newAmount
        }
          }
         // return Object.assign({},state, {originalAmout: action.data})
         case (types.CHANGE_DESTINATION_AMOUNT):
    }
            return {
}
                ...state,
 
                destinationAmount: action.data.newAmount
var store = createStore();
          }
        case (types.CHANGE_ORIGIN_CURRENCY):
            return {
                ...state,
                originCurrency: action.data.newCurrency
            }
</syntaxhighlight>


store.subscribe(function() {
    console.log('state', store.getState());
})


Here is an example of an action
store.dispatch({type:'CHANGE_ORIGIN_AMOUNT', data: 30.00});
store.dispatch({type:'', data: 30.00});
store.dispatch({type:'', data: 30.00});


<syntaxhighlight lang="javascript">
export function changeOriginAmount(newAmount) {
  return {
    type:types.CHANGE_ORIGIN_AMOUNT,
    data:{newAmount: newAmount}
  }
}
</syntaxhighlight>
</syntaxhighlight>


Because the page is connected to redux via the connect when state changes the properties change
=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 {


<syntaxhighlight lang="javascript">
    componentDidMount() {
export default connect((state, props) => {
        store.subscribe( () => {
    return {
            this.setState({});
         originAmount: state.amount.originAmount,
         } )
        destinationAmount: state.amount.destinationAmount,
        originCurrency: state.amount.originCurrency,
        destinationCurrency: state.amount.destinationCurrency,
        conversionRate: state.amount.conversionRate,
        feeAmount: state.amount.feeAmount,
        totalCost: state.amount.totalCost,
        errorMsg: state.error.errorMsg
     }
     }


})(Conversion);
    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>        )
    }
}