ReactJS Redux, the Right Way.

This is the React Convention™: convention over configuration step-by-step guide for the React Native and ReactJS developers.

This is the online convention and tutorial's e-book for React.JS Developers. Learn the React Redux best practises.

This e-book is 100% online (no emails required, no PDFs to downlad etc.). It contains learning materials for junior and senior front-end devs who want to master React Redux (for React Native tutorial click here).

In case of any problems and feedback, feel free to contact me at kamil.przeorski@gmail.com

The React Convention™ Tutorial's Content Table

Chapter #1: ReactJS

Chapter #2: React Native

For the React Native content table please click here.


Why you should learn and use React

1) Because of timing

Currently, React has the best momentum between all the front-end frameworks/solutions/libraries. Below we can find the stats of visits within Angular vs. React subreddits:


reactjs momentum

Above, we can see that React subreddit has overpasses with popularity (unique visits) the AngularJS.


"A good hockey player plays where the puck is. A great hockey player plays where the puck is going to be" - Wayne Gretzky


As you can see, the advice from Wayne Gretzky may be quite useful: play where "the puck" is going to be (React, not Angular).

ReactJS related skills are going to be very demanded on the job market. Here you can learn basics and advanced concepts behind React Redux, the right way.

2) Because of better job market

Who Is Hiring on Hacker News are monthly updates of tech companies who are looking for certain skillsets:


reactjs who is hiring


The screenshot above shows the stats of Angular and React. Where the black line that’s down-trending represents Angular and the light-blue line that’s up-trending represents React.

Second part of that point is the geography.


Angular is searched mostly in poor cities while React has more traction in SF, Sydney or Austin:

reactjs google trends


As you can see on the locations' list - the Angular.JS is mostly searched in countries like India and the React.JS is more popular in places creating TRENDS as for example San Francisco, Sydney or Austin (that mean, it's going to be more popular over time).

3) Because of it's simplicity over Angular and other

React.JS API is short and sweet. Redux helps in building more complex apps. You can learn more about both below.

4) Because of it's robust ecosystem

React has the most robust community in terms of modular components which are shared online for reusing purpose.

Why Redux

Redux is "kind of" replacement of Service or Factory in Angular. It keeps your application model in one place and helps organize data of your application.

Redux's ANALOGY #1: Redux is "kind of back-end on the front-end" - it's your database of your front-end application.

Redux's ANALOGY #2 (TO SQL): it helps you to query SELECT, INSERT and UPDATE the client-side data in it's single state tree (you will learn about it later).

In that e-book you will learn the basics and advanced things regarding "how to use React Redux, the right way".

Why to use Redux:

1) It's simpler to learn (than Facebook's FLUX singleton approach)

From my experience of teaching developers FLUX and Redux - I have found that Redux is easier to grasp.

2) It's more powerful than other solutions

It has many features which are unique and very useful in real life web apps development (like immutability).

3) It's the most used across the React community

Redux is the most standard tool to use for the uni-directional data flow.

A good example is the ReactJS Convention™ which you can learn here - it deploys Redux in 100%.

4) Server side rendering with Redux is great

Redux is one of best solutions for server-side React's applications. It's very unique and useful feature that is mostly used on websites where the user experience must be top-notch (the less latency of the server's response the better) - for example AirBnb or most popular news services have server rendering implemented, so that their users can have content served much quicker than in a regular (non server-side rendering) single-page-application.


Learning EcmaScript6 (JS)


I assume that most people who came to the ReactJS Convention™ e-book's website don't know too much about React and Redux.

If you feel you are strong in using React and Redux then you can skip Preface #1 and go directly to Why you should use the React Convention™ and start learning advanced things.

Knowledge about React and Redux is very important to understand concepts behind React Convention™.

Because of that I will share with you online resources which are the most useful for a someone who:
  • Has background in HTML5, CSS3 and VanillaJS
  • Likely, has experience in jQuery, Angular or other "older" front-end solutions
  • Wants to learn React.JS basics
  • Likes the React Convention™ idea and wants to make highly scalable and manageable client-side apps

THINGS TO LEARN AND MASTER before advancing into the React Convention™ concept:


1) Functional programming concepts in VanilaJS:

Higher-order functions - Part 1 of Functional Programming in JavaScript :
https://www.youtube.com/watch?v=BMUiFMZr7vk

Map - Part 2 of Functional Programming in JavaScript :
https://www.youtube.com/watch?v=bCqtb-Z5YGQ

Reduce basics - Part 3 of Functional Programming in JavaScript :
https://www.youtube.com/watch?v=Wl98eZpkp-c

Reduce Advanced - Part 4 of Functional Programming in JavaScript:
https://www.youtube.com/watch?v=1DMolJ2FrNY

