diff --git a/README.md b/README.md index e23524d..b7738f2 100644 --- a/README.md +++ b/README.md @@ -7,3 +7,44 @@ This project was created with [Create React App](https://github.com/facebook/cre - 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 + +## Branches + +Check out the different branches to see how this repo evolved over a week of React lectures + +- (main) React components & props +- (react-state) React state with useState & useEffect +- (react-lifting-state) React lifting state +- (react-router) Reacter router + +You can see the changes from day to day in the ["Pull Requests"](https://github.com/TechmongersNL/fs04-react/pulls) in this repo + +## 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 (like Redux that we'll learn next week), 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 efficient, 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. React will render something on the screen based on state diff --git a/package-lock.json b/package-lock.json index fcfdb30..8e7ff14 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.5", "react": "^18.2.0", "react-dom": "^18.2.0", "react-scripts": "5.0.1", @@ -5413,6 +5414,29 @@ "node": ">=4" } }, + "node_modules/axios": { + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.5.tgz", + "integrity": "sha512-Ii012v05KEVuUoFWmMW/UQv9aRIc3ZwkWDcM+h5Il8izZCtRVpDUfwpoFf7eOtajT3QiGR4yDUx7lPqHJULgbg==", + "dependencies": { + "follow-redirects": "^1.15.4", + "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", @@ -14612,6 +14636,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 d3a860f..906fb59 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.5", "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 7ca67c2..358e3b6 100644 --- a/src/App.js +++ b/src/App.js @@ -1,54 +1,14 @@ import "./App.css"; -import Character from "./components/character"; -import charactersData from "./charactersData"; +import CharacterList from "./components/character-list"; // import something from 'some place' // App.js is the entry point to the rest of your app // This is default behavior for create-react-app function App() { - const charactersComponents = () => { - return charactersData.map((character) => { - return ( - - ); - }); - }; - return (
- {/* If we want a Character component for each character in our data, we could repeat it like below: */} - - - - {/* ... and so on. But this gets repetitive really fast!! We can use .map to fix this */} - {/* The below function uses an array iterator (map), which 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 */} - {charactersComponents()} + {/* Now have a new CharactersList component, to keep our App component more clean and organized */} +
); } diff --git a/src/components/character-list.js b/src/components/character-list.js new file mode 100644 index 0000000..a373903 --- /dev/null +++ b/src/components/character-list.js @@ -0,0 +1,52 @@ +import Character from "./character"; +import axios from "axios"; +import { useState, useEffect } 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" + ); + setCharacters(response.data); + console.log(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 charactersComponents = () => { + return characters.map((character) => { + return ( + + ); + }); + }; + + // If we do the below, we will get an error saying something like "cannot map on null", because initially characters is null! + // return
{charactersComponents()}
; + + // To fix this, we add a ternary conditional: + // If characters data is not null (which is the initial value of the characters state variable) + // then I want to show Characters components + // else I want to show "loading..." + return
{characters ? charactersComponents() : "Loading..."}
; +}; + +export default CharacterList; diff --git a/src/components/character.js b/src/components/character.js index 5288581..912f056 100644 --- a/src/components/character.js +++ b/src/components/character.js @@ -1,4 +1,5 @@ // You can import other components and use them too! +import Counter from "./counter"; import Image from "./image"; // A React component is just a function that returns some JSX @@ -22,6 +23,7 @@ const Character = (props) => {

{props.quote}

{/* using a component within a component also works! */} +
); diff --git a/src/components/counter.js b/src/components/counter.js new file mode 100644 index 0000000..5d32a9c --- /dev/null +++ b/src/components/counter.js @@ -0,0 +1,37 @@ +import { useState } from "react"; + +const Counter = () => { + // count and favorite are now special React state variables! + // When the value of count or favorite changes (by using setCount or setFavorite), React will re-render the Counter component + const [count, setCount] = useState(0); + const [favorite, setFavorite] = useState(false); + // ^ ^ ^ + // state variable ^ initial value of the state variable + // function to update the state variable + + const favoriteClicked = () => { + setFavorite(!favorite); + }; + + return ( +
+

Likes: {count}

+ +
+ + {/* If favorite is true, show the star, otherwise, show nothing */} + {favorite ? "⭐️" : ""} +
+ ); +}; + +export default Counter;