Storybook
Tags: react, redux, googlecloud, javascript
Published: 2021-05-26 15:32:02

This is it. 🥺
 
This is module 5. 🤓
 
This is my **last** module at Flatiron School. 😱

What a journey it has been, 10 months of coding from building a [Command Line Interface (CLI)](https://dev.to/fentybit/api-cli-space-exploration-108f), [Sinatra](https://dev.to/fentybit/sinatra-x-street-fighter-1hgl), [Ruby on Rails](https://dev.to/fentybit/plan-my-md-visit-c0l), [Vanilla JavaScript](https://dev.to/fentybit/know-it-all-2kpp) and now, **ReactJS/Redux**. I have grown so much, and am truly excited to learn more languages, frameworks and libraries upon graduation. While my previous mod projects encompass personal interests of mine (from space exploration, Street Fighter, tele-health platform to trivia game app), I have kept this particular idea until the very end. 

I have been a long-advocate for having a meaningful connection through self-reflection. Having a digital journaling app to log events, places, moods, and self-reflections from different points of view would fulfill my personal pursuits of journaling experience. While some journal apps I have seen simply record special memories and events, some focus more on mental health, mindfulness and self-care. I have decided to approach this app build with basic features of recorded events and necessary attributes. Future improvements can possibly include mood tracker with A.I. collecting info on how the user is doing. After dedicating sometime to research on a few journal apps such as [Reflectly](https://apps.apple.com/us/app/reflectly-journal-ai-diary/id1241229134), [Diaro](https://diaroapp.com/), [Daylio](https://daylio.net/), and others, I emulate most of my app build features following [Day One](https://dayoneapp.com/) and [Notion](https://www.notion.so/). I love the overall user flow from Day One, and the postulation of *all-in-one workspace* from Notion. There are a couple of technical challenges I would like to pursue personally such as working with Google Maps API.

---

#### Table of Contents
1. [User Story and Model Associations](#chapter-1)
2. [Rails API Back-End](#chapter-2)
3. [Routes, Controllers and Serializers](#chapter-3)
4. [React — Getting Started](#chapter-4)
5. [`Action` → `Reducer` → `New State`](#chapter-5)
6. [Nested Routes in React Router](#chapter-6)
7. [Google Maps Platform APIs](#chapter-7)
8. [Material-UI and Lessons Learned](#chapter-8)
9. [Build Status and Future Improvement](#chapter-9)

---

#### 1. User Story and Model Associations <a name="chapter-1"></a>

I brainstormed my app through building wireframes at first. The exercise helped me to collect some understanding of model relationships, necessary attributes, components and overall user interface. I realized that my wireframing exercise eventually became an overarching goal. 🥺

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0bsav7almji7lueyuufw.png)

As the user begins their journaling experience, the user will be prompted to fill out a form of an *entry event*. Each entry event carries *event title, date, time, location, vibe, description and photo*. The user can personalize each entry by assigning a *category*. After several entries and categories propagated over some time, when the user selects a category, it should list its respective event entries. For example, under category *'restaurants'*, the user will see all of their food ventures entries. As the user selects a specific entry, it will prompt a *show page* specific to the selected event `id`. The user can reflect all of their journal entries through various points of view: *calendar*, *map* and *photos*. For example, if the user selects a map view, it will show all pinpoints of recorded places. The user can select each pinpoint, and it should also display event details in correspond to the selected entry `id`.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/87h3f8pylh2kc1ohqum0.png)

There are 4 main models `User`, `Category`, `Event` and `Image` with their associations as follows.

user `has_many` :events 

category `has_many` :events

event `belongs_to` :user
event `belongs_to` :category
event `has_one` :image

image `belongs_to` :event

---

#### 2. Rails API Back-End <a name="chapter-2"></a>

I have built Rails API previously, and surprisingly... I have only a small memory recollection. 😅  

I initiated the prompt command `rails new Storybook_backend --database=postgresql --api --no-test-framework`. The `--api` will remove unnecessary features and middleware with controllers inheriting from `ActionController::API`, and `--no-test-framework` will remove any testing framework. PostgreSQL database is helpful when I need to deploy on Heroku. Make sure to include gem `rack-cors` and `bcrypt` when `bundle install`. The next step is to generate Active Record Models for `User`, `Category`, `Event` and `Image`, and execute `rails db:create && rails db:migrate`.

```ruby
ActiveRecord::Schema.define(version: 2021_05_24_194555) do
  create_table "categories", force: :cascade do |t|
    t.string "name"
  end

  create_table "events", force: :cascade do |t|
    t.bigint "category_id", null: false
    t.bigint "user_id", null: false
    t.string "title"
    t.date "date"
    t.time "time"
    t.string "location"
    t.string "latitude"
    t.string "longitude"
    t.string "vibe"
    t.string "description"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.index ["category_id"], name: "index_events_on_category_id"
    t.index ["user_id"], name: "index_events_on_user_id"
  end

  create_table "images", force: :cascade do |t|
    t.bigint "event_id", null: false
    t.string "url"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.index ["event_id"], name: "index_images_on_event_id"
  end

  create_table "users", force: :cascade do |t|
    t.string "username"
    t.string "password_digest"
    t.string "firstname"
    t.string "lastname"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
  end

  add_foreign_key "events", "categories"
  add_foreign_key "events", "users"
  add_foreign_key "images", "events"
end
```

I am content with my `schema.rb` build, and it is always a good practice to test my models and associations with `rails console`.

---

#### 3. Routes, Controllers and Serializers <a name="chapter-3"></a>

I provided only the required back-end routes for my front-end's asynchronous `fetch()` actions.

```ruby
Rails.application.routes.draw do
  namespace :api do
    namespace :v1 do
      resources :users, only: [:index, :create]
      post '/login', to: 'auth#create'
      get '/profile', to: 'users#profile'

      resources :events, only: [:index, :create, :show, :update]
      resources :categories, only: [:index, :create]
      resources :images, only: [:index]
    end 
  end 
end
```

Moving on to my controllers, I spent most of my time applying **JWT** *(JSON Web Tokens)* in my `ApplicationController`, `Api::V1::UsersController` and `Api::V1::AuthController`. The `ApplicationController` defines `JWT.encode`, `JWT.decode` and most importantly `authorized` instance method to barricade access to the other controllers. Only an *authorized* user can access other controllers. The `AuthController` *create action* will provide authentication for users logging in, and the `UsersController` *create action* allows a new user signing up.

```ruby
class Api::V1::EventsController < ApplicationController
  skip_before_action :authorized, only: [:create]
  ...

  def create 
    if params[:category] != ''
      @category = Category.find_or_create_by(name: params[:category])
	
      @event = Event.create(title: params[:title], vibe: params[:vibe], date: params[:date], time: params[:time], location: params[:location], latitude: params[:latitude], longitude: params[:longitude], description: params[:description], category_id: @category.id, user_id: current_user.id)
	
      if params[:image] != ''
        uploaded_image = Cloudinary::Uploader.upload(params[:image])

        @image = Image.create(url: uploaded_image['url'], event_id: @event.id)
      end 
	
      render json: { event: EventSerializer.new(@event), category: CategorySerializer.new(@category) }, status: :created  
    else 
      render json: { error: 'Failed to create Event.' }, status: :not_acceptable
    end 
  end
	
  ...
end
```

I had many `byebug` exercises on `create` and `update` actions in `Api::V1::EventsController`. Not only a new `event` will be created, but also its respective `category` and `image`. I have an event entry form on my front-end to accommodate user inputs. I utilize [Cloudinary](https://cloudinary.com/) in order to manipulate images with a URL-based API. The rest of my controllers' actions are mostly `index` and `show`. This is where [Active Model Serializers](https://github.com/rails-api/active_model_serializers) helps with displaying any intended attributes to pass information necessary over to front-end's Redux state management. Including model relationships helps to display arrays of event's category and image in one single *Object*.

```ruby 
class EventSerializer < ActiveModel::Serializer
  attributes :id, :title, :date, :date_strftime, :time, :time_strftime, :location, :latitude, :longitude, :vibe, :description

  belongs_to :category
  belongs_to :user
  has_one :image
end
```

I believe that's all I have for my back-end! I have attached my GitHub repo below.

{% github fentybit/Storybook_backend %}

--- 

#### 4. React — Getting Started <a name="chapter-4"></a>

I started with `npx create-react-app storybook` and `npm install redux && npm install react-redux` as for Redux state management. I learned that NPM packages do not allow upper case characters because unix filesystems are case-sensitive (as I previously tried `Storybook`, and 🤨  it failed). For whatever reason, I froze for quite sometime, not knowing where to get a start with my React app. I have decided to step back and brainstorm a file structuring diagram, which helped tremendously as I progressed through my code.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zmlv102x3y7fynqwjg6w.png)

I started with my `index.js` file, and set up my `Provider` and `store`. Following [best practice](https://www.freecodecamp.org/news/a-better-way-to-structure-react-projects/), I kept `actions`, `reducers` and `store.js` inside of the `Redux` folder. The `App.js` carries the first parent container for my `ProfileContainer`. This component becomes a portal once a user successfully signs in, and it will navigate the user to 3 *container components*, `NavBar`, `EventViewList` and `DisplayContainer`. The rests are *presentational components* and most of them are built as functional components which rely mainly on *props*. With all that said, I definitely spent a good chunk of time with file-naming, aligning file structures and folder hierarchy. On another note, [Redux DevTools](https://github.com/zalmoxisus/redux-devtools-extension) is a great tool that I set up in order to view Redux state.

---

#### 5. `Action` → `Reducer` → `New State` <a name="chapter-5"></a>

`connect()` and `Provider` play a big role as part of **React Redux** middleware. `Provider` ensures that my React app can access data from the store, and `connect()` allows whichever component to specify which state and actions the app needs access to. I implemented `combineReducers` to consolidate all reducers and set the Redux state management.

```javascript
export const fetchEvent = (eventId) => {
  return (dispatch) => {
    if (localStorage.getItem('token')) {
      let token = localStorage.getItem('token')

      fetch(`https://your-storybook.herokuapp.com/api/v1/events/${eventId}`, {
        headers: {
          'Authorization': `bearer ${token}`
        }
      })
        .then(resp => resp.json())
        .then(data => dispatch({ type: 'GET_EVENT', payload: data }))
    }
  }
}
```

One example of my actions would be the `fetchEvent(eventId)` that asynchronously fetches my back-end route, and `dispatch` a reducer to return a value.

```javascript
function eventReducer(state = [], action) {
  switch (action.type) {
    case 'GET_EVENT':
      return action.payload.event

    default:
      return state
  }
}

export default eventReducer;
```

I should be able to access the object value of `event` with `mapStateToProps` in any desirable component to display the current state of event entry. I have a total of 8 reducers from category, error, user, token and others under one `rootReducer`.

---

#### 6. Nested Routes in React Router <a name="chapter-6"></a>

ReactJS relies upon **Client-Side** routing to handle routing, fetching and displaying data in the browser. It is after all a **Single-Page Application (SPA)**. While it benefits in speed, it also presents more design challenges. I tried my best to achieve proper RESTful routing.

```javascript
import React from 'react';
import { Switch, Route } from 'react-router-dom';
...

export default function EventViewList({ categories, events, props, token, url, user }) {
  ...

  return (
    <div align='center'>
      <Switch>
        <Route path={`${url}/calendar/:eventId`} render={() => <InfiniteCalendar Component={withMultipleDates(Calendar)} interpolateSelection={defaultMultipleDateInterpolation} onSelect={date => renderSelectedEventDate(date)} selected={selectedDatesArray} />} />
        <Route path={`${url}/calendar`} render={() => <InfiniteCalendar Component={withMultipleDates(Calendar)} interpolateSelection={defaultMultipleDateInterpolation} onSelect={date => renderSelectedEventDate(date)} selected={selectedDatesArray} />} />
        <Route path={`${url}/map/:eventId`} render={(routerProps) => <MapView {...routerProps} events={events} />} />
        <Route path={`${url}/map`} render={(routerProps) => <MapView {...routerProps} events={events} />} />
        <Route path={`${url}/newentry`} render={() => <InfiniteCalendar selected={today} />} />
        <Route path={`${url}/photos/:eventId`} render={() => <PhotosView />} />
        <Route path={`${url}/photos`} render={() => <PhotosView />} />
        <Route path={`${url}/:categoryId/:eventId/edit`} render={(routerProps) => <CalendarView {...routerProps} categories={categories} events={events} token={token} user={user} />} />
        <Route path={`${url}/:categoryId/:eventId`} render={(routerProps) => <CalendarView {...routerProps} categories={categories} events={events} token={token} user={user} />} />
        <Route path={`${url}/:categoryId`} render={(routerProps) => <CalendarView {...routerProps} categories={categories} events={events} token={token} user={user} />} />
        <Route path={url} render={() => <InfiniteCalendar selected={today} />} />
      </Switch>
    </div>
  )
}
```

The `EventViewList` component is my middle presentational component that displays various UI components in correspond to the left navigation bar. I would refer my `EventViewList` as an intermediary. As the user navigates through, my right presentational component, `EventDisplay`, will exhibit detailed information. Snippet below represents Route path `${url}/calendar/:eventId` where `calendar` view displays propagated entry dates the user had previously recorded, and `eventId` will fetch the associated event entry from provided `events` state from Redux store.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/79soe9jveukwo7j7ud6p.png)

---

#### 7. Google Maps Platform APIs <a name="chapter-7"></a>

I have decided to utilize [google-maps-react](https://www.npmjs.com/package/google-maps-react) and [react-google-autocomplete](https://www.npmjs.com/package/react-google-autocomplete) NPM packages. Their documentation is pretty solid and provides a straightforward code implementation to my Storybook MVP needs. API can be retrieved from Google Developers Console, and I include Geocoding API, Maps JavaScript API and Places API. Once `GoogleApiWrapper` from `'google-maps-react'` and `PlacesAutocomplete`  from `'react-places-autocomplete'` are imported to my `Form` component, the user can automatically submit an address and/or location from the autocomplete textfield. It should automatically send an API request to retrieve the location's latitude and longitude. Each location and its respective coordinates will be saved in the PostgreSQL database, and that's how I was able to collect an array of various coordinates and propagate them to a map view. I also learned how to save an API_KEY by adding `REACT_APP_` to my API key in the `.env` file.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s4a7i4ts46fqtuq78q7d.gif)

---

#### 8. Material-UI and Lessons Learned <a name="chapter-8"></a>

I had much fun perusing through [Material-UI](https://material-ui.com/) library. If given more time, I would love to develop Storybook mobile UI. Current project build is focused on browser desktop UI. There are a lot of customization theming that piques my design interest. 

Anyhow... I am glad I had the chance to learn ReactJS/Redux, and it definitely [speaks on its own popularity and demand](https://gist.github.com/tkrotoff/b1caa4c3a185629299ec234d2314e190). React provides a modular way to separate code and functionality in *declarative writing* structure, producing highly reusable and independent entities. I now feel comfortable with JSX syntax, container vs. presentational components, Redux state management, Client Routing and finally, implementing Google Maps API. Check out my GitHub repo!

{% github fentybit/Storybook_frontend %}

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i6o8a8jphtni302slpng.gif)

---

#### 9. Build Status and Future Improvement <a name="chapter-9"></a>
Storybook was completed in a 2-week timeframe from implementing Rails back-end, ReactJS front-end, Cloudinary API, Google Maps API and Material-UI library. I have several ideas as I progressed through building my MVP (Minimum Viable Product). Future cycle of product development as follows:

- Search Bar. Over the time, the user will have many events, and it gets troublesome when the user needs to immediately access a specific event entry. A search bar to quickly type event title and access the journal entry would be useful.
- Add `Friend` to model associations. I envision my app to emulate similar concept such as Instagram. Instead of creating a simple journaling app, what about a social journaling platform. Each user can personalize their privacy whether or not they'd like to share with their friends. ![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yfajn4d8bpaqt174xej0.png)
- Adding mood tracker. Current attribute `vibe` to capture my preliminary attempt of gathering user mood data on each event entry. I found a [mood tracker API](https://github.com/drypycode/MoodTracker-API) that I would love to integrate in future project build. User can view their journal entries based on `Mood` under View NavBar.
- Current event entry only allows one image upload. User should be able to upload multiple images, insert GIF and video upload.
- Create a toggle track for dark mode. 😎

***

Post Scriptum:
This is my Module 5 capstone project with Flatiron School. I believe one of the catalyst to becoming a good programmer is to welcome constructive criticism. Feel free to drop a message. 🙂
 
Keep Calm, and Code On.

External Sources:
[ReactJS Documentation](https://reactjs.org/)
[Cloudinary](https://cloudinary.com/)
[Google Maps React](https://www.npmjs.com/package/google-maps-react)
[React Google Autocomplete](https://www.npmjs.com/package/react-google-autocomplete)
[React Infinite Calendar](https://github.com/clauderic/react-infinite-calendar)
[Material-UI](https://material-ui.com/)
[Unsplash](https://unsplash.com/)

***
<center>[**fentybit**](https://fentybit.me/) | [GitHub](https://github.com/fentybit) | [Twitter](https://twitter.com/fentybit) | [LinkedIn](https://www.linkedin.com/in/fentybit/)</center>