diff --git a/README.md b/README.md index 9d59581..d41a055 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,43 @@ -# Coderbyte React +# Coderbyte React Challenges -My Coderbyte solutions for the React Interview Kit challenges. +- This repo contains my solutions for the [React & React Native challenges](https://coderbyte.com/challenges). -All received a 10/10 score. +- All solutions received a 10/10 score. -Feel free to use any of my solutions for yourself! +- Feel free to use any of my solutions for yourself! + +## Updates + +#### 1/8/23 + +Added missing solutions for the newer challenges Coderbyte released. + +#### 6/13/23 + +Refactored a few solutions. + +#### 9/21/23 + +Slight editing of `README`. + +#### 1/20/24 + +- Refactored all previous solutions to use React 18 and use .jsx/.tsx file extensions +- Redid solution for `tictactoe.jsx` +- Added new solutions for the following challenges: + - `color-dropdown.jsx` + - `letter-tiles.jsx` + - `live-paragraph.jsx` + - `quiz-builder.jsx` + - `weather-dashboard.jsx` +- Updated `README` + +## FAQ + +**Can you include the challenge's question in your solution?** + +Unfortunately, no since I don't want to violate Coderbyte's [Terms of Use](https://coderbyte.com/terms). My solutions are only meant as a resource to check against your own or just to study from in general! You'll need to pay for Coderbyte to see the questions to their code challenges. + +**Do you accept submissions for alternative solutions?** + +No, not at this time. I originally intended to use this repo as a way to keep a copy of my solutions I wrote when I ended my Coderbyte subscription. However, since this repo has grown in popularity, if there is more interest to open this repo up and accept alternative solution submissions, I may consider this! diff --git a/solutions/button-toggle.js b/solutions/button-toggle.js deleted file mode 100644 index d530905..0000000 --- a/solutions/button-toggle.js +++ /dev/null @@ -1,22 +0,0 @@ -import React, { useState } from 'react'; -import ReactDOM from 'react-dom'; - -const Toggle = () => { - - const [toggle, setToggle] = useState(false); - - const handleClick = () => { - setToggle(!toggle); - }; - - return ( - - ); -}; - -ReactDOM.render( - , - document.getElementById('root') -); \ No newline at end of file diff --git a/solutions/button-toggle.jsx b/solutions/button-toggle.jsx new file mode 100644 index 0000000..b75f0f1 --- /dev/null +++ b/solutions/button-toggle.jsx @@ -0,0 +1,19 @@ +import React, { useState } from "react" +import { createRoot } from "react-dom/client" + +const Toggle = () => { + const [toggle, setToggle] = useState(false) + + const handleClick = () => { + setToggle(!toggle) + } + + return ( + + ) +} + +const root = createRoot(document.getElementById("root")) +root.render() diff --git a/solutions/color-dropdown.jsx b/solutions/color-dropdown.jsx new file mode 100644 index 0000000..17e5e89 --- /dev/null +++ b/solutions/color-dropdown.jsx @@ -0,0 +1,21 @@ +import React, { useState } from "react" +import { createRoot } from "react-dom/client" + +function ColorSelector() { + const colors = { red: "Red", blue: "Blue", green: "Green" } + const [color, setColor] = useState(colors.red) + const onChange = e => setColor(e.target.value) + return ( + <> + + {color &&

