22. create a template for reactjs with webpack

home

Contents

1. Description

     steps to to the app running.
     step 1: In code visual studio, I create a folder, cloning from the sample code.
     step 2: In my mac, I drag and drop the folder into the ide.
     step 3: From the menu view, click integrated window.
     step 4: in the terminal window, I enter npm install 
     step 5: edit package.json as below
                 "script": {
                    "build": "webpack",
                    "dev": "webpack-dev-server"
                 }
     step 6.1: enter npm run dev to launch the server
     step 6.2: open a browser, enter locahost:3000, the page is like below:
         |-----------------------------------------------------
         |                1 icon1 days                  
         |   1 icon2 days            0 icon2 days
         |                                                     
         |-----------------------------------------------------|
         |        home            add         list             |     
         |-----------------------------------------------------|
    
         * the above is the home page, include two parts
             - the summary of a user's ski data
             - the menu for home, add, list
         * there are some other pages, home, add, list...
                          

     examine a subfoler structure  
          |dist
             | assets
             index.html      

      examine webpack.config.js
          output: {
		      path: "dist/assets",
		      filename: "bundle.js",
		      publicPath: "assets"
	      },
      examine /dist/index.html
         in script tag    type="text/javascript" src="assets/bundle.js"
      
          - When you run the server, the bundle file is a virtual one.
          - It is not a physical file, it is in memory. It is easy to easy for listen and respond.
          - It you stop the server, and enter npm run build, a real file is created.
          - For either way, folder assets is referred.
      
      

