Redux Basics

For my final month at the Flat Iron School I was tasked with creating a react-redux project. For this I created a portfolio/crypto tracker. To accomplish this, I needed to use store, state, reducers, and actions. This guide will follow my experience in merging data from a rails api backend to a react frontend.

Actions

Actions are the only source of information for the store. It carries a "payload" which is the information you want to be held in the store. To access the information we want to be stored, we first need to make a fetch to the backend. By using thunk middleware when creating our store, we are able to use asynchronous logic to interact with our store, More on that later.

//actions/crypto.js
export const getCryptos = () => {
    return (dispatch) => {
        fetch("http://localhost:3000/cryptos")
            .then(resp => resp.json())
            .then(cryptos => {
                dispatch({
                    type: "GET_CRYPTOS",
                    payload: cryptos
                })
            })
        }
}

dispatch within that fetch is trying to send a state update to our store. We can follow a similar layout for our fetch request if trying to post to our backend as well (below).

//actions/portfolio.js
export const createPortfolio = (portfolio) => {    
    return (dispatch) => {
        fetch(`http://localhost:3000/users/${localStorage.user}/portfolios`, {
            method: 'POST',
            headers: {
                "Content-Type": "application/json",
                Accept: "application/json",
            },
            body: JSON.stringify(portfolio),
        })
            .then((response) => response.json())
            .then((portfolio) => {
                dispatch({ type: "CREATE_PORTFOLIO", payload: portfolio})
            })
    }
}

what is payload within these fetch requests? that is our action. Actions are written as plain javascript objects. So what do we do with these actions after we set up our fetch? We can pass it into a reducer.

Reducers

//reducers/cryptoReducer.js
const initialState = {
    cryptos: [],
    loading: false
}
const cryptosReducer = (state = initialState, action ) => {
    switch(action.type) {
        case "GET_CRYPTOS":
            return {
                ...state,
                cryptos: [...state.cryptos, ...action.payload]
            }
        default:
            return state
    }
}

A reducer is how we modify state in Redux. A reducer accepts the previous state and action, then will return the next state. Within your reducer is where you want to write your logic, however, logic such as API calls are non-pure functions (any function that attempts to change an input or affect state). How does this work? First within our return we have ...state. By spreading the state, we are able to keep the previous state as well as add or overwrite information in the new state. Without doing this we would overwrite the state with our new information and destroy the old information. This is not what we want to do as a reducer is meant to be a pure function in which we do not mutate the state.

State

So we've talked about state, but what is it?
The state of a component is an object that holds information and can change over the lifetime of the component. An example would be a button. If the button isn't clicked yet, its state is simply "not clicked", however, if we click it, its state is now "clicked". One thing to keep in mind is that state is Immutable, or unmodifiable. So how the heck is the button clicked right? Thats where our reducer comes in that we previously talked about. The reducer took in its previous state as well as an action and produced a new state of "clicked" so we can keep a history of previous state changes rather than modifying a single state.

Store

//index.js
const store = createStore(rootReducer, compose(applyMiddleware(thunk), window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()))

Now that we are fetch our information, we need to create a store. We can create a store by passing in our reducer and in my case as I require a fetch within my action, the thunk middleware. This allows us to deal with asynchronous logic and promises. You can read more about this here. The final part (window.__REDUX_blahblahblah) isn't necessary, its used for a chrome extension which allows you to view the information within your store to see what you have access to. We then need to pass our store into a provider inorder to access this within other components.

//index.js
ReactDOM.render(
  <React.StrictMode>
    <Provider store={store}>
    <App />
    </Provider>
  </React.StrictMode>,
  document.getElementById('root')
);

Now that we have a store, how do we access whats inside?

mapStateToProps and mapDispatchToProps

//App.js
const mapStateToProps = (state) => {
  return{
    cryptos: state.cryptos,
    portfolio: state.portfolio,
    loading: state.loading
  }
}

With mapStateToProps of course! with this we are able take the state (state.cryptos, state.portfolio, etc.) and create an array which holds that information. With this I can call props.cryptos.map and pass this information in my case to a card component which would loop each individual crypto within props.cryptos and create a list with the information I specify within my card component.

export default connect(mapStateToProps)(Component);

It's important to remember when exporting we do so like this in order to connect the component to the store. In this format, mapDispatchToProps is received by default. If you want to specify you would write it like this:

export default connect(mapStateToProps, { getCryptos, getPortfolio })(App);

By writing out our mapDispatchToProps, we can tell the component what actions we need to dispatch. Like it sounds, it is similar to mapStateToProps as instead of passing state, it passes a dispatch function to the props. Heres a quick example of how it would be manually written out like mapStateToProps:

const mapDispatchToProps = dispatch => {
  return {
    addItem: () => {
      dispatch(addItem())
    }
  };
};

Finally here is a list of imports you may need:

import { connect } from 'react-redux';
import { Provider } from 'react-redux'
import { applyMiddleware, createStore, compose } from 'redux'
import { combineReducers } from 'redux'
import ReactDOM from 'react-dom';
import thunk from 'redux-thunk';

26