DEV Community

Cover image for Notes to Myself (React-Redux: entry#1a - Architecture)
Alexander Rovang
Alexander Rovang

Posted on • Edited on

Notes to Myself (React-Redux: entry#1a - Architecture)

Working with React-Redux for the first time is a little bit like walking into a House of Mirrors. It's very cool, but also very disorienting. My intent in these next few blog posts is to gain a top-level view of this framework to aid in the creation of further React-Redux projects.

The framework consists of 6 distinct parts, and while one could argue how these parts work sequentially, I'm going to propose we mentally organize them like so:

1) Index
2) App
3) Container(s)
4) Component(s)
5) Action(s)
6) Reducer(s)
Enter fullscreen mode Exit fullscreen mode

From a user's perspective, the first 3 of these are responsible for what it actually rendered to the page, and they do so in a nested fashion.

Index

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(
  <React.StrictMode>
        <App />
  </React.StrictMode>,
  document.getElementById('root')
);
Enter fullscreen mode Exit fullscreen mode

App

import React from 'react';
class App extends React.Component {
  render(){
    return (
        <Container />
    )
  }
}
export default App
Enter fullscreen mode Exit fullscreen mode

Container

import React from 'react'
class Container extends React.Component{
  render(){
      return(
         <MyComponent/>
      )
   }
}
export default Container
Enter fullscreen mode Exit fullscreen mode

The Container (or Containers, depending on the size of your program) has several responsibilities, but its primary function is to group together Components of a similar nature.

Components can be thought of like Partials in Rails. They are pieces of a page that can be lifted out and replaced without damaging the integrity of the base structure.

In another blog I will be writing at length about the relationship between the API's database and what React-Redux calls the Store, but for now suffice it to say that this framework depends on 2 databases. One way to access this information (which are called props) is in the Container via the componentDidMount() function.

import React from 'react'

class Container extends React.Component{
componentDidMount(){
  this.props.fetchTable()
}
    render(){
      return(
        <div>
        </div>
      )
    }
}

export default Container
Enter fullscreen mode Exit fullscreen mode

This function only runs one time, at the beginning of your program, and calls an Action method (more on this later) to gather information from the backend. By adding 2 other functions (connect(), and mapStateToProps()), the Container has access to & is able to send that information to whichever Component needs it. Components also have this ability - accessing the store & passing on Props - which is the beauty of Redux (see: the Dangers of Prop Drilling).

import React from 'react'
import { connect } from 'react-redux'
import { fetchTable } from '../actions/fetchTable'
import { MyComponent } from '../components/MyComponent'

class Container extends React.Component{
componentDidMount(){
  this.props.fetchTable()
}
    render(){
      return(
        <div>
          <MyComponent attributes={this.props.attributes}/>
        </div>
      )
    }
}
const mapStateToProps = (state) =>{
  return {
    table: state.table,
  }
}
export default connect(mapStateToProps, {fetchTable})(Container)
Enter fullscreen mode Exit fullscreen mode

Component

When sending Props to a Component, you have the option of creating a class component (which has the ability to define its own state, thus allowing for possible changes to that state) or a functional component like the one below. Functional components do not render anything, since their primary function is to implement logic on the Props that have been sent to them by the Container.

import React from 'react'

const MyComponent = props =>{
  let myInstance = props.allInstances[props.match.params.id - 1]

    return(
      <div>
          {myInstance ? myInstance.attribute: null}
      </div>
    )
  }
  export default MyComponent
Enter fullscreen mode Exit fullscreen mode

If, on the other hand, you know that your state could change (as in the case of a Form), it is possible to create a Class Component.

import React from 'react'
import { connect } from 'react-redux'
import { createInstances } from '../actions/createInstances'

class InstanceForm extends React.Component{
  state = {
    key: value
  }
  handleSumbit = (e) =>{
    e.preventDefault()
    let formData = {
      value: this.state.value
    }
    this.props.createInstances(formData)
  }  
  handleChange = (e) =>{
    this.setState({
      [e.target.name]: e.target.value
    })
  }
  render() {
    return(
      <div>
        <form onSubmit={e => this.handleSumbit(e)}>
          <input onChange={this.handleChange} 
            name="value" value={this.state.value}>
          <button>Submit</button>
        </form>
      </div>
    )
  }
}

export default connect(null,{createInstances})(InstanceForm)
Enter fullscreen mode Exit fullscreen mode

Class Components are similar to Containers in that they both utilize the connect() function, they both render something to the page, and they both rely on an Action to send information to the backend.

Action

export function fetchInstances(){
  return (dispatch) => {
    fetch('http://127.0.0.1:3000/api/v1/instances')
    .then(r=>r.json())
    .then(instances=> dispatch({
      type: "FETCH_INSTANCES", 
      payload: instances
    }))
  }
}
Enter fullscreen mode Exit fullscreen mode

Action files contain fetch requests. As far as the request itself goes, they are identical to how one would use fetch in Rails. The difference in a React-Redux app is what we do with the converted JSON on the frontend. Through a Redux library called Thunk (which is imported in the index.js file) we have access to a method called dispatch(). Dispatch invokes our final piece - the Reducer - and sends that function the React Store & an Action. The Action is an object which typically has two keys: a type and a payload, which will be discussed in the following section.

Reducer

export default function cardReducer(state = [], action){
  switch(action.type){
    case "FETCH_CARDS":
      return action.payload
    default:
        return state
  }
}
Enter fullscreen mode Exit fullscreen mode

The Reducer, invoked by dispatch(), creates a new State for the React store, using switch statements to determine what new information has been acquired by the fetch request in the Action file. The "type" key in the Action Object becomes the determining factor in the switch statements, and the "payload" key represents the new information. If no new information has been sent it simply returns the original state, but if there is new information it merges that with the current state and sends it back to the app for rendering.

Obviously this is a deeply simplified roadmap, and in the next several blogs we'll be digging more deeply into how all of these pieces are connected by various imports, routes, and the Provider... but hopefully it's a useful reference for your next React-Redux project!

Top comments (0)