Friday, 27 August 2021

Put component, app, store together in React Redux

Redux store like a React state database. To display a component, we need retrieve states from store. When we take some actions on component, some states will be updated and we need to save these states in store. How do we connect all these pieces together? We will use Redux connect API!

Step one: define mapStateToProps function in component

To display a component, we need props. These props comes from states. We need a mapping between component's props and store's states. This function take state as parameter and return a mapping object.

const mapStateToProps = (state) => {
    return {
        user: state.app.user,
        name: getName(state),
        ....
    }
};

Noted we can do a direct mapping. Also we can call a function in reducer to do mapping.

These props can be used in function component directly

function myfuncComponent({user, name}) {
...
}

Also can access prop using the syntax below

 this.props.user

Also noted we can access props generated by step two in the same way.

There is another pattern to do mapStateToProps. Map some props to states by modify states. Then copy all left states as props. See below

const mapStateToProps = (state) => {
    const values = {
     user: state.app.user,
        name: getName(state),
        ....
    };
    //the first parameter is target. Here merge values 
    //with state. Then copy to the target and return target
    return Object.assign({}, state, values);
    
    //may do. only overwrite counut property and leave other properties intact
    return {...state, count:state.count+1}
};

Step two: define mapDispatchToProps function in component

This function uses dispatch as a paramter. It defines mapping between user action and redux action by dispatch user action. Reduct action will pass the action event to reducer. Reducer will change states. States will map props, and component display will be changed.

const mapDispatchToProps = (disptach, ownProps) => {
    return {
    	//when use handleSubmit, we can pass values
        handleSubmit: values => dispatch(saveValues(values))
        ...
    }
}

Example to use ownProps of mapDispatchToProps

const mapDispatchToProps = (dispatch, ownProps) => {
  return {
    toggleTodo: () => dispatch(toggleTodo(ownProps.todoId))
  }
}

Step 3: use connect api to create a container and export it

export default connect(
    mapStateToProps,
    mapDispatchToProps
)(ComponentName)

connect is a high order component. After call connect, it returns a new function. This new function will accept a component as argument and return an enchanced component. To understand this, we can do this in two step.

const newfunction = connect(mapStateToProps, mapDispatchToProps)
export default newfunction(ComponentName)

When write a component, we need some props and some event handlers. This is why we need to do state mapping and dispatch mapping.

Step 4: connect app, store and component. It is done in index.js of app

Use provider to provide store and put our container into provider. Our app will render provider in root element. By far, things are put together.

import {Provider} from 'react-redux'

import store from './redux/store'

//we export container as default in myComponent
import container from './myComponent'

//create a store. To create a store, you should 
//have written a reducer
import reducers from 'yourReducer'
import {createStore} from 'redux'

const store = createStore(reducers)

const root = document.getElementById('root')

ReactDOM.render(
        <Provider store={store}>
                <container />
        </Provider>, root
    );

Some nice explanation from stackoverflow

To see states of store

store.subscribe(() => console.log("state", store.getState()))

shouldComponentUpdate

Once I find that state is updated, but component is not updated. Eventually, I figured out that was caused by shouldComponentUpdate. The following prograph comes from React documentation.

Use shouldComponentUpdate() to let React know if a component’s output is not affected by the current change in state or props. The default behavior is to re-render on every state change, and in the vast majority of cases you should rely on the default behavior. shouldComponentUpdate() is invoked before rendering when new props or state are being received. Defaults to true. This method is not called for the initial render or when forceUpdate() is used.

Redux form

To create a redux form

//create a normal react component. 
//It can be a function component or class component
let myComponent = (props) => {
 return <form onSubmit={props.handleSubmit}> .... </form>
}

//convert to a redux-form component. 
//Noted that reduxFormCreator is a function
let reduxFormCreator = reduxForm({form: 'my-form'})
myComponent = reduxFormCreator(myComponent)

//we can combine the above two lines into one line
myComponent = reduxForm({form: 'my-form'})(myComponent)

//redux form provide a Field component. 
//Form elements like input or select needs to be replaced by Field
//this field component to make input to connect to redux store
<Field name="my_input_name" component="input" type="text" 
onChange={some function}/>
//another Field example
<Field name="group_by_session" onChange={this.updateAndFetch.bind(this, "group_by_session")}
 id="group_by_session" component="input" type="checkbox" />

The value from component prop of Field can be a stateless or stateful component. It can access these props

How to set intial values in Fields of Redux Form

submit form

//Redux-Form decorates your component with handleSubmit prop
let MyForm = ({
    handleSubmit, handleCloseRequestChangeModal, error, handleCloseRequestChangeModalError, formErrors
}) => {
    return (<>
        <Field
            name="storeName"
            label = "Store Name"
            component="input"
            validate={[formFieldValidator.required, formFieldValidator.notEmpty]}
        />
        <button className={"btn btn-success margin-right-3"} onClick={handleSubmit}>Save</button>
        </>)
}

When use this redux form, we need to pass a function for onSubmit

<MyForm  onSubmit={saveIntegration}/>

React function component

If a component only needs render function and props, we can write it in function component.

//create one
function Square(props) {
  return (
    <button className="square" onClick={props.onClick}>
      {props.value}
    </button>
  );
}