2. data

      Other subfolder structure is as below:
              | src
                 | components
                     . Whoops404.sj
                     . App.js
                     . Menu.js
                     . SkiDayCount.js
                     . AddDayForm.js
                     . SkiDayList.js
                     . SkiDayRow.js

      /src/components/App.js
            export class App extends Component {
	        constructor(props) {
		        super(props)
		        this.state = {
			        allSkiDays: [
                    
			        {
				        resort: "Squaw Valley",
				        date: "2016-01-02",
				        powder: true,
				        backcountry: false
			        }
                    
		        ]
		        }
		        this.addDay = this.addDay.bind(this)
                        
                            - addDay is an event handler.
                            - binding is required.
                        
	        }     

            

3. JSX and Component

         ----  home page    ------                
             router                     
                App                  
                  div                    
                     Menu
                     SkiDayCount

             note: In addition to display the hierarchy, the state data is also display.
                 allSkiDays(array)
                    resort, date, powder, backcountry
           
           
         ----  list page --------
              router                     
                 App                  
                   div                    
                      Menu
                      SkiDayList
                          table
                            thead
                            tbody
                               SkiDayRow(many)

         ----  form page --------
              router                     
                 App                  
                   div                    
                      Menu
                      AddDayForm
                         
                

4. index.js

... import { Router, Route, hashHistory } from 'react-router' window.React = React render( <Router history={hashHistory}> <Route path="/" component={App}/> <Route path="list-days" component={App}> <Route path=":filter" component={App} /> </Route> <Route path="add-day" component={App} /> <Route path="*" component={Whoops404}/> </Router>, document.getElementById('react-container') )

5. Menu.js

import { Link } from 'react-router' import HomeIcon from 'react-icons/lib/fa/home' ... import '../stylesheets/ui.scss' export const Menu = () => <nav className="menu"> <Link to="/" activeClassName="selected"> <HomeIcon /> </Link> .... </nav>
    ---- one test on css ----------
         1. open /src/stylesheets/ui.scss
         2. edit
               nav.menu {
                   ...
                   bottom: 0;    
                   ...
               }
               change bottom to top
         3. save
         4. the server will be automatically update.
            The menu will be postioned from the bottom to the top.
             

6. App.js - render method

render() { return ( <div className="app"> <Menu /> {(this.props.location.pathname === "/") ? <SkiDayCount total={this.countDays()} powder={this.countDays( "powder" )} backcountry={this.countDays( "backcountry" )}/> : (this.props.location.pathname === "/add-day") ? <AddDayForm onNewDay={this.addDay}/> : <SkiDayList days={this.state.allSkiDays} filter={this.props.params.filter}/> } </div> ) }

7. App.js and SkiDayCount.js

    7.1.1 -----------  Component App is defined in App.js with ES6 class syntax
    export class App extends Component {
	constructor(props) {
		super(props)
		this.state = {                    // state data
			allSkiDays: [      
			{
				resort: "Squaw Valley",
				date: "2016-01-02",
				powder: true,
				backcountry: false
			}
		]
		}
		.....
	}        

    7.1.2 -----------  Component App has a helper method ------
    countDays(filter) {
		const { allSkiDays } = this.state
		return allSkiDays.filter(
			(day) => (filter) ? day[filter] : day).length
	}
          // Array.filter(anonymous function) to get subset of array
              // if a filter is provided, and the filter is matched, put it in the subset.
              // if a filter is not provided, put it in the subset anyway.
          // get the length of the subset of array
        
7.1.3 ----------- Component App uses SkiDayCount in its render method------ <SkiDayCount total={this.countDays()} powder={this.countDays("powder")} backcountry={this.countDays("backcountry")}/>
7.1.3 ------------ using the react developer tool to examine -----
7.2 ----------- component SkiDayCount --------------------
... const calcGoalProgress = (total, goal) => { // helper method return percentToDecimal(total/goal) } // SkieDay is defined as an stateless function. // the function parameters is the default values // ignore the defaults, if they are provided from the caller like this case. export const SkiDayCount = ({total=70, powder=20, backcountry=10, goal=100}) => ( <div className="ski-day-count"> <div className="total-days"> <span>{total}</span> <Calendar /> <span>days</span> </div> <div className="powder-days"> <span>{powder}</span> <SnowFlake /> <span>days</span> </div>... </div> ) SkiDayCount.propTypes = { // define the attribute types total: PropTypes.number, powder: PropTypes.number, backcountry: PropTypes.number, goal: PropTypes.number }

8. Tabular display

8.1 Using the react tool to examine in the chrome

         -----  8.1.1 page display   --------------
             Date   Resort     Powder   Backcountry     // table title
            all days    powder days  backcountry days   // links for filter
          2018-4-7  BlueHill     *                      // table row
          2018-1-1  White Hill               *          // table row


         ------ 8.1.2 JSX hierarchy  --------------
           router
              App
                Menu
                SkiDayList
                   table
                     thead
                          tr
                            th - date
                            th - resort
                            th - powder
                            th - backcountry
                          tr
                            
                            link        
                            link        
                            link
                            link
                            
                      tbody
                        SkiDayRow
                           tr
                           tr
               
               note: If you click the link for powder days, 
                        you'll see params= {filter:"powder"}
                     inside the hierachy window - App tag 
               
               
               note: * If you click the link, the url will be created.
                     * like localhost:3000/#/list-days/powder
                     * powder is a property for filter.
                     * Both the list and the filter will be passed from App to SkiDayList as attributes.
                     * The filter is used for the selected list.
              
               
            

8.2 index.js

<Route path="list-days" component={App}> <Route path=":filter" component={App} /> </Route>

8.3 App.js

<SkiDayList days={this.state.allSkiDays} filter={this.props.params.filter}/>

8.4 SkiDayList.js


        8.4.1  SkiDayList is implemented as a stateless function.
              export const SkiDayList = ({days, filter}) => {...}
            
8.4.2 Three links for filtering is implmented in table header area. <table> <thead> <tr> <th>Date</th> <th>Resort</th> <th>Powder</th> <th>Backcountry</th> </tr> <tr> <td colSpan={4}> <Link to="/list-days"> All Days </Link> <Link to="/list-days/powder"> Powder Days </Link> <Link to="/list-days/backcountry"> Backcountry Days </Link> </td> </tr> 8.4.2 The following is the implementation for table row (1 to many) <tbody> {filteredDays.map((day, i) => <SkiDayRow key={i} {...day}/> )} </tbody>
            the sequences are listed as below:
            1. When component SkiDayList is created, two attributes are passed  in - days, filter.
            2. Create a constant filteredDay as below:
                
                const filteredDays = (!filter || 
  		                    !filter.match(/powder|backcountry/))?
  		                    days:
  		                    days.filter(day => day[filter])
                
            3.  tablular dispaly
                
                In tag tbody, use map method to use data to format for each.
                Component SkiDayRow is the child to encapsulate the row implementation.
                The rows are peers. The list and the row is in 1 to many relationship.
                
             
8.5 SkiDayRow.js import { PropTypes } from 'react' ... export const SkiDayRow = ({resort, date, // stateless function powder, backcountry}) => ( <tr> <td> {date} </td>... <td> {(powder) ? <SnowFlake/> : null} // if true, use that icon </td>... </tr> ) SkiDayRow.propTypes = { resort: PropTypes.string.isRequired, ... }

9. form and passing data to parent component

* 9.3 in App.js, render method, the child is created with the following code <AddDayForm onNewDay={this.addDay}/> * 9.4.1 in AddDayForm.js, create the form for data entry. let _resort, _date, _powder, _backcountry const submit = (e) => { .... _resort.value = '' .... // preparing the data to the parent. } return ( <form> <input id="resort" ref={input => _resort = input}/> ...... )
         * 9.4.2  data from child to parent
                the event handler for button click
                const submit = (e) => {
		        e.preventDefault()
		        onNewDay({
			        resort: _resort.value,
			        date: _date.value,
			        powder: _powder.checked,
			        backcountry: _backcountry.checked
		        })
		        _resort.value = ''
		        _date.value = ''
		        _powder.checked = false
		        _backcountry.checked = false
	            }

                Component AddDayForm is defined as a stateless function
                export const AddDayForm = 
                    ({ resort, date,powder,backcountry,
					onNewDay }) => {...}
            
* 9.5 using the form data in App, the parent. // create the child component <AddDayForm onNewDay={this.addDay}/> .... // the event handler addDay(newDay) { this.setState({ allSkiDays: [ ...this.state.allSkiDays, newDay ] }) // in the constructor, the following code is needed // because addDay is an event handler. // Method bind to make it as a component method. this.addDay = this.addDay.bind(this)

10. connect to rest services

        -------------------   get demo  -----------------------------
        
        // in App.js before render method, 
        // add a component life method, componentWillMount
        // Normally, when do so, the service side work must be prepared.
        // Here, a rest api services, not for this client, is simulated for this demo.
        // First make sure the library for fetch is installed.
        //         import fetch from 'isomorphic-fetch'
        // line 1: fetch is to connect.
        // line 2: convert the data to json
        // line 3: get part of the json data
        // line 4: update the state data
        // then, hijack with the data for this demo.
        // run and see the result.
        
        componentWillMount(){ 
            fetch('https://api.randomuser.me/?nat=US&results=12')
            .then(response => response.json())
            .then(json => json.results)
            .then(data => this.setState({
              allSkiDays: [
                  ...this.state.allSkiDays,
                  {
                     resort: "White MT", date: "2018-04-10",
                     powder: false,
                     backcountry: true
                  }
                ]       
            }) )
         }

         -------------------   post intro   -------------------
         in AddDayForm.js, the submit handler for rest post request.
 
         choose some libraries like axios,
         use methos like axios.post