Skip to content

Commit f730084

Browse files
author
Antoine Lelaisant
committed
feat: step 4 - introduced redux and react-redux
1 parent 31a0c7b commit f730084

File tree

13 files changed

+142
-77
lines changed

13 files changed

+142
-77
lines changed

jsconfig.json

+1
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@
88
"paths": {
99
"assets": ["./assets/*"],
1010
"components": ["./components/*"]
11+
"modules": ["./modules/*"]
1112
}
1213
}

package.json

+2
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@
99
"prop-types": "^15.7.2",
1010
"react": "^17.0.1",
1111
"react-dom": "^17.0.1",
12+
"react-redux": "^7.2.2",
1213
"react-router-dom": "^5.2.0",
1314
"react-scripts": "4.0.2",
15+
"redux": "^4.0.5",
1416
"web-vitals": "^1.0.1"
1517
},
1618
"scripts": {

src/components/App.js

+7-3
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,29 @@ import {
44
Switch,
55
Route,
66
} from 'react-router-dom'
7+
import { Provider } from 'react-redux'
78
import CssBaseline from '@material-ui/core/CssBaseline'
89
import './App.css'
910
import { Game } from './Game'
1011
import { Home } from './Home'
12+
import configureStore from '../configureStore'
13+
14+
const store = configureStore()
1115

1216
export const App = () => {
1317
return (
14-
<>
18+
<Provider store={store}>
1519
<CssBaseline />
1620
<Router>
1721
<Switch>
1822
<Route exact path="/">
1923
<Home />
2024
</Route>
21-
<Route path="/">
25+
<Route path="/game">
2226
<Game />
2327
</Route>
2428
</Switch>
2529
</Router>
26-
</>
30+
</Provider>
2731
)
2832
}

src/components/Game/Game.js

+10-40
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,40 @@
1-
import React, { useState, useEffect } from 'react'
2-
import './Game.css'
1+
import React, { useEffect } from 'react'
2+
import { useDispatch } from 'react-redux'
33
import Paper from '@material-ui/core/Paper'
4+
import './Game.css'
5+
import { loop } from 'modules/game'
46
import { Navbar } from 'components/layout/Navbar'
57
import { Gitcoin } from 'components/Gitcoin'
68
import { Score } from 'components/Score'
79
import { Store } from 'components/Store/Store'
810
import { Skills } from 'components/Skills'
9-
import items from '../../items'
1011

1112
export const Game = () => {
12-
const [lines, setLines] = useState(0)
13-
const [linesPerMillisecond, setLinesPerMillisecond] = useState(0)
14-
const [ownedItems, setOwnedItems] = useState({})
13+
const dispatch = useDispatch()
1514

1615
useEffect(() => {
1716
const interval = setInterval(() => {
18-
setLines(lines + linesPerMillisecond)
17+
dispatch(loop())
1918
}, 100)
2019

2120
return () => clearInterval(interval)
2221
})
2322

24-
useEffect(() => {
25-
let count = 0
26-
27-
Object.keys(ownedItems).forEach(name => {
28-
const item = items.find(element => element.name === name)
29-
count += item.multiplier * ownedItems[name]
30-
})
31-
32-
setLinesPerMillisecond(count)
33-
}, [ownedItems])
34-
35-
const handleClick = () => {
36-
setLines(lines + 1)
37-
}
38-
39-
const handleBuy = item => {
40-
setLines(lines - item.price)
41-
setOwnedItems({
42-
...ownedItems,
43-
[item.name]: (ownedItems[item.name] || 0) + 1
44-
})
45-
}
46-
4723
return (
4824
<>
4925
<Navbar />
5026
<main className="game">
5127
<Paper elevation={3} className="left">
52-
<Score
53-
lines={lines}
54-
linesPerSecond={parseInt(linesPerMillisecond * 10)}
55-
/>
56-
<Gitcoin onClick={handleClick} />
28+
<Score />
29+
<Gitcoin />
5730
</Paper>
5831

5932
<Paper elevation={3} className="center">
60-
<Skills skills={ownedItems} />
33+
<Skills />
6134
</Paper>
6235

6336
<Paper elevation={3} className="right">
64-
<Store
65-
lines={lines}
66-
onBuy={handleBuy}
67-
/>
37+
<Store />
6838
</Paper>
6939
</main>
7040
</>

src/components/Gitcoin/Gitcoin.js

+8-7
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
11
import React from 'react'
2-
import PropTypes from 'prop-types'
2+
import { useDispatch } from 'react-redux'
33
import './Gitcoin.css'
4+
import { click } from 'modules/game'
45
import githubIcon from 'assets/github.svg'
56

6-
export const Gitcoin = ({ onClick }) => {
7+
export const Gitcoin = () => {
8+
const disptach = useDispatch()
9+
10+
const handleClick = () => disptach(click())
11+
712
return (
813
<button
914
className="gitcoin"
10-
onClick={onClick}
15+
onClick={handleClick}
1116
>
1217
<img src={githubIcon} alt="Gitcoin" />
1318
</button>
1419
)
1520
}
16-
17-
Gitcoin.propTypes = {
18-
onClick: PropTypes.func.isRequired,
19-
}

src/components/Home.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export const Home = () => {
3636
<div className={classes.heroButtons}>
3737
<Grid container spacing={2} justify="center">
3838
<Grid item>
39-
<Link to="/gitclicker">
39+
<Link to="/game">
4040
<Button variant="contained" color="primary">
4141
Play
4242
</Button>

src/components/Score.js

+10-10
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import React from 'react'
2-
import PropTypes from 'prop-types'
2+
import { useSelector } from 'react-redux'
33

4-
export const Score = ({ lines, linesPerSecond }) => (
5-
<>
6-
<h3 style={{fontFamily: 'Orbitron'}}>{parseInt(lines)} lines</h3>
7-
<small>per second: {linesPerSecond}</small>
8-
</>
9-
)
4+
export const Score = () => {
5+
const lines = useSelector(state => parseInt(state.game.lines))
6+
const linesPerSecond = useSelector(state => parseInt(state.game.linesPerMillisecond * 10))
107

11-
Score.propTypes = {
12-
lines: PropTypes.number.isRequired,
13-
linesPerSecond: PropTypes.number.isRequired,
8+
return (
9+
<>
10+
<h3 style={{fontFamily: 'Orbitron'}}>{lines} lines</h3>
11+
<small>per second: {linesPerSecond}</small>
12+
</>
13+
)
1414
}

src/components/Skills/Skills.js

+4-6
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import React from 'react'
2+
import { useSelector } from 'react-redux'
23
import './Skills.css'
3-
import PropTypes from 'prop-types'
44
import Typography from '@material-ui/core/Typography'
55
import { Section } from './Section'
66

7-
export const Skills = ({ skills }) => {
7+
export const Skills = () => {
8+
const skills = useSelector(state => state.game.skills)
9+
810
return (
911
<>
1012
<Typography variant="h5">Skills</Typography>
@@ -20,7 +22,3 @@ export const Skills = ({ skills }) => {
2022
</>
2123
)
2224
}
23-
24-
Skills.propTypes = {
25-
skills: PropTypes.objectOf(PropTypes.number).isRequired,
26-
}

src/components/Store/Store.js

+11-8
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,19 @@
11
import React from 'react'
2-
import PropTypes from 'prop-types'
2+
import { useSelector, useDispatch } from 'react-redux'
33
import Typography from '@material-ui/core/Typography'
44
import './Store.css'
5+
import { buyItem } from 'modules/game'
56
import { Item } from './Item'
67
import items from '../../items'
78

8-
export const Store = ({ lines, onBuy }) => {
9+
export const Store = () => {
10+
const dispatch = useDispatch()
11+
const lines = useSelector(state => state.game.lines)
12+
13+
const handleBuy = item => {
14+
dispatch(buyItem(item))
15+
}
16+
917
return (
1018
<div className="store">
1119
<Typography variant="h5">Store</Typography>
@@ -15,14 +23,9 @@ export const Store = ({ lines, onBuy }) => {
1523
key={key}
1624
item={item}
1725
lines={lines}
18-
onBuy={onBuy}
26+
onBuy={handleBuy}
1927
/>
2028
)}
2129
</div>
2230
)
2331
}
24-
25-
Store.propTypes = {
26-
lines: PropTypes.number.isRequired,
27-
onBuy: PropTypes.func.isRequired,
28-
}

src/configureStore.js

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { createStore } from 'redux'
2+
import { rootReducer } from './modules'
3+
4+
export default () => {
5+
const store = createStore(rootReducer)
6+
7+
return store
8+
}

src/modules/game.js

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Actions
2+
const CLICK = 'game::CLICK'
3+
const BUY_ITEM = 'game::BUY_ITEM'
4+
const LOOP = 'game::LOOP'
5+
6+
// Action creators
7+
export const click = () => ({
8+
type: CLICK,
9+
})
10+
11+
export const buyItem = item => ({
12+
type: BUY_ITEM,
13+
item
14+
})
15+
16+
export const loop = () => ({
17+
type: LOOP
18+
})
19+
20+
const INITIAL_STATE = {
21+
lines: 0,
22+
linesPerMillisecond: 0,
23+
skills: []
24+
}
25+
26+
export const reducer = (state = INITIAL_STATE, action) => {
27+
const { type } = action
28+
29+
if (type === LOOP) {
30+
return { ...state, lines: state.lines + state.linesPerMillisecond }
31+
}
32+
33+
if (type === CLICK) {
34+
return { ...state, lines: state.lines + 1 }
35+
}
36+
37+
if (type === BUY_ITEM) {
38+
const { item: { name, price, multiplier }} = action
39+
const { skills, lines, linesPerMillisecond } = state
40+
41+
return {
42+
...state,
43+
lines: lines - price,
44+
linesPerMillisecond: linesPerMillisecond + multiplier,
45+
skills: {
46+
...skills,
47+
[name]: (skills[name] || 0) + 1
48+
}
49+
}
50+
}
51+
52+
return state
53+
}

src/modules/index.js

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { combineReducers } from 'redux'
2+
import { reducer as game } from './game'
3+
4+
export const rootReducer = combineReducers({
5+
game
6+
})

yarn.lock

+21-2
Original file line numberDiff line numberDiff line change
@@ -9773,7 +9773,7 @@ react-error-overlay@^6.0.9:
97739773
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.9.tgz#3c743010c9359608c375ecd6bc76f35d93995b0a"
97749774
integrity sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew==
97759775

9776-
react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1:
9776+
react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1:
97779777
version "16.13.1"
97789778
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
97799779
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
@@ -9783,6 +9783,17 @@ react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1:
97839783
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz#5b3531bd76a645a4c9fb6e693ed36419e3301339"
97849784
integrity sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==
97859785

9786+
react-redux@^7.2.2:
9787+
version "7.2.2"
9788+
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.2.tgz#03862e803a30b6b9ef8582dadcc810947f74b736"
9789+
integrity sha512-8+CQ1EvIVFkYL/vu6Olo7JFLWop1qRUeb46sGtIMDCSpgwPQq8fPLpirIB0iTqFe9XYEFPHssdX8/UwN6pAkEA==
9790+
dependencies:
9791+
"@babel/runtime" "^7.12.1"
9792+
hoist-non-react-statics "^3.3.2"
9793+
loose-envify "^1.4.0"
9794+
prop-types "^15.7.2"
9795+
react-is "^16.13.1"
9796+
97869797
react-refresh@^0.8.3:
97879798
version "0.8.3"
97889799
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f"
@@ -9990,6 +10001,14 @@ redent@^3.0.0:
999010001
indent-string "^4.0.0"
999110002
strip-indent "^3.0.0"
999210003

10004+
redux@^4.0.5:
10005+
version "4.0.5"
10006+
resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.5.tgz#4db5de5816e17891de8a80c424232d06f051d93f"
10007+
integrity sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w==
10008+
dependencies:
10009+
loose-envify "^1.4.0"
10010+
symbol-observable "^1.2.0"
10011+
999310012
regenerate-unicode-properties@^8.2.0:
999410013
version "8.2.0"
999510014
resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec"
@@ -11208,7 +11227,7 @@ svgo@^1.0.0, svgo@^1.2.2:
1120811227
unquote "~1.1.1"
1120911228
util.promisify "~1.0.0"
1121011229

11211-
11230+
[email protected], symbol-observable@^1.2.0:
1121211231
version "1.2.0"
1121311232
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804"
1121411233
integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==

0 commit comments

Comments
 (0)