├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .nvmrc ├── .prettierrc.json ├── CODE_OF_CONDUCT.md ├── README.md ├── netlify.toml ├── package-lock.json ├── package.json ├── public ├── favicon.ico └── index.html ├── scripts └── setup └── src ├── 00-begin ├── App.js ├── README.md └── api.js ├── 01-jsx ├── App.js ├── README.md └── api.js ├── 02-query-field ├── App.js ├── README.md └── api.js ├── 03-api ├── App.js ├── README.md └── api.js ├── 04-lists ├── App.js ├── README.md └── api.js ├── 05-form-submit ├── App.js ├── README.md └── api.js ├── 06-components ├── App.js ├── README.md ├── Results.js ├── ResultsItem.js ├── SearchForm.js └── api.js ├── 07-prop-types ├── App.js ├── README.md ├── Results.js ├── ResultsItem.js ├── SearchForm.js └── api.js ├── 08-search-focus ├── App.js ├── README.md ├── Results.js ├── ResultsItem.js ├── SearchForm.js └── api.js ├── 09-custom-hook ├── App.js ├── README.md ├── Results.js ├── ResultsItem.js ├── SearchForm.js ├── api.js └── useGiphy.js ├── 10-loading-states ├── App.js ├── README.md ├── Results.js ├── ResultsItem.js ├── SearchForm.js ├── api.js └── useGiphy.js ├── end ├── App.js ├── README.md ├── Results.js ├── ResultsItem.js ├── SearchForm.js ├── api.js └── useGiphy.js ├── index.css ├── index.js └── quiz └── README.md /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | main: 7 | name: Test app build 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - name: Checkout repo 12 | uses: actions/checkout@v1 13 | 14 | - name: Use Node 12 15 | uses: actions/setup-node@v1 16 | with: 17 | node-version: 12 18 | 19 | - name: Install NPM dependencies 20 | run: npm ci 21 | 22 | - name: Run app build 23 | run: npm run build 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | node_modules 28 | 29 | # Optional npm cache directory 30 | .npm 31 | 32 | # Optional REPL history 33 | .node_repl_history 34 | 35 | # Webpack build folders 36 | **/build/ 37 | **/dist 38 | 39 | # Workshop directory (and backups) 40 | src/workshop* 41 | 42 | # Editor settings 43 | .vscode 44 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v12.14.1 2 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "trailingComma": "all" 5 | } 6 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Workshop Code of Conduct 2 | 3 | All attendees, speakers, sponsors and volunteers at this workshop are required to agree with the following code of conduct. Organizers will enforce this code throughout the event. We expect cooperation from all participants to help ensure a safe environment for everybody. 4 | 5 | ## Have questions or need to report an issue? 6 | 7 | Please email Ben Ilegbodu at ben@benmvp.com. 8 | 9 | ## The Quick Version 10 | 11 | Our workshop is dedicated to providing a harassment-free workshop experience for everyone, regardless of gender, gender identity and expression, age, sexual orientation, disability, physical appearance, body size, race, ethnicity, religion (or lack thereof), or technology choices. We do not tolerate harassment of workshop participants in any form. Sexual language and imagery is not appropriate for any workshop venue, including presentations, comments, questions, video chat, Twitter and other online media. Workshop participants violating these rules may be sanctioned or expelled from the workshop _without a refund_ at the discretion of the workshop organizers. 12 | 13 | ## The Less Quick Version 14 | 15 | Harassment includes offensive verbal comments related to gender, gender identity and expression, age, sexual orientation, disability, physical appearance, body size, race, ethnicity, religion, technology choices, sexual images in public spaces, deliberate intimidation, stalking, following, harassing photography or recording, sustained disruption of talks or other events, inappropriate physical contact, and unwelcome sexual attention. 16 | 17 | Participants asked to stop any harassing behavior are expected to comply immediately. 18 | 19 | If a participant engages in harassing behavior, the workshop organizers may take any action they deem appropriate, including warning the offender or expulsion from the workshop _with no refund_. 20 | 21 | If you are being harassed, notice that someone else is being harassed, or have any other concerns, please contact a member of workshop staff immediately. 22 | 23 | Workshop staff will be happy to help participants contact venue security or local law enforcement, provide escorts, or otherwise assist those experiencing harassment to feel safe for the duration of the workshop. We value your attendance. 24 | 25 | We expect participants to follow these rules at workshop and workshop venues and workshop-related social events. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React FUNdamentals Workshop with Ben Ilegbodu 2 | 3 | [![Maintenance Status](https://img.shields.io/badge/status-maintained-brightgreen.svg)](https://github.com/benmvp/react-workshop/pulse) 4 | [![Build Status](https://github.com/benmvp/react-workshop/workflows/CI/badge.svg)](https://github.com/benmvp/react-workshop/actions) 5 | [![license](https://img.shields.io/badge/license-GPL%20v3-blue)](#license) 6 | [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](http://makeapullrequest.com) 7 | 8 | [![Watch on GitHub](https://img.shields.io/github/watchers/benmvp/react-workshop.svg?style=social)](https://github.com/benmvp/react-workshop/watchers) 9 | [![Star on GitHub](https://img.shields.io/github/stars/benmvp/react-workshop.svg?style=social)](https://github.com/benmvp/react-workshop/stargazers) 10 | [![Tweet](https://img.shields.io/twitter/url/https/github.com/benmvp/react-workshop.svg?style=social)](https://twitter.com/intent/tweet?text=Check%20out%20React%20Fundamentals%20Workshop%20by%20%40benmvp!%0A%0Ahttps%3A%2F%2Fgithub.com%2Fbenmvp%2Freact-workshop) 11 | 12 | A step-by-step workshop to build a React application, all while learning React fundamentals. Best if accompanied with live facilitation by me 🙂. 13 | 14 | ## Pre-Workshop Instructions 15 | 16 | In order to maximize our time _during_ the workshop, please complete the following tasks in advance: 17 | 18 | - [ ] Set up the project (follow [setup instructions](#system-requirements) below) 19 | - [ ] Install and run [Zoom](https://zoom.us/) on the computer you'll be developing with (for remote workshops) 20 | - [ ] Set up dual monitors for live coding, if possible (for remote workshops) 21 | - [ ] Install React Developer Tools for [Chrome](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi) (recommended) or [Firefox](https://addons.mozilla.org/en-GB/firefox/addon/react-devtools/) 22 | - [ ] Install a JSX-friendly code editor, such as [Visual Studio Code](https://code.visualstudio.com/) 23 | - [ ] Brush up on modern [ES.next](http://www.benmvp.com/learning-es6-series/) features, if they are unfamiliar to you 24 | - [ ] Have experience building websites with HTML, CSS, and JavaScript DOM APIs 25 | 26 | The more prepared you are for the workshop, the better it will go for you! 👍🏾 27 | 28 | ## System Requirements 29 | 30 | - [git](https://git-scm.com/) v2 or higher 31 | - [Node.js](https://nodejs.org/en/) v10 or higher 32 | - [npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) v6 or higher 33 | 34 | All of these must also be available in your `PATH` in order to be run globally. To verify things are set up properly, run: 35 | 36 | ```sh 37 | git --version 38 | node --version 39 | npm --version 40 | ``` 41 | 42 | If your node version is version 9 or lower, you can [install `nvm`](https://github.com/creationix/nvm#install-script) to manage multiple versions of node. 43 | 44 | If you have trouble with any of these, learn more about the `PATH` environment variable and how to fix it here for [Windows](https://www.howtogeek.com/118594/how-to-edit-your-system-path-for-easy-command-line-access/) or [Mac/Linux](http://stackoverflow.com/a/24322978/971592). 45 | 46 | ## Setup 47 | 48 | After you have verified that you have the proper tools installed (and at the proper versions), getting setup _should_ be a breeze. Run the following commands: 49 | 50 | ```sh 51 | git clone https://github.com/benmvp/react-workshop.git 52 | cd react-workshop 53 | npm run setup 54 | ``` 55 | 56 | This will likely take a **few minutes** to run. It will clone the repo, install all of the JavaScript dependencies needed to build our app, and setup our workshop dev directory. 57 | 58 | If it fails, please read through the error logs and see if you can figure out what the problem is. Double check that you have the proper [system requirements](#system-requirements) installed. If you are unable to figure out the problem on your own, please feel free to [file an issue](https://github.com/benmvp/react-workshop/issues/new) with _everything_ (and I mean everything) from the output of the commands you ran. 59 | 60 | ## Running the app 61 | 62 | We will be build a Giphy search app step-by-step in this workshop. To get started and verify that everything has been installed correctly, run: 63 | 64 | ```sh 65 | npm start 66 | ``` 67 | 68 | The app should pop up in your default browser running at http://localhost:3000/. The app should be **completely blank** because we haven't built anything yet! But you can check out the app [deployed online](https://react-workshop.benmvp.com/) to see what the final state will look like. 69 | 70 | For those interested, the app is a standard app bootstrapped by [Create React App](https://create-react-app.dev/). 71 | 72 | ## Workshop Outline 73 | 74 | Let's learn the React fundamentals! ⚛️ 75 | 76 | ### 🧔🏾 About Me 77 | 78 | Hiya! 👋🏾 My name is Ben Ilegbodu. 😄 79 | 80 | - Christian, Husband, Father of 👌🏾 81 | - Pittsburg, California 82 | - Principal Frontend Engineer at [Stitch Fix](https://www.stitchfix.com/) (and yes [we're hiring](https://www.stitchfix.com/careers/jobs)!) 83 | - [@benmvp](https://twitter.com/benmvp) 84 | - www.benmvp.com 85 | - Go Rockets! 🚀🏀 86 | 87 | ### 🕘 Schedule 88 | 89 | Each step in the workshop builds on top of the previous one. If at any point you get stuck, you can find the answers in the source code of the current step. Any step can be used as a starting point to continue on to the remaining steps. 90 | 91 | - Setup / Logistics / Intro 92 | - [Step 1 - JSX](src/01-jsx/) 93 | - [Step 2 - Query Field](src/02-query-field/) 94 | - [Step 3 - API](src/03-api/) 95 | - 😴 15 minutes 96 | - [Step 4 - Lists](src/04-lists/) 97 | - [Step 5 - Form Submit](src/05-form-submit/) 98 | - 🍕 45 minutes 99 | - [Step 6 - Components](src/06-components/) 100 | - [Step 7 - Prop Types](src/07-prop-types/) 101 | - [Step 8 - Search Focus](src/08-search-focus/) 102 | - 😴 15 minutes 103 | - [Step 9 - Custom Hook](src/09-custom-hook/) 104 | - [Step 10 - Loading States](src/10-loading-states/) 105 | - 😴 15 minutes 106 | - ❓ Q & A 107 | - [Final Quiz!](src/quiz/) 108 | - 👋🏾 Goodbye! 109 | 110 | ### ❓ Asking Questions 111 | 112 | - Please **interrupt me** and ask questions! Others likely will have the same question. 113 | - However, unrelated questions are better sent to [Twitter](https://twitter.com/benmvp) or [my AMA](http://www.benmvp.com/ama). 114 | 115 | ### 🖥️ Zoom Hygiene (for remote workshops) 116 | 117 | - Keep your **video on** (if possible) to make it feel more human and lively 118 | - Keep your **microphone muted** unless your talking to avoid background noise distractions 119 | - Answer each other's questions in the chat 120 | - Use breakout rooms to help each other 121 | 122 | ### ⭐ FUNdamental Concepts 123 | 124 | Here is what you'll come away knowing at the end of the workshop... 125 | 126 | - Using JSX syntax ([Step 1](src/01-jsx/)) 127 | - Maintaining component state with `useState` hook ([Step 2](src/02-query-field/)) 128 | - Handling user interaction ([Step 2](src/02-query-field/)) 129 | - Making API calls with `useEffect` hook ([Step 3](src/03-api/)) 130 | - Rendering dynamic lists of data ([Step 4](src/04-lists/)) 131 | - Conditionally rendering components ([Step 4](src/04-lists/)) 132 | - Handling HTML forms & form elements ([Step 5](src/05-form-submit/)) 133 | - Writing readable, reusable and composable components ([Step 6](src/06-components/)) 134 | - Type-checking props ([Step 7](src/07-prop-types/)) 135 | - Interacting with the DOM directly with `useRef` hook ([Step 8](src/08-search-focus/)) 136 | - Factoring out logic from components into custom hooks ([Step 9](src/09-custom-hook/)) 137 | - Leveraging ES6+ to maintain application state with `useReducer` hook ([Step 10](src/10-loading-states/)) 138 | - Applying component styling with CSS classes (throughout) 139 | 140 | ## 🧠 Elaboration & Feedback 141 | 142 | Each step has an Elaboration & Feedback form link at the end. After you're done with the exercise and before jumping to the next step, please take a few minutes to fill that out to solidify your learning. 143 | 144 | At the end of the workshop, I would greatly appreciate your overall feedback. [Share your workshop feedback](https://bit.ly/react-fun-ws-feedbck). 145 | 146 | ### 🤝 Code of Conduct 147 | 148 | All attendees, speakers, sponsors and volunteers at this workshop are required to agree with the [code of conduct](CODE_OF_CONDUCT.md). Organizers will enforce this code throughout the event. We expect cooperation from all participants to help ensure a safe environment for everybody. 149 | 150 | ### 👉🏾 First Step 151 | 152 | Go to [Step 0 - Begin](src/00-begin/). 153 | 154 | ## License 155 | 156 | All of the workshop material is available for **private, non-commercial use** under the [GPL version 3](http://www.gnu.org/licenses/gpl-3.0-standalone.html) license. If you would like to use this workshop to conduct your own workshop, please contact me first at ben@benmvp.com. 157 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | command = "npm run build" 3 | publish = "build/" 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-workshop", 3 | "private": true, 4 | "description": "A step-by-step workshop for learning React fundamentals.", 5 | "homepage": "https://react-workshop.benmvp.com/", 6 | "license": "GPL-3.0-only", 7 | "engines": { 8 | "node": ">=10", 9 | "npm": ">=6" 10 | }, 11 | "scripts": { 12 | "setup": "scripts/setup", 13 | "start": "react-scripts start", 14 | "build": "react-scripts build", 15 | "test": "react-scripts test --env=jsdom", 16 | "eject": "react-scripts eject" 17 | }, 18 | "dependencies": { 19 | "classnames": "^2.2.6", 20 | "prop-types": "^15.7.2", 21 | "react": "^16.13.1", 22 | "react-dom": "^16.13.1", 23 | "react-scripts": "^3.4.1", 24 | "url-lib": "^3.0.3" 25 | }, 26 | "devDependencies": { 27 | "fs-extra": "^9.0.0", 28 | "prettier": "2.0.1" 29 | }, 30 | "browserslist": { 31 | "production": [ 32 | ">0.2%", 33 | "not dead", 34 | "not op_mini all" 35 | ], 36 | "development": [ 37 | "last 1 chrome version", 38 | "last 1 firefox version", 39 | "last 1 safari version" 40 | ] 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benmvp/react-workshop/cb7f3783b23b365b3721644f5e44cd765be23600/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 15 | 23 | 24 | 33 | React FUNdamentals Workshop with Ben Ilegbodu 34 | 35 | 36 | 37 | 40 |
41 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /scripts/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const { spawnSync } = require('child_process') 4 | const { resolve } = require('path') 5 | 6 | const [, , exerciseToCopyPath = 'src/00-begin'] = process.argv 7 | 8 | const CWD = process.cwd() 9 | const WORKSHOP_PATH = resolve(CWD, 'src/workshop') 10 | const EXERCISE_PATH = resolve(CWD, exerciseToCopyPath) 11 | const INDEX_PATH = resolve(CWD, 'src/index.js') 12 | 13 | const COLOR_STYLES = { 14 | blue: { open: '\u001b[34m', close: '\u001b[39m' }, 15 | dim: { open: '\u001b[2m', close: '\u001b[22m' }, 16 | red: { open: '\u001b[31m', close: '\u001b[39m' }, 17 | green: { open: '\u001b[32m', close: '\u001b[39m' }, 18 | } 19 | 20 | const color = (modifier, message) => { 21 | return COLOR_STYLES[modifier].open + message + COLOR_STYLES[modifier].close 22 | } 23 | const blue = (message) => color('blue', message) 24 | const dim = (message) => color('dim', message) 25 | const red = (message) => color('red', message) 26 | const green = (message) => color('green', message) 27 | 28 | const logRunStart = (title, subtitle) => { 29 | console.log(blue(`▶️ Starting: ${title}`)) 30 | console.log(` ${subtitle}`) 31 | } 32 | 33 | const logRunSuccess = (title) => { 34 | console.log(green(`✅ Success: ${title}\n\n`)) 35 | } 36 | 37 | const run = (title, subtitle, command) => { 38 | logRunStart(title, subtitle) 39 | console.log(dim(` Running the following command: ${command}`)) 40 | 41 | const result = spawnSync(command, { stdio: 'inherit', shell: true }) 42 | 43 | if (result.status !== 0) { 44 | console.error( 45 | red( 46 | `🚨 Failure: ${title}. Please review the messages above for information on how to troubleshoot and resolve this issue.`, 47 | ), 48 | ) 49 | process.exit(result.status) 50 | } 51 | 52 | logRunSuccess(title) 53 | } 54 | 55 | const main = async () => { 56 | run( 57 | 'System Validation', 58 | 'Ensuring the correct versions of tools are installed on this computer.', 59 | 'npx check-engine', 60 | ) 61 | 62 | run( 63 | 'Dependency Installation', 64 | 'Installing third party code dependencies so the workshop works properly on this computer.', 65 | 'npm install', 66 | ) 67 | 68 | // Now that the dependencies have been installed we can use `fs-extra` 69 | const { pathExists, move, copy, readFile, writeFile } = require('fs-extra') 70 | 71 | const WORKSHOP_CREATION_TITLE = 'Workshop Folder Creation' 72 | const WORKSHOP_CREATION_SUBTITLE = `Creating workshop directory from ${exerciseToCopyPath}.` 73 | 74 | logRunStart(WORKSHOP_CREATION_TITLE, WORKSHOP_CREATION_SUBTITLE) 75 | 76 | // create a backup of the workshop folder if it exists 77 | if (await pathExists(WORKSHOP_PATH)) { 78 | const now = Date.now() 79 | 80 | console.log( 81 | dim( 82 | ` Workshop folder already exists. Backing up to src/workshop-${now}`, 83 | ), 84 | ) 85 | await move(WORKSHOP_PATH, resolve(`${WORKSHOP_PATH}-${now}`)) 86 | } 87 | 88 | await copy(EXERCISE_PATH, WORKSHOP_PATH) 89 | await writeFile( 90 | INDEX_PATH, 91 | (await readFile(INDEX_PATH, 'utf8')).replace( 92 | /\.\/.*\/App/, 93 | './workshop/App', 94 | ), 95 | ) 96 | 97 | logRunSuccess(WORKSHOP_CREATION_TITLE) 98 | } 99 | 100 | main() 101 | -------------------------------------------------------------------------------- /src/00-begin/App.js: -------------------------------------------------------------------------------- 1 | const App = () => { 2 | return null 3 | } 4 | 5 | export default App 6 | -------------------------------------------------------------------------------- /src/00-begin/README.md: -------------------------------------------------------------------------------- 1 | # Step 0 - Begin React Workshop 2 | 3 | 🏅 The goal of this step is to ensure you have everything set up with a running (but blank) app. We will be working in a step-by-step fashion to build a [Giphy search app](https://react-workshop.benmvp.com/). 4 | 5 | ## 📝 Tasks 6 | 7 | Complete the [setup instructions](../../README.md#setup)! It's your **last chance**! 🏃🏾‍♂️ 8 | 9 | If you ran the setup **before today**, pull any changes to the repo and re-run the setup to ensure that you have the most up-to-date code examples: 10 | 11 | ```sh 12 | git pull --rebase=false 13 | npm run setup 14 | ``` 15 | 16 | This should run pretty quickly. 17 | 18 | Finally, run the app if you haven't already! 19 | 20 | ```sh 21 | npm start 22 | ``` 23 | 24 | Let's get started! 🎉 25 | 26 | ## 👉🏾 Next Step 27 | 28 | Go to [Step 1 - JSX](../01-jsx/). 29 | -------------------------------------------------------------------------------- /src/00-begin/api.js: -------------------------------------------------------------------------------- 1 | import { formatUrl } from 'url-lib' 2 | 3 | /** 4 | * Waits the specified amount of time and returns a resolved Promise when done 5 | * @param {number} waitTimeMs The amount of time to wait in milliseconds 6 | * @returns {Promise} Signal that waiting is done 7 | */ 8 | const wait = (waitTimeMs = 0) => { 9 | return new Promise((resolve) => { 10 | setTimeout(resolve, waitTimeMs) 11 | }) 12 | } 13 | 14 | /** 15 | * @typedef {'' | 'g' | 'pg' | 'pg-13' | 'r'} RatingFiler The MPAA-style rating for a GIF 16 | * @typedef {'G' | 'PG' | 'PG-13' | 'R'} Rating The MPAA-style rating for a GIF 17 | * 18 | * @typedef GiphyResult 19 | * @type {object} 20 | * @property {string} id The GIF's unique ID 21 | * @property {string} title The title that appears on giphy.com for this GIF 22 | * @property {string} url The unique URL for the GIF 23 | * @property {Rating} rating The MPAA-style rating for the GIF 24 | * @property {string} previewUrl The URL for the GIF in .MP4 format 25 | * 26 | * @typedef SearchParams 27 | * @type {object} 28 | * @property {string} [params.searchQuery=''] Search query term or phrase 29 | * @property {RatingFilter} [params.rating=''] Filters results by specified rating. Not specifying a rating, returns all possible ratings 30 | * @property {number} [params.limit=12] The maximum number of images to return 31 | * @property {number} [params.offset=0] Specifies the starting position of the results. 32 | * 33 | * Retrieves a list of giphy image info matching the specified search parameters 34 | * @param {SearchParams} [params] Search parameters 35 | * @returns {{results: GiphyResult[], total: number}} 36 | */ 37 | export const getResults = async ({ 38 | searchQuery = '', 39 | rating = '', 40 | limit = 12, 41 | offset = 0, 42 | } = {}) => { 43 | // Increase the number below to give the appearance of a slow API response 44 | await wait(500) 45 | 46 | const resp = await fetch( 47 | formatUrl( 48 | 'https://api.giphy.com/v1/gifs/search?api_key=7B4oce3a0BmGU5YC22uOFOVg7JJtWcpH', 49 | { 50 | q: searchQuery, 51 | rating, 52 | limit, 53 | offset, 54 | lang: 'en', 55 | }, 56 | ), 57 | ) 58 | const data = await resp.json() 59 | const results = data.data.map(({ id, title, url, images, rating }) => ({ 60 | id, 61 | title, 62 | url, 63 | rating: rating.toUpperCase(), 64 | previewUrl: images.preview.mp4, 65 | })) 66 | 67 | return { results, total: data.pagination['total_count'] } 68 | } 69 | -------------------------------------------------------------------------------- /src/01-jsx/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const App = () => { 4 | return ( 5 |
6 |

Giphy Search!

7 |
8 | ) 9 | } 10 | 11 | export default App 12 | -------------------------------------------------------------------------------- /src/01-jsx/README.md: -------------------------------------------------------------------------------- 1 | # Step 1 - JSX 2 | 3 | [JSX](https://reactjs.org/docs/jsx-in-depth.html) is syntactic sugar for the plain JavaScript function [`React.createElement()`](https://reactjs.org/docs/react-api.html#createelement). React elements are the smallest building blocks of React apps that describe what you want to see on the screen. 4 | 5 | Unlike browser DOM elements, React elements are plain objects, and are cheap to create. [`ReactDOM`](https://reactjs.org/docs/react-dom.html) takes care of updating the DOM to match the React elements. 6 | 7 | 🏅 The goal of this step is to practice with JSX. 8 | 9 | > NOTE: One might confuse elements with a more widely known concept of "components." We will look closer at creating and composing components in the [Step 6](../06-components/). Elements are what components are "made of." 10 | 11 | If you run into trouble with the [tasks](#tasks) or [exercises](#exercises), you can take a peek at the final [source code](./). 12 | 13 | ## 🐇 Jump Around 14 | 15 | [Concepts](#-concepts) | [Tasks](#-tasks) | [Exercises](#-exercises) | [Elaboration & Feedback](#-elaboration--feedback) | [Resources](#-resources) 16 | 17 | ## ⭐ Concepts 18 | 19 | - Rendering elements with JSX 20 | - Handling special element attribute names 21 | - Adding inline styles 22 | 23 | ## 📝 Tasks 24 | 25 | In [`src/workshop/App.js`](App.js), replace `null` with JSX markup. For example: 26 | 27 | ```js 28 | const App = () => { 29 | return
Giphy Search!
30 | } 31 | ``` 32 | 33 | You will need to import `React` in order use JSX. 34 | 35 | ```js 36 | import React from 'react' 37 | 38 | const App = () => { 39 | return
Giphy Search!
40 | } 41 | 42 | export default App 43 | ``` 44 | 45 | Add nested JSX markup. For example: 46 | 47 | ```js 48 | const App = () => { 49 | return ( 50 |
51 |

Giphy Search!

52 |

This is a paragraph of text written in React

53 |
54 | ) 55 | } 56 | ``` 57 | 58 | Add attributes to the nested JSX markup. For example: 59 | 60 | ```js 61 | const App = () => { 62 | return ( 63 |
64 |

Giphy Search!

65 |

This is a paragraph of text written in React

66 | 69 |
70 | ) 71 | } 72 | ``` 73 | 74 | Try adding classes to JSX markup, or a `