React: Difference between revisions

From bibbleWiki
Jump to navigation Jump to search
No edit summary
 
(4 intermediate revisions by the same user not shown)
Line 5: Line 5:
A template for a project can be found https://github.com/jscomplete/rgs-template
A template for a project can be found https://github.com/jscomplete/rgs-template
= Production Setup =
= Production Setup =
Below are examples of dev vs production scripts
=== Packages build scripts ===
=== Packages build scripts ===
Additional changes to NPM build scripts for production
Additional changes to NPM build scripts for production
<syntaxhighlight lang="javascript">
<syntaxhighlight lang="js">
     "test:ci": "jest",
     "test:ci": "jest",
     "clean:build": "rimraf ./build && mkdir build",
     "clean:build": "rimraf ./build && mkdir build",
Line 18: Line 19:
=== Webpack differences ===
=== Webpack differences ===
Production
Production
<syntaxhighlight lang="javascript">
<syntaxhighlight lang="js">
const webpack = require("webpack");
const webpack = require("webpack");
const path = require("path");
const path = require("path");
Line 152: Line 153:
</syntaxhighlight>
</syntaxhighlight>


=== Source switches ===
===Source switches===
<syntaxhighlight lang="javascript">
<syntaxhighlight lang="js">
// Use CommonJS require below so we can dynamically import during build-time.
// Use CommonJS require below so we can dynamically import during build-time.
if (process.env.NODE_ENV === "production") {
if (process.env.NODE_ENV === "production") {
Line 163: Line 164:


=Variables=
=Variables=
<syntaxhighlight lang="javascript" line="line">
<syntaxhighlight lang="js" line="line">
  const myVar = (
  const myVar = (
   <h1>Love it</h1>
   <h1>Love it</h1>
Line 175: Line 176:


This is because you have not bound the function in the class to this e.g.
This is because you have not bound the function in the class to this e.g.
<syntaxhighlight lang="javascript">
<syntaxhighlight lang="js">
this.onTitleChange = this.onTitleChange.bind(this);
this.onTitleChange = this.onTitleChange.bind(this);
</syntaxhighlight>
</syntaxhighlight>
Line 182: Line 183:
== Classes ==  
== Classes ==  
Below is the old class approach with binding and constructor
Below is the old class approach with binding and constructor
<syntaxhighlight lang="javascript">
<syntaxhighlight lang="jsx">


class InputPage extends React.Component {
class InputPage extends React.Component {
Line 222: Line 223:
And now old class approach without binding and constructor and using spread
And now old class approach without binding and constructor and using spread


<syntaxhighlight lang="javascript">
<syntaxhighlight lang="jsx">


class InputPage extends React.Component {
class InputPage extends React.Component {
Line 258: Line 259:
Example of setting state
Example of setting state


<syntaxhighlight lang="javascript">
<syntaxhighlight lang="jsx">
const InputElement = () => {
const InputElement = () => {


Line 273: Line 274:
You can get a reference from the DOM using UseRef
You can get a reference from the DOM using UseRef


<syntaxhighlight lang="javascript">
<syntaxhighlight lang="jsx">
     const imageRef = useRef(null)
     const imageRef = useRef(null)


Line 293: Line 294:
Add and remove scroll handler
Add and remove scroll handler


<syntaxhighlight lang="javascript">
<syntaxhighlight lang="jsx">
     useEffect( () = > {
     useEffect( () = > {
       window.addEventListener("scroll", scrollHandler)
       window.addEventListener("scroll", scrollHandler)
Line 306: Line 307:
e.g. Maybe a checkbox value
e.g. Maybe a checkbox value


<syntaxhighlight lang="javascript">
<syntaxhighlight lang="jsx">
     const [checkboxValue, setCheckboxValue] = useState(false)
     const [checkboxValue, setCheckboxValue] = useState(false)


Line 325: Line 326:
Top Level, wraps a context around pages
Top Level, wraps a context around pages


<syntaxhighlight lang="javascript">
<syntaxhighlight lang="jsx">
     import React from "react";
     import React from "react";
     import Home from "./Home";
     import Home from "./Home";
Line 354: Line 355:
A the usage on the page.
A the usage on the page.


<syntaxhighlight lang="javascript">
<syntaxhighlight lang="jsx">
     import React, { useState, useEffect, useContext} from "react";
     import React, { useState, useEffect, useContext} from "react";
     import  { AppContext } from "./App"
     import  { AppContext } from "./App"
Line 371: Line 372:
UseState is built on Reducer i.e.
UseState is built on Reducer i.e.


<syntaxhighlight lang="javascript">
<syntaxhighlight lang="jsx">
     const [speakerList, setSpeakerList] = useState([])
     const [speakerList, setSpeakerList] = useState([])
</syntaxhighlight>
</syntaxhighlight>
Line 377: Line 378:
Is the same as
Is the same as


<syntaxhighlight lang="javascript">
<syntaxhighlight lang="jsx">
     const [speakerList, setSpeakerList] = useReducer( (state,action) => action, [])
     const [speakerList, setSpeakerList] = useReducer( (state,action) => action, [])
</syntaxhighlight>
</syntaxhighlight>
Line 383: Line 384:
Is the same as
Is the same as


<syntaxhighlight lang="javascript">
<syntaxhighlight lang="jsx">
     function speakerReducer(state, action) {
     function speakerReducer(state, action) {
         return action
         return action
Line 393: Line 394:
We can now make this work the same a the useState
We can now make this work the same a the useState


<syntaxhighlight lang="javascript">
<syntaxhighlight lang="jsx">
     function speakerReducer(state, action) {
     function speakerReducer(state, action) {
         switch(action.type) {
         switch(action.type) {
Line 411: Line 412:


So the old usage was  
So the old usage was  
<syntaxhighlight lang="javascript">
<syntaxhighlight lang="jsx">
     setSpeakerList(mySpeakerList)
     setSpeakerList(mySpeakerList)
</syntaxhighlight>
</syntaxhighlight>


Using the reducer gives
Using the reducer gives
<syntaxhighlight lang="javascript">
<syntaxhighlight lang="jsx">
     dispatch( {
     dispatch( {
         type: "setSpeakerList",
         type: "setSpeakerList",
Line 426: Line 427:
This caches a function. Given the function below,
This caches a function. Given the function below,


<syntaxhighlight lang="javascript">
<syntaxhighlight lang="jsx">
   const heartFavoriteHandler = (e, favoriteValue) => {
   const heartFavoriteHandler = (e, favoriteValue) => {
     e.preventDefault();
     e.preventDefault();
Line 439: Line 440:
We can cache this using UseCallback
We can cache this using UseCallback


<syntaxhighlight lang="javascript">
<syntaxhighlight lang="jsx">
   const heartFavoriteHandler = useCallback( (e, favoriteValue) => {
   const heartFavoriteHandler = useCallback( (e, favoriteValue) => {
     e.preventDefault();
     e.preventDefault();
Line 452: Line 453:
However for this to work you will need to wrap the content it is reloading in React.memo. In this case it was SpeakerDetail
However for this to work you will need to wrap the content it is reloading in React.memo. In this case it was SpeakerDetail


<syntaxhighlight lang="javascript">
<syntaxhighlight lang="jsx">
           <div className="card-deck">
           <div className="card-deck">
             {speakerListFiltered.map(
             {speakerListFiltered.map(
Line 474: Line 475:
so this gave  
so this gave  


<syntaxhighlight lang="javascript">
<syntaxhighlight lang="jsx">


const SpeakerDetail = React.memo( ({
const SpeakerDetail = React.memo( ({
Line 530: Line 531:
Similarly you can cache a value using UseMemo. e.g.
Similarly you can cache a value using UseMemo. e.g.


<syntaxhighlight lang="javascript">
<syntaxhighlight lang="jsx">
   const mySortedSpeakerList = useMemo( ()=> speakerList  
   const mySortedSpeakerList = useMemo( ()=> speakerList  
     .filter(
     .filter(
Line 551: Line 552:
An example of a reducer with a custom hook using axios
An example of a reducer with a custom hook using axios


<syntaxhighlight lang="javascript">
<syntaxhighlight lang="jsx">
const dataFetchReducer = (state, action) => {
const dataFetchReducer = (state, action) => {
   switch (action.type) {
   switch (action.type) {
Line 589: Line 590:
</syntaxhighlight>
</syntaxhighlight>


<syntaxhighlight lang="javascript">
<syntaxhighlight lang="jsx">
const useAxiosFetch = (initialUrl, initialData) => {
const useAxiosFetch = (initialUrl, initialData) => {
   const [url] = useState(initialUrl);
   const [url] = useState(initialUrl);

Latest revision as of 01:45, 12 July 2021

Quick Reference

Development Setup

A good resource for this can be found https://jscomplete.com/learn/1rd-reactful' by Samer Buna
A template for a project can be found https://github.com/jscomplete/rgs-template

Production Setup

Below are examples of dev vs production scripts

Packages build scripts

Additional changes to NPM build scripts for production

    "test:ci": "jest",
    "clean:build": "rimraf ./build && mkdir build",
    "prebuild": "run-p clean:build test:ci",
    "build": "webpack --config webpack.config.prod.js",
    "postbuild": "run-p start:api serve:build",
    "serve:build": "http-server ./build"

Webpack differences

Production

const webpack = require("webpack");
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const webpackBundleAnalyzer = require("webpack-bundle-analyzer");

process.env.NODE_ENV = "production";

module.exports = {
  mode: "production",
  target: "web",
  devtool: "source-map",
  entry: "./src/index",
  output: {
    path: path.resolve(__dirname, "build"),
    publicPath: "/",
    filename: "bundle.js"
  },
  plugins: [
    // Display bundle stats
    new webpackBundleAnalyzer.BundleAnalyzerPlugin({ analyzerMode: "static" }),

    new MiniCssExtractPlugin({
      filename: "[name].[contenthash].css"
    }),

    new webpack.DefinePlugin({
      // This global makes sure React is built in prod mode.
      "process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV),
      "process.env.API_URL": JSON.stringify("http://localhost:3001")
    }),
    new HtmlWebpackPlugin({
      template: "src/index.html",
      favicon: "src/favicon.ico",
      minify: {
        // see https://github.com/kangax/html-minifier#options-quick-reference
        removeComments: true,
        collapseWhitespace: true,
        removeRedundantAttributes: true,
        useShortDoctype: true,
        removeEmptyAttributes: true,
        removeStyleLinkTypeAttributes: true,
        keepClosingSlash: true,
        minifyJS: true,
        minifyCSS: true,
        minifyURLs: true
      }
    })
  ],
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: ["babel-loader", "eslint-loader"]
      },
      {
        test: /(\.css)$/,
        use: [
          MiniCssExtractPlugin.loader,
          {
            loader: "css-loader",
            options: {
              sourceMap: true
            }
          },
          {
            loader: "postcss-loader",
            options: {
              plugins: () => [require("cssnano")],
              sourceMap: true
            }
          }
        ]
      }
    ]
  }
};

Development

const webpack = require("webpack");
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

process.env.NODE_ENV = "development";

module.exports = {
  mode: "development",
  target: "web",
  devtool: "cheap-module-source-map",
  entry: "./src/index",
  output: {
    path: path.resolve(__dirname, "build"),
    publicPath: "/",
    filename: "bundle.js"
  },
  devServer: {
    stats: "minimal",
    overlay: true,
    historyApiFallback: true,
    disableHostCheck: true,
    headers: { "Access-Control-Allow-Origin": "*" },
    https: false
  },
  plugins: [
    new webpack.DefinePlugin({
      "process.env.API_URL": JSON.stringify("http://localhost:3001")
    }),
    new HtmlWebpackPlugin({
      template: "src/index.html",
      favicon: "src/favicon.ico"
    })
  ],
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: ["babel-loader", "eslint-loader"]
      },
      {
        test: /(\.css)$/,
        use: ["style-loader", "css-loader"]
      }
    ]
  }
};

Source switches

// Use CommonJS require below so we can dynamically import during build-time.
if (process.env.NODE_ENV === "production") {
  module.exports = require("./configureStore.prod");
} else {
  module.exports = require("./configureStore.dev");
}

Variables

 const myVar = (
   <h1>Love it</h1>
 );

Common Errors

When using classes and you have forgotten to bind you get

Cannot read property 'setState' of undefined

This is because you have not bound the function in the class to this e.g.

this.onTitleChange = this.onTitleChange.bind(this);

Form Example

Classes

Below is the old class approach with binding and constructor

class InputPage extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      course : {
        title: ""
      }
    };

    this.onTitleChange = this.onTitleChange.bind(this);
  }

  onTitleChange(event) {
    this.setState({
      course: {
        title: event.target.value,
      },
    });
  }

  render() {
    return (
      <form >
        <input
          type="text"
          value={this.state.course.title}
          onChange={this.onTitleChange}
        />
      </form>
    );
  }
}

And now old class approach without binding and constructor and using spread

class InputPage extends React.Component {

    this.state = {
      course : {
          title: ""
      }
    }

    onTitleChange = event => {
        const course = {...this.state.course, title: event.target.value}
        this.setState( {course} )
    }
  
    render() {
        return (
        <form >
            <input
            type="text"
            value={this.state.course.title}
            onChange={this.onTitleChange}
            />
         </form>
    )
}

Hooks

Hooks can only be used at the top level. You can set rules in your editor to ensure this.

UseState

Example of setting state

const InputElement = () => {

    const [inputText, setInputText] = useState("")
    
    return ( 
        <input placeholder="some text" onChange={(event) => { setInputText(event.target.value) } } />
    )
}

UseRef

You can get a reference from the DOM using UseRef

    const imageRef = useRef(null)

    console.info("Got here")

    return (
        <img 
            onMouseOver={ ()=>{ imageRef.current.src = secondaryImg} }
            onMouseOut={ ()=>{ imageRef.current.src = primaryImg} }
            src={primaryImg}
            ref={imageRef}
        />
        )

UseEffect

The first argument to useEffect is a function which executes when the component first mounts. The first argument of the return should be a function which remove the resources, if applicable, used as the first argument. e.g.
Add and remove scroll handler

    useEffect( () = > {
       window.addEventListener("scroll", scrollHandler)
       return ( ()=> {
           window.removeEventListener("scroll", scrollHandler)
       })
    })

The second argument is a array of values which, if changed, will cause the effect to be called again.

e.g. Maybe a checkbox value

    const [checkboxValue, setCheckboxValue] = useState(false)

    useEffect( () = > {
       window.addEventListener("scroll", scrollHandler)
       return ( ()=> {
           window.removeEventListener("scroll", scrollHandler)
       }, [checkboxValue])
    })

The effect will be called each time the checkbox is clicked.

UseConfig

This allows passing of config to different pages.

Top Level, wraps a context around pages

    import React from "react";
    import Home from "./Home";
    import Speakers from "./Speakers";

    export const AppContext = React.createContext();

    const pageToShow = pageName => {
      if (pageName === "Home") return <Home />;
      if (pageName === "Speakers") return <Speakers />;
      return <div>Not Found</div>;
    };

    const configValue = {
      showSpeakerSpeakingDays : true
    }

    const App = ({ pageName }) => {
      return  <AppContext.Provider value={configValue}>
                <div>{pageToShow(pageName)}</div>
              </AppContext.Provider>
    };

    export default App;

A the usage on the page.

    import React, { useState, useEffect, useContext} from "react";
    import  { AppContext } from "./App"

    const PageFunction = ({}) => {
       const context = useContext(AppContext)
    }

UseReducer

A reducer is defined as something which takes a previous state and function and returns a new state

(previousState, action) => newState


UseState is built on Reducer i.e.

    const [speakerList, setSpeakerList] = useState([])

Is the same as

    const [speakerList, setSpeakerList] = useReducer( (state,action) => action, [])

Is the same as

    function speakerReducer(state, action) {
        return action
    }

    const [speakerList, setSpeakerList] = useReducer( speakerReducer, [])

We can now make this work the same a the useState

    function speakerReducer(state, action) {
        switch(action.type) {
            case "setSpeakerList" : {
                return action.data;
            }
            default: 
                return state;
        }
    }

    // So replaced setSpeakList, which is not returned with a dispatch name, we called it dispatch but we could have called derek

    // const [speakerList, setSpeakerList] = useReducer( speakerReducer, [])
    const [speakerList, dispatch] = useReducer( speakerReducer, [])

So the old usage was

    setSpeakerList(mySpeakerList)

Using the reducer gives

    dispatch( {
        type: "setSpeakerList",
        data: mySpeakerList 
    })

UseCallback

This caches a function. Given the function below,

  const heartFavoriteHandler = (e, favoriteValue) => {
    e.preventDefault();
    const sessionId = parseInt(e.target.attributes["data-sessionid"].value);
    
    dispatch({
      type : favoriteValue === true ? "setFavourite" : "setUnfavourite",
      sessionId
    });

We can cache this using UseCallback

  const heartFavoriteHandler = useCallback( (e, favoriteValue) => {
    e.preventDefault();
    const sessionId = parseInt(e.target.attributes["data-sessionid"].value);
    
    dispatch({
      type : favoriteValue === true ? "setFavourite" : "setUnfavourite",
      sessionId
    }, [] );

However for this to work you will need to wrap the content it is reloading in React.memo. In this case it was SpeakerDetail

          <div className="card-deck">
            {speakerListFiltered.map(
              ({ id, firstName, lastName, bio, favorite }) => {
                return (
                  <SpeakerDetail
                    key={id}
                    id={id}
                    favorite={favorite}
                    onHeartFavoriteHandler={heartFavoriteHandler}
                    firstName={firstName}
                    lastName={lastName}
                    bio={bio}
                  />
                );
              }
            )}
          </div>

so this gave

const SpeakerDetail = React.memo( ({
                           id,
                           firstName,
                           lastName,
                           favorite,
                           bio,
                           onHeartFavoriteHandler
                       }) => {
    console.log(`SpeakerDetail:${id} ${firstName} ${lastName} ${favorite}`);
    return (
        <div className="card col-4 cardmin">
            <ImageToggleOnScroll
                className="card-img-top"
                primaryImg={`/static/speakers/bw/Speaker-${id}.jpg`}
                secondaryImg={`/static/speakers/Speaker-${id}.jpg`}
                alt="{firstName} {lastName}"
            />
            <div className="card-body">
                <h4 className="card-title">
                    <button
                        data-sessionid={id}
                         const mySortedSpeakList = useMemo( ()=> speakerList 
    .filter(
      ({ sat, sun }) => (speakingSaturday && sat) || (speakingSunday && sun)
    )
    .sort(function(a, b) {
      if (a.firstName < b.firstName) {
        return -1;
      }
      if (a.firstName > b.firstName) {
        return 1;
      }
      return 0;
    }), [speakingSaturday,speakingSunday,speakerList]); className={favorite ? "heartredbutton" : "heartdarkbutton"}
                        onClick={e => {
                            onHeartFavoriteHandler(e, !favorite);
                        }}
                    />
                    <span>
            {firstName} {lastName}
          </span>
                </h4>

                <span>{bio}</span>
            </div>
        </div>
    );
});

UseMemo

Similarly you can cache a value using UseMemo. e.g.

  const mySortedSpeakerList = useMemo( ()=> speakerList 
    .filter(
      ({ sat, sun }) => (speakingSaturday && sat) || (speakingSunday && sun)
    )
    .sort(function(a, b) {
      if (a.firstName < b.firstName) {
        return -1;
      }
      if (a.firstName > b.firstName) {
        return 1;
      }
      return 0;
    }), [speakingSaturday,speakingSunday,speakerList]);

The value will only change if one of the speakingSaturday,speakingSunday,speakerList changes

Custom Hook

An example of a reducer with a custom hook using axios

const dataFetchReducer = (state, action) => {
  switch (action.type) {
    case "FETCH_INIT":
      return { ...state, isLoading: true, isError: false };
    case "FETCH_SUCCESS":
      return {
        ...state,
        isLoading: false,
        hasErrored: false,
        errorMessage: "",
        data: action.payload
      };
    case "FETCH_FAILURE":
      return {
        ...state,
        isLoading: false,
        hasErrored: true,
        errorMessage: "Data Retrieve Failure"
      };
    case "REPLACE_DATA":
      // The record passed (state.data) must have the attribute "id"
      const newData = state.data.map(rec => {
        return rec.id === action.replacerecord.id ? action.replacerecord : rec;
      });
      return {
        ...state,
        isLoading: false,
        hasErrored: false,
        errorMessage: "",
        data: newData
      };
    default:
      throw new Error();
  }
};
const useAxiosFetch = (initialUrl, initialData) => {
  const [url] = useState(initialUrl);

  const [state, dispatch] = useReducer(dataFetchReducer, {
    isLoading: false,
    hasErrored: false,
    errorMessage: "",
    data: initialData
  });

  useEffect(() => {
    let didCancel = false;

    const fetchData = async () => {
      dispatch({ type: "FETCH_INIT" });

      try {
        let result = await axios.get(url);
        if (!didCancel) {
          dispatch({ type: "FETCH_SUCCESS", payload: result.data });
        }
      } catch (err) {
        if (!didCancel) {
          dispatch({ type: "FETCH_FAILURE" });
        }
      }
    };

    fetchData();

    return () => {
      didCancel = true;
    };
  }, [url]);

  const updateDataRecord = record => {
    dispatch({
      type: "REPLACE_DATA",
      replacerecord: record
    });
  };

  return { ...state, updateDataRecord };
};

Maps

 // Only do this if items have no stable IDs
 
 const todo = ["fix", "syntax", "highlighting"];
 const todoItems = todos.map((todo, index) =>
 <li key={index}>
   {todo.text}
 </li>
);

Component Example

import React, {Component} from 'react';
import ReactDOM from 'react-dom'; 

class Parent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {name: "Frarthur"};
  }
  render() {
    return <div></div>;
  }  
};

Passing Props to child

Parent.json

import React from 'react';
import ReactDOM from 'react-dom';
import { Child } from './Child';

class Parent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { name: 'Frarthur' };
  }

  render() {
    return <Child name={this.state.name} />;
  }
}

ReactDOM.render(<Parent />, document.getElementById('app'));

Child.json

import React from 'react';

export class Child extends React.Component {
  render() {
    return <h1>Hey, my name is {this.props.name}!</h1>;
  }
}

Forms with Ref

You can capture import using the React.createRef() e.g.

class Form extends React.Component
{
   userNameInput = React.createRef();

   handleSubmit = (event) => {
     event.preventDefault()
     console.log(userNameInput.current.value)
   }
   render()
   {
       return (
           <form onSubmit={this.handleSubmit} >
               <input 
                   type="text"
                   placeHolder="background text"
                   ref={this.userNameInput}
                   required
               />
           />
       ) 
    
   }
}

Forms without Ref

A Better way may be to use a state to hold the name.

class Form extends React.Component
{
   state = { userName : ''}

   render()
   {
       return (
           <form onSubmit={this.handleSubmit} >
               <input 
                   type="text"
                   value={this.state.userName}
                   OnChange = { 
                       event => {
                           this.setState( {userName: event.target.value} )
                       } 
                   }
                   required
               />
           />
       ) 
    
   }
}

Binding a function

import React from 'react';
import ReactDOM from 'react-dom';
import { Video } from './Video';
import { Menu } from './Menu';

const VIDEOS = {
  fast: 'https://s3.amazonaws.com/codecademy-content/courses/React/react_video-fast.mp4',
  slow: 'https://s3.amazonaws.com/codecademy-content/courses/React/react_video-slow.mp4',
  cute: 'https://s3.amazonaws.com/codecademy-content/courses/React/react_video-cute.mp4',
  eek: 'https://s3.amazonaws.com/codecademy-content/courses/React/react_video-eek.mp4'
};

class App extends React.Component {
  constructor(props) {
    this.chooseVideo = this.chooseVideo.bind(this);
    super(props);
    this.state = { 
      src: VIDEOS.fast 
    };
  }
  
  chooseView(newView)   {
    this.setState({
      src: VIDEOS[newVideo]
    });
  }
  render() {
    
    return (
      <div>
        <h1>Video Player</h1>
        <Menu chooseVideo={this.chooseView()} />
        <Video src={this.state.src} />
      </div>
    );
  }
}

ReactDOM.render(
  <App />, 
  document.getElementById('app')
);

import React from 'react';

export class Menu extends React.Component {
  
  constructor() {
    super(props)
    this.handleClick = this.handleClick.bind(this);
  }
  
  handleClick(e) {
    var text = e.target.value;
    this.props.chooseView(text);
  }
  
  render() {
    return (
      <form OnClick={this.handleClick}>
        <input type="radio" name="src" value="fast" /> fast
        <input type="radio" name="src" value="slow" /> slow
        <input type="radio" name="src" value="cute" /> cute
        <input type="radio" name="src" value="eek" /> eek
      </form>
    );
  }
}

import React from 'react';

export class Video extends React.Component {
  render() {
    return (
      <div>
        <video controls autostart autoPlay muted src={this.props.src} />
      </div>
    );
  }
}

Inline Styling

React needs double quotes to inject style in JSX e.g.

import React from 'react';
import ReactDOM from 'react-dom';

const styleMe = <h1 style={{ background: 'lightblue', color: 'darkred' }}>Please style me! I am so bland!</h1>;

ReactDOM.render(
	styleMe, 
	document.getElementById('app')
);

Another example

mport React from 'react';
import ReactDOM from 'react-dom';
const styles = {
  background: 'lightblue',
  color:      'darkred',
  marginTop: '100px',
  fontSize: '50px'
}

const styleMe = <h1 style={styles}>Please style me! I am so bland!</h1>;

ReactDOM.render(
	styleMe, 
	document.getElementById('app')
);

You can write px styles without quotes e.g.

const styles = {
  background: 'lightblue',
  color:      'darkred',
  marginTop: 100,
  fontSize: 50
}

Stateless Classes

A component class written in the usual way:

export class MyComponentClass extends React.Component {
  render() {
    return <h1>Hello world</h1>;
  }
}

// The same component class, written as a stateless functional component:
export const MyComponentClass = () => {
  return <h1>Hello world</h1>;
}

You can pass the properties from the container class like this

import React from 'react';

export const GuineaPigs = (props) => {
  let src = props.src;
  return (
    <div>
      <h1>Cute Guinea Pigs</h1>
      <img src={src} />
    </div>
  );
}

Prototypes

Add them at the bottom so people can see how to use the code

import React from 'react';

export class BestSeller extends React.Component {
  render() {
    return (
      <li>
        Title: <span>
          {this.props.title}
        </span><br />
        
        Author: <span>
          {this.props.author}
        </span><br />
        
        Weeks: <span>
          {this.props.weeksOnList}
        </span>
      </li>
    );
  }
}

// This propTypes object should have
// one property for each expected prop:
BestSeller.propTypes = {
  title: React.PropTypes.string.isRequired,
  author: React.PropTypes.string.isRequired,
  weeksOnList: React.PropTypes.number.isRequired,
};

And a render example

import React from 'react';
import ReactDOM from 'react-dom';
import { BestSeller } from './BestSeller';

export class BookList extends React.Component {
  render() {
    return (
      <div>
        <h1>Best Sellers</h1>
        <div>
          <ol>
            <BestSeller 
              title="Glory and War Stuff for Dads" 
              author="Sir Eldrich Van Hoorsgaard" 
              weeksOnList={10} />
            <BestSeller 
              title="The Crime Criminals!" 
              author="Brenda Sqrentun" 
              weeksOnList={2} />
            <BestSeller
              title="Subprime Lending For Punk Rockers" 
              author="Malcolm McLaren" 
              weeksOnList={600} />
          </ol>
        </div>
      </div>
    );
  }
}

ReactDOM.render(<BookList />, document.getElementById('app'));

And for stateless functions

import React from 'react';

export class GuineaPigs extends React.Component {
  render() {
    let src = this.props.src;
    return (
      <div>
        <h1>Cute Guinea Pigs</h1>
        <img src={src} />
      </div>
    );
  }
}

GuineaPigs.propTypes = {
  src: React.PropTypes.string.isRequired
};

Form Example where we track the input value

import React from 'react';
import ReactDOM from 'react-dom';

export class Input extends React.Component {
  constructor(props) {
    super(props);
    
    this.state = { userInput: '' };
    
    this.handleUserInput = this.handleUserInput.bind(this);
  }
  
  handleUserInput(e) {
    this.setState({userInput: e.target.value});
  }

  render() {
    return (
      <div>
        <input type="text" onChange={this.handleUserInput} value={this.state.userInput} />
        <h1>{this.state.userInput}</h1>
      </div>
    );
  }
}

ReactDOM.render(
	<Input />,
	document.getElementById('app')
);

The New Way Example using custom hooks

This is the example from the react course where they broke the game into a custom hook

Display Elements

const StarsDisplay = props => (
  <>
    {utils.range(1, props.count).map(starId => (
      <div key={starId} className="star" />
    ))}
  </>
);

const PlayNumber = props => (
  <button
    className="number"
    style={{backgroundColor: colors[props.status]}}
    onClick={() => props.onClick(props.number, props.status)}
  >
    {props.number}
  </button>
);

const PlayAgain = props => (
	<div className="game-done">
  	<div 
    	className="message"
      style={{ color: props.gameStatus === 'lost' ? 'red' : 'green'}}
    >
  	  {props.gameStatus === 'lost' ? 'Game Over' : 'Nice'}
  	</div>
	  <button onClick={props.onClick}>Play Again</button>
	</div>
);

Custom Hook

Here is the custom hook which abstracts the state and returns what is necessary for the display element of the game

const useGameState = timeLimit => {
  const [stars, setStars] = useState(utils.random(1, 9));
  const [availableNums, setAvailableNums] = useState(utils.range(1, 9));
  const [candidateNums, setCandidateNums] = useState([]);
  const [secondsLeft, setSecondsLeft] = useState(10);

  useEffect(() => {
    if (secondsLeft > 0 && availableNums.length > 0) {
      const timerId = setTimeout(() => setSecondsLeft(secondsLeft - 1), 1000);
      return () => clearTimeout(timerId);
    }
  });

  const setGameState = (newCandidateNums) => {
    if (utils.sum(newCandidateNums) !== stars) {
			setCandidateNums(newCandidateNums);
    } else {
      const newAvailableNums = availableNums.filter(
        n => !newCandidateNums.includes(n)
      );
      setStars(utils.randomSumIn(newAvailableNums, 9));
      setAvailableNums(newAvailableNums);
      setCandidateNums([]);
    }
  };

  return { stars, availableNums, candidateNums, secondsLeft, setGameState };
};


The Game

const Game = props => {
  const {
    stars,
    availableNums,
    candidateNums,
    secondsLeft,
    setGameState,
  } = useGameState();

  const candidatesAreWrong = utils.sum(candidateNums) > stars;
  const gameStatus = availableNums.length === 0 
  	? 'won'
    : secondsLeft === 0 ? 'lost' : 'active'

  const numberStatus = number => {
    if (!availableNums.includes(number)) {
      return 'used';
    }

    if (candidateNums.includes(number)) {
      return candidatesAreWrong ? 'wrong' : 'candidate';
    }

    return 'available';
  };

  const onNumberClick = (number, currentStatus) => {
    if (currentStatus === 'used' || secondsLeft === 0) {
      return;
    }

    const newCandidateNums =
      currentStatus === 'available'
        ? candidateNums.concat(number)
        : candidateNums.filter(cn => cn !== number);

    setGameState(newCandidateNums);
  };

  return (
    <div className="game">
      <div className="help">
        Pick 1 or more numbers that sum to the number of stars
      </div>
      <div className="body">
        <div className="left">
          {gameStatus !== 'active' ? (
          	<PlayAgain onClick={props.startNewGame} gameStatus={gameStatus} />
          ) : (
          	<StarsDisplay count={stars} />
          )}
        </div>
        <div className="right">
          {utils.range(1, 9).map(number => (
            <PlayNumber
              key={number}
              status={numberStatus(number)}
              number={number}
              onClick={onNumberClick}
            />
          ))}
        </div>
      </div>
      <div className="timer">Time Remaining: {secondsLeft}</div>
    </div>
  );
};

const StarMatch = () => {
	const [gameId, setGameId] = useState(1);
	return <Game key={gameId} startNewGame={() => setGameId(gameId + 1)}/>;
}

== Utilities ==

// Color Theme
const colors = {
  available: 'lightgray',
  used: 'lightgreen',
  wrong: 'lightcoral',
  candidate: 'deepskyblue',
};

// Math science
const utils = {
  // Sum an array
  sum: arr => arr.reduce((acc, curr) => acc + curr, 0),

  // create an array of numbers between min and max (edges included)
  range: (min, max) => Array.from({length: max - min + 1}, (_, i) => min + i),

  // pick a random number between min and max (edges included)
  random: (min, max) => min + Math.floor(Math.random() * (max - min + 1)),

  // Given an array of numbers and a max...
  // Pick a random sum (< max) from the set of all available sums in arr
  randomSumIn: (arr, max) => {
    const sets = [[]];
    const sums = [];
    for (let i = 0; i < arr.length; i++) {
      for (let j = 0, len = sets.length; j < len; j++) {
        const candidateSet = sets[j].concat(arr[i]);
        const candidateSum = utils.sum(candidateSet);
        if (candidateSum <= max) {
          sets.push(candidateSet);
          sums.push(candidateSum);
        }
      }
    }
    return sums[utils.random(0, sums.length - 1)];
  },
};

ReactDOM.render(<StarMatch />, mountNode);

Type Script

This is another way to valid props

 import * as React from "react";

 interface Props {
   name: string
 }

 function Greeting(props : Props) {
    return (
        <h1>Hello {props.name}</h1>
    )
 }

Lifecycle

A component “mounts” when it renders for the first time. This is when mounting lifecycle methods get called.

There are three mounting lifecycle methods:

  • componentWillMount
  • render
  • componentDidMount

There are five updating lifecycle methods:

  • componentWillReceiveProps
  • shouldComponentUpdate
  • componentWillUpdate
  • render
  • componentDidUpdate

Some words of wisdom

A React component should use props to store information that can be changed, but can only be changed by a different component.

A React component should use state to store information that the component itself can change.