diff --git a/challengeSolution.md b/challengeSolution.md new file mode 100644 index 0000000..b49d244 --- /dev/null +++ b/challengeSolution.md @@ -0,0 +1,210 @@ + +# Challenge Solution Walkthrough +​ +Warning: spoilers ahead for the FirstReactLab challenge! +​ +### Contents +[Creating a Counter State](https://github.com/learninglab-dev/ll-first-reactLab/blob/master/challengesolution.md#creating-a-counter-state")\ +[Counter Value Functions](https://github.com/learninglab-dev/ll-first-reactLab/blob/master/challengesolution.md#counter-value-functions)\ +[Edit Click Handler Function](https://github.com/learninglab-dev/ll-first-reactLab/blob/master/challengesolution.md#edit-click-handler-function)\ +[Adding the Message](https://github.com/learninglab-dev/ll-first-reactLab/blob/master/challengesolution.md#adding-the-message)\ +​ +### Creating a Counter State +This challenge asks us to keep track of the number of GIFs we're displaying using a counter. To solve this task, we can put our new knowledge of state to use by creating a state variable for our counter! +​ +Two key things to know for creating this state: for one, state variables don't have to have boolean values—they can be numbers, strings, functions, etc! In this case, we'll want to make our state a number that will increment as GIFs appear on the display. The second thing to know is that we'll use this counter to track the state of multiple components in our app. Remember that `App`, the top-level parent, has multiple children. Each image on our page is created with a copy of our component `Switch`. So `App` has as many children as you have switches, well plus `Layout`. And then `Switch` has it's own children as we continue down the tree. So, it'll be important to think about where we put our counter. It has to be high enough up the tree to "keep track" of all of the GIFs in your webpage. + +Before you read below these lines, take a moment to think about where you might put your counter. Which component is high enough up the tree that it can "see" each of the images? + +--------------------------------------------- + **Answers Below** + +--------------------------------------------- + +We're going to put our counter in `App`. Why? Because because only `App` has access to each one of our `Switch` components and can share props with them. The instances of `Switch` can't share any information with each other directly. + +To implement our counter, we're going to want to use the same `useState` syntax we used for showImg and setShowImg. In the file, `App.js`, add a new state variable: +```diff ++ import React, { useState } from 'react' + import Layout from './Layout' + import Switch from './Switch' + import imgSources from './data' + ​ + ​ + export default function App() { ++ const [count, setCount] = useState(0) + const imgToGIFs = imgSources.map((urls, i) => { + return + }) + return ( + + {imgToGIFs} + + ) + } +``` +Note that we added `useState` to the collection of things we're importing from the React library in line 1! +​ +Also, note that our counter state is a number initialized at zero (`useState(0)`), which we know is how many GIFs are showing when a user first loads our app--none! Next we need functions that allow us to increment and decrement this counter. We'll create these in our next step! +​ +### Counter Value Functions +Now that we have a counter state that we're keeping track of, we can implement a function that will increment it as images are changed to GIFs and vice versa. +​ +For this step, a key new piece of information to know is that you can pass any type of value as a prop to child components—functions included! Our `increment` and `decrement` functions will live in `App`, but in order to be called at the right time (in the click handler, i.e. when the user clicks), we have to pass our functions down to `Switch` as props. +​ +Let's write the functions first! For our increment function, we want to increase our count value by 1, and for our decrement function, we want to decrease the count value by 1. To do so, we can use fat arrow function notation and `setCount`: +```diff +import React, { useState } from 'react' +import Layout from './Layout' +import Switch from './Switch' +import imgSources from './data' +​ +​ +export default function App() { + const [count, setCount] = useState(0) ++ const increment = () => { ++ setCount(count + 1) ++ } ++ const decrement = () -> { ++ setCount(count - 1) ++ } + const imgToGIFs = imgSources.map((urls, i) => { + return + }) + return ( + + {imgToGIFs} + + ) +} +``` +This step isn't done yet, though. Now that we have `increment` and `decrement`, we need to pass them down to `Switch` as props and call them in the appropriate click handlers. Recall from the Walkthrough that, in order to pass a prop, we need to include it in the return statement that includes our `Switch` component. That's going to look like this: +```diff +const imgToGIFs = imgSources.map((urls, i) => { ++ return +}) +​ +``` + +### Edit Click Handler Function +Now, move over to `Switch.js`. Here we'll call `increment` and `decrement` as our `onClick` functions, so that we update our counter only when the user clicks on an image. For this challenge, it's helpful to use the conditional rendering approach for the `return`—although it's clunkier than packing the conditional into the definition of `src`, it'll help us see where exactly to call our `increment` and `decrement` functions. Before we even add the functions, let's remind ourselves what that version of `Switch` looks like: +```js +import React, { useState } from 'react' +import Img from './Img' +​ +export default function Switch(props) { + const { img, GIF, alt } = props + const [showImg, setShowImg] = useState(true) + if (showImg) { + return {alt} setShowImg(false)}/> + } else { + return {alt} setShowImg(true)}/> + } +} +``` +Now let's add a couple of minor things we know we'll need. First, let's add `increment` and `decrement` to things we're grabbing from the `props` object. Second, we'll put `setShowImg()` in curly braces within the `onClick` function. We can no longer use the abbreviated syntax when we add `increment` and `decrement` in there as well. +```diff + import React, { useState } from 'react' + import Img from './Img' + ​ + export default function Switch(props) { ++ const { img, GIF, alt, increment, decrement } = props + const [showImg, setShowImg] = useState(true) + if (showImg) { + return {alt} { + + setShowImg(false) + + }} + /> + } else { + return {alt} { ++ setShowImg(true) ++ }} + /> + } + } +``` +Now we want to call our new functions after we set `showImg` to a GIF (false) or an image (true). We want to increment `count` when changing to a GIF, because that means we have one more GIF on display; and we want to decrement `count` when changing to an image, because there's then one less GIF on display. That should look like this: +```diff + import React, { useState } from 'react' + import Img from './Img' + ​ + export default function Switch(props) { ++ const { img, GIF, alt, increment, decrement } = props + const [showImg, setShowImg] = useState(true) + if (showImg) { + return {alt} { + setShowImg(false) + + increment() + }} + /> + } else { + return {alt} { + setShowImg(true) ++ decrement() + }} + /> + } + } +``` +We've done a lot here, so now would be a good time to check over in your browser to verify that everything is working. You'll want to add a `console.log()` somewhere, so you can output the value of `count` The easiest place to do this is probably just at the top inside `App`. Once you've got that log, go ahead and test your app. If all went well, and your counter is counting, it's time to tackle the secret message portion of this challenge! +​ +### Adding the Message +The ultimate goal of this challenge is to have a "secret" message pop up when we reach a magic number of GIFs on display. To do that, we need to add one more thing to `App`: +```diff +export default function App() { + const [count, setCount] = useState(0) ++ const message = count === 3 ? '[YOUR SECRET MESSAGE]' : '' + const increment = () => { + setCount(count + 1) + } + const decrement = () => { + setCount(count - 1) + } + const imgToGIFs = imgSources.map((urls, i) => { + return + }) +​ + return ( + + {imgToGIFs} +