//to use it, we do NOT do a function call. Use it as normal component.
//pass two props. one is called value and the other is called onClick
 <Square
    value={this.state.squares[i]}
    onClick={() => this.handleClick(i)}
 />

Disptach

The Redux store has a method called dispatch. The only way to update the state is to call store.dispatch() and pass in an action object. The store will run its reducer function and save the new state value inside.

//here is how to apply this tech

//in the action.js, create a action creator
export const CHANGE_RESULTS_VIEW = 'CHANGE_RESULTS_VIEW';
export function changeResultsView(view) {
    return { type: CHANGE_RESULTS_VIEW, view: view}
}

//in container, import that action creator. When do mapDispatchToProps,
//use action creator to create a action, and this action is passed into
//dispatch. dispatch will change state of store using logic defined
//in reducer.
const mapDispatchToProps = (dispatch, ownProps) => {
    return {
       
        changeResultsView: view => dispatch(changeResultsView(view)),
        ...
        }
    }

in line conditional render

can use js logical && to add a element based on some condition

{books.length > 0 &&
	<h2>
		You have {books.length} books.
	</h2>
} 

Use update from immutability helpers

Update Library

Use update to keep the original object unchanged. Then create a upated new object. Here we use $set to set new value. Update has other commands such as $push

var update = require('immutability-helper');
 const obj = {a:5, b:3}
 const newObj = update(obj, {b: {$set:10}})
 console.log("new obj:", newObj)
 //newObj is {a:5, b:10}
 
 //the below is example for $merge. use to update multiple values
 update(state, {$merge:{details: '', showModal: false}})

Add inline css in jsx

properties with two names, like background-color, must be written with camel case syntax

<h1 style={{backgroundColor: "white"}}>Hello World!</h1>

//this css will keep spaces.jsx will remove space as default
<div style={{ whiteSpace: 'pre-wrap' }}>some text  <div>

Workflow to add event handler in react component using redux

step one: define a function in mapDispatchToProp

const mapDispatchToProps = (dispatch, ownProps) => {
    return {
        handleCloseDetailsModal: () => dispatch({ type: CLOSE_DETAILS_MODAL })
    }
};

step two: in render function, use object spread to get the function from props

step three: use that function in component. Note we put a function name. NOT call the function.

render() {
        const {handleCloseDetailsModal } = this.props;
        return (
            <div><DetailsModal
                isOpen={showModal}
                closeDetailsModal={handleCloseDetailsModal}
                details={details}
            /></div>
        );
    }

React hook and context

Good Explain

7 Hooks to know

Good tutorial

Create a context
//create a context object. Then create a function to return provider
const MyContext = createContext();
const GlobalContext = ({ children }) => {
	//define some state here
    var states = {...}
    
    //if it is class component, use this.props.children
    return (<MyContext.Provider values={...states}> {children} 
    </MyContext.Provider>)
}
To consume context

There are several different ways. By now the most popular is to use useContext

//here useGlobalContext has values and we can put this 
//line in any component. Therefore, this is just another way to share
//states between different components. Now useGlobalContext has values
//defined in the provider
  const useGlobalContext = useContext(Context)
import {useState} from 'react';

function TestB() {
    const [state, setState] = useState(0)

    return (
        <div>
            <p>{state}</p>
            <button onClick={() => setState(state + 1)}>More</button>
        </div>
    )
}

setState function in React.Component

We can use setState() to change the state of the component directly as well as through an arrow function.

//change directly by passing an object
setState({ stateName : updatedStateValue })

//use function to change state
setState((prevState) => ({ 
   stateName: prevState.stateName + 1 
}))

React Context

Introduction to React Context

React Hook Form

Good Tutorial

JS Cheating Sheet

Sheet

Class Component Benefit

export default class GooglePay extends React.Component {
    constructor(props) {
        super(props);

        //Google payment client
        this.paymentsClient = new google.payments.api.PaymentsClient({
            environment: props.cardInfo.googlePayEnv // 'TEST' Or 'PRODUCTION'
        });

        //request sent to Google
        this.paymentDataRequest = null;

        //need this method to create payment data request
        this.cardPaymentMethod = null;

        //create Google Pay Button
        this.createGooglePayButton();
    }

    state = {
        isLoading: false
    }
......

If parent component render multiple time and the child component is class component, the child component renders only once. However, if child component is function component, it will render the same time as parent component.

React and Bootsrap

import { Container, Row, Col } from "react-bootstrap";
return (
      <div className="App">
        <Container>
          <Row>
            <Col className="bg-primary p-2">1 of 2</Col>
            <Col className="bg-success p-2">2 of 2</Col>
          </Row>
          <Row>
            <Col className="bg-success p-2">1 of 3</Col>
            <Col className="bg-secondary p-2">2 of 3</Col>
            <Col className="bg-danger p-2">3 of 3</Col>
          </Row>
        </Container>
      </div>
    );


need to add bootstrap css

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.4.1/dist/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">

The below should work too. But I found it did not work. Have not figured out.

import 'bootstrap/dist/css/bootstrap.min.css';

React and TypeScript

Create a Project

Documentation

If you have created a folder already, do not put my_app and instead use "."

npx create-react-app . --template typescript

Access element in event hander

function MyButton() {
  function handleClick(event) {
    let button = event.target;
    alert(button.id);
  }
  return (
    
  );
}

No comments:

Post a Comment