{`You have selected: ${color}`}

} + + ) +} + +const root = createRoot(document.getElementById("root")) +root.render() diff --git a/solutions/context-api.jsx b/solutions/context-api.jsx new file mode 100644 index 0000000..ed846af --- /dev/null +++ b/solutions/context-api.jsx @@ -0,0 +1,39 @@ +import React, { useState, createContext, useContext } from "react" +import { createRoot } from "react-dom/client" + +const languages = ["JavaScript", "Python"] +const LanguageContext = createContext({ + languages, + language: languages[0], + setLanguage: () => {}, +}) + +const MainSection = () => { + const { languages, language, setLanguage } = useContext(LanguageContext) + const currentIndex = languages.indexOf(language) + const toggleLanguage = () => + currentIndex === languages.length - 1 + ? setLanguage(languages[0]) + : setLanguage(languages[currentIndex + 1]) + + return ( +
+

{`Favorite programming language: ${language}`}

+ +
+ ) +} + +const App = () => { + const [language, setLanguage] = useState(languages[0]) + return ( + + + + ) +} + +const root = createRoot(document.getElementById("root")) +root.render() diff --git a/solutions/letter-tiles.jsx b/solutions/letter-tiles.jsx new file mode 100644 index 0000000..8821b9c --- /dev/null +++ b/solutions/letter-tiles.jsx @@ -0,0 +1,83 @@ +import React, { useState, useCallback, useMemo } from "react" +import { createRoot } from "react-dom/client" + +const style = { + letterContainer: { + overflow: "auto", + marginBottom: "10px", + display: "flex", + flexWrap: "wrap", + justifyContent: "center", + alignItems: "center", + }, + letter: { + float: "left", + padding: "10px 10px", + background: "#c9e4ed", + borderRadius: "5px", + marginRight: "5px", + marginTop: "5px", + cursor: "pointer", + }, + outputString: { + marginTop: "20px", + textAlign: "center", + }, +} + +const Tile = ({ letter, outputArray, setOutputArray, tally, setTally }) => { + const onClick = useCallback(() => { + if (!tally[letter]) { + setTally({ ...tally, [letter]: 1 }) + setOutputArray([...outputArray, letter]) + } else if (tally[letter] && tally[letter] === 2) { + setTally({ ...tally, [letter]: 0 }) + const slicedArray = outputArray.slice(0, outputArray.length - 2) + setOutputArray([...slicedArray, "_"]) + } else { + setTally({ ...tally, [letter]: tally[letter] + 1 }) + setOutputArray([...outputArray, letter]) + } + }, [letter, outputArray, setOutputArray, tally, setTally]) + + return ( + + ) +} + +const Application = () => { + const [outputArray, setOutputArray] = useState([]) + const [tally, setTally] = useState({}) + const alphabetArray = useMemo(() => { + const arr = [] + for (let i = 65; i <= 90; i++) { + const char = String.fromCharCode(i) + arr.push(char) + } + return arr + }, []) + return ( +
+ +
+ {outputArray.join("")} +
+
+ ) +} + +const root = createRoot(document.getElementById("root")) +root.render() diff --git a/solutions/list.js b/solutions/list.js deleted file mode 100644 index 820a0d2..0000000 --- a/solutions/list.js +++ /dev/null @@ -1,43 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; - -const data = [ - { name: 'Daniel', age: 25 }, - { name: 'John', age: 24 }, - { name: 'Jen', age: 31 }, -]; - -const DataItem = ({ name, age}) => { - - return ( -
  • - {name}{` `} - {age} -
  • - ); -}; - -const DataList = () => { - - return ( -
    -

    Data List

    -
      - {data.map((dataItem, index) => { - return ( - - ); - })} -
    -
    - ); -}; - -ReactDOM.render( - , - document.getElementById('root') -); \ No newline at end of file diff --git a/solutions/list.jsx b/solutions/list.jsx new file mode 100644 index 0000000..432df61 --- /dev/null +++ b/solutions/list.jsx @@ -0,0 +1,33 @@ +import React from "react" +import { createRoot } from "react-dom/client" + +const data = [ + { name: "Daniel", age: 25 }, + { name: "John", age: 24 }, + { name: "Jen", age: 31 }, +] + +const DataItem = ({ name, age }) => ( +
  • + {`${name} `} + {age} +
  • +) + +const DataList = ({ data }) => ( +
    +

    Data List

    +
      + {data.map((dataItem, index) => ( + + ))} +
    +
    +) + +const root = createRoot(document.getElementById("root")) +root.render() diff --git a/solutions/live-paragraph.jsx b/solutions/live-paragraph.jsx new file mode 100644 index 0000000..e5b9ea9 --- /dev/null +++ b/solutions/live-paragraph.jsx @@ -0,0 +1,19 @@ +import React, { useState } from "react" +import { createRoot } from "react-dom/client" + +function LiveText() { + const [text, setText] = useState("") + + const onChange = ({ target: { value } }) => { + setText(value) + } + return ( + <> + +

    {text}

    + + ) +} + +const root = createRoot(document.getElementById("root")) +root.render() diff --git a/solutions/phone-book.jsx b/solutions/phone-book.jsx new file mode 100644 index 0000000..c5b73d0 --- /dev/null +++ b/solutions/phone-book.jsx @@ -0,0 +1,162 @@ +import React, { useState, useReducer, useCallback } from "react" +import { createRoot } from "react-dom/client" + +const style = { + table: { + borderCollapse: "collapse", + }, + tableCell: { + border: "1px solid gray", + margin: 0, + padding: "5px 10px", + width: "max-content", + minWidth: "150px", + }, + form: { + container: { + padding: "20px", + border: "1px solid #F0F8FF", + borderRadius: "15px", + width: "max-content", + marginBottom: "40px", + }, + inputs: { + marginBottom: "5px", + }, + submitBtn: { + marginTop: "10px", + padding: "10px 15px", + border: "none", + backgroundColor: "lightseagreen", + fontSize: "14px", + borderRadius: "5px", + }, + }, +} + +function PhoneBookForm({ entries, setEntries }) { + const initialFormState = { + userFirstname: "Coder", + userLastname: "Byte", + userPhone: "8885559999", + } + + const formReducer = (state, { type, payload }) => { + switch (type) { + case "RESET": + return { userFirstname: "", userLastname: "", userPhone: "" } + default: + return { ...state, [type]: payload } + } + } + + const [formState, dispatch] = useReducer(formReducer, initialFormState) + + const onChange = ({ target: { name, value } }) => + dispatch({ type: name, payload: value }) + + const addEntryToPhoneBook = useCallback(() => { + const { userFirstname, userLastname, userPhone } = formState + const newEntries = [ + ...entries, + { userFirstname, userLastname, userPhone }, + ] + const newSortedEntries = newEntries.sort((a, b) => { + const userLastnameA = a.userLastname.toLowerCase() + const userLastnameB = b.userLastname.toLowerCase() + return userLastnameA < userLastnameB + ? -1 + : userLastnameA > userLastnameB + ? 1 + : 0 + }) + setEntries(newSortedEntries) + }, [formState]) + + return ( +
    { + e.preventDefault() + addEntryToPhoneBook(formState) + dispatch({ type: "RESET" }) + }} + style={style.form.container} + > + +
    + +
    + +
    + +
    + +
    + +
    + +
    + ) +} + +function InformationTable({ entries }) { + return ( + + + + + + + + {entries.map( + ({ userFirstname, userLastname, userPhone }, index) => ( + + + + + + ) + )} + +
    First nameLast namePhone
    {userFirstname}{userLastname}{userPhone}
    + ) +} + +function Application() { + const [entries, setEntries] = useState([]) + return ( +
    + + +
    + ) +} + +const root = createRoot(document.getElementById("root")) +root.render() diff --git a/solutions/quiz-builder.jsx b/solutions/quiz-builder.jsx new file mode 100644 index 0000000..6d96c0f --- /dev/null +++ b/solutions/quiz-builder.jsx @@ -0,0 +1,130 @@ +import React, { useState, useMemo } from "react" +import { createRoot } from "react-dom/client" + +const style = { + container: { + padding: "20px", + border: "1px solid #E0E0E0", + borderRadius: "15px", + width: "max-content", + marginBottom: "40px", + }, + question: { + fontWeight: "bold", + marginBottom: "10px", + }, + options: { + marginBottom: "5px", + }, + button: { + marginTop: "10px", + padding: "10px 15px", + border: "none", + backgroundColor: "#007BFF", + color: "#FFF", + fontSize: "14px", + borderRadius: "5px", + cursor: "pointer", + }, + feedback: { + marginTop: "10px", + fontSize: "14px", + }, +} + +const QuizOption = ({ option, index, answer, setAnswer }) => { + const onChange = ({ target: { value } }) => setAnswer(value) + return ( + <> + + + + ) +} + +function QuizApp() { + // do not modify the questions or answers below + const questions = useMemo( + () => [ + { + question: "What is the capital of France?", + options: ["London", "Paris", "Berlin", "Madrid"], + correct: "Paris", + }, + { + question: "What is the capital of Germany?", + options: ["Berlin", "Munich", "Frankfurt", "Hamburg"], + correct: "Berlin", + }, + ], + [] + ) + + const questionsTotal = useMemo(() => questions.length, [questions]) + + const [questionsIndex, setQuestionsIndex] = useState(0) + const [score, setScore] = useState(0) + const [feedback, setFeedback] = useState(null) + const [answer, setAnswer] = useState(null) + const [completedQuiz, setCompletedQuiz] = useState(false) + + const submit = () => { + if (answer === questions[questionsIndex].correct) { + setScore(score + 1) + setFeedback("Correct!") + } else { + setFeedback("Incorrect!") + } + + if (questionsIndex === questionsTotal - 1) { + setCompletedQuiz(true) + } else { + setQuestionsIndex(questionsIndex + 1) + setAnswer(null) + } + } + + return ( +
    +
    + {`${questions[questionsIndex].question}`} +
    +
    + {questions[questionsIndex].options.map((option, index) => ( + + ))} +
    + +
    + {questionsIndex !== 0 && !completedQuiz && `${feedback}`} +
    +
    + {completedQuiz && + `Quiz complete! You scored ${score} out of ${questions.length}!`} +
    +
    + ) +} + +const root = createRoot(document.getElementById("root")) +root.render() diff --git a/solutions/react-native-simple-counter.jsx b/solutions/react-native-simple-counter.jsx new file mode 100644 index 0000000..c6b6dbe --- /dev/null +++ b/solutions/react-native-simple-counter.jsx @@ -0,0 +1,33 @@ +import React, { useState } from "react" +import { Text, View, StyleSheet } from "react-native" + +const SimpleCounter = () => { + const [count, setCount] = useState(0) + + const increment = () => setCount(count + 1) + + return ( + + + button count: {count} + + + + ) +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: "center", + marginHorizontal: 10, + }, + counter: { + textAlign: "center", + marginVertical: 20, + }, +}) + +export default SimpleCounter diff --git a/solutions/simple-counter.js b/solutions/simple-counter.js deleted file mode 100644 index c4c92b4..0000000 --- a/solutions/simple-counter.js +++ /dev/null @@ -1,30 +0,0 @@ -import React, { useState } from 'react'; -import ReactDOM from 'react-dom'; - -const Counter = () => { - const [count, setCount] = useState(0); - - increment = () => { - setCount(prevCount => prevCount + 1); - }; - - return ( -
    -

    - Button Count: - {count} -

    - -
    - ); -}; - -ReactDOM.render( - , - document.getElementById('root') -); \ No newline at end of file diff --git a/solutions/simple-counter.jsx b/solutions/simple-counter.jsx new file mode 100644 index 0000000..ebea834 --- /dev/null +++ b/solutions/simple-counter.jsx @@ -0,0 +1,25 @@ +import React, { useState } from "react" +import { createRoot } from "react-dom/client" + +const Counter = () => { + const [count, setCount] = useState(0) + + const increment = () => { + setCount(prevCount => prevCount + 1) + } + + return ( +
    +

    + {`Button Count: `} + {count} +

    + +
    + ) +} + +const root = createRoot(document.getElementById("root")) +root.render() diff --git a/solutions/tic-tac-toe.js b/solutions/tic-tac-toe.js deleted file mode 100644 index 38289ee..0000000 --- a/solutions/tic-tac-toe.js +++ /dev/null @@ -1,288 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import ReactDOM from 'react-dom'; - -const rowStyle = { - display: 'flex' -} - -const squareStyle = { - 'width':'60px', - 'height':'60px', - 'backgroundColor': '#ddd', - 'margin': '4px', - 'display': 'flex', - 'justifyContent': 'center', - 'alignItems': 'center', - 'fontSize': '20px', - 'color': 'white', - 'cursor': 'pointer', -} - -const boardStyle = { - 'backgroundColor': '#eee', - 'width': '208px', - 'alignItems': 'center', - 'justifyContent': 'center', - 'display': 'flex', - 'flexDirection': 'column', - 'border': '3px #eee solid' -} - -const containerStyle = { - 'display': 'flex', - 'alignItems': 'center', - 'flexDirection': 'column' -} - -const instructionsStyle = { - 'marginTop': '5px', - 'marginBottom': '5px', - 'fontWeight': 'bold', - 'fontSize': '16px', -} - -const buttonStyle = { - 'marginTop': '15px', - 'marginBottom': '16px', - 'width': '80px', - 'height': '40px', - 'backgroundColor': '#8acaca', - 'color': 'white', - 'fontSize': '16px', - 'cursor': 'pointer', -} - -const Square = ({playerXSquares, playerOSquares, handleClick, value, disabled }) => { - return ( -
    { - if (!disabled) { - handleClick(value) - } - }} - > - - {playerXSquares.indexOf(value) > -1 ? 'X' : playerOSquares.indexOf(value) > -1 ? 'O' : ''} - -
    - ); -}; - -const Board = () => { - const [currentPlayer, setCurrentPlayer] = useState('player X'); - const [winner, setWinner] = useState('None') - const [playerXSquares, setPlayerXSquares] = useState([]); - const [playerOSquares, setPlayerOSquares] = useState([]); - - const handleClick = value => { - if (currentPlayer === 'player X') { - setPlayerXSquares([...playerXSquares, value]); - setCurrentPlayer('player O'); - } else { - setPlayerOSquares([...playerOSquares, value]); - setCurrentPlayer('player X'); - } - } - - const determineWinner = (playerXSquares, playerOSquares) => { - function checkRows() { - if ( - (playerXSquares.includes(0) && playerXSquares.includes(1) && playerXSquares.includes(2)) || - (playerXSquares.includes(3) && playerXSquares.includes(4) && playerXSquares.includes(5)) || - (playerXSquares.includes(6) && playerXSquares.includes(7) && playerXSquares.includes(8)) - ) { - return setWinner('Player X'); - } - - if ( - (playerOSquares.includes(0) && playerOSquares.includes(1) && playerOSquares.includes(2)) || - (playerOSquares.includes(3) && playerOSquares.includes(4) && playerOSquares.includes(5)) || - (playerOSquares.includes(6) && playerOSquares.includes(7) && playerOSquares.includes(8)) - ) { - return setWinner('Player O'); - } - } - - function checkColumns() { - if ( - (playerXSquares.includes(0) && playerXSquares.includes(3) && playerXSquares.includes(6)) || - (playerXSquares.includes(1) && playerXSquares.includes(4) && playerXSquares.includes(7)) || - (playerXSquares.includes(2) && playerXSquares.includes(5) && playerXSquares.includes(8)) - ) { - return setWinner('Player X'); - } - - if ( - (playerOSquares.includes(0) && playerOSquares.includes(3) && playerOSquares.includes(6)) || - (playerOSquares.includes(1) && playerOSquares.includes(4) && playerOSquares.includes(7)) || - (playerOSquares.includes(2) && playerOSquares.includes(5) && playerOSquares.includes(8)) - ) { - return setWinner('Player O'); - } - } - - function checkDiagonals() { - if (playerXSquares.includes(4)) { - if ((playerXSquares.includes(0) && playerXSquares.includes(8)) || (playerXSquares.includes(2) && playerXSquares.includes(6))) { - return setWinner('Player X'); - } - } - if (playerOSquares.includes(4)) { - if ((playerOSquares.includes(0) && playerOSquares.includes(8)) || (playerOSquares.includes(2) && playerOSquares.includes(6))) { - return setWinner('Player O'); - } - } - } - - if (winner === 'None') { - checkRows(); - checkColumns(); - checkDiagonals(); - } - } - - - const reset = () => { - setCurrentPlayer('Player X'); - setPlayerXSquares([]); - setPlayerOSquares([]); - setWinner('None'); - } - - useEffect(() => { - determineWinner(playerXSquares, playerOSquares); - }, [playerXSquares, playerOSquares]); - - return ( -
    -
    - Next player: {currentPlayer === 'Player X' ? 'Player O' : 'Player X'} -
    -
    - Winner: {winner} -
    - -
    -
    - - - -
    -
    - - - -
    -
    - - - -
    -
    -
    - ); -}; - -const Game = () => { - return ( -
    -
    - -
    -
    - ); -}; - -ReactDOM.render( - , - document.getElementById('root') -); \ No newline at end of file diff --git a/solutions/tic-tac-toe.jsx b/solutions/tic-tac-toe.jsx new file mode 100644 index 0000000..5b7e8ee --- /dev/null +++ b/solutions/tic-tac-toe.jsx @@ -0,0 +1,202 @@ +import React, { useState } from "react" +import { createRoot } from "react-dom/client" + +const rowStyle = { + display: "flex", +} + +const squareStyle = { + width: "60px", + height: "60px", + backgroundColor: "#ddd", + margin: "4px", + display: "flex", + justifyContent: "center", + alignItems: "center", + fontSize: "20px", + color: "white", +} + +const disabledSquareStyle = { + ...squareStyle, + cursor: "not-allowed", +} + +const boardStyle = { + backgroundColor: "#eee", + width: "208px", + alignItems: "center", + justifyContent: "center", + display: "flex", + flexDirection: "column", + border: "3px #eee solid", +} + +const containerStyle = { + display: "flex", + alignItems: "center", + flexDirection: "column", +} + +const instructionsStyle = { + marginTop: "5px", + marginBottom: "5px", + fontWeight: "bold", + fontSize: "16px", +} + +const buttonStyle = { + marginTop: "15px", + marginBottom: "16px", + width: "80px", + height: "40px", + backgroundColor: "#8acaca", + color: "white", + fontSize: "16px", +} + +const Square = ({ value, onClick, winner }) => { + return ( + + ) +} + +const Board = () => { + const [player, setPlayer] = useState("X") + const [winner, setWinner] = useState("None") + const [board, setBoard] = useState(() => Array(9).fill(null)) + + const matches = [ + [0, 1, 2], + [3, 4, 5], + [6, 7, 8], + [0, 3, 6], + [1, 4, 7], + [2, 5, 8], + [0, 4, 8], + [2, 4, 6], + ] + + const checkWinner = newBoard => { + for (let i = 0; i < matches.length; i++) { + const [a, b, c] = matches[i] + if ( + newBoard[a] === player && + newBoard[b] === player && + newBoard[c] === player + ) { + return true + } + } + return false + } + + const onClick = index => { + const newBoard = [...board] + newBoard[index] = player + setBoard(newBoard) + + if (checkWinner(newBoard)) { + setWinner(player) + } else { + setPlayer(player === "X" ? "O" : "X") + } + } + + const onReset = () => { + setBoard(Array(9).fill(null)) + setPlayer("X") + setWinner("None") + } + + return ( +
    +
    + Next player: {player === "X" ? "O" : "X"} +
    +
    + Winner: {winner !== "None" && winner} +
    + +
    +
    + onClick(0)} + winner={winner} + /> + onClick(1)} + winner={winner} + /> + onClick(2)} + winner={winner} + /> +
    +
    + onClick(3)} + winner={winner} + /> + onClick(4)} + winner={winner} + /> + onClick(5)} + winner={winner} + /> +
    +
    + onClick(6)} + winner={winner} + /> + onClick(7)} + winner={winner} + /> + onClick(8)} + winner={winner} + /> +
    +
    +
    + ) +} + +const Game = () => { + return ( +
    +
    + +
    +
    + ) +} + +const root = createRoot(document.getElementById("root")) +root.render() diff --git a/solutions/typescript-button-toggle.tsx b/solutions/typescript-button-toggle.tsx new file mode 100644 index 0000000..7cc1f07 --- /dev/null +++ b/solutions/typescript-button-toggle.tsx @@ -0,0 +1,15 @@ +import React, { useState, ReactNode } from "react" +import { createRoot } from "react-dom/client" + +const Toggle: ReactNode = () => { + const [toggle, setToggle] = useState(true) + + const onClick = (): void => { + setToggle(!toggle) + } + + return +} + +const root = createRoot(document.getElementById("root")) +root.render() diff --git a/solutions/weather-dashboard.jsx b/solutions/weather-dashboard.jsx new file mode 100644 index 0000000..677f148 --- /dev/null +++ b/solutions/weather-dashboard.jsx @@ -0,0 +1,121 @@ +import React, { useState } from "react" +import { createRoot } from "react-dom/client" + +const style = { + marginTop: { + marginTop: "10px", + }, + marginRight: { + marginRight: "10px", + }, +} + +const WeatherDashboard = () => { + // instead of requesting data from an API, use this mock data + const mockWeatherData = { + "New York": { + temperature: "22°C", + humidity: "56%", + windSpeed: "15 km/h", + }, + "Los Angeles": { + temperature: "27°C", + humidity: "45%", + windSpeed: "10 km/h", + }, + London: { + temperature: "15°C", + humidity: "70%", + windSpeed: "20 km/h", + }, + } + + const defaultWeather = { + temperature: "", + humidity: "", + windSpeed: "", + } + + const [city, setCity] = useState("") + const [cache, setCache] = useState({}) + const [notFound, setNotFound] = useState(false) + const [previousSearches, setPreviousSearches] = useState([]) + const [weather, setWeather] = useState(defaultWeather) + + const mockFetchWeatherData = city => { + return new Promise((resolve, reject) => { + setTimeout(() => { + if (mockWeatherData[city]) { + resolve(mockWeatherData[city]) + } else { + reject(new Error("City not found.")) + setNotFound(true) + setWeather(defaultWeather) + } + }, 500) + }) + } + + const search = async city => { + setNotFound(false) + + if (!city || city === "") { + setWeather(defaultWeather) + setNotFound(true) + return + } + + if (cache[city]) { + setWeather(cache[city]) + return + } + + try { + const data = await mockFetchWeatherData(city) + setCache({ ...cache, [city]: data }) + setWeather(data) + setPreviousSearches([...previousSearches, city]) + } catch { + throw new Error("Could not fetch weather data.") + } + } + + return ( +
    + setCity(e.target.value)} + /> + +
    +
    Temperature: {weather.temperature}
    +
    Humidity: {weather.humidity}
    +
    Wind Speed: {weather.windSpeed}
    + {notFound &&
    City not found.
    } +
    +
    + {previousSearches.length > 0 && + previousSearches.map((previousSearch, index) => ( + + ))} +
    +
    + ) +} + +const root = createRoot(document.getElementById("root")) +root.render() pFad - Phonifier reborn

    Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

    Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


    Alternative Proxies:

    Alternative Proxy

    pFad Proxy

    pFad v3 Proxy

    pFad v4 Proxy