{message}

+
+ ) +} +``` +Here we've added the message in a very simple way. We defined a variable, `message` conditionally. We're returning it on every render, but most of the time it's an empty string, so the user sees nothing. If we didn't want to see an empty `

` in our html, we could instead conditionally render the `

`. To do this we, don't actually need `message` at all: +```diff +export default function App() { + const [count, setCount] = useState(0) +- const message = count === 3 ? '[YOUR SECRET MESSAGE]' : '' + const increment = () => { + setCount(count + 1) + } + const decrement = () => { + setCount(count - 1) + } + const imgToGIFs = imgSources.map((urls, i) => { + return + }) +​ + return ( + + {imgToGIFs} ++ {count === 3 &&

[YOUR SECRET MESSAGE]

} +
+ ) +} +``` +And that's it! ​Go to `localhost:3000` and click until you have 3 GIFs. When you've done that, you should be able to see the secret message! Challenge: completed! diff --git a/challengeSolution_useEffect.md b/challengeSolution_useEffect.md new file mode 100644 index 0000000..8c4c154 --- /dev/null +++ b/challengeSolution_useEffect.md @@ -0,0 +1,233 @@ + +# Challenge Solution Walkthrough +​ +Warning: spoilers ahead for the FirstReactLab challenge! +​ +### Contents +[Creating a Counter State](https://github.com/learninglab-dev/ll-first-reactLab/blob/master/challengesolution.md#creating-a-counter-state")\ +[Counter Value Functions](https://github.com/learninglab-dev/ll-first-reactLab/blob/master/challengesolution.md#counter-value-functions)\ +[Edit Click Handler Function](https://github.com/learninglab-dev/ll-first-reactLab/blob/master/challengesolution.md#edit-click-handler-function)\ +[Creating a Message State](https://github.com/learninglab-dev/ll-first-reactLab/blob/master/challengesolution.md#creating-a-message-state)\ +[Build useEffect](https://github.com/learninglab-dev/ll-first-reactLab/blob/master/challengesolution.md#build-useeffect)\ +​ +### Creating a Counter State +This challenge asks us to keep track of the number of GIFs we're displaying using a counter. To solve this task, we can put our new knowledge of state to use by creating a state variable for our counter! +​ +Two key things to know for creating this state: for one, state variables don't have to have boolean values—they can be numbers, strings, functions, etc! In this case, we'll want to make our state a number that will increment as GIFs appear on the display. The second thing to know is that we'll use this counter to track the state of multiple components in our app. Remember that `App`, the top-level parent, has multiple children. Each image on our page is created with a copy of our component `Switch`. So `App` has as many children as you have switches, well plus `Layout`. And then `Switch` has it's own children as we continue down the tree. So, it'll be important to think about where we put our counter. It has to be high enough up the tree to "keep track" of all of the GIFs in your webpage. + +Before you read below these lines, take a moment to think about where you might put your counter. Which component is high enough up the tree that it can "see" each of the images? + +--------------------------------------------- + **Answers Below** + +--------------------------------------------- + +We're going to put our counter in `App`. Why? Because because only `App` has access to each one of our `Switch` components and can share props with them. The instances of `Switch` can't share any information with each other directly. + +To implement our counter, we're going to want to use the same `useState` syntax we used for showImg and setShowImg. In the file, `App.js`, add a new state variable: +```diff ++ import React, { useState } from 'react' + import Layout from './Layout' + import Switch from './Switch' + import imgSources from './data' + ​ + ​ + export default function App() { ++ const [count, setCount] = useState(0) + const imgToGifs = imgSources.map((urls, i) => { + return + }) + return ( + + {imgToGifs} + + ) + } +``` +Note that we added `useState` to the collection of things we're importing from the React library in line 1! +​ +Also, note that our counter state is a number initialized at zero (`useState(0)`), which we know is how many gifs are showing when a user first loads our app--none! Next we need functions that allow us to increment and decrement this counter. We'll create these in our next step! +​ +### Counter Value Functions +Now that we have a counter state that we're keeping track of, we can implement a function that will increment it as images are changed to GIFs and vice versa. +​ +For this step, a key new piece of information to know is that you can pass any type of value as a prop to child components—functions included! Our `increment` and `decrement` functions will live in `App`, but in order to be called at the right time (in the click handler, i.e. when the user clicks), we have to pass our functions down to `Switch` as props. +​ +Let's write the functions first! For our increment function, we want to increase our count value by 1, and for our decrement function, we want to decrease the count value by 1. To do so, we can use fat arrow function notation and `setCount`: +```diff +import React, { useState } from 'react' +import Layout from './Layout' +import Switch from './Switch' +import imgSources from './data' +​ +​ +export default function App() { + const [count, setCount] = useState(0) ++ const increment = () => { ++ setCount(count + 1) ++ } ++ const decrement = () -> { ++ setCount(count - 1) ++ } + const imgToGifs = imgSources.map((urls, i) => { + return + }) + return ( + + {imgToGifs} + + ) +} +``` +This step isn't done yet, though. Now that we have `increment` and `decrement`, we need to pass them down to `Switch` as props and call them in the appropriate click handlers. Recall from the Walkthrough that, in order to pass a prop, we need to include it in the return statement that includes our `Switch` component. That's going to look like this: +```diff +const imgToGifs = imgSources.map((urls, i) => { ++ return +}) +​ +``` +### Edit Click Handler Function +​ +Now, move over to `Switch.js`. Here we'll call `increment` and `decrement` as our `onClick` functions, so that we update our counter only when the user clicks on an image. For this challenge, it's helpful to use the conditional rendering approach for the `return`—although it's clunkier than packing the conditional into the definition of `src`, it'll help us see where exactly to call our `increment` and `decrement` functions. Before we even add the functions, let's remind ourselves what that version of `Switch` looks like: +```js +import React, { useState } from 'react' +import Img from './Img' +​ +export default function Switch(props) { + const { img, gif, alt } = props + const [showImg, setShowImg] = useState(true) + if (showImg) { + return {alt} setShowImg(false)}/> + } else { + return {alt} setShowImg(true)}/> + } +} +``` +Now let's add a couple of minor things we know we'll need. First, let's add `increment` and `decrement` to things we're grabbing from the `props` object. Second, we'll put `setShowImg()` in curly braces within the `onClick` function. We can no longer use the abbreviated syntax when we add `increment` and `decrement` in there as well. +```diff + import React, { useState } from 'react' + import Img from './Img' + ​ + export default function Switch(props) { ++ const { img, gif, alt, increment, decrement } = props + const [showImg, setShowImg] = useState(true) + if (showImg) { + return {alt} { + + setShowImg(false) + + }} + /> + } else { + return {alt} { ++ setShowImg(true) ++ }} + /> + } + } +``` +Now we want to call our new functions after we set `showImg` to a GIF (false) or an image (true). We want to increment `count` when changing to a GIF, because that means we have one more GIF on display; and we want to decrement `count` when changing to an image, because there's then one less GIF on display. That should look like this: +```diff + import React, { useState } from 'react' + import Img from './Img' + ​ + export default function Switch(props) { ++ const { img, gif, alt, increment, decrement } = props + const [showImg, setShowImg] = useState(true) + if (showImg) { + return {alt} { + setShowImg(false) + + increment() + }} + /> + } else { + return {alt} { + setShowImg(true) ++ decrement() + }} + /> + } + } +``` +We've done a lot here, so now would be a good time to check over in your browser to verify that everything is working. You'll want to add a `console.log()` somewhere, so you can output the value of `count` The easiest place to do this is probably just at the top inside `App`. Once you've got that log, go ahead and test your app. If all went well, and your counter is counting, it's time to tackle the secret message portion of this challenge! +​ +### Adding the Message +The ultimate goal of this challenge is to have a "secret" message pop up when we reach a magic number of GIFs on display. One way to make that happen is to make our message a state variable, where we can set it as whatever string we want once the counter gets to a certain value. That's where `useEffect` will come in—but first, let's add our message state to App.js. +​ +```js +export default function App() { + const [count, setCount] = useState(0) + const [message, setMessage] = useState("") + const increment = () => { + setCount(count + 1) + } + const decrement = () => { + setCount(count - 1) + } + //usEffect function + const imgToGifs = imgSources.map((urls, i) => { + return + }) +​ + return ( + + {imgToGifs} +

{message}

+
+ ) +} +``` +In addition to the new const with our message state established as a string, we've also added an `

