# [Redux](https://redux.js.org/)

Redux is a pattern and library for managing and updating application state, using events called *actions*. It serves as a centralized store for state that needs to be used across the entire application, with rules ensuring that the state can only be updated in a predictable fashion.

## Actions, Store, Immutability & Reducers

### Actions & Action Creators

An **Action** is a plain JavaScript object that has a `type` field. An action object can have other fields with additional information about what happened.  
By convention, that information is stored in a field called `payload`.

**Action Creators** are functions that creates and return an action object.

```js
function actionCreator(data)
{
    return { type: ACTION_TYPE, payload: data };  // action obj
}
```

### Store

The current Redux application state lives in an object called the **store**.  
The store is created by passing in a reducer, and has a method called `getState` that returns the current state value.

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.

**Selectors** are functions that know how to extract specific pieces of information from a store state value.

In `initialState.js`;

```js
export default {
    // initial state here
}
```

In `configStore.js`:

```js
// configStore.js
import { createStore, applyMiddleware, compose } from "redux";

export function configStore(initialState) {
    const composeEnhancers =
        window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; // support for redux devtools

    return  createStore(
        rootReducer, 
        initialState, 
        composeEnhancers(applyMiddleware(middleware, ...))
    );
}

// available functions & methods
replaceReducer(newReducer);  // replace an existing reducer, useful for Hot Reload
store.dispatch(action);  // trigger a state change based on an action
store.subscribe(listener);
store.getState();  // retrieve current state
```

### Reducers

**Reducers** are functions that receives the current state and an action, decide how to update the state if necessary, and return the new state.

Reducers must **always** follow some specific rules:

- They should only calculate the new state value based on the `state` and `action` arguments
- They are not allowed to modify the existing `state`.
Instead, they must make *immutable updates*, by copying the existing `state` and making changes to the copied values.
- They must not do any asynchronous logic, calculate random values, or cause other "side effects"

```js
import initialState from "path/to/initialState";

function reducer(state = initialState, action) {
    switch(action.type){
        case "ACTION_TYPE":
            return { ...state, prop: value };  // return modified copy of state (using spread operator)
            break;

        default:
            return state;  // return unchanged state (NEEDED)
    }
}

// combining reducers
import { combineReducers } from "redux";

const rootReducer = combineReducers({
    entity: entityReducer
});
```

**NOTE**: multiple reducers can be triggered by the same action since each one operates on a different portion of the state.

## [React-Redux](https://react-redux.js.org/)

### Container vs Presentational Components

Container Components:

- Focus on how thing work
- Aware of Redux
- Subscribe to Redux State
- Dispatch Redux actions

Presentational Components:

- Focus on how things look
- Unaware of Redux
- Read data from props
- Invoke callbacks on props

### Provider Component & Connect

Used at the root component and wraps all the application.

```js
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';

import { configStore } from 'path/to/configStore';
import initialState from "path/to/initialState";
import App from './App';

const store = configStore(initialState);

const rootElement = document.getElementById('root');
ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  rootElement
);
```

```js
// Component.js
import { connect } from 'react-redux';
import { increment, decrement, reset } from './actionCreators';

// const Component = ...

// specifies which state is passed to the component (called on state change)
const mapStateToProps = (state, ownProps /* optional */) => {
    // structure of the props passed to the component
    return { propName: state.property };
};

// specifies the action passed to a component (the key is the name that the prop will have )
const mapDispatchToProps = { actionCreator: actionCreator };
// or
function mapDispatchToProps(dispatch) {
    return {
        // wrap action creators
        actionCreator: (args) => dispatch(actionCreator(args))
    };
}
// or
function mapDispatchToProps(dispatch) {
    return {
        actionCreator: bindActionCreators(actionCreator, dispatch),
        actions: bindActionCreators(allActionCreators, dispatch)
    };
}

// both args are optional
// if mapDispatch is missing the dispatch function is added to the props
export default connect(mapStateToProps, mapDispatchToProps)(Component);
```

## Async Operations with [Redux-Thunk](https://github.com/reduxjs/redux-thunk)

**Note**: Redux middleware runs *after* and action and *before* it's reducer.

Redux-Thunk allows to return functions instead of objects from action creators.  
A "thunk" is a function that wraps an expression to delay it's evaluation.

In `configStore.js`:

```js
import { createStore, applyMiddleware, compose } from "redux";
import thunk from "redux-thunk";

function configStore(initialState) {
    const composeEnhancers =
        window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; // support for redux devtools

    return  createStore(
        rootReducer, 
        initialState, 
        composeEnhancers(applyMiddleware(thunk, ...))  // add thunks middleware
    );
}
```

```js
// usually action on async func success
function actionCreator(arg) {
    return { type: TYPE, data: arg };
}

export function thunk() {
    return function (dispatch) {  // redux-thunk injects dispatch as arg
        return asyncFunction().then((data) => {  // async function returns a promise
                dispatch(actionCreator(data));
            })
            .catch((error) => {
                throw error;
            });
    };
}

// or using async/await
export async function thunk() {
    return function (dispatch) {  // redux-thunk injects dispatch as arg
        try {
            let data = await asyncFunction();
            return dispatch(actionCreator(data));
        } catch(error) {
                throw error;
        }
    }
}
```