diff --git a/README.md b/README.md index cf7f850..9651a8d 100644 --- a/README.md +++ b/README.md @@ -5,4 +5,30 @@ This project was bootstrapped with [Create React App](https://github.com/faceboo ## Setup - Git clone this repo - Run `npm install` to install all the dependencies listed in package.json -- Run `npm run start` to start the local development server \ No newline at end of file +- Run `npm run start` to start the local development server + +## What are React Hooks? +- Functions that let you perform a specific task inside React, like "hooking into" React state or component lifecycles +- Start with the word "use" -- useState and useEffect for example +- Pieces of code (functions) that someone wrote +- Hooks can be inside React or you can import them from somewhere else, but today we'll focus on just useState and useEffect from React +- You can write your own hooks (custom hooks) + +## useState +- A hook that creates state variables that React listens to. It "reacts" when the values of these state variables change +- Used when we have variables whose value should change when an event happens (user generated) +- When the value of the state variable changes, React re-renders the component in a smart, optimized way for you + +## useEffect +- A hook thats used when you want React to re-render without user interaction +- Used to fetch data (async) +- Helps control the amount of re-renders on the page + +Fetching data from API flow +1. Write an async function +2. Make a request with axios +3. Console.log what I'm getting back to make sure it's what I expect (and don't forget to call the function) +4. Import useEffect from 'react' +5. Call the async function inside useEffect +6. Check my console.log and put the data in the state +7. Render something on the screen based on state diff --git a/package-lock.json b/package-lock.json index ae01267..3424db4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", + "axios": "^1.6.2", "react": "^18.2.0", "react-dom": "^18.2.0", "react-scripts": "5.0.1", @@ -5321,6 +5322,29 @@ "node": ">=4" } }, + "node_modules/axios": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz", + "integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==", + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axios/node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/axobject-query": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz", @@ -14443,6 +14467,11 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", diff --git a/package.json b/package.json index 6e75333..e232f7a 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", + "axios": "^1.6.2", "react": "^18.2.0", "react-dom": "^18.2.0", "react-scripts": "5.0.1", diff --git a/src/App.js b/src/App.js index c44fa02..7dbbc3d 100644 --- a/src/App.js +++ b/src/App.js @@ -1,6 +1,7 @@ import './App.css'; import Character from './components/character'; import charactersData from './charactersData'; +import CharacterList from './components/character-list'; // import something from 'some place' // Creating a new function component in the same file @@ -23,23 +24,8 @@ function App() { { /* The below array iterator (map), loops over every element in the charactersData array */} { /* The result of map is another array with new data */} { /* In our case, this new data is a Character component */} - { - charactersData.map((character) => { - return ( - - ) - }) - } + { /* Using array iterator (map) instead of repeating like below: */ } - - - ); } diff --git a/src/components/character-list.js b/src/components/character-list.js new file mode 100644 index 0000000..23d9f2e --- /dev/null +++ b/src/components/character-list.js @@ -0,0 +1,48 @@ +import Character from "./character"; +import axios from 'axios'; +import {useEffect, useState} from 'react'; +//import charactersData from "../charactersData"; +// ^ we don't have to import from charactersData anymore! We can now make a network request with axios + +const CharacterList = () => { + const [characters, setCharacters] = useState(null); + + const getCharacters = async () => { + const response = await axios.get('https://my-json-server.typicode.com/TechmongersNL/fs03-react/characters'); + console.log(response.data); + setCharacters(response.data); + } + + // If you don't put getCharacters in a useEffect hook, getCharacters will be called (and will make an Axios request) every time CharactersList gets re-rendered + // We only want getCharacters to be called once, the first time getCharacters is rendered, which we can do by using useEffect with an empty dependency array at the end + // Don't do this!: + //getCharacters(); + // Instead, do this: + useEffect(() => { + getCharacters(); + }, []); + + const getCharactersComponents = () => { + return characters.map((character, index) => { + return ( + + ) + }) + } + + return ( + // if characters data is not null (the initial value of the characters state variable) + // then I want to show Characters components + // else I want to show "loading..." + characters ? getCharactersComponents() : 'Loading...' + ) +} + +export default CharacterList; \ No newline at end of file diff --git a/src/components/character.js b/src/components/character.js index 7868b4a..51ea4eb 100644 --- a/src/components/character.js +++ b/src/components/character.js @@ -1,5 +1,6 @@ // You can import other components and use them too! import Image from './image'; +import LikeCounter from './like-counter'; // A React component is just a function that returns some JSX // Specifically, it returns some JSX with only one parent element @@ -8,7 +9,7 @@ import Image from './image'; // the keys of the props object match the attributes added into the component when you use it // Character component usage: const Character = (props) => { - console.log(props) + //console.log(props) // React requires you return only one parent element // You can use a React Fragment (an empty html element) to fix this issue return ( @@ -21,6 +22,7 @@ const Character = (props) => {

Quote

{props.quote}

+
) diff --git a/src/components/like-counter.js b/src/components/like-counter.js new file mode 100644 index 0000000..762cfdb --- /dev/null +++ b/src/components/like-counter.js @@ -0,0 +1,35 @@ +import {useState} from 'react'; +import '../styles/like-counter.css'; + +const LikeCounter = () => { + const [count, setCount] = useState(0); + const [favorite, setFavorite] = useState(false); + + const whenIncreaseButtonClicked = () => { + setCount(count + 1); + }; + const whenDecreaseButtonClicked = () => { + if (count > 0) { + setCount(count - 1); + } + } + + return ( + <> +
+

Number of likes: {count}

+ + +
+
+

{favorite && '⭐️'}

+ +
+ + ) +} + +export default LikeCounter; \ No newline at end of file diff --git a/src/styles/like-counter.css b/src/styles/like-counter.css new file mode 100644 index 0000000..d63f939 --- /dev/null +++ b/src/styles/like-counter.css @@ -0,0 +1,4 @@ +.buttonStyle { + padding: 10px; + background-color: pink; +} \ No newline at end of file