` tag in our layout where the message will go once the counter has reached the magic number & our `useEffect` function changes the state of the message to whatever string we choose. Let's move on to building our `useEffect` function so that we can see the message state in action! +​ +### Build useEffect +Hopefully you've read the documentation for `useEffect` by now, but as a refresher there are two parameters required by `useEffect`: a function, and an array whose changes `useEffect` is keeping track of. Our function here will be a conditional checking whether count has reached the magic number (in this case, let's do the total number of images, so that the magic number signals when all of them have been changed to GIFs: AKA, `imgSources.length`). Our array will just be count, the state that function is dependent on! Here's what that will look like: +```js +import React, { useState, useEffect } from 'react' +import Layout from './Layout' +import Switch from './Switch' +import imgSources from './data' +​ +export default function App() { + const [count, setCount] = useState(0) + const [message, setMessage] = useState("") + const increment = () => { + setCount(count + 1) + } + const decrement = () => { + setCount(count - 1) + } + useEffect(() => { + if (count === imgSources.length) { + setMessage("hey, you have 3 GIFs!") + } + else { + setMessage("") + } + }, [count] +​ + ) + const imgToGifs = imgSources.map((urls, i) => { + return + }) +​ + return ( + + {imgToGifs} +

{message}

+
+ ) +} +``` +Note that we've also included `useEffect` in our list of things to be imported from the React library—without that, your app won't work! +​ +Go to `localhost:3000` and test this code by changing all of the images to GIFs. When you've done that, you should be able to see the secret message! Challenge: completed! diff --git a/package-lock.json b/package-lock.json index c16f200..be081b3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1206,6 +1206,11 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz", "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==" }, + "@sheerun/mutationobserver-shim": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@sheerun/mutationobserver-shim/-/mutationobserver-shim-0.3.2.tgz", + "integrity": "sha512-vTCdPp/T/Q3oSqwHmZ5Kpa9oI7iLtGl3RQaA/NyLHikvcrPxACkkKVr/XzkSPJWXHRhKGzVvb0urJsbMlRxi1Q==" + }, "@svgr/babel-plugin-add-jsx-attribute": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-4.2.0.tgz", @@ -1315,6 +1320,182 @@ "loader-utils": "^1.2.3" } }, + "@testing-library/dom": { + "version": "6.12.2", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-6.12.2.tgz", + "integrity": "sha512-KCnvHra5fV+wDxg3wJObGvZFxq7v1DJt829GNFLuRDjKxVNc/B5AdsylNF5PMHFbWMXDsHwM26d2NZcZO9KjbQ==", + "requires": { + "@babel/runtime": "^7.6.2", + "@sheerun/mutationobserver-shim": "^0.3.2", + "@types/testing-library__dom": "^6.0.0", + "aria-query": "3.0.0", + "pretty-format": "^24.9.0", + "wait-for-expect": "^3.0.0" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.4.tgz", + "integrity": "sha512-neAp3zt80trRVBI1x0azq6c57aNBqYZH8KhMm3TaB7wEI5Q4A2SHfBHE8w9gOhI/lrqxtEbXZgQIrHP+wvSGwQ==", + "requires": { + "regenerator-runtime": "^0.13.2" + } + } + } + }, + "@testing-library/jest-dom": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.1.1.tgz", + "integrity": "sha512-7xnmBFcUmmUVAUhFiZ/u3CxFh1e46THAwra4SiiKNCW4By26RedCRwEk0rtleFPZG0wlTSNOKDvJjWYy93dp0w==", + "requires": { + "@babel/runtime": "^7.8.3", + "@types/testing-library__jest-dom": "^5.0.0", + "chalk": "^3.0.0", + "css": "^2.2.4", + "css.escape": "^1.5.1", + "jest-diff": "^25.1.0", + "jest-matcher-utils": "^25.1.0", + "lodash": "^4.17.15", + "pretty-format": "^25.1.0", + "redent": "^3.0.0" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.4.tgz", + "integrity": "sha512-neAp3zt80trRVBI1x0azq6c57aNBqYZH8KhMm3TaB7wEI5Q4A2SHfBHE8w9gOhI/lrqxtEbXZgQIrHP+wvSGwQ==", + "requires": { + "regenerator-runtime": "^0.13.2" + } + }, + "@jest/types": { + "version": "25.1.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.1.0.tgz", + "integrity": "sha512-VpOtt7tCrgvamWZh1reVsGADujKigBUFTi19mlRjqEGsE8qH4r3s+skY33dNdXOwyZIvuftZ5tqdF1IgsMejMA==", + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^3.0.0" + } + }, + "@types/yargs": { + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.3.tgz", + "integrity": "sha512-XCMQRK6kfpNBixHLyHUsGmXrpEmFFxzMrcnSXFMziHd8CoNJo8l16FkHyQq4x+xbM7E2XL83/O78OD8u+iZTdQ==", + "requires": { + "@types/yargs-parser": "*" + } + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "diff-sequences": { + "version": "25.1.0", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-25.1.0.tgz", + "integrity": "sha512-nFIfVk5B/NStCsJ+zaPO4vYuLjlzQ6uFvPxzYyHlejNZ/UGa7G/n7peOXVrVNvRuyfstt+mZQYGpjxg9Z6N8Kw==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "jest-diff": { + "version": "25.1.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-25.1.0.tgz", + "integrity": "sha512-nepXgajT+h017APJTreSieh4zCqnSHEJ1iT8HDlewu630lSJ4Kjjr9KNzm+kzGwwcpsDE6Snx1GJGzzsefaEHw==", + "requires": { + "chalk": "^3.0.0", + "diff-sequences": "^25.1.0", + "jest-get-type": "^25.1.0", + "pretty-format": "^25.1.0" + } + }, + "jest-get-type": { + "version": "25.1.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-25.1.0.tgz", + "integrity": "sha512-yWkBnT+5tMr8ANB6V+OjmrIJufHtCAqI5ic2H40v+tRqxDmE0PGnIiTyvRWFOMtmVHYpwRqyazDbTnhpjsGvLw==" + }, + "jest-matcher-utils": { + "version": "25.1.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-25.1.0.tgz", + "integrity": "sha512-KGOAFcSFbclXIFE7bS4C53iYobKI20ZWleAdAFun4W1Wz1Kkej8Ng6RRbhL8leaEvIOjGXhGf/a1JjO8bkxIWQ==", + "requires": { + "chalk": "^3.0.0", + "jest-diff": "^25.1.0", + "jest-get-type": "^25.1.0", + "pretty-format": "^25.1.0" + } + }, + "pretty-format": { + "version": "25.1.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.1.0.tgz", + "integrity": "sha512-46zLRSGLd02Rp+Lhad9zzuNZ+swunitn8zIpfD2B4OPCRLXbM87RJT2aBLBWYOznNUML/2l/ReMyWNC80PJBUQ==", + "requires": { + "@jest/types": "^25.1.0", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^16.12.0" + } + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@testing-library/react": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-9.4.0.tgz", + "integrity": "sha512-XdhDWkI4GktUPsz0AYyeQ8M9qS/JFie06kcSnUVcpgOwFjAu9vhwR83qBl+lw9yZWkbECjL8Hd+n5hH6C0oWqg==", + "requires": { + "@babel/runtime": "^7.7.6", + "@testing-library/dom": "^6.11.0", + "@types/testing-library__react": "^9.1.2" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.4.tgz", + "integrity": "sha512-neAp3zt80trRVBI1x0azq6c57aNBqYZH8KhMm3TaB7wEI5Q4A2SHfBHE8w9gOhI/lrqxtEbXZgQIrHP+wvSGwQ==", + "requires": { + "regenerator-runtime": "^0.13.2" + } + } + } + }, "@types/babel__core": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.3.tgz", @@ -1352,6 +1533,11 @@ "@babel/types": "^7.3.0" } }, + "@types/color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" + }, "@types/eslint-visitor-keys": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", @@ -1379,21 +1565,174 @@ "@types/istanbul-lib-report": "*" } }, + "@types/jest": { + "version": "25.1.2", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-25.1.2.tgz", + "integrity": "sha512-EsPIgEsonlXmYV7GzUqcvORsSS9Gqxw/OvkGwHfAdpjduNRxMlhsav0O5Kb0zijc/eXSO/uW6SJt9nwull8AUQ==", + "requires": { + "jest-diff": "^25.1.0", + "pretty-format": "^25.1.0" + }, + "dependencies": { + "@jest/types": { + "version": "25.1.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.1.0.tgz", + "integrity": "sha512-VpOtt7tCrgvamWZh1reVsGADujKigBUFTi19mlRjqEGsE8qH4r3s+skY33dNdXOwyZIvuftZ5tqdF1IgsMejMA==", + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^3.0.0" + } + }, + "@types/yargs": { + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.3.tgz", + "integrity": "sha512-XCMQRK6kfpNBixHLyHUsGmXrpEmFFxzMrcnSXFMziHd8CoNJo8l16FkHyQq4x+xbM7E2XL83/O78OD8u+iZTdQ==", + "requires": { + "@types/yargs-parser": "*" + } + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "diff-sequences": { + "version": "25.1.0", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-25.1.0.tgz", + "integrity": "sha512-nFIfVk5B/NStCsJ+zaPO4vYuLjlzQ6uFvPxzYyHlejNZ/UGa7G/n7peOXVrVNvRuyfstt+mZQYGpjxg9Z6N8Kw==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "jest-diff": { + "version": "25.1.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-25.1.0.tgz", + "integrity": "sha512-nepXgajT+h017APJTreSieh4zCqnSHEJ1iT8HDlewu630lSJ4Kjjr9KNzm+kzGwwcpsDE6Snx1GJGzzsefaEHw==", + "requires": { + "chalk": "^3.0.0", + "diff-sequences": "^25.1.0", + "jest-get-type": "^25.1.0", + "pretty-format": "^25.1.0" + } + }, + "jest-get-type": { + "version": "25.1.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-25.1.0.tgz", + "integrity": "sha512-yWkBnT+5tMr8ANB6V+OjmrIJufHtCAqI5ic2H40v+tRqxDmE0PGnIiTyvRWFOMtmVHYpwRqyazDbTnhpjsGvLw==" + }, + "pretty-format": { + "version": "25.1.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.1.0.tgz", + "integrity": "sha512-46zLRSGLd02Rp+Lhad9zzuNZ+swunitn8zIpfD2B4OPCRLXbM87RJT2aBLBWYOznNUML/2l/ReMyWNC80PJBUQ==", + "requires": { + "@jest/types": "^25.1.0", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^16.12.0" + } + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "@types/json-schema": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.3.tgz", "integrity": "sha512-Il2DtDVRGDcqjDtE+rF8iqg1CArehSK84HZJCT7AMITlyXRBpuPhqGLDQMowraqqu1coEaimg4ZOqggt6L6L+A==" }, + "@types/prop-types": { + "version": "15.7.3", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz", + "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==" + }, "@types/q": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.2.tgz", "integrity": "sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw==" }, + "@types/react": { + "version": "16.9.19", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.19.tgz", + "integrity": "sha512-LJV97//H+zqKWMms0kvxaKYJDG05U2TtQB3chRLF8MPNs+MQh/H1aGlyDUxjaHvu08EAGerdX2z4LTBc7ns77A==", + "requires": { + "@types/prop-types": "*", + "csstype": "^2.2.0" + } + }, + "@types/react-dom": { + "version": "16.9.5", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.5.tgz", + "integrity": "sha512-BX6RQ8s9D+2/gDhxrj8OW+YD4R+8hj7FEM/OJHGNR0KipE1h1mSsf39YeyC81qafkq+N3rU3h3RFbLSwE5VqUg==", + "requires": { + "@types/react": "*" + } + }, "@types/stack-utils": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz", "integrity": "sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==" }, + "@types/testing-library__dom": { + "version": "6.12.1", + "resolved": "https://registry.npmjs.org/@types/testing-library__dom/-/testing-library__dom-6.12.1.tgz", + "integrity": "sha512-cgqnEjxKk31tQt29j4baSWaZPNjQf3bHalj2gcHQTpW5SuHRal76gOpF0vypeEo6o+sS5inOvvNdzLY0B3FB2A==", + "requires": { + "pretty-format": "^24.3.0" + } + }, + "@types/testing-library__jest-dom": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.0.1.tgz", + "integrity": "sha512-GiPXQBVF9O4DG9cssD2d266vozBJvC5Tnv6aeH5ujgYJgys1DYm9AFCz7YC+STR5ksGxq3zCt+yP8T1wbk2DFg==", + "requires": { + "@types/jest": "*" + } + }, + "@types/testing-library__react": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@types/testing-library__react/-/testing-library__react-9.1.2.tgz", + "integrity": "sha512-CYaMqrswQ+cJACy268jsLAw355DZtPZGt3Jwmmotlcu8O/tkoXBI6AeZ84oZBJsIsesozPKzWzmv/0TIU+1E9Q==", + "requires": { + "@types/react-dom": "*", + "@types/testing-library__dom": "*" + } + }, "@types/yargs": { "version": "13.0.3", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.3.tgz", @@ -4162,6 +4501,11 @@ "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.2.1.tgz", "integrity": "sha512-WwOrosiQTvyms+Ti5ZC5vGEK0Vod3FTt1ca+payZqvKuGJF+dq7bG63DstxtN0dpm6FxY27a/zS3Wten+gEtGw==" }, + "css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s=" + }, "cssdb": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-4.4.0.tgz", @@ -4264,6 +4608,11 @@ "cssom": "0.3.x" } }, + "csstype": { + "version": "2.6.8", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.8.tgz", + "integrity": "sha512-msVS9qTuMT5zwAGCVm4mxfrZ18BNc6Csd0oJAtiFMZ1FAx1CCvy2+5MDmYoix63LM/6NDbNtodCiGYGmFgO0dA==" + }, "cyclist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz", @@ -6814,6 +7163,11 @@ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" }, + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==" + }, "indexes-of": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz", @@ -8858,6 +9212,11 @@ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.0.0.tgz", "integrity": "sha512-8ilDoEapqA4uQ3TwS0jakGONKXVJqpy+RpM+3b7pLdOjghCrEiGp9SRkFbUHAmZW9vdnrENWHjaweIoTIJExSQ==" }, + "min-indent": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.0.tgz", + "integrity": "sha1-z8RcN+nsDY8KDsPdTvf3w6vjklY=" + }, "mini-css-extract-plugin": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.8.0.tgz", @@ -11468,6 +11827,15 @@ "minimatch": "3.0.4" } }, + "redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "requires": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + } + }, "regenerate": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", @@ -12858,6 +13226,14 @@ "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" }, + "strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "requires": { + "min-indent": "^1.0.0" + } + }, "strip-json-comments": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz", @@ -13633,6 +14009,11 @@ "xml-name-validator": "^3.0.0" } }, + "wait-for-expect": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/wait-for-expect/-/wait-for-expect-3.0.2.tgz", + "integrity": "sha512-cfS1+DZxuav1aBYbaO/kE06EOS8yRw7qOFoD3XtjTkYvCvh3zUvNST8DXK/nPaeqIzIv3P3kL3lRJn8iwOiSag==" + }, "walker": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", diff --git a/package.json b/package.json index d801d28..767c91f 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,8 @@ "version": "0.1.0", "private": true, "dependencies": { + "@testing-library/jest-dom": "^5.1.1", + "@testing-library/react": "^9.4.0", "polished": "^3.4.2", "react": "^16.12.0", "react-dom": "^16.12.0", diff --git a/src/02_starter/App.js b/src/02_starter/App.js index 0196597..7db86be 100644 --- a/src/02_starter/App.js +++ b/src/02_starter/App.js @@ -1,12 +1,19 @@ import React from 'react' import Layout from './Layout' -import imgSources from './data' +import urls from './data' +import Image from './Img' +import Switch from './Switch' +import { Row, Col } from 'shards-react' //the top-level component for our app. I've added some basic styling and a flexbox layout for you so your gifs make a nice grid :) export default function App() { + const imgToGifs = urls.map( item => { + return + }) return ( + {imgToGifs} ) } diff --git a/src/02_starter/Img.js b/src/02_starter/Img.js index 98bfceb..2bd7d99 100644 --- a/src/02_starter/Img.js +++ b/src/02_starter/Img.js @@ -1 +1,21 @@ //a component that displays an image goes here +import React from 'react' +import Button from './Button' + +export default function Image({ url, alt, onClick }) { + + // const { url, alt } = props + // props = { + // url: 'url goes here', + // alt: 'alt' + // test: 'test', + // another: 'another' + // } + + return ( + + ) + +} diff --git a/src/02_starter/Switch.js b/src/02_starter/Switch.js index 6c51ac7..687a9af 100644 --- a/src/02_starter/Switch.js +++ b/src/02_starter/Switch.js @@ -1 +1,15 @@ //a component that handles the state of our img/gif button goes here +import React, { useState } from 'react' +import Image from './Img' + + +export default function Switch(props) { + const { imgUrl, gifUrl } = props + const [showImage, setShowImage] = useState(true) + // [true, () => {updates showImage}] + if (showImage) { + return img setShowImage(false)}/> + } else { + return gif setShowImage(true)}/> + } +} diff --git a/src/02_starter/data.js b/src/02_starter/data.js index 0b7b4e4..5d73dca 100644 --- a/src/02_starter/data.js +++ b/src/02_starter/data.js @@ -1,8 +1,16 @@ //the data structure we'll use to build our app const urls = [ { - img: '', - gif: '', + img: 'https://images.pexels.com/photos/416160/pexels-photo-416160.jpeg', + gif: 'https://media.giphy.com/media/HZUHce5TyYixq/giphy.gif', + }, + { + img: 'https://images.pexels.com/photos/127027/pexels-photo-127027.jpeg', + gif: 'https://media.giphy.com/media/VS3f7UiKom7hS/giphy.gif', + }, + { + img: 'https://images.pexels.com/photos/1564506/pexels-photo-1564506.jpeg', + gif: 'https://media.giphy.com/media/XHY2TRKxJQfsrwS33v/giphy.gif' } ] diff --git a/src/index.js b/src/index.js index e8c592b..c88d24a 100644 --- a/src/index.js +++ b/src/index.js @@ -1,6 +1,6 @@ import React from 'react' import ReactDOM from 'react-dom' -import App from './01_example/App' -// import App from './02_starter/App' /*uncomment this import and comment out the one above when you're ready to write your own code*/ +// import App from './01_example/App' +import App from './02_starter/App' /*uncomment this import and comment out the one above when you're ready to write your own code*/ ReactDOM.render(, document.getElementById('root')) diff --git a/src/tests/App.test.js b/src/tests/App.test.js new file mode 100644 index 0000000..e31cff2 --- /dev/null +++ b/src/tests/App.test.js @@ -0,0 +1,13 @@ +import React from 'react' +import { render } from '@testing-library/react' +import '@testing-library/jest-dom/extend-expect' +import App from '../02_starter/App' + +test('it renders and images display', () => { + const { asFragment, getByAltText } = render( + + ) + expect(asFragment()).toMatchSnapshot() + expect(getByAltText('cute kitten')).toHaveAttribute('src', 'https://images.pexels.com/photos/416160/pexels-photo-416160.jpeg') + expect(getByAltText('also cute')).toHaveAttribute('src', 'https://images.pexels.com/photos/127027/pexels-photo-127027.jpeg') +}) diff --git a/src/tests/__snapshots__/App.test.js.snap b/src/tests/__snapshots__/App.test.js.snap new file mode 100644 index 0000000..c58d88c --- /dev/null +++ b/src/tests/__snapshots__/App.test.js.snap @@ -0,0 +1,26 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`it renders and images display 1`] = ` + +
+ cute kitten + also cute + not a kitten +
+
+`;