diff --git a/README.md b/README.md index 0baaa60..d41a055 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,43 @@ -![coderbyte](https://user-images.githubusercontent.com/22095244/211191145-4aa983e0-3efa-440d-accd-df48853f572d.png) +# Coderbyte React Challenges +- This repo contains my solutions for the [React & React Native challenges](https://coderbyte.com/challenges). -# Coderbyte React Challenges +- All solutions received a 10/10 score. + +- 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 -- This repo contains my solutions for the React Interview Kit challenges. +**Can you include the challenge's question in your solution?** -- All solutions received a 10/10 score. +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. -- Feel free to use any of my solutions for yourself! +**Do you accept submissions for alternative solutions?** -### Update 1/8/23 -I added the missing solutions to the newer challenges Coderbyte made for React/React Native since I first created this repo. As of now, all challenges are solved. Since this repo seems to be my most popular repo, I will try to periodicallly check and update it with more solutions if Coderbyte decides to add more React challenges. +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.js b/solutions/context-api.jsx similarity index 63% rename from solutions/context-api.js rename to solutions/context-api.jsx index 246e596..ed846af 100644 --- a/solutions/context-api.js +++ b/solutions/context-api.jsx @@ -1,5 +1,5 @@ import React, { useState, createContext, useContext } from "react" -import ReactDOM from "react-dom" +import { createRoot } from "react-dom/client" const languages = ["JavaScript", "Python"] const LanguageContext = createContext({ @@ -8,28 +8,17 @@ const LanguageContext = createContext({ setLanguage: () => {}, }) -function App() { - const [language, setLanguage] = useState(languages[0]) - return ( - - - - ) -} - -function MainSection() { +const MainSection = () => { const { languages, language, setLanguage } = useContext(LanguageContext) const currentIndex = languages.indexOf(language) - const toggleLanguage = () => { - if (currentIndex === languages.length - 1) { - setLanguage(languages[0]) - } else { - setLanguage(languages[currentIndex + 1]) - } - } + const toggleLanguage = () => + currentIndex === languages.length - 1 + ? setLanguage(languages[0]) + : setLanguage(languages[currentIndex + 1]) + return (
-

{`Favorite programing language: ${language}`}

+

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

@@ -37,4 +26,14 @@ function MainSection() { ) } -ReactDOM.render(, document.getElementById("root")) +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.js b/solutions/phone-book.jsx similarity index 83% rename from solutions/phone-book.js rename to solutions/phone-book.jsx index 3394f85..c5b73d0 100644 --- a/solutions/phone-book.js +++ b/solutions/phone-book.jsx @@ -1,5 +1,5 @@ -import React, { useState, useReducer } from "react" -import ReactDOM from "react-dom" +import React, { useState, useReducer, useCallback } from "react" +import { createRoot } from "react-dom/client" const style = { table: { @@ -34,7 +34,7 @@ const style = { }, } -function PhoneBookForm({ addEntryToPhoneBook }) { +function PhoneBookForm({ entries, setEntries }) { const initialFormState = { userFirstname: "Coder", userLastname: "Byte", @@ -52,6 +52,27 @@ function PhoneBookForm({ addEntryToPhoneBook }) { 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 (
    { @@ -68,9 +89,7 @@ function PhoneBookForm({ addEntryToPhoneBook }) { className="userFirstname" name="userFirstname" type="text" - onChange={({ target: { name, value } }) => - dispatch({ type: name, payload: value }) - } + onChange={onChange} value={formState.userFirstname} />
    @@ -81,9 +100,7 @@ function PhoneBookForm({ addEntryToPhoneBook }) { className="userLastname" name="userLastname" type="text" - onChange={({ target: { name, value } }) => - dispatch({ type: name, payload: value }) - } + onChange={onChange} value={formState.userLastname} />
    @@ -94,9 +111,7 @@ function PhoneBookForm({ addEntryToPhoneBook }) { className="userPhone" name="userPhone" type="text" - onChange={({ target: { name, value } }) => - dispatch({ type: name, payload: value }) - } + onChange={onChange} value={formState.userPhone} />
    @@ -135,34 +150,13 @@ function InformationTable({ entries }) { function Application() { const [entries, setEntries] = useState([]) - - const addEntryToPhoneBook = ({ - userFirstname, - userLastname, - userPhone, - }) => { - 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) - } - return (
    - +
    ) } -ReactDOM.render(, document.getElementById("root")) +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.js b/solutions/react-native-simple-counter.jsx similarity index 93% rename from solutions/react-native-simple-counter.js rename to solutions/react-native-simple-counter.jsx index 13d48fb..c6b6dbe 100644 --- a/solutions/react-native-simple-counter.js +++ b/solutions/react-native-simple-counter.jsx @@ -5,13 +5,14 @@ const SimpleCounter = () => { const [count, setCount] = useState(0) const increment = () => setCount(count + 1) + return ( button count: {count} ) @@ -25,7 +26,7 @@ const styles = StyleSheet.create({ }, counter: { textAlign: "center", - marginVertical: 10, + marginVertical: 20, }, }) 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.ts b/solutions/typescript-button-toggle.ts deleted file mode 100644 index d3afd05..0000000 --- a/solutions/typescript-button-toggle.ts +++ /dev/null @@ -1,18 +0,0 @@ -import React, { useState, ReactNode } from "react" -import ReactDOM from "react-dom" - -function Toggle(): ReactNode { - const [toggle, setToggle] = useState(true) - - function handleClick() { - setToggle(!toggle) - } - - return ( - - ) -} - -ReactDOM.render(, document.getElementById("root")) 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