// REACT - Javascript Library - by Richard Rembert /**/ // REACT FRAMEWORKS // create-react-app -> 1st party; simple and basic; it is opinionated; many dependencies that could be useless // next.js -> ssr or ssg; very fast; integrates best with Vercel hosting which costs // gatsby -> very fast; plugins and themes available; hard to migrate; official gatsby hosting costs // blitz -> not very established; good doc but not for migration; fast even with API query; needs a server (except on Vercel) // redwood -> not very established; JAM stack; tricky migration; deploy everywhere; meant to be serverless // remix -> very recent // SETUP DEV // start a new react project from scratch -> https://jscomplete.com/learn/1rd-reactful cd projectFolder // create package.json npm init -y // express to run node server npm i express // install react & react-dom npm i react react-dom // install webpack, a module bundler npm i webpack webpack-cli // install babel npm i babel-loader @babel/core @babel/node @babel/preset-env @babel/preset-react // dev dependencies // nodemon or alternative to change server code without restarting node npm i -D nodemon // eslint -> add a .eslintrc.json file npm i -D eslint babel-eslint eslint-plugin-react eslint-plugin-react-hooks /* eslint-config-prettier eslint-config-airbnb eslint-plugin-cypress eslint-plugin-import eslint-plugin-jest eslint-plugin-jsx-a11y eslint-plugin-prettier */ // jest for testing npm i -D jest babel-jest react-test-renderer // start a new react project with a builder create-react-app project-name create-react-app project-name --template typescript // to have typescript set up npm start // very useful to install react devtool // package.json { "name": "ps-redux", "description": "React and Redux Pluralsight course by Cory House", "scripts": { "start": "webpack serve --config webpack.config.dev.js --port 3000" }, "dependencies": { "bootstrap": "5.0.2", "immer": "9.0.5", "prop-types": "15.7.2", "react": "17.0.2", "react-dom": "17.0.2", "react-redux": "7.2.4", "react-router-dom": "5.2.0", "react-toastify": "7.0.4", "redux": "4.1.0", "redux-thunk": "2.3.0", "reselect": "4.0.0" }, "devDependencies": { "@babel/core": "7.14.6", "@testing-library/react": "^12.0.0", "@wojtekmaj/enzyme-adapter-react-17": "^0.6.2", "babel-eslint": "10.1.0", "babel-loader": "8.2.2", "babel-preset-react-app": "10.0.0", "css-loader": "5.2.6", "cssnano": "5.0.6", "enzyme": "3.11.0", "eslint": "7.30.0", "eslint-loader": "4.0.2", "eslint-plugin-import": "2.23.4", "eslint-plugin-react": "7.24.0", "fetch-mock": "9.11.0", "html-webpack-plugin": "5.3.2", "http-server": "0.12.3", "jest": "27.0.6", "json-server": "0.16.3", "mini-css-extract-plugin": "2.1.0", "node-fetch": "^2.6.1", "npm-run-all": "4.1.5", "postcss": "^8.3.5", "postcss-loader": "6.1.1", "react-test-renderer": "17.0.2", "redux-immutable-state-invariant": "2.1.0", "redux-mock-store": "1.5.4", "rimraf": "3.0.2", "style-loader": "3.0.0", "webpack": "5.44.0", "webpack-bundle-analyzer": "4.4.2", "webpack-cli": "4.9.0", "webpack-dev-server": "3.11.2" }, "engines": { "node": ">=8" }, "babel": { "presets": [ "babel-preset-react-app" ] }, "eslintConfig": { "extends": [ "eslint:recommended", "plugin:react/recommended", "plugin:import/errors", "plugin:import/warnings" ], "parser": "babel-eslint", "parserOptions": { "ecmaVersion": 2018, "sourceType": "module", "ecmaFeatures": { "jsx": true } }, "env": { "browser": true, "node": true, "es6": true, "jest": true }, "rules": { "no-debugger": "off", "no-console": "off", "no-unused-vars": "warn", "react/prop-types": "warn" }, "settings": { "react": { "version": "detect" } }, "root": true } } // webpack.config.dev.js 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-Oriigin': '*' }, https: false, }, plugins: [ 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'], }, ], }, }; // SET UP PRODUCTION BUILD // the goal is to end up with a build folder composed of index.html, bundle.js and styles.css // package.json { "name": "ps-redux", "description": "React and Redux Pluralsight course by Cory House", "scripts": { "start": "run-p start:dev start:api", "start:dev": "webpack serve --config webpack.config.dev.js --port 3000", "prestart:api": "node tools/createMockDb.js", "start:api": "node tools/apiServer.js", "test": "jest --watchAll", "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" }, "jest": { "setupFiles": [ "./tools/testSetup.js" ], "testEnvironment": "jsdom", "moduleNameMapper": { "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "/tools/fileMock.js", "\\.(css|less)$": "/tools/styleMock.js" } }, "dependencies": { "bootstrap": "5.0.2", "immer": "9.0.5", "prop-types": "15.7.2", "react": "17.0.2", "react-dom": "17.0.2", "react-redux": "7.2.4", "react-router-dom": "5.2.0", "react-toastify": "7.0.4", "redux": "4.1.0", "redux-thunk": "2.3.0", "reselect": "4.0.0" }, "devDependencies": { "@babel/core": "7.14.6", "@testing-library/react": "^12.0.0", "@wojtekmaj/enzyme-adapter-react-17": "^0.6.2", "babel-eslint": "10.1.0", "babel-loader": "8.2.2", "babel-preset-react-app": "10.0.0", "css-loader": "5.2.6", "cssnano": "5.0.6", "enzyme": "3.11.0", "eslint": "7.30.0", "eslint-loader": "4.0.2", "eslint-plugin-import": "2.23.4", "eslint-plugin-react": "7.24.0", "fetch-mock": "9.11.0", "html-webpack-plugin": "5.3.2", "http-server": "0.12.3", "jest": "27.0.6", "json-server": "0.16.3", "mini-css-extract-plugin": "2.1.0", "node-fetch": "^2.6.1", "npm-run-all": "4.1.5", "postcss": "^8.3.5", "postcss-loader": "6.1.1", "react-test-renderer": "17.0.2", "redux-immutable-state-invariant": "2.1.0", "redux-mock-store": "1.5.4", "rimraf": "3.0.2", "style-loader": "3.0.0", "webpack": "5.44.0", "webpack-bundle-analyzer": "4.4.2", "webpack-cli": "4.9.0", "webpack-dev-server": "3.11.2" }, "engines": { "node": ">=8" }, "babel": { "presets": [ "babel-preset-react-app" ] }, "eslintConfig": { "extends": [ "eslint:recommended", "plugin:react/recommended", "plugin:import/errors", "plugin:import/warnings" ], "parser": "babel-eslint", "parserOptions": { "ecmaVersion": 2018, "sourceType": "module", "ecmaFeatures": { "jsx": true } }, "env": { "browser": true, "node": true, "es6": true, "jest": true }, "rules": { "no-debugger": "off", "no-console": "off", "no-unused-vars": "warn", "react/prop-types": "warn" }, "settings": { "react": { "version": "detect" } }, "root": true } } // webpack.config.prod.js 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: [ new webpackBundleAnalyzer.BundleAnalyzerPlugin({ analyzerMode: 'static' }), new MiniCssExtractPlugin({ filename: '[name].[contenthash].css', }), new webpack.DefinePlugin({ '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: { postcssOptions: { plugins: [() => [require('cssnano')]], }, sourceMap: true, }, }, ], }, ], }, }; // BASICS // use those import everytime! They are needed in all examples import React from "react"; import ReactDOM from "react-dom"; // write JSX elements just as HTML const h1 =

