From eddb47e8b156d9fb312d7c3e5136ce25a43f826e Mon Sep 17 00:00:00 2001 From: Melvin Valster Date: Sat, 20 Jul 2019 16:11:28 +0200 Subject: [PATCH] Prevent unnecessary re-renders --- src/App.scss | 49 +++++++++++++++---- src/components/Calculator.tsx | 86 +++++++++++++++++++--------------- src/components/ClassPicker.tsx | 12 +++-- src/components/IndexRoute.tsx | 44 ++++++++--------- src/components/TalentSlot.scss | 22 +++++---- src/components/TalentSlot.tsx | 18 ++++--- src/components/TalentTree.tsx | 50 +++++++++++--------- src/index.tsx | 2 +- src/lib/tree.ts | 2 +- 9 files changed, 174 insertions(+), 111 deletions(-) diff --git a/src/App.scss b/src/App.scss index 387eb44..d48cb51 100644 --- a/src/App.scss +++ b/src/App.scss @@ -1,21 +1,54 @@ -.App { - text-align: center; +body { + background-color: #111; } .calculator { + + &__points { + color: white; + text-align: center; + } } .trees { display: flex; + justify-content: center; } .tree { position: relative; - min-width: 300px; - height: 600px; - border: 1px solid black; - background-size: cover; - background-position: center; - + min-width: 300px; color: white; + margin-right: 1em; + + &:last-child { + margin-right: 0; + } + + &__header { + text-align: center; + } + + &__body { + position: relative; + height: 520px; + border: 1px solid black; + background-size: cover; + background-position: center; + } } + +.class-picker { + display: flex; + + &__class { + margin-right: 2em; + } + + // TODO: Make BEM + a { + &.active { + font-weight: bold; + } + } +} \ No newline at end of file diff --git a/src/components/Calculator.tsx b/src/components/Calculator.tsx index c171615..cdef45f 100644 --- a/src/components/Calculator.tsx +++ b/src/components/Calculator.tsx @@ -1,4 +1,4 @@ -import React, { useState, useCallback, useEffect } from 'react' +import React from 'react' import { Map } from 'immutable' import { TalentTree } from './TalentTree' import { @@ -12,48 +12,56 @@ interface Props { selectedClass: string } -const initMap = Map() +const EMPTY_TALENTS = Map() -// TODO: Wrap in "IndexRoute" or something similar to take care of the url params -// Calculator doesn't need to know about URL params +export class Calculator extends React.PureComponent { + static whyDidYouRender = true -export const Calculator: React.FC = ({ selectedClass }) => { - const [knownTalents, setKnownTalents] = useState(initMap) + state = { + knownTalents: EMPTY_TALENTS + } - const handleTalentPress = useCallback((specId: number, talentId: number, modifier: 1 | -1) => { + componentDidUpdate(prevProps: Props) { + if (prevProps.selectedClass !== this.props.selectedClass) { + this.setState({ + knownTalents: EMPTY_TALENTS + }) + } + } + + handleTalentPress = (specId: number, talentId: number, modifier: 1 | -1) => { const talent = talentsBySpec[specId][talentId] - setKnownTalents(knownTalents => - modifyTalentPoint(knownTalents, talent, modifier) + this.setState({ + knownTalents: modifyTalentPoint(this.state.knownTalents, talent, modifier) + }) + } + + render() { + const { selectedClass } = this.props + const { knownTalents } = this.state + + const classData = classByName[selectedClass] + const availablePoints = calcAvailablePoints(knownTalents) + + return ( +
+
+ {classData.specs.map((specId) => ( + + ))} +
+ +
+ Points: {availablePoints} +
+
) - }, []) - - // Reset known talents when switching class - useEffect(() => { - setKnownTalents(initMap) - }, [selectedClass]) - - const classData = classByName[selectedClass] - const availablePoints = calcAvailablePoints(knownTalents) - - return ( -
-
- {classData.specs.map((specId) => ( - - ))} -
- -
- Points: {availablePoints} -
-
- ) + } } -(Calculator as any).whyDidYouRender = true \ No newline at end of file diff --git a/src/components/ClassPicker.tsx b/src/components/ClassPicker.tsx index 41aa782..374e451 100644 --- a/src/components/ClassPicker.tsx +++ b/src/components/ClassPicker.tsx @@ -1,12 +1,18 @@ import React from 'react' +import { NavLink } from 'react-router-dom' +import { classByName } from '../data/classes' interface Props { } export const ClassPicker: React.FC = () => { return ( -
- Pick your class -
+
    + {Object.values(classByName).map((c) => +
  • + {c.name} +
  • + )} +
) } \ No newline at end of file diff --git a/src/components/IndexRoute.tsx b/src/components/IndexRoute.tsx index db31c18..e591825 100644 --- a/src/components/IndexRoute.tsx +++ b/src/components/IndexRoute.tsx @@ -1,6 +1,6 @@ import React from 'react' import { Calculator } from './Calculator' -import { Link } from 'react-router-dom'; +import { ClassPicker } from './ClassPicker' interface Props { pointString?: string // e.g. 2305302300--001 @@ -8,28 +8,28 @@ interface Props { history: any } -const ClassPicker = () => { - return
    -
  • Warlock
  • -
  • Paladin
  • -
-} +export class IndexRoute extends React.PureComponent { + static whyDidYouRender = true -export const IndexRoute: React.FC = ({ match, history }) => { - const { selectedClass, pointString } = match.params + render() { + const { match, history } = this.props + const { selectedClass, pointString } = match.params - if (!selectedClass) { - history.replace('/warlock') - return null + if (!selectedClass) { + history.replace('/warlock') + return null + } + + return ( +
+ + + {selectedClass && + + } +
+ ) } - - return ( -
- - - {selectedClass && - - } -
- ) } \ No newline at end of file diff --git a/src/components/TalentSlot.scss b/src/components/TalentSlot.scss index 773f106..7cd8cb7 100644 --- a/src/components/TalentSlot.scss +++ b/src/components/TalentSlot.scss @@ -1,3 +1,8 @@ +$row-distance: 70px; + +@mixin rowStyle($rowNr) { + top: 30px + (($rowNr) * 70px); +} .talent { position: absolute; @@ -33,18 +38,15 @@ } - $row-distance: 70px; + @for $i from 0 through 6 { + &[data-row="#{$i}"] { + @include rowStyle($i) + } + } - &[data-row="0"] { top: $row-distance; } - &[data-row="1"] { top: $row-distance * 2; } - &[data-row="2"] { top: $row-distance * 3; } - &[data-row="3"] { top: $row-distance * 4; } - &[data-row="4"] { top: $row-distance * 5; } - &[data-row="5"] { top: $row-distance * 6; } - &[data-row="6"] { top: $row-distance * 7; } - $col-distance-offset: 30px; - $col-distance: 50px; + $col-distance-offset: 44px; + $col-distance: 56px; &[data-col="0"] { left: $col-distance-offset; } &[data-col="1"] { left: $col-distance-offset + ($col-distance * 1); } diff --git a/src/components/TalentSlot.tsx b/src/components/TalentSlot.tsx index 5e5a40d..152f3c2 100644 --- a/src/components/TalentSlot.tsx +++ b/src/components/TalentSlot.tsx @@ -2,7 +2,6 @@ import './TalentSlot.scss' import React, { FC } from 'react' import { Icon } from './Icon' import classNames from 'classnames' -import { spells } from '../data/spells' import { Map } from 'immutable'; import { getPointsInSpec, calcMeetsRequirements } from '../lib/tree'; @@ -14,8 +13,13 @@ interface Props { knownTalents: Map /** Disabled override */ disabled?: boolean - onClick?: (e: any) => void - onRightClick?: (e: any) => void + onClick?: (talentId: number) => void + onRightClick?: (talentId: number) => void +} + +const defaultProps: Partial = { + onClick: () => undefined, + onRightClick: () => undefined } const isAvailable = (talent: TalentData, specId: number, knownTalents: Map): boolean => { @@ -39,7 +43,7 @@ export const TalentSlot: FC = (props) => { }) const handleContextMenu = (e) => { - if (props.onRightClick) props.onRightClick(e) + if (props.onRightClick) props.onRightClick(talent.id) e.preventDefault() return false } @@ -50,7 +54,7 @@ export const TalentSlot: FC = (props) => { title={talent.ranks[0].toString()} data-row={talent.row} data-col={talent.col} - onClick={!disabled ? props.onClick : () => {}} + onClick={!disabled ? () => props.onClick(talent.id) : () => {}} onContextMenu={handleContextMenu} > @@ -62,4 +66,6 @@ export const TalentSlot: FC = (props) => { ) } -(TalentSlot as any).whyDidYouRender = true \ No newline at end of file +TalentSlot.defaultProps = defaultProps + +// ;(TalentSlot as any).whyDidYouRender = true \ No newline at end of file diff --git a/src/components/TalentTree.tsx b/src/components/TalentTree.tsx index c2e007d..00dedb7 100644 --- a/src/components/TalentTree.tsx +++ b/src/components/TalentTree.tsx @@ -1,4 +1,4 @@ -import React, { MouseEvent, useCallback } from 'react' +import React, { useCallback } from 'react' import { Map } from 'immutable' import { TalentSlot } from './TalentSlot'; import { getPointsInSpec } from '../lib/tree'; @@ -14,32 +14,40 @@ interface Props { export const TalentTree: React.FC = ({ specId, knownTalents, availablePoints, onTalentPress }) => { const talents = Object.values(talentsBySpec[specId]) - const handleTalentPress = useCallback((talentId: number, modifier: 1 | -1) => { - return (e: MouseEvent) => { - onTalentPress(specId, talentId, modifier) - } - }, [specId, onTalentPress]) + const handleClick = useCallback( + (talentId) => onTalentPress(specId, talentId, 1), + [specId, onTalentPress] + ) + const handleRightClick = useCallback( + (talentId) => onTalentPress(specId, talentId, -1), + [specId, onTalentPress] + ) - const style = { + const bodyStyle = { backgroundImage: `url("https://wow.zamimg.com/images/wow/talents/backgrounds/classic/${specId}.jpg")` } return ( -
-

{specNames[specId]} ({getPointsInSpec(specId, knownTalents)})

- {talents.map((talent, index) => - - )} +
+
+

{specNames[specId]} ({getPointsInSpec(specId, knownTalents)})

+
+ +
+ {talents.map((talent) => + + )} +
) } -(TalentTree as any).whyDidYouRender = true \ No newline at end of file +;(TalentTree as any).whyDidYouRender = true \ No newline at end of file diff --git a/src/index.tsx b/src/index.tsx index 272e93e..81c2f16 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -5,7 +5,7 @@ import App from './App'; import * as serviceWorker from './serviceWorker'; if (process.env.NODE_ENV !== 'production') { - const whyDidYouRender = require('@welldone-software/why-did-you-render') + const whyDidYouRender = require('@welldone-software/why-did-you-render/dist/no-classes-transpile/umd/whyDidYouRender.min.js') whyDidYouRender(React) } diff --git a/src/lib/tree.ts b/src/lib/tree.ts index e520f1e..1f247de 100644 --- a/src/lib/tree.ts +++ b/src/lib/tree.ts @@ -95,7 +95,7 @@ export function parsePointString(str: string): List> { const list: Array = [] const trees = str.split('-') - trees.map((stringForTree, index) => { + trees.forEach((stringForTree, index) => { const points = stringForTree.split('').map(a => parseInt(a, 10)) list[index] = points })