Closures - Part 5 of Functional Programming in JavaScript :
https://www.youtube.com/watch?v=CQqwU2Ixu-U

Currying - Part 6 of Functional Programming in JavaScript :
https://www.youtube.com/watch?v=iZLP4qOwY8I

Recursion - Part 7 of Functional Programming in JavaScript :
https://www.youtube.com/watch?v=k7-N8R0-KY4

Promises - Part 8 of Functional Programming in JavaScript:
https://www.youtube.com/watch?v=2d7s3spWAzo

If you are a JS developer, then you should be already familiar with the concepts from the links above (^^). I'm putting the links for other people who want to master the JS. In React we use much more VanillaJS than in other solutions like AngularJS or EmberJS.

How to learn React and Redux basics

After you are familiar with the tutorials linked to YouTube from the above list, then you can start learning ES6 and more JS from Egghead (or skip it and go directly to resources related to React and Redux in case if you feel strong in that area).

2) Egghead resources about ES6 and JS:

EcmaScript 6:
https://egghead.io/courses/learn-es6-ecmascript-2015

Reduce Data with Javascript Array:
https://egghead.io/courses/reduce-data-with-javascript

JavaScript Arrays in Depth:
https://egghead.io/courses/javascript-arrays-in-depth


3) Useful resources about React and Redux:

React Fundamentals:
https://egghead.io/courses/react-fundamentals

Important Redux's Three Principles:
https://github.com/reactjs/redux/blob/master/docs/introduction/ThreePrinciples.md

You need to be aware of three principles:
  • Single source of truth
  • State is read-only
  • Changes are made with pure functions

All the three principles are explained in that video series: "Getting Started with Redux":
https://egghead.io/courses/getting-started-with-redux

Once you are familiar with the materials listed above, then you are ready to learn the React Convention™ in order to make SPA apps, the right way.


Why you should use the React Convention™

"So many projects, so little time." - Kamil Przeorski

ReactJS Convention™ connects different things.

Learning materials + tested project structure with over 5000 stars + support on FB group in case any problems.

This is the best place in the internet, where you can find all the learning materials about React Redux, the right way. You can be sure that after finishing this guide you will be able to make client-side single-page-apps which are profesionall and scalable.

Have fun while learning :-) !

In 2013 ReactJS was almost "nothing", today it's "something" for the JavaScript world. We developers, have so little time and so much work to do. The main problem with anyone who wants to learn how to make single-page-apps with React is that there are no really conventions that one can follow. I am writing about those problems as a founder of the ReactJS/React-Native Webshop called MobileWeb Pro (www.MWP.io) - we made over 15+ React projects for clients from 4 continents and I can tell you that we faced so many frustrations (and this is why we want to share with you the ReactJS Convention™ for free). Mostly when a client who had already an MVP wanted us to expand based on his codebase. No convention at all, many projects structure codebases and almost each one had it's weak points - this led us to many frustrations. Also, another source of annoyance is, when you need to introduce a new developer to your "well known to you" codebase and you want to make sure that he doesn't make any "newbie" mistakes. In context of this preface a "newbie" means a someone who isn't acquainted with the project's structure. Because he is not familizarized with the project's configuration, then he is not really productive in his first month of work and additionally slows your progress down. You need to teach him about your configuration which will take you decent amount of time to pass all your knowledge to your new teammate in the project. The React Convention™ e-book helps you to save time on transferring knowledge about your project's structure to any new member.

The main ReactC's goal is to create "ready to go" convention that you can share with your colleagues.

SHARE THE REACT CONVENTION IF YOU LIKE THE IDEA.

We will familiarize your new teammate with your React's project structure so that you will save a lot of money when following it. You will save 90% of your time on explaining why, how and what has been done with the project's codebase.

The ReactC e-book is also a good for people who want to start a new project from scratch as it guides you on how to properly equip the new starter with your codebase and guarantees that any new future members will be introduced to your codebase quicker than any other method.

Welcome to the React Convention™ way of doing single-page apps.

PLEASE SEND US FEEDBACK, SO WE CAN IMPROVE THAT BOOK BASED ON YOUR FINDINGS AND ISSUES - mail us with details at kamil.przeorski@gmail.com



How to use the React Convention™ e-book


In order to get benefits, you need already to be familiarized with:

1) ES6/ES7 syntax

2) Concept of React Life Cycle

3) Basic Redux stuff

All the above things can be learned from the How to learn basic things in React and Redux. If you are already familiar with all that basic knowledge, then please continue.

IMPORTANT: if you are a beginner and you need a list of resources where to learn those three things, then join the FB group ReactJS Convention™ Facebook Group. I will share it with you there. Join the group if you have any trouble with this e-book as well.

