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

Variables

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

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;
        }
        return action
    }

    // 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, [])

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.