├── .gitignore ├── README.md ├── package.json ├── public ├── favicon.ico ├── index.html └── manifest.json ├── src ├── App.css ├── App.js ├── assets │ ├── cat-13.png │ └── ref_demo_music.mp3 ├── components │ ├── fragments │ │ ├── README.md │ │ ├── fragments.css │ │ └── fragments.jsx │ ├── higherOrderComponents │ │ ├── HOC.css │ │ ├── HOCDemo.jsx │ │ ├── JokeReader.jsx │ │ ├── README.md │ │ ├── api_util.js │ │ ├── withLoader.jsx │ │ └── withLogger.jsx │ ├── propTypes │ │ ├── PropTypesDemo.jsx │ │ └── README.md │ └── refs │ │ ├── README.md │ │ ├── refsDemo.css │ │ ├── refsDemo.jsx │ │ └── uncontrolledForm.jsx ├── index.css ├── index.js ├── logo.svg └── registerServiceWorker.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React: Beyond the Basics 2 | 3 | App Academy Job Seeker Technical Workshop 4/26/18 4 | 5 | ### Learning Goals 6 | 7 | Graduates of the aA curriculum have a solid foundational understanding of the React framework. However, there is not time in the course to cover many of its features or some common design patterns used in React development. In this workshop, we will be exploring and demoing the following React features: 8 | 9 | * type checking with propTypes & using default props 10 | * interacting with the native DOM via refs 11 | * controlled vs uncontrolled components 12 | * Fragments 13 | 14 | We will also look at some common React design patterns including: 15 | 16 | * Higher Order Components 17 | 18 | Along the way we'll brush up against some ES7+ JavaScript syntax and web APIs with which you may be unfamiliar: 19 | 20 | * Async / Await syntax for Promises 21 | * Object Rest/Spread Properties 22 | * Fetch for making AJAX requests (rather than JQuery's `$.ajax()`) 23 | 24 | ### Next Lecture 25 | 26 | On May 10, we'll have React: Beyond the Basics THE SEQUEL, where we'll cover: 27 | 28 | * render props 29 | * compound components 30 | * the context API 31 | * the suspense API (maybe) 32 | 33 | ### Demos 34 | 35 | * [PropTypes && default props](src/components/propTypes) 36 | * [Refs](src/components/refs) 37 | * [Fragments](src/components/fragments) 38 | * [Higher Order Components](src/components/higherOrderComponents) 39 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-workshop", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "lodash": "^4.17.10", 7 | "react": "^16.3.2", 8 | "react-dom": "^16.3.2", 9 | "react-scripts": "1.1.4" 10 | }, 11 | "scripts": { 12 | "start": "react-scripts start", 13 | "build": "react-scripts build", 14 | "test": "react-scripts test --env=jsdom", 15 | "eject": "react-scripts eject" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthaws/react-workshop/c31aa6ab23449fd801c178a4d6e430d11da9047a/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 14 | 23 | React App 24 | 25 | 26 | 29 |
30 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .welcome { 6 | background-color: black; 7 | color: lime; 8 | margin: 0; 9 | font-size: 60px; 10 | height: 100vh; 11 | padding: 100px; 12 | font-family: "Rock Salt"; 13 | } 14 | 15 | .App-logo { 16 | animation: App-logo-spin infinite 20s linear; 17 | height: 80px; 18 | } 19 | 20 | .App-header { 21 | background-color: #222; 22 | height: 150px; 23 | padding: 20px; 24 | color: white; 25 | } 26 | 27 | .App-title { 28 | font-size: 1.5em; 29 | } 30 | 31 | .App-intro { 32 | font-size: large; 33 | } 34 | 35 | @keyframes App-logo-spin { 36 | from { 37 | transform: rotate(0deg); 38 | } 39 | to { 40 | transform: rotate(360deg); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import PropTypesDemo from "./components/propTypes/PropTypesDemo"; 3 | import RefsDemo from "./components/refs/refsDemo"; 4 | import UncontrolledForm from "./components/refs/uncontrolledForm"; 5 | import FragmentDemo from "./components/fragments/fragments"; 6 | import HOCDemo from "./components/higherOrderComponents/HOCDemo.jsx"; 7 | import RenderPropsDemo from "./components/renderProps/renderProps.jsx"; 8 | import CompoundDemo from "./components/compoundComponents/compoundDemo.jsx"; 9 | import "./App.css"; 10 | 11 | class App extends Component { 12 | render() { 13 | return ( 14 |
15 | 16 |
17 | ); 18 | } 19 | } 20 | 21 | export default App; 22 | -------------------------------------------------------------------------------- /src/assets/cat-13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthaws/react-workshop/c31aa6ab23449fd801c178a4d6e430d11da9047a/src/assets/cat-13.png -------------------------------------------------------------------------------- /src/assets/ref_demo_music.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthaws/react-workshop/c31aa6ab23449fd801c178a4d6e430d11da9047a/src/assets/ref_demo_music.mp3 -------------------------------------------------------------------------------- /src/components/fragments/README.md: -------------------------------------------------------------------------------- 1 | # Fragments 2 | 3 | As you know, React components typically have to return one parent element with children. You couldn't, for example, do something like this: 4 | 5 | ```javascript 6 | return ( 7 |
  • 1
  • 8 |
  • 2
  • 9 |
  • 3
  • 10 | ) 11 | ``` 12 | 13 | You've probably run into that warning in the console that yells at you and tell you that you need a parent wrapper, so something like this: 14 | 15 | ```javascript 16 | return ( 17 | 22 | ); 23 | ``` 24 | 25 | But what if you _really_ wanted that ul to be in the parent component? What if you _really really_ want a React component to return siblings that are NOT wrapped in one parent component? Turns out plenty of developers wanted exactly that, and its now supported via the use of React Fragments. 26 | 27 | ```javascript 28 | return ( 29 | 30 |
  • 1
  • 31 |
  • 2
  • 32 |
  • 3
  • 33 |
    34 | ); 35 | ``` 36 | 37 | See the code in this folder for a further demo. In that example, returning the children in a wrapping div messes up the CSS flex properties on the parent. Returning the children wrapped in the React.Fragment syntax will allow the flex to work properly. 38 | 39 | [More on React Fragments here in the official docs.](https://reactjs.org/docs/fragments.html) 40 | -------------------------------------------------------------------------------- /src/components/fragments/fragments.css: -------------------------------------------------------------------------------- 1 | .parent-element { 2 | display: flex; 3 | padding: 100px; 4 | } 5 | 6 | .circle { 7 | width: 50px; 8 | height: 50px; 9 | border-radius: 50%; 10 | background-color: hotpink; 11 | margin: 10px; 12 | } 13 | -------------------------------------------------------------------------------- /src/components/fragments/fragments.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import PropTypes from "prop-types"; 3 | import "./fragments.css"; 4 | 5 | const FragmentDemo = () => { 6 | return ( 7 |
    8 | 9 |
    10 | ); 11 | }; 12 | 13 | const FlexedChildren = ({ number }) => { 14 | const divs = []; 15 | for (let i = 0; i < number; i++) { 16 | divs.push(
    ); 17 | } 18 | 19 | return {divs}; 20 | }; 21 | 22 | FlexedChildren.propTypes = { 23 | number: PropTypes.number 24 | }; 25 | 26 | export default FragmentDemo; 27 | -------------------------------------------------------------------------------- /src/components/higherOrderComponents/HOC.css: -------------------------------------------------------------------------------- 1 | .button { 2 | background-image: linear-gradient(blue, green); 3 | } 4 | 5 | .spinner, 6 | .spinner:before, 7 | .spinner:after { 8 | border-radius: 50%; 9 | } 10 | .spinner { 11 | color: #ffffff; 12 | font-size: 11px; 13 | text-indent: -99999em; 14 | margin: 55px auto; 15 | position: relative; 16 | width: 10em; 17 | height: 10em; 18 | box-shadow: inset 0 0 0 1em; 19 | -webkit-transform: translateZ(0); 20 | -ms-transform: translateZ(0); 21 | transform: translateZ(0); 22 | } 23 | .spinner:before, 24 | .spinner:after { 25 | position: absolute; 26 | content: ""; 27 | } 28 | .spinner:before { 29 | width: 5.2em; 30 | height: 10.2em; 31 | background: #0dc5c1; 32 | border-radius: 10.2em 0 0 10.2em; 33 | top: -0.1em; 34 | left: -0.1em; 35 | -webkit-transform-origin: 5.2em 5.1em; 36 | transform-origin: 5.2em 5.1em; 37 | -webkit-animation: load2 2s infinite ease 1.5s; 38 | animation: load2 2s infinite ease 1.5s; 39 | } 40 | .spinner:after { 41 | width: 5.2em; 42 | height: 10.2em; 43 | background: #0dc5c1; 44 | border-radius: 0 10.2em 10.2em 0; 45 | top: -0.1em; 46 | left: 5.1em; 47 | -webkit-transform-origin: 0px 5.1em; 48 | transform-origin: 0px 5.1em; 49 | -webkit-animation: load2 2s infinite ease; 50 | animation: load2 2s infinite ease; 51 | } 52 | @-webkit-keyframes load2 { 53 | 0% { 54 | -webkit-transform: rotate(0deg); 55 | transform: rotate(0deg); 56 | } 57 | 100% { 58 | -webkit-transform: rotate(360deg); 59 | transform: rotate(360deg); 60 | } 61 | } 62 | @keyframes load2 { 63 | 0% { 64 | -webkit-transform: rotate(0deg); 65 | transform: rotate(0deg); 66 | } 67 | 100% { 68 | -webkit-transform: rotate(360deg); 69 | transform: rotate(360deg); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/components/higherOrderComponents/HOCDemo.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import compose from "lodash/fp/compose"; 3 | import withLoader from "./withLoader.jsx"; 4 | import withLogger from "./withLogger.jsx"; 5 | import JokeReader from "./JokeReader.jsx"; 6 | import fetchJoke from "./api_util"; 7 | 8 | const HOCDemo = () => { 9 | const doAThing = () => { 10 | console.log("Did a thing!"); 11 | }; 12 | const JokeWithLoader = compose(withLogger, withLoader)(JokeReader); 13 | 14 | return ; 15 | }; 16 | 17 | const withButton = customOnClick => { 18 | return class Button extends Component { 19 | render() { 20 | return ( 21 | 24 | ); 25 | } 26 | }; 27 | }; 28 | 29 | export default HOCDemo; 30 | -------------------------------------------------------------------------------- /src/components/higherOrderComponents/JokeReader.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | 3 | class JokeReader extends Component { 4 | constructor(props) { 5 | super(props); 6 | this.state = { joke: this.props.payload.joke }; 7 | } 8 | 9 | componentDidMount() { 10 | this.tellJoke(this.state.joke); 11 | } 12 | 13 | tellJoke(joke) { 14 | this.speech = new SpeechSynthesisUtterance(joke); 15 | window.speechSynthesis.speak(this.speech); 16 | } 17 | 18 | static getDerivedStateFromProps(nextProps, prevState) { 19 | if (nextProps.payload.joke !== prevState.joke) { 20 | const joke = nextProps.payload.joke; 21 | return { joke }; 22 | } else { 23 | return prevState; 24 | } 25 | } 26 | 27 | componentDidUpdate(prevProps, prevState) { 28 | if (prevState.joke !== this.state.joke) { 29 | this.tellJoke(this.state.joke); 30 | } 31 | } 32 | 33 | render() { 34 | const { repeatAsyncCall } = this.props; 35 | return ( 36 |
    37 |

    I'm telling a joke!

    38 | 39 |
    40 | ); 41 | } 42 | } 43 | 44 | export default JokeReader; 45 | -------------------------------------------------------------------------------- /src/components/higherOrderComponents/README.md: -------------------------------------------------------------------------------- 1 | # Higher Order Components 2 | 3 | A Higher Order Component is simply a component that renders another component. You can think of it as a wrapper for other components that modifies or customizes their behavior. The connected container components we use in the Redux cycle are a common example of HOCs in work, but you can make your own in order to have customizable behavior! 4 | 5 | Here's a very simple example to start. Let's make a custom Button component that we can use whenever we want a ` 19 | ); 20 | } 21 | } 22 | ``` 23 | 24 | Now we can easily use this component to make buttons out of HTML elements or even other components! 25 | 26 | ```javascript 27 | render() { 28 | return ( 29 |
    30 | 34 |
    35 | ) 36 | } 37 | ``` 38 | 39 | Another common pattern is to make use of a function and closure to return a new component. Let's say for example we wanted to create a reusable HOC that will allow us to log to the console all of the props that its child component is receiving, useful for debugging. 40 | 41 | ```javascript 42 | const withLogger = ConnectedComponent => { 43 | return props => { 44 | console.log(props); 45 | return ; 46 | }; 47 | }; 48 | 49 | const ComponentWithLogger = withLogger(DemoComponent); 50 | ``` 51 | 52 | `ComponentWithLogger` is now a wrapped version of `DemoComponent` that will log its props to the console on each render. 53 | 54 | Here's a slightly more complicated example. Let's make a reusable Loader component that will display a spinner until an asynchronous API call is returned. There are a lot of libraries and patterns out there that make these kind of DataFetcher or Loader components really efficiently and with robust options, but lets make a very simple example of our own: 55 | 56 | ```javascript 57 | const withLoader = ChildComponent => { 58 | return class Loader extends React.Component { 59 | constructor(props) { 60 | super(props); 61 | this.state = { isLoaded: false, payload: null }; 62 | } 63 | 64 | componentDidMount() { 65 | this.props 66 | .asyncCall() 67 | .then(payload => this.setState({ isLoaded: true, payload })); 68 | } 69 | 70 | // ES7 async/await syntax which can replace .then syntax for promises. 71 | repeatAsyncCall = async e => { 72 | const payload = await this.props.asyncCall(); 73 | this.setState({ payload }); 74 | }; 75 | 76 | render() { 77 | // ES7 syntax, we destructure asyncCall out of the props and then use ... to indicate that everything else in the props object be pulled out into a new object called otherProps. 78 | const { asyncCall, ...otherProps } = this.props; 79 | 80 | if (this.state.isLoaded) { 81 | return ( 82 | 88 | ); 89 | } else { 90 | return
    ; 91 | } 92 | } 93 | }; 94 | }; 95 | ``` 96 | 97 | Now by calling `withLoader(DemoComponent)` we get back a wrapped version of `DemoComponent` that expects an `asyncCall` prop, will call that prop on mounting and initially render a spinner. When the async call comes back, it will render the wrapped component instead, passing down any props intended for that component along with a `payload` which is the returned data from the API. 98 | 99 | The real power of having these utility higher order components is that we can pick and choose between them. A powerful, functional-programming way of doing this is using `compose`, a powerful function you can get from a library like Lodash or write yourself if you are feeling up to a challenge. Compose takes as an argument any number of functions that you want. It runs those functions in order, passing in the return value from each function as an argument to the next one. This all returns a function that just needs the arguments for the first function in the composed chain in order to get started. Its a cool way to chain up functions! Here's an example: 100 | 101 | ```javascript 102 | const CoolComponent = compose(withLogger, withLoader)(DemoComponent); 103 | ``` 104 | 105 | Now we have a component wrapped with both a Logger and a Loader! As you can imagine, now we can easily customize all our app's components with utility HOCs. 106 | 107 | This is just scratching the surface, but as you can see higher order components can be very powerful! 108 | -------------------------------------------------------------------------------- /src/components/higherOrderComponents/api_util.js: -------------------------------------------------------------------------------- 1 | // "Accept", "application/json" 2 | // "https://icanhazdadjoke.com/" 3 | 4 | const fetchJoke = async () => { 5 | const headers = new Headers(); 6 | headers.append("Accept", "application/json"); 7 | 8 | const response = await fetch("https://icanhazdadjoke.com/", { 9 | method: "GET", 10 | headers 11 | }); 12 | 13 | const payload = await response.json(); 14 | 15 | return payload; 16 | }; 17 | 18 | export default fetchJoke; 19 | -------------------------------------------------------------------------------- /src/components/higherOrderComponents/withLoader.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import "./HOC.css"; 4 | 5 | const withLoader = ChildComponent => { 6 | return class Loader extends React.Component { 7 | constructor(props) { 8 | super(props); 9 | this.state = { isLoaded: false, payload: null }; 10 | } 11 | 12 | componentDidMount() { 13 | this.props 14 | .asyncCall() 15 | .then(payload => this.setState({ payload, isLoaded: true })); 16 | } 17 | 18 | repeatAsyncCall = () => { 19 | this.props.asyncCall().then(payload => this.setState({ payload })); 20 | }; 21 | 22 | static propTypes = { 23 | asyncCall: PropTypes.func.isRequired 24 | }; 25 | 26 | render() { 27 | const { asyncCall, ...otherProps } = this.props; 28 | return ( 29 |
    30 | {this.state.isLoaded ? ( 31 | 36 | ) : ( 37 |
    38 | )} 39 |
    40 | ); 41 | } 42 | }; 43 | }; 44 | 45 | export default withLoader; 46 | -------------------------------------------------------------------------------- /src/components/higherOrderComponents/withLogger.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | 3 | const withLogger = ChildComponent => { 4 | return props => { 5 | console.log(props); 6 | return ; 7 | }; 8 | }; 9 | 10 | export default withLogger; 11 | -------------------------------------------------------------------------------- /src/components/propTypes/PropTypesDemo.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import PropTypes from "prop-types"; 3 | 4 | const PropTypesDemo = ({ 5 | string, 6 | number, 7 | component: Component, 8 | func, 9 | isOn 10 | }) => { 11 | const funcResult = func(); 12 | 13 | if (isOn) { 14 | return ( 15 |
    16 |

    String: {string.toUpperCase()}

    17 |

    Number: {number * 5}

    18 |

    Function result: {funcResult}

    19 |

    20 | 21 |

    22 |
    23 | ); 24 | } else { 25 | return null; 26 | } 27 | }; 28 | 29 | class DemoComponent extends Component { 30 | static propTypes = { 31 | sampleProps: PropTypes.string 32 | }; 33 | 34 | static defaultProps = { 35 | sampleProps: "this is a string" 36 | }; 37 | } 38 | 39 | PropTypesDemo.propTypes = { 40 | string: PropTypes.string, 41 | number: PropTypes.number, 42 | func: PropTypes.func.isRequired, 43 | isOn: PropTypes.bool 44 | }; 45 | 46 | PropTypesDemo.defaultProps = { 47 | string: "String", 48 | number: 0, 49 | isOn: true 50 | }; 51 | 52 | export default PropTypesDemo; 53 | -------------------------------------------------------------------------------- /src/components/propTypes/README.md: -------------------------------------------------------------------------------- 1 | ## PropTypes 2 | 3 | In very large applications with multiple teams, its important that everyone is on the same page when it comes to using each React component in the app. That means everybody is giving each component the right kind of props that the component needs to work. A lot of bugs can be prevented by _enforcing_ that a component's props be of the types it needs. There are third-party extensions that some companies use to do this, but React comes with a native way of checking and enforcing prop types: called, shockingly enough, PropTypes. 4 | 5 | In the React component defined in this folder, we have an incredibly simple and rather abstract little functional component that takes in a number of props: a string, a number, a function, a boolean, and a React component. Each of those props are used in a way that would likely cause the app to break or behave strangely if they were not the type we expect. 6 | 7 | ```javascript 8 | const PropTypesDemo = ({ 9 | string, 10 | number, 11 | component: Component, 12 | func, 13 | isOn 14 | }) => { 15 | const funcResult = func(); 16 | 17 | if (isOn) { 18 | return ( 19 |
    20 |

    String: {string.toUpperCase()}

    21 |

    Number: {number * 5}

    22 |

    Function result: {funcResult}

    23 |

    24 | 25 |

    26 |
    27 | ); 28 | } else { 29 | return null; 30 | } 31 | }; 32 | ``` 33 | 34 | We could leave this as is and just hope we remember what each prop is supposed to be, or hope that everybody on our team just remembers or looks closely at the code before using it. OR we could not be silly and just make sure React checks these props for us by defining a `propTypes` property on the component itself: 35 | 36 | ```javascript 37 | propTypesDemo.propTypes = { 38 | string: PropTypes.string, 39 | number: PropTypes.number, 40 | component: PropTypes.element, 41 | func: PropTypes.func, 42 | isOn: PropTypes.bool 43 | }; 44 | ``` 45 | 46 | [See this link](https://reactjs.org/docs/typechecking-with-proptypes.html) for the full list of the different types you can check for, its rather comprehensive. 47 | 48 | We will now get a helpful error in the console, alerting the developer that a prop was not the type expected so that we can address it immediately rather than having to debug and track down what the heck is going wrong later. 49 | 50 | We can go a step further and enforce that some of these props are _required_ and have React raise an error if they are not provided. For the others, we can define reasonable defaults that will be used if that prop is not given: 51 | 52 | ```javascript 53 | propTypesDemo.propTypes = { 54 | string: PropTypes.string, 55 | number: PropTypes.number, 56 | component: PropTypes.element.isRequired, 57 | func: PropTypes.func.isRequired, 58 | isOn: PropTypes.bool 59 | }; 60 | 61 | propTypesDemo.defaultProps = { 62 | string: "", 63 | number: 0, 64 | isOn: true 65 | }; 66 | ``` 67 | 68 | Default props will be used if that key is `undefined` in the given props. Note: it will not use the default if the key is `null` or some other false-y value besides `undefined`. 69 | 70 | Default props are useful, but you are _strongly encouraged_ to define prop types on your React components in your projects moving forward. 71 | -------------------------------------------------------------------------------- /src/components/refs/README.md: -------------------------------------------------------------------------------- 1 | # Refs 2 | 3 | #### Using React with the native DOM 4 | 5 | You've been taught to use React's declarative syntax to tell the app what you want the DOM to look like and then to leave it to the framework's under-the-hood functionality to make it happen as efficiently as possible using its virtual DOM. That's why we generally avoid messing with the real DOM with something like JQuery when using React - get out of React's way and let it do its thing! 6 | 7 | There are a few use cases, however, when there actually isn't a great React-only way to manipulate the DOM in exactly the way that you want. For those cases, React has a feature which will give you access to the _real_ DOM node from within the component that is representing it. These associations are called "refs". 8 | 9 | Common use cases for refs are things like manipulating focus on input elements or handling media playback. [Full info is available in the official React docs.](https://reactjs.org/docs/refs-and-the-dom.html) 10 | 11 | Declaring a ref is very straightforward: 12 | 13 | ```JavaScript 14 | constructor() { 15 | super(); 16 | this.audio = React.createRef(); 17 | } 18 | 19 | render() { 20 | return ( 21 |
    22 |
    24 | ); 25 | } 26 | ``` 27 | 28 | We create ref using React's `createRef()` function, then assign that via a prop to the appropriate element. We can now access the actual HTML audio element via `this.audio.current`. Now that we have a "ref"erence to the native DOM element, we can interact with it directly as we would normally. In the case of an audio element as in the example above, that means we have access to all the methods exposed by the HTML5 audio API: 29 | 30 | ```javascript 31 | startMusic = () => this.audio.current.play(); 32 | 33 | stopMusic = () => this.audio.current.pause(); 34 | 35 | render() { 36 | return ( 37 |
    38 |

    LETS ROCK!

    39 |
    47 | ); 48 | } 49 | ``` 50 | 51 | Another fairly common and simple way to define a ref is by giving a callback to the ref prop on the element itself. This creates a very basic ref without some of the extra features you get with `createRef()`. In the example below, we'll be able to access this input field just by calling `this.myInput` instead of `this.myInput.current` like we did in the audio example. That `current` property is one of the the things that `createRef()` gives you. 52 | 53 | ```javascript 54 | (this.myInput = input)} /> 55 | ``` 56 | 57 | It's fairly common to use refs to interact with input fields directly. This allows us to manipulate focus directly. For example, if we wanted our input field to be focused when this component loads up, we might do somethign like this: 58 | 59 | ```javascript 60 | componentDidMount() { 61 | this.myInput.focus(); 62 | } 63 | 64 | render() { 65 | return ( 66 |
    this.input = myInput} /> 68 |
    69 | ); 70 | } 71 | ``` 72 | 73 | Refs can be very useful BUT the docs caution you not to over-use them. Nine times out of ten, there's a more React-appropriate way to manipulate the DOM. Don't give in to the temptation to use refs to frequently circumvent React and manipulate the DOM directly or you may cause unexpected behaviors. Trust React to do what it does, and only fall back on refs for very specific scenarios where they are necessary. 74 | -------------------------------------------------------------------------------- /src/components/refs/refsDemo.css: -------------------------------------------------------------------------------- 1 | .lets-rock { 2 | font-family: "Rock Salt", cursive; 3 | } 4 | 5 | .lets-rock button { 6 | width: 100px; 7 | height: 100px; 8 | background-color: white; 9 | background-image: none; 10 | font-family: "Rock Salt"; 11 | cursor: pointer; 12 | margin: 10px; 13 | } 14 | 15 | .demo-input { 16 | margin-top: 50px; 17 | height: 25px; 18 | width: 100px; 19 | transition: all 1s linear; 20 | } 21 | 22 | .demo-input:focus { 23 | outline-color: green; 24 | width: 200px; 25 | height: 75px; 26 | font-size: 30px; 27 | } 28 | -------------------------------------------------------------------------------- /src/components/refs/refsDemo.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import PropTypes from "prop-types"; 3 | import demo_music from "../../assets/ref_demo_music.mp3"; 4 | import "./refsDemo.css"; 5 | 6 | class RefsDemo extends Component { 7 | constructor(props) { 8 | super(props); 9 | this.myInput = React.createRef(); 10 | } 11 | 12 | componentDidMount() { 13 | this.myInput.current.focus(); 14 | } 15 | 16 | startMusic = () => this.audio.play(); 17 | stopMusic = () => this.audio.pause(); 18 | 19 | render() { 20 | return ( 21 |
    22 |

    LETS ROCK!

    23 |
    34 | ); 35 | } 36 | } 37 | 38 | export default RefsDemo; 39 | -------------------------------------------------------------------------------- /src/components/refs/uncontrolledForm.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component, createRef } from "react"; 2 | 3 | // This is an example of an "uncontrolled" component. The value of the input fields 4 | // is handled exclusively by the actual HTML elements on the DOM and not held in 5 | // React state. While this limits us in what we can do with those values in React, 6 | // it's a very simple pattern with little code that might be appropriate for small forms. 7 | 8 | class UncontrolledForm extends Component { 9 | handleSubmit = e => { 10 | e.preventDefault(); 11 | logInUser(this.nameInput.value, this.passwordInput.value); 12 | }; 13 | 14 | render() { 15 | return ( 16 |
    17 | 21 | 22 | 26 | 27 | 28 |
    29 | ); 30 | } 31 | } 32 | 33 | const logInUser = (username, password) => { 34 | console.log(`${username}: ${password}`); 35 | }; 36 | 37 | export default UncontrolledForm; 38 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import "./index.css"; 4 | import App from "./App"; 5 | import registerServiceWorker from "./registerServiceWorker"; 6 | 7 | ReactDOM.render(, document.getElementById("root")); 8 | registerServiceWorker(); 9 | -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/registerServiceWorker.js: -------------------------------------------------------------------------------- 1 | // In production, we register a service worker to serve assets from local cache. 2 | 3 | // This lets the app load faster on subsequent visits in production, and gives 4 | // it offline capabilities. However, it also means that developers (and users) 5 | // will only see deployed updates on the "N+1" visit to a page, since previously 6 | // cached resources are updated in the background. 7 | 8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy. 9 | // This link also includes instructions on opting out of this behavior. 10 | 11 | const isLocalhost = Boolean( 12 | window.location.hostname === 'localhost' || 13 | // [::1] is the IPv6 localhost address. 14 | window.location.hostname === '[::1]' || 15 | // 127.0.0.1/8 is considered localhost for IPv4. 16 | window.location.hostname.match( 17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 18 | ) 19 | ); 20 | 21 | export default function register() { 22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 23 | // The URL constructor is available in all browsers that support SW. 24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location); 25 | if (publicUrl.origin !== window.location.origin) { 26 | // Our service worker won't work if PUBLIC_URL is on a different origin 27 | // from what our page is served on. This might happen if a CDN is used to 28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374 29 | return; 30 | } 31 | 32 | window.addEventListener('load', () => { 33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 34 | 35 | if (isLocalhost) { 36 | // This is running on localhost. Lets check if a service worker still exists or not. 37 | checkValidServiceWorker(swUrl); 38 | 39 | // Add some additional logging to localhost, pointing developers to the 40 | // service worker/PWA documentation. 41 | navigator.serviceWorker.ready.then(() => { 42 | console.log( 43 | 'This web app is being served cache-first by a service ' + 44 | 'worker. To learn more, visit https://goo.gl/SC7cgQ' 45 | ); 46 | }); 47 | } else { 48 | // Is not local host. Just register service worker 49 | registerValidSW(swUrl); 50 | } 51 | }); 52 | } 53 | } 54 | 55 | function registerValidSW(swUrl) { 56 | navigator.serviceWorker 57 | .register(swUrl) 58 | .then(registration => { 59 | registration.onupdatefound = () => { 60 | const installingWorker = registration.installing; 61 | installingWorker.onstatechange = () => { 62 | if (installingWorker.state === 'installed') { 63 | if (navigator.serviceWorker.controller) { 64 | // At this point, the old content will have been purged and 65 | // the fresh content will have been added to the cache. 66 | // It's the perfect time to display a "New content is 67 | // available; please refresh." message in your web app. 68 | console.log('New content is available; please refresh.'); 69 | } else { 70 | // At this point, everything has been precached. 71 | // It's the perfect time to display a 72 | // "Content is cached for offline use." message. 73 | console.log('Content is cached for offline use.'); 74 | } 75 | } 76 | }; 77 | }; 78 | }) 79 | .catch(error => { 80 | console.error('Error during service worker registration:', error); 81 | }); 82 | } 83 | 84 | function checkValidServiceWorker(swUrl) { 85 | // Check if the service worker can be found. If it can't reload the page. 86 | fetch(swUrl) 87 | .then(response => { 88 | // Ensure service worker exists, and that we really are getting a JS file. 89 | if ( 90 | response.status === 404 || 91 | response.headers.get('content-type').indexOf('javascript') === -1 92 | ) { 93 | // No service worker found. Probably a different app. Reload the page. 94 | navigator.serviceWorker.ready.then(registration => { 95 | registration.unregister().then(() => { 96 | window.location.reload(); 97 | }); 98 | }); 99 | } else { 100 | // Service worker found. Proceed as normal. 101 | registerValidSW(swUrl); 102 | } 103 | }) 104 | .catch(() => { 105 | console.log( 106 | 'No internet connection found. App is running in offline mode.' 107 | ); 108 | }); 109 | } 110 | 111 | export function unregister() { 112 | if ('serviceWorker' in navigator) { 113 | navigator.serviceWorker.ready.then(registration => { 114 | registration.unregister(); 115 | }); 116 | } 117 | } 118 | --------------------------------------------------------------------------------