React

From bibbleWiki
Jump to navigation Jump to search

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.