Other requirement required to get all the benefits mentioned in the preface is that you will need to start your project with that react-redux-starter-kit:

https://github.com/davezuko/react-redux-starter-kit

We have found this starter, best (we know what we are talking about because we made over 15+ different React projects so far). Even Dan Abramov has noticed that this starter kit is really an awesome one:


Dan Abramov is mostly famous for creating Redux and currently he is the main guy behind the React.JS development team (he works for Facebook).


Dan Abramov

Once you start using this starter kit, definitely the e-book will provide you the value which will save your team a plenty of time at work.


In case of any issue, we are on Facebook in order to help you - just ping us on the FB or send an email.


Your first step with the React Convention™


In a default starter kit from the @davezuko, you can see a home page and a counter component which look exactly as the image below:

starter animation redux

This is a simple application with a counter, and through this e-book you will implement other views and components which will help you understand whole codebase from a big picture perspective.

To explain the whole code structure, we will start with developing a new route called Dashboard with some basic features. When we are done with it, then we will implement a registration and login which will ensure that you know how to do it 100% just on your own.

First step is to clone the starter kit and start working on this commit (the e-book was written on that commit, so for better experience you shall work on the same one):

9a03e99c0dd2e7102d43264cc495bbdd4e10dcdd

... so you will be truly working on that starting codebase:

https://github.com/davezuko/react-redux-starter-kit/tree/9a03e99c0dd2e7102d43264cc495bbdd4e10dcdd

After you have cloned the repo and you are at the same commit as me, then we can continue with the fun.

General client codebase structure of the React Convention™


We will focus on the client-side explanation for now, so let's discuss the "src" directory that has following structure:

├── src                      # Application source code
│   ├── index.html           # Main HTML page container for app
│   ├── main.js              # Application bootstrap and rendering
│   ├── components           # Reusable Presentational Components
│   ├── containers           # Reusable Container Components
│   ├── layouts              # Components that dictate major page structure
│   │   └── CoreLayout.js    # CoreLayout which receives children for each route
│   │   └── CoreLayout.scss  # Styles related to the CoreLayout
│   │   └── index.js         # Main file for layout
│   ├── routes               # Main route definitions and async split points
│   │   ├── index.js         # Bootstrap main application routes with store
│   │   ├── Home             # Fractal route
│   │   │   ├── index.js     # Route definitions and async split points
│   │   │   ├── assets       # Assets required to render components
│   │   │   ├── components   # Presentational React Components
│   │   │   └── routes **    # Fractal sub-routes (** optional)
│   │   └── Counter          # Fractal route
│   │       ├── index.js     # Counter route definition
│   │       ├── container    # Connect components to actions and store
│   │       ├── modules      # Collections of reducers/constants/actions
│   │       └── routes **    # Fractal sub-routes (** optional)
│   ├── static               # Static assets (not imported anywhere in source code)
│   ├── store                # Redux-specific pieces
│   │   ├── createStore.js   # Create and instrument redux store
│   │   └── reducers.js      # Reducer registry and injection
│   └── styles               # Application-wide styles (generally settings)
└── tests                    # Unit tests

If you were working on any FLUX or redux projects, then the project structure should be quite familiar to you. You will learn all the things located in that project step-by-step (bottom-up approach).

Don't worry if you don't get the structure so far, you will ramp up with knowledge about it during the following instructions. We will also explain each step so you will learn by doing.

Let's start with implementing a new route called "DASHBOARD".

Have you cloned the repo to your local machine? Great, now you can follow the steps below.

New dashboard route


