diff --git a/app/components/Battle.js b/app/components/Battle.js index 4458e7e..6576e8f 100644 --- a/app/components/Battle.js +++ b/app/components/Battle.js @@ -2,82 +2,72 @@ import React from 'react' import { FaUserFriends, FaFighterJet, FaTrophy, FaTimesCircle } from 'react-icons/fa' import PropTypes from 'prop-types' import Results from './Results' -import { ThemeConsumer } from '../contexts/theme' +import ThemeContext from '../contexts/theme' import { Link } from 'react-router-dom' function Instructions () { + const theme = React.useContext(ThemeContext) + return ( - - {({ theme }) => ( -
-

- Instructions -

-
    -
  1. -

    Enter two Github users

    - -
  2. -
  3. -

    Battle

    - -
  4. -
  5. -

    See the winners

    - -
  6. -
-
- )} -
+
+

+ Instructions +

+
    +
  1. +

    Enter two Github users

    + +
  2. +
  3. +

    Battle

    + +
  4. +
  5. +

    See the winners

    + +
  6. +
+
) } -class PlayerInput extends React.Component { - state = { - username: '' - } - handleSubmit = (event) => { - event.preventDefault() +function PlayerInput ({ onSubmit, label }) { + const [username, setUsername] = React.useState('') - this.props.onSubmit(this.state.username) - } - handleChange = (event) => { - this.setState({ - username: event.target.value - }) - } - render() { - return ( - - {({ theme }) => ( -
- -
- - -
-
- )} -
- ) + const handleSubmit = (e) => { + e.preventDefault() + + onSubmit(username) } + + const handleChange = (event) => setUsername(event.target.value) + const theme = React.useContext(ThemeContext) + + return ( +
+ +
+ + +
+
+ ) } PlayerInput.propTypes = { @@ -86,31 +76,29 @@ PlayerInput.propTypes = { } function PlayerPreview ({ username, onReset, label }) { + const theme = React.useContext(ThemeContext) + return ( - - {({ theme }) => ( -
-

{label}

-
-
- {`Avatar - - {username} - -
- -
+
+

{label}

+
+
+ {`Avatar + + {username} +
- )} - + +
+
) } @@ -120,70 +108,63 @@ PlayerPreview.propTypes = { label: PropTypes.string.isRequired } -export default class Battle extends React.Component { - state = { - playerOne: null, - playerTwo: null, - } - handleSubmit = (id, player) => { - this.setState({ - [id]: player - }) - } - handleReset = (id) => { - this.setState({ - [id]: null - }) - } - render() { - const { playerOne, playerTwo } = this.state - - return ( - - - -
-

Players

-
- {playerOne === null - ? this.handleSubmit('playerOne', player)} - /> - : this.handleReset('playerOne')} - /> - } - - {playerTwo === null - ? this.handleSubmit('playerTwo', player)} - /> - : this.handleReset('playerTwo')} - /> - } -
- - - {playerOne && playerTwo && ( - - Battle - - )} +export default function Battle () { + const [playerOne, setPlayerOne] = React.useState(null) + const [playerTwo, setPlayerTwo] = React.useState(null) + + const handleSubmit = (id, player) => id === 'playerOne' + ? setPlayerOne(player) + : setPlayerTwo(player) + + const handleReset = (id) => id === 'playerOne' + ? setPlayerOne(null) + : setPlayerTwo(null) + + return ( + + + +
+

Players

+
+ {playerOne === null + ? handleSubmit('playerOne', player)} + /> + : handleReset('playerOne')} + /> + } + + {playerTwo === null + ? handleSubmit('playerTwo', player)} + /> + : handleReset('playerTwo')} + /> + }
- - ) - } + + + {playerOne && playerTwo && ( + + Battle + + )} +
+
+ ) } \ No newline at end of file diff --git a/app/components/Card.js b/app/components/Card.js index 05dcbc5..d719e5a 100644 --- a/app/components/Card.js +++ b/app/components/Card.js @@ -1,34 +1,32 @@ import React from 'react' import PropTypes from 'prop-types' -import { ThemeConsumer } from '../contexts/theme' +import ThemeContext from '../contexts/theme' export default function Card ({ header, subheader, avatar, href, name, children }) { + const theme = React.useContext(ThemeContext) + return ( - - {({ theme }) => ( -
-

- {header} -

- {`Avatar - {subheader && ( -

- {subheader} -

- )} -

- - {name} - -

- {children} -
+
+

+ {header} +

+ {`Avatar + {subheader && ( +

+ {subheader} +

)} - +

+ + {name} + +

+ {children} +
) } diff --git a/app/components/Hover.js b/app/components/Hover.js deleted file mode 100644 index de236f0..0000000 --- a/app/components/Hover.js +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react' - -export default class Hover extends React.Component { - state = { hovering: false } - mouseOver = () => this.setState({ hovering: true }) - mouseOut = () => this.setState({ hovering: false }) - render () { - return ( -
- {this.props.children(this.state.hovering)} -
- ) - } -} \ No newline at end of file diff --git a/app/components/Loading.js b/app/components/Loading.js index af58539..99f1aee 100644 --- a/app/components/Loading.js +++ b/app/components/Loading.js @@ -12,35 +12,29 @@ const styles = { } } -export default class Loading extends React.Component { - state = { content: this.props.text } - componentDidMount () { - const { speed, text } = this.props +export default function Loading ({ text = 'Loading', speed = 300 }) { + const [content, setContent] = React.useState(text) - this.interval = window.setInterval(() => { - this.state.content === text + '...' - ? this.setState({ content: text }) - : this.setState(({ content }) => ({ content: content + '.' })) + React.useEffect(() => { + const id = window.setInterval(() => { + setContent((content) => { + return content === `${text}...` + ? text + : `${content}.` + }) }, speed) - } - componentWillUnmount () { - window.clearInterval(this.interval) - } - render() { - return ( -

- {this.state.content} -

- ) - } -} -Loading.propTypes = { - text: PropTypes.string.isRequired, - speed: PropTypes.number.isRequired, + return () => window.clearInterval(id) + }, [text, speed]) + + return ( +

+ {content} +

+ ) } -Loading.defaultProps = { - text: 'Loading', - speed: 300 +Loading.propTypes = { + text: PropTypes.string, + speed: PropTypes.number, } \ No newline at end of file diff --git a/app/components/Nav.js b/app/components/Nav.js index 26a514a..90b0cab 100644 --- a/app/components/Nav.js +++ b/app/components/Nav.js @@ -1,44 +1,42 @@ import React from 'react' -import { ThemeConsumer } from '../contexts/theme' +import ThemeContext from '../contexts/theme' import { NavLink } from 'react-router-dom' const activeStyle = { color: 'rgb(187, 46, 31)' } -export default function Nav () { +export default function Nav ({ toggleTheme }) { + const theme = React.useContext(ThemeContext) + return ( - - {({ theme, toggleTheme }) => ( - - )} - + ) } \ No newline at end of file diff --git a/app/components/Popular.js b/app/components/Popular.js index 76ca21f..f8b6d05 100644 --- a/app/components/Popular.js +++ b/app/components/Popular.js @@ -79,61 +79,56 @@ ReposGrid.propTypes = { repos: PropTypes.array.isRequired } -export default class Popular extends React.Component { - state = { - selectedLanguage: 'All', - repos: {}, - error: null, - } - componentDidMount () { - this.updateLanguage(this.state.selectedLanguage) - } - updateLanguage = (selectedLanguage) => { - this.setState({ - selectedLanguage, +function popularReducer (state, action) { + if (action.type === 'success') { + return { + ...state, + [action.selectedLanguage]: action.repos, error: null, - }) + } + } else if (action.type === 'error') { + return { + ...state, + error: action.error.message + } + } else { + throw new Error(`That action type isn't supported.`) + } +} + +export default function Popular () { + const [selectedLanguage, setSelectedLanguage] = React.useState('All') + const [state, dispatch] = React.useReducer( + popularReducer, + { error: null } + ) + + const fetchedLanguages = React.useRef([]) + + React.useEffect(() => { + if (fetchedLanguages.current.includes(selectedLanguage) === false) { + fetchedLanguages.current.push(selectedLanguage) - if (!this.state.repos[selectedLanguage]) { fetchPopularRepos(selectedLanguage) - .then((data) => { - this.setState(({ repos }) => ({ - repos: { - ...repos, - [selectedLanguage]: data - } - })) - }) - .catch(() => { - console.warn('Error fetching repos: ', error) - - this.setState({ - error: `There was an error fetching the repositories.` - }) - }) + .then((repos) => dispatch({ type: 'success', selectedLanguage, repos })) + .catch((error) => dispatch({ type: 'error', error })) } - } - isLoading = () => { - const { selectedLanguage, repos, error } = this.state + }, [fetchedLanguages, selectedLanguage]) - return !repos[selectedLanguage] && error === null - } - render() { - const { selectedLanguage, repos, error } = this.state + const isLoading = () => !state[selectedLanguage] && state.error === null - return ( - - + return ( + + - {this.isLoading() && } + {isLoading() && } - {error &&

{error}

} + {state.error &&

{state.error}

} - {repos[selectedLanguage] && } -
- ) - } + {state[selectedLanguage] && } +
+ ) } \ No newline at end of file diff --git a/app/components/Tooltip.js b/app/components/Tooltip.js index a10cd55..09fce1e 100644 --- a/app/components/Tooltip.js +++ b/app/components/Tooltip.js @@ -1,6 +1,6 @@ import React from 'react' import PropTypes from 'prop-types' -import Hover from './Hover' +import useHover from '../hooks/useHover' const styles = { container: { @@ -25,15 +25,13 @@ const styles = { } export default function Tooltip ({ text, children }) { + const [hovering, attrs] = useHover() + return ( - - {(hovering) => ( -
- {hovering === true &&
{text}
} - {children} -
- )} -
+
+ {hovering === true &&
{text}
} + {children} +
) } diff --git a/app/contexts/theme.js b/app/contexts/theme.js index 7e4742b..9373368 100644 --- a/app/contexts/theme.js +++ b/app/contexts/theme.js @@ -1,6 +1,7 @@ import React from 'react' -const { Consumer, Provider } = React.createContext() +const ThemeContext = React.createContext() -export const ThemeConsumer = Consumer -export const ThemeProvider = Provider \ No newline at end of file +export default ThemeContext +export const ThemeConsumer = ThemeContext.Consumer +export const ThemeProvider = ThemeContext.Provider diff --git a/app/hooks/useHover.js b/app/hooks/useHover.js new file mode 100644 index 0000000..9a6807f --- /dev/null +++ b/app/hooks/useHover.js @@ -0,0 +1,13 @@ +import React from 'react' + +export default function useHover () { + const [hovering, setHovering] = React.useState(false) + + const onMouseOver = () => setHovering(true) + const onMouseOut = () => setHovering(false) + + return [hovering, { + onMouseOut, + onMouseOver + }] +} \ No newline at end of file diff --git a/app/index.js b/app/index.js index 33a7308..09766cd 100644 --- a/app/index.js +++ b/app/index.js @@ -10,37 +10,30 @@ const Popular = React.lazy(() => import('./components/Popular')) const Battle = React.lazy(() => import('./components/Battle')) const Results = React.lazy(() => import('./components/Results')) -class App extends React.Component { - state = { - theme: 'light', - toggleTheme: () => { - this.setState(({ theme }) => ({ - theme: theme === 'light' ? 'dark' : 'light' - })) - } - } - render() { - return ( - - -
-
-
+ return ( + + +
+
+
- - - ) - } +
+
+
+ ) } ReactDOM.render(