React JS

; // define element attribute just as in HTML const a = What a link!; // multiline element are possible using parenthesis () const ah1 = (

What a big link!

); // a JSX expression must have exactly one outermost element // good habit is to have a
, or <> (, ) wrapping everything const blog = (

Main title

Subtitle

); // render your HTML; first argument is the JSX element and the second points to the HTML where it will be rendered // ->
ReactDOM.render(

Hello world

, document.getElementById("app")); // you can use variable of course const myElt =

Render me!

; ReactDOM.render(myElt, document.getElementById("app")); // or create it without JSX const myElt = React.createElement("h1", null, "Hello world"); ReactDOM.render(myElt, document.getElementById("app")); // class property is special, contrary to HTML, JSX need it to be called className const myDiv =
; // self closing tags as well, they NEED the back slash const myImg = ; const myBr =
; // javascript into JSX thanks to curly braces {} const text = "The result of 2 + 3 is:"; const myJS =

{text + " " + 2 + 3}

; // you can have comments that way const myCom = (

Comment

{/* here is a commented text */}
); // create event listeners function myFunc() { alert("Click on this image"); } // if else are not possibile inside JSX --> use ternary operator const isTrue =

{1 === 1 ? "true" : "false"}

; // JSX conditionals; will render the HTML or not based on the left of the logical operator const showParagraph = true; const myDiv = (
{showParagraph &&

I'm rendered because the const is true

}
); const hideParagraph = false; const myDiv = (
{hideParagraph ||

I'm rendered because the const is false

}
); // map method and JSX; React understand it needs to make a list out of the array // use key attribute to make list item identifiable! makte each key unique and avoid index!! const numbers = ["one", "two", "three"]; const list = numbers.map((number, i) =>
  • {number}
  • ); ReactDOM.render(
      {list}
    , document.getElementById("app")); // filter helps with filtering maps const numbers = [ {n: "one", ok: true}, {n: "twoo", ok: false}, {n: "three", ok: true} ]; const list = numbers.filter(number => number.ok).map((numberFiltered, i) =>
  • {numberFiltered.n}
  • ); ReactDOM.render(
      {list}
    , document.getElementById("app")); // COMPONENTS // React Component class MyComponentClass extends React.Component { render() { return

    Hello world

    ; } }; ReactDOM.render(, document.getElementById('app')); // React Component multiline class QuoteMaker extends React.Component { render() { return (

    The world is full of objects, more or less interesting; I do not wish to add any more.

    Douglas Huebler
    ); } }; ReactDOM.render(, document.getElementById('app')); // React Component with variables const owl = { title: 'Excellent Owl', src: 'https://s3.amazonaws.com/codecademy-content/courses/React/react_photo-owl.jpg' }; class Owl extends React.Component { render() { return (

    {owl.title}

    {owl.title}
    ); } } ReactDOM.render(, document.getElementById('app')); // React Component render with logic class Random extends React.Component { render() { const n = Math.floor(Math.random() * 10 + 1); return

    The number is {n}!

    ; } } ReactDOM.render(, document.getElementById('app')); // conditionals in components const fiftyFifty = Math.random() < 0.5; class TonightsPlan extends React.Component { render() { if (fiftyFifty) { return

    Tonight I'm going out WOOO

    ; } else { return

    Tonight I'm going to bed WOOO

    ; } } } ReactDOM.render(, document.getElementById('app')); // Components and this class MyName extends React.Component { get name() { return 'Einstein'; } render() { return

    My name is {this.name}.

    ; } } ReactDOM.render(, document.getElementById('app')); // Event listener in Component class Button extends React.Component { scream() { alert('AAAAAAAAHHH!!!!!'); } render() { return ; } } ReactDOM.render( ); } } import React from 'react'; import ReactDOM from 'react-dom'; import { Button } from './Button'; class Talker extends React.Component { handleClick() { let speech = ''; for (let i = 0; i < 10000; i++) { speech += 'blah '; } alert(speech); } render() { return ); } } Button.defaultProps = {text: 'I am a button'}; ReactDOM.render(
    ); } } ReactDOM.render(, document.getElementById('app')); // react forms (a form is uncontrolled when react doesn't manage it, meaning real DOM is in charge) class ControlledInput extends React.Component { constructor(props) { super(props); this.state = { input: '' }; this.handleChange = this.handleChange.bind(this); } handleChange(event) { this.setState({ input: event.target.value }); } // to avoid binding we can make handleChange an arrow function // handleChange = (event) => { this.setState({ input: event.target.value }) } render() { return (

    Controlled Input:

    {this.state.input}

    ); } }; // component lifecycle methods import React from 'react'; export class Clock extends React.Component { constructor(props) { super(props); this.state = { date: new Date() }; } startInterval() { let delay = this.props.isPrecise ? 100 : 1000; this.intervalID = setInterval(() => { this.setState({ date: new Date() }); }, delay); } render() { return (
    {this.props.isPrecise ? this.state.date.toISOString() : this.state.date.toLocaleTimeString()}
    ); } componentDidMount() { this.startInterval(); } componentDidUpdate(prevProps) { if (this.props.isPrecise === prevProps.isPrecise) { return; } clearInterval(this.intervalID); this.startInterval(); } componentWillUnmount() { clearInterval(this.intervalID); } } // FUNCTIONAL COMPONENTS // state, ref and lyfecycle arrived in react v16.8 with hooks // still can't use componentDidError and getSnapshotBeforeUpdate // React Components const FunctionalComponent() { return

    Hello world

    ; }; const FunctionalComponent = () => (

    Hello world

    ; ); // React Component multiline const FunctionalComponent() { return (

    Hello world

    How you doing?

    ); } const FunctionalComponent = () => (

    Hello world

    How you doing?

    ); // Render Component import React from "react"; const FunctionalComponent = () => { return

    Hello world

    ; }; ReactDOM.render(, document.getElementById('app')); // OR import React from "react"; const FunctionalComponent = () => { return

    Hello world

    ; }; export default FunctionalComponent; // OR import React from "react"; export default function FunctionalComponent() { return

    Hello world

    ; }; // Pay attention to the export method !!! export default function ComponentName() {}; import ComponentName from './path'; const ComponentName = () => {}; export default ComponentName; import ComponentName from './path'; export function ComponentName() {}; import { ComponentName } from './path'; export const ComponentName = () => {}; import { ComponentName } from './path'; // PROPS import PropTypes from 'prop-types'; // needs 'npm install prop-types' const FunctionalComponent = (props) => { return

    Hello, {props.name}

    ; }; // OR const FunctionalComponent = ({ name }) => { return

    Hello, {name}

    ; }; // OR const FunctionalComponent = ({ name, ...props }) => { return

    Hello, {name} {props.surname}

    ; }; // prop types arrayOf(number), objectOf(string)) -> https://reactjs.org/docs/typechecking-with-proptypes.html FunctionalComponent.propTypes = { name: PropTypes.string.isRequired, age: PropTypes.number, fun: PropTypes.bool, arr: PropTypes.array, obj: PropTypes.obj, el: PropTypes.element, one: PropTypes.oneOf([number, string]) }; // default props FunctionalComponent.defaultProps = { age: 16 }; } one={1} /> // PROPS FROM PARENT TO CHILD import { ChildComponent } from './ChildComponent'; const ParentComponent = () => { return ; }; export const ChildComponent = ({ name, ...props }) => { return

    Hello, {name}

    }; // PROPS FROM CHILD TO PARENT import { ChildComponent } from './ChildComponent'; const ParentComponent = () => { const getFromChild = (data) => { console.log(data); } return ; }; export const ChildComponent = ({ func, ...props }) => { func('This is data') return <> }; // STATE import { useState } from 'react'; const FunctionalComponent = () => { const [count, setCount] = useState(0); return (

    count: {count}

    ); }; // OR conditional rendering based on state const FunctionalComponent = () => { const [show, setShow] = useState(false); return (
    {show &&

    {show ? `Ì'm visible` : `Ì'm not visible`}

    }
    ); }; // EFFECT import { useEffect } from 'react'; const FunctionalComponent = () => { // On Mounting useEffect( () => console.log("mount"), [] ); // update specific data useEffect( () => console.log("will update data"), [ data ] ); // update all useEffect( () => console.log("will update any") ); // update specific data or unmount useEffect( () => () => console.log("will update data or unmount"), [ data ] ); // On Unmounting useEffect( () => () => console.log("unmount"), [] ); // updated data returned return

    {data}

    ; }; // skip first render with useEffect import { useEffect, useRef } from 'react'; const FunctionalComponent = () => { // useref to avoid wasted renders const notInitialRender = useRef(false) useEffect(() => { if (notInitialRender.current) { // do your magic here } else { notInitialRender.current = true } }, [data]) return

    {data}

    ; } // REF import { useRef } from 'react'; const FunctionalComponent = () => { const inputEl = useRef(null); const onButtonClick = () => { // `current` points to the mounted text input element inputEl.current.focus(); }; return ( <> ); } // REDUCER // useReducer instead of useState for complex state logic import { useReducer } from 'react'; function reducer(state, action) { switch (action.type) { case 'increment': return {count: state.count + 1}; case 'decrement': return {count: state.count - 1}; default: throw new Error(); } } const FunctionalComponent = () => { const [state, dispatch] = useReducer(reducer, 0); return (

    count: {state.count}

    ); }; // MEMO // memoize a function to update only when a dependency prop has changed in the array import { useMemo } from 'react'; const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]); // CALLBACK // memoize a returned value to update only when a dependency prop has changed in the array import { useCallback } from 'react'; const memoizedCallback = useCallback( () => { doSomething(a, b); }, [a, b], ); // CONTEXT // very useful to pass data deep in the tree import { createContext, useState } from 'react'; export const CountContext = createContext(); const FunctionalComponent = () => { const [count, setCount] = useState(0); return ( ) } import { useContext } from 'react'; import { CountContext } from './FunctionalComponent'; const ChildComponent = () => { const {setCount, count} = useContext(CountContext); return (

    count: {count}

    ) } // EVENTS const EventComponents = () => ( <> {/* and many more -> https://reactjs.org/docs/events.html */} ) // FORMS (controlled, react manage the state of the form) import { useState } from 'react'; const ControlledInput = () => { const [input, setInput] = useState(''); const [textarea, setTextarea] = useState(''); const [select, setSelect] = useState(1); const [checkbox, setCheckbox] = useState(false); const [radio, setRadio] = useState(false); function handleChange(e) { setInput(() => e.target.value); } return (
    {/* external OR inline onChange function */} setInput(e.target.value)} />