Copy the Counter route directory and rename it as Dashboard (location is src/routes/*): animated dashboard copy

IMPORTANT: from that point we will create a new component based on a copy of the Counter route that orginally is located at src/routes/Counter in the the @davezuko's redux starter.

Next step is to find any related things to counter and then:

a) rename things from counter to dashboard

b) We will rename action called COUNTER_INCREMENT to DASHBOARD_VISITS_COUNT (this will be a number of visits to dashboard during one sessions without refreshing the browser)

animated search for counter word

Generally all the code diffs below are simply copies of Counter's component renamed with Dashboard.

Source code of the new dashboard init creation: https://github.com/przeor/ReactC/commit/d3f5d0293045af4ce75522324c06c9bf44d16a90

Create a dashboard component (from the Counter copy)


We have to copy the directory from src/components/Counter and name it Dashboard. Then rename all the variables, values and comments related to counter's route as on the below's example:

New file (copied from Counter example - you can click the diffs image to make it larger) :
src/components/Dashboard/Dashboard.js

code1

FYI: For better code diffs quality, please click on it.
New file (copied from Counter example - you can click the diffs image to make it larger):
src/components/Dashboard/Dashboard.scss

code2

FYI: For better code diffs quality, please click on it.
New file (copied from Counter example - you can click the diffs image to make it larger):
src/components/Dashboard/index.js

code3

FYI: For better code diffs quality, please click on it.

Next step is to create a link in the Header component:

Modfiy the file (you can click the diffs image to make it larger):
src/components/Header/Header.js

code4

FYI: For better code diffs quality, please click on it.

Further, we simply need to rename the files and replace all the "counter" matches to "dashboard" (again):

Renaming and changes in (you can click the diffs image to make it larger):
.../Dashboard/containers/CounterContainer.js → ...ashboard/containers/DashboardContainer.js

code5

Changes in (you can click the diffs image to make it larger):
src/routes/Dashboard/index.js

code6

Renaming and changes in (you can click the diffs image to make it larger):
src/routes/Dashboard/modules/counter.js → src/routes/Dashboard/modules/dashboard.js

code7

Changes in (you can click the diffs image to make it larger):
src/routes/index.js

code8

Here above you need to modify the routes/index.js so we will add the dashboard route.

After you run the project with:

npm run start

You shall be able to find the below app running:

913_dashboard_version1

As you can find above, there are two different routes with different reducers but with exactly the same feature - the counter has different number on both routes.

Let's improve the dashboard.

All the above screenshots were made on that commit:

https://github.com/przeor/ReactC/commit/29aad0775fd8b14eeeba519e7080f5871e881f4e

Next steps in implementing our dashboard


In the Dashboard.js file we will do some little improvements in order to progress the development:

Changes in (you can click the diffs image to make it larger):
src/components/Dashboard/Dashboard.js

914_code1

Below we have introduced statefull DashboardContainer - we need it to do this way as we are using componentDidMount for invoking the function called this.props.dashboardVisitIncrement().

IMPORTANT: The statefull DashboardContainer component is required here because our feature (increment by one on every visit of the dashboard route) require using a componentDidMount and we assume that in the components directory we want to have only "dumb components". In the src/components we will keep only stateless components (in other words they are also called dumb components).

Changes in (you can click the diffs image to make it larger):
src/routes/Dashboard/containers/DashboardContainer.js

915_code2

In the modules/dashboard.js we have made some improvements and cleanup of unnecessary code that was copied initially from the Counter:

Changes in (you can click the diffs image to make it larger):
src/routes/Dashboard/modules/dashboard.js

916_code3

... at this point you shall have an app which increments on componentDidMount of the Dashboard component as on the animation below:

917_gif_works_dashboard_v2

As you can find above, it still a very simple dashboard. The next step is to add ability of adding a list of dashboard items that has ability:

  • add an item to the list
  • remove an item to the list
  • edit an item on the list
Source code from the screenshots: https://github.com/przeor/ReactC/commit/02ed268623f69ebed59a2d4b7bb3c1c44a5c3ffc

Mocked items list on the dashboard list


Changes in (you can click the diffs image to make it larger):
src/components/Dashboard/Dashboard.js

918_code1

Above you need to add a listJSX mapping code with use of:

const listJSX = props.dashboard.dashboardItems.map((item, i) => {
    return <h4 key={i}>{item.label}</h4>
})

and then modify the:

{props.dashboard.visitsCount}

... all that changes above are required because we have modified our reducer's structure (as you can find below):

Changes in (you can click the diffs image to make it larger):
src/routes/Dashboard/modules/dashboard.js

919_code2

As you can find above we have changed the old initialState:

// old dashboard reducer structure
const initialState = 0

with the new code:

// new dashboard reducer structure
const initialState = {
  visitsCount: 0,
  dashboardItems: [
    {key: 0, label: 'Angular'},
    {key: 1, label: 'JQuery'},
    {key: 2, label: 'Polymer'},
    {key: 3, label: 'ReactJS'}
  ]
}

So generally, we have changed simple integer initialState to an object, as you can find above. We have improved also the DASHBOARD_VISITS_COUNT action:

// old codebase
[DASHBOARD_VISITS_COUNT]: (state, action) => state + action.payload

with new:

// updated codebase
  [DASHBOARD_VISITS_COUNT]: (state, action) => { 
    return Object.assign({}, state, {
      visitsCount: state.visitsCount + action.payload
    })
  }

That DASHBOARD_VISITS_COUNT change was required because as was written earlier, we have modified the initalState's structure of the dashboard reducer (so we need also to change the function that handles it). The Object.assign's visitsCount is used the same way as on the Redux's tutorial's videos on EggHead.

As a final currently, we have a little improved dashboard with a list and the visitsCount also works as previously (alongside with improved dashboard's reducer):

920_app_dashboard_with_list.gif

Source code from the screenshots: https://github.com/przeor/ReactC/commit/1b2df4cd38872810257008d82559e8a0116f011b

Add/edit item on the dashboard list


First thing to do is to prepare the action and handlers in the modules for DASHBOARD_ADD_ITEM and DASHBOARD_EDIT_ITEM:

Changes in (you can click the diffs image to make it larger):
src/routes/Dashboard/modules/dashboard.js

921_dashboard_reducer_actions

As you can find above, we have added and exported two new actions:

export function dashboardAddItem (value) {
  return {
    type: DASHBOARD_ADD_ITEM,
    payload: value
  }
}

export function dashboardEditItem (value) {
  return {
    type: DASHBOARD_EDIT_ITEM,
    payload: value
  }
}

... then after an action is requrested from a Dashboard's component, we handle it with:


  [DASHBOARD_ADD_ITEM]: (state, action) => { 
    const mockedId = Math.floor(Date.now() / 1000)
    const newItem = {
      label: action.payload,
      id: mockedId
    }
    return Object.assign({}, state, {
      dashboardItems: [...state.dashboardItems, newItem]
    })
  },
  [DASHBOARD_EDIT_ITEM]: (state, action) => { 
    const newLabel = action.payload.val
    const index = action.payload.editedItemIndex
    let immutableDashboardItems = [...state.dashboardItems]
    immutableDashboardItems[index].label = newLabel
    return Object.assign({}, state, {
      dashboardItems: immutableDashboardItems
    })
  }

In both functions above, we are returining a new object with return Object.assign. Rest of the code shall be self-explaining for you.

Changes in (you can click the diffs image to make it larger):
src/routes/Dashboard/containers/DashboardContainer.js

922_dashboard_container

Above we have simply added the functions that were created in the Dashboard/modules/dashboard.js file so we import dashboardAddItem and dashboardEditItem .. and continuation of the same file below:

continuation of src/routes/Dashboard/containers/DashboardContainer.js

923_dashboard_container

On the above code base, we have added new functions called inputOnChange, onSubmit and itemOnEdit so we need improve our constructor as well:

  constructor(props) {
    super(props)

    this.inputOnChange = this.inputOnChange.bind(this)
    this.onSubmit = this.onSubmit.bind(this)
    this.itemOnEdit = this.itemOnEdit.bind(this)


    this.state = {
      inputValue: '',
      editedItemIndex: null
    }
  }

... the inputValue keeps the current value of a text input. In the editedItemIndex we keep an ID of currently edited item, if there is none in edit then we make it null.

Later we handle all the functions logic with:

  inputOnChange(e) {
    this.setState({ inputValue: e.target.value })
  }

  itemOnEdit(itemIndex) {
    const editedItem = this.props.dashboard.dashboardItems[itemIndex]
    this.setState({ inputValue: editedItem.label, editedItemIndex: itemIndex })
  }

  onSubmit(e) {
    e.preventDefault()
    const val = this.state.inputValue
    const editedItemIndex = this.state.editedItemIndex
    if(val && editedItemIndex !== null) {
      this.props.dashboardEditItem({ val, editedItemIndex })
      this.setState({ inputValue: '', editedItemIndex: null })
    } else if(val) {
      this.props.dashboardAddItem(val)
      this.setState({ inputValue: '' })
    } else {
      alert(`Value can't be empty`)
    }
  }

There isn't nothing fancy so I won't describe too much in details (if you think it requires more detailed description then please mail us at kamil.przeorski@gmail.com).

Final part is the render method:

  render () {
    return (
        <Dashboard {...this.props} 
          editedItemIndex={this.state.editedItemIndex}
          itemOnEdit={this.itemOnEdit}
          inputValue={this.state.inputValue}
          inputOnChange={this.inputOnChange}
          onSubmit={this.onSubmit} />
    );
  }

... we are passing some callbacks as this.inputOnChange, this.onSubmit and this.itemOnEdit - those callbacks are required to send to our dashboard "dumb component".

Changes in (you can click the diffs image to make it larger):
src/components/Dashboard/Dashboard.js

924_dashboard_component_dumb

Above, we have improved our listJSX map function:

  const listJSX = props.dashboard.dashboardItems.map((item, i) => {
    let itemJSX;
    if(props.editedItemIndex === i) {
      itemJSX = <p><b><u>{item.label}</u></b></p>
    } else {
      itemJSX = <p>{item.label}</p>
    }
    return <h4 
            key={i} 
            onClick={props.itemOnEdit.bind(undefined,i)}
            style={{cursor: 'pointer'}}>
              {itemJSX}
          </h4>
  })

... so now it make an edited item bold and underlined.

Next we have improved the render function:

  return (
  <div>
      <h2 className={classes.dashboardContainer}>
        Dashboard visits:
        {' '}
        <span className={classes['dashboard--green']}>
          {props.dashboard.visitsCount}
        </span>
      </h2>
    <form onSubmit={props.onSubmit}>
      <input 
        value={props.inputValue}
        type='input' 
        placeholder='type here a value' 
        style={{width: 300}}
        onChange={props.inputOnChange} />
      <input 
        type='submit' 
        value={ props.editedItemIndex === null ? 'Add New Item To The List' : 'Edit Item' } />
    </form>
    {listJSX}
  </div>
)}

We have added standard form and inputs - they are communicating with DashboardContainer with use of callbacks (the value={props.inputValue} and onChange={props.inputOnChange} take care of it). The submit button value is determined by the props.editedItemIndex - so if it is a null then that means that a user hasn't clicked any item, yet.

This is the end results of all the changes we've made above:

925_edit_add_anim.gif

Source of the commit's screenshots: 
https://github.com/przeor/ReactC/commit/b94cf9935ab632065ee0d9b5f9126159317c1178

Reorder an item on the dashboard list (pure React Drag and Drop example)

We will implement the reordering in a proper React way without using any external reordering components or libraries.

Let's start from the reducers and actions that are related to the reordering feature:

Changes m the server if the user has provided correct or incorrin (you can click the diffs image to make it larger):
src/routes/Dashboard/modules/dashboard.js

926_reducer_action_reorder

An explanation for the code from the diffs - you need to understand what we will send as a payload (const reorder = action.payload) in the function below:

  [DASHBOARD_REORDER_ITEM]: (state, action) => { 
    const reorder = action.payload
    const reorderItem = state.dashboardItems[reorder.start]
    let newDashboardItems = []
    state.dashboardItems.map((item, i) => {
      if(i === reorder.start) {
        return
      }

      // we need that if statement because
      // the behaviour is determined if someone is dragging
      // an item from higher to lower place on the list or vice versa
      if(reorder.end < reorder.start) {
        if(i === reorder.end) {
          newDashboardItems.push(reorderItem)
        }
        newDashboardItems.push(item)
      } else {
        newDashboardItems.push(item)
        if(i === reorder.end) {
          newDashboardItems.push(reorderItem)
        }
      }
    })

    return Object.assign({}, state, {
      dashboardItems: newDashboardItems
    })
  }

We will send an object with the following start and end properties as on the example below:

// just an example schema of
// const reorder = action.payload
// so you can see what values are sent to the DASHBOARD_REORDER_ITEM
{ 
  start: parseInt(this.state.draggedItemIndex),
  end: parseInt(droppedItemId)
}

The start is a number of order in the dashboardItems array. The end property is an order number of a dropped-on-the-item div (the id of an item that a user dropped on the dragged item). We map over all the items in our array and based on the dragging data (start and end) we create a new array called newDashboardItems. Rest of the code shall be self-explained.

Changes in (you can click the diffs image to make it larger):
src/routes/Dashboard/containers/DashboardContainer.js

927_dashboardContainer1

Above we have added draggedItemIndex to the state which will be set in the handleOnDragStart function. Beside that we are also binding this to the handleOnDrop (here we handle login when a user drops a dragged div) and handleOnDragOver (this function is more like placeholder, when you can add more custom logic if you want make this dragging functionality more fancy).

... and continuation of the containers/DashboardContainer.js:

928_dashboardContainer2

From the above diffs we can find new functions related to the DnD:

  handleOnDragStart (e) {
    const id = e.target.id
    this.setState({ draggedItemIndex: id })
  }

  handleOnDragOver (e) {
    e.preventDefault()
    e.dataTransfer.dropEffect = 'move';  // See the section on the DataTransfer object.
    // You can add here more logic if required
  }

  handleOnDrop (e) {
    const droppedItemId = e.currentTarget.id
    let reorderVal = { 
      start: parseInt(this.state.draggedItemIndex),
      end: parseInt(droppedItemId)
    }

    // the div ids have to be numbers to reorder correctly
    // and the start and end value has to be different (otherwise reorder is not required)
    const reorderIsCorrect = !isNaN(reorderVal.start) && !isNaN(reorderVal.end) && reorderVal.start !== reorderVal.end

    if(reorderIsCorrect) {
      this.props.dashboardReorderItems(reorderVal)
    }

    this.setState({ draggedItemIndex: null })
  }

The most important part to understand this code above is that the div has an id as a number (check the code from the src/components/Dashboard/Dashboard.js which is listed below) and that number is an order in the newDashboardItems array that is kept in the dashboard reducer.

Based on that assumption, we can use:

handleOnDragStart (e) {
  const id = e.target.id
  this.setState({ draggedItemIndex: id })
}

to set the start item. Below you can find how we get the final end newDashboardItems number

handleOnDrop (e) {
  const droppedItemId = e.currentTarget.id
  let reorderVal = { 
    start: parseInt(this.state.draggedItemIndex),
    end: parseInt(droppedItemId)
  }
  // rest of the code below has been striped out

Rest of the code shall be self-explanied (if something is unclear, please contact us at kamil.przeorski@gmail.com).

Changes in (you can click the diffs image to make it larger):
src/components/Dashboard/Dashboard.js

929_changes_in_dashboard_map

  const listJSX = props.dashboard.dashboardItems.map((item, i) => {
    let itemJSX;
    if(props.editedItemIndex === i) {
      itemJSX = <p><b><u>{item.label}</u></b></p>
    } else {
      itemJSX = <p>{item.label}</p>
    }
    return <h4 
            id={i}
            draggable='true'
            onDragOver={props.handleOnDragOver}
            onDragStart={props.handleOnDragStart}
            onDrop={props.handleOnDrop}
            key={i} 
            onClick={props.itemOnEdit.bind(undefined, i)}
            style={{cursor: 'pointer'}}>
              {itemJSX}
          </h4>
  })

The last part is to add callbacks (onDragOver, onDragStart and onDrop), modify style and add the id={i} (so we can take a DIV's id as an information required to make DnD works).

This is how the app shall behave after that all our changes:

930_dashboard_reordering

Source of the commit's screenshots: 
https://github.com/przeor/ReactC/commit/1f40f87a9b0ab4bd823e7a922f88f7a8a8ab1b9a

Login with mocked data (front-end)

We will implement login functionality that works purely on front-end (later we will create a simple backend enpoint). First step is to prepare new session reducer which will be app-wise (it will be used all across the app).

Create a new directory at "src/modules" location
.. and then create a new file src/modules/session.js
IMPORTANT: we are creating this module directory in the main src/** location because that one reducer need to be available all across the app. In the session reducer we will keep all the actions and data related to our user session (like a token or login status).

931_code1

Above we have created SESSION_LOGIN_SUCCESS and SESSION_LOGIN_FAIL actions.

The interesting part is the loginAsync function which looks like below:

export const loginAsync = (loginObj) => {
  return async (dispatch, getState) => {
    let loginToken = await new Promise((resolve) => {
      setTimeout(() => {
        resolve()
      }, 200)
    }).then(() => {

      if(loginObj.user === 'przeor' && loginObj.password === 'mwp.io') {
        return 'www.mwp.io' // just a mocked token
      } else {
        return 'invalid' // mocked non successful login
      }
    })

    if(loginToken !== 'invalid') {
      dispatch(loginSuccess(loginToken))
      dispatch(push('/dashboard'))
    } else {
      dispatch(loginFail(loginToken))
    }

  }
}

The function received the loginObj which is composed of two keys:

loginObj.user
loginObj.password

That data will be sent to the backend server (after we will unmock now), currenly we only check if the logins are correct with:

if(loginObj.user === 'przeor' && loginObj.password === 'mwp.io') {

Also as you can find, we have created an asynchronous function (return async (dispatch, getState) => {) which awaits on the promise resolve after 200 milliseconds timeout (currently it's a mock, later it will be real POST to the server). Then depending on if you have provided correct login details, it returns:

    }).then(() => {

      if(loginObj.user === 'przeor' && loginObj.password === 'mwp.io') {
        return 'www.mwp.io' // just a mocked token
      } else {
        return 'invalid' // mocked non successful login
      }
    })

The 'invalid' means, that you have provided incorrect login details (later that will be sent back from the server).

The last executing code of the loginAsync is:

    if(loginToken !== 'invalid') {
      dispatch(loginSuccess(loginToken))
      dispatch(push('/dashboard'))
    } else {
      dispatch(loginFail(loginToken))
    }

The dispatch comes from the react-thunk - that means, that the loginAsync is returned immediately and waits for lazy evaluation (in our case on a respond from the server if the user has provided correct or incorrect details) and then dispatch an action depending on the server response.

On a valid details, we dispatch two actions:

dispatch(loginSuccess(loginToken))
dispatch(push('/dashboard'))

First dispath is for the loginSuccess with the loginToken value as a variable and the second the push which comes from the react-router-redux (import {push} from 'react-router-redux') - this function simply push user to /dashboard route if he is on a different one with use of push function from the routing librare that we use..

continuation of src/modules/session.js

932_code2

... and above you can find the remaining action handlers and the session reducer is looking as following:

// ------------------------------------
// Action Handlers
// ------------------------------------
const ACTION_HANDLERS = {
  [SESSION_LOGIN_SUCCESS]: (state, action) => {
    return Object.assign({}, state, {
      loginToken: action.payload,
      isNotLoggedIn: false
    })
  },
  [SESSION_LOGIN_FAIL]: (state, action) => {
    return Object.assign({}, state, {
      loginToken: action.payload
    })
  }
}

// ------------------------------------
// Reducer
// ------------------------------------
const initialState = { 
  count: 0,
  isNotLoggedIn: true,
  loginToken: 'none'
}

There is a flag for simplicity which handles if a user is logged in (isNotLoggedIn) and we keep in that reducer the token which shall be sent to the backend on each request while a user is logged in (the loginToken may come from the backend's JSON Web Token).

For now we are implementing very simple login solution and then we will build up on it to make it more powerful.

Changes in (you can click the diffs image to make it larger):
src/store/reducers.js

933_code3

Above we are simply importing the session reducer with import session from '../modules/session' and then we add it to the combineReducers:

export const makeRootReducer = (asyncReducers) => {
  return combineReducers({
    // Add sync reducers here
    router,
    session,
    ...asyncReducers
  })
}

That's all there, then we need to improve CoreLayout.js:

Changes in (you can click the diffs image to make it larger):
src/layouts/CoreLayout/CoreLayout.js

934_code4

Above, we make the CoreLayout as a smart component which is connected to the session reducer with use of:

import React, { Component, PropTypes } from 'react'
import Header from '../../components/Header'
import classes from './CoreLayout.scss'
import '../../styles/core.scss'
import { connect } from 'react-redux'
import { loginAsync } from '../../modules/session'

const mapActionCreators = {
  loginAsync
}

const mapStateToProps = (state) => ({
  session: state.session
})

As you shall already now the mapActionCreators and mapStateToProps are connected to the redux via import { connect } from 'react-redux'.

Then we improve the CoreLayout component as following:

class CoreLayout extends Component {
  static propTypes = {
    children: PropTypes.object.isRequired
  }

  constructor(props) {
    super(props)
    this.handleLogin = this.handleLogin.bind(this)
  }

  handleLogin(loginObj, e) {
    e.preventDefault()
    this.props.loginAsync(loginObj)
  }

  render () {
    return (
      <div className='container text-center'>
        <Header 
          handleLogin={this.handleLogin} 
          session={this.props.session} />
        <div className={classes.mainContainer}>
          {this.props.children}
        </div>
      </div>)

    }
}


export default connect(mapStateToProps, mapActionCreators)(CoreLayout)

In the CoreLayout you can find a function with handles login called handleLogin(the e.preventDefault() is a standard thing so I won't explain how it works here) which sends the user and password's object to the session reducer with use of this.props.loginAsync(loginObj).

To the Header's component we send down the handleLogin and the session's reducer data (session={this.props.session}).

Changes in (you can click the diffs image to make it larger):
src/components/Header/Header.js

935_code5

Above the standard thing for login forms as prepareLoginJSX function which returns a form. There are some on change as onChange={usernameOnChange} and onChange={passwordOnChange} which updates the loginObj on each user's input. Later after a user hits the submit button, then the loginObj is sent via callback to the session reducer (onSubmit={props.handleLogin.bind(undefined, loginObj)}).

936_code6

... and in the export const Header = (props) => { we simply return the login form in case if a user is not logged in props.session.isNotLoggedIn and in case if a user put's wrong details then we show him a message:

if(props.session.loginToken === 'invalid') {
  loginMessageJSX = <p>Invalid login details, please try with correct user and password</p>
}

Everything is done besides the Dashboard improvements:

937_code7

Above we simply add the session reducer to the DashboardContainer and then check if a user is logged in and if not then we show him a message via render function:

render () {
  if(this.props.session.isNotLoggedIn) {
    return <h4>Please login in order to access your dashboard</h4>
}

Based on that all changes we have made then you shall be able to run this app with login required to see the dashboard as on the animation below:

938_anim

Commits screenshots source: https://github.com/przeor/ReactC/commit/52ac2ef6317e53ad736355506db346eab898c7e2

A summary (front-end part)

This is first part of the e-book related to front-end implementations.

How do you like it? What we can improve? Please mail us with your feedback.

Do you want the backend implementation? Then we need your feedback what backend tech stack you would like to use in your future projects?

WHAT TO USE ON THE BACKEND IN THE React Convention™ e-book:

a) GraphQL + Relay

b) FalcorJS

c) Standalone REST API implementation with Axios on the front-end

Please mail us at kamil.przeorski@gmail.com and based on your feedback we will continue this free book.

Here you can post any suggestions about the e-book: ReactJS, React Native, GraphQL developers on FB.

Let's get in touch:
GitHub.com/przeor

Twitter.com/przeor



Search In Google For ReactJS Convention™