Prevent unnecessary re-renders
This commit is contained in:
+38
-5
@@ -1,21 +1,54 @@
|
|||||||
.App {
|
body {
|
||||||
text-align: center;
|
background-color: #111;
|
||||||
}
|
}
|
||||||
|
|
||||||
.calculator {
|
.calculator {
|
||||||
|
|
||||||
|
&__points {
|
||||||
|
color: white;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.trees {
|
.trees {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tree {
|
.tree {
|
||||||
position: relative;
|
position: relative;
|
||||||
min-width: 300px;
|
min-width: 300px;
|
||||||
height: 600px;
|
color: white;
|
||||||
|
margin-right: 1em;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__header {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__body {
|
||||||
|
position: relative;
|
||||||
|
height: 520px;
|
||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
background-position: center;
|
background-position: center;
|
||||||
|
}
|
||||||
color: white;
|
}
|
||||||
|
|
||||||
|
.class-picker {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
&__class {
|
||||||
|
margin-right: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Make BEM
|
||||||
|
a {
|
||||||
|
&.active {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useCallback, useEffect } from 'react'
|
import React from 'react'
|
||||||
import { Map } from 'immutable'
|
import { Map } from 'immutable'
|
||||||
import { TalentTree } from './TalentTree'
|
import { TalentTree } from './TalentTree'
|
||||||
import {
|
import {
|
||||||
@@ -12,25 +12,33 @@ interface Props {
|
|||||||
selectedClass: string
|
selectedClass: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const initMap = Map<number, number>()
|
const EMPTY_TALENTS = Map<number, number>()
|
||||||
|
|
||||||
// TODO: Wrap in "IndexRoute" or something similar to take care of the url params
|
export class Calculator extends React.PureComponent<Props> {
|
||||||
// Calculator doesn't need to know about URL params
|
static whyDidYouRender = true
|
||||||
|
|
||||||
export const Calculator: React.FC<Props> = ({ selectedClass }) => {
|
state = {
|
||||||
const [knownTalents, setKnownTalents] = useState(initMap)
|
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]
|
const talent = talentsBySpec[specId][talentId]
|
||||||
setKnownTalents(knownTalents =>
|
this.setState({
|
||||||
modifyTalentPoint(knownTalents, talent, modifier)
|
knownTalents: modifyTalentPoint(this.state.knownTalents, talent, modifier)
|
||||||
)
|
})
|
||||||
}, [])
|
}
|
||||||
|
|
||||||
// Reset known talents when switching class
|
render() {
|
||||||
useEffect(() => {
|
const { selectedClass } = this.props
|
||||||
setKnownTalents(initMap)
|
const { knownTalents } = this.state
|
||||||
}, [selectedClass])
|
|
||||||
|
|
||||||
const classData = classByName[selectedClass]
|
const classData = classByName[selectedClass]
|
||||||
const availablePoints = calcAvailablePoints(knownTalents)
|
const availablePoints = calcAvailablePoints(knownTalents)
|
||||||
@@ -44,7 +52,7 @@ export const Calculator: React.FC<Props> = ({ selectedClass }) => {
|
|||||||
specId={specId}
|
specId={specId}
|
||||||
availablePoints={availablePoints}
|
availablePoints={availablePoints}
|
||||||
knownTalents={knownTalents}
|
knownTalents={knownTalents}
|
||||||
onTalentPress={handleTalentPress}
|
onTalentPress={this.handleTalentPress}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -55,5 +63,5 @@ export const Calculator: React.FC<Props> = ({ selectedClass }) => {
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
(Calculator as any).whyDidYouRender = true
|
|
||||||
@@ -1,12 +1,18 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import { NavLink } from 'react-router-dom'
|
||||||
|
import { classByName } from '../data/classes'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ClassPicker: React.FC<Props> = () => {
|
export const ClassPicker: React.FC<Props> = () => {
|
||||||
return (
|
return (
|
||||||
<div className="">
|
<ul className="class-picker">
|
||||||
Pick your class
|
{Object.values(classByName).map((c) =>
|
||||||
</div>
|
<li key={c.id} className="class-picker__class">
|
||||||
|
<NavLink to={`/${c.name.toLowerCase()}`}>{c.name}</NavLink>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Calculator } from './Calculator'
|
import { Calculator } from './Calculator'
|
||||||
import { Link } from 'react-router-dom';
|
import { ClassPicker } from './ClassPicker'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
pointString?: string // e.g. 2305302300--001
|
pointString?: string // e.g. 2305302300--001
|
||||||
@@ -8,14 +8,11 @@ interface Props {
|
|||||||
history: any
|
history: any
|
||||||
}
|
}
|
||||||
|
|
||||||
const ClassPicker = () => {
|
export class IndexRoute extends React.PureComponent<Props> {
|
||||||
return <ul>
|
static whyDidYouRender = true
|
||||||
<li><Link to="/warlock">Warlock</Link></li>
|
|
||||||
<li><Link to="/paladin">Paladin</Link></li>
|
|
||||||
</ul>
|
|
||||||
}
|
|
||||||
|
|
||||||
export const IndexRoute: React.FC<Props> = ({ match, history }) => {
|
render() {
|
||||||
|
const { match, history } = this.props
|
||||||
const { selectedClass, pointString } = match.params
|
const { selectedClass, pointString } = match.params
|
||||||
|
|
||||||
if (!selectedClass) {
|
if (!selectedClass) {
|
||||||
@@ -28,8 +25,11 @@ export const IndexRoute: React.FC<Props> = ({ match, history }) => {
|
|||||||
<ClassPicker />
|
<ClassPicker />
|
||||||
|
|
||||||
{selectedClass &&
|
{selectedClass &&
|
||||||
<Calculator selectedClass={selectedClass} />
|
<Calculator
|
||||||
|
selectedClass={selectedClass}
|
||||||
|
/>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,8 @@
|
|||||||
|
$row-distance: 70px;
|
||||||
|
|
||||||
|
@mixin rowStyle($rowNr) {
|
||||||
|
top: 30px + (($rowNr) * 70px);
|
||||||
|
}
|
||||||
|
|
||||||
.talent {
|
.talent {
|
||||||
position: absolute;
|
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-offset: 44px;
|
||||||
$col-distance: 50px;
|
$col-distance: 56px;
|
||||||
|
|
||||||
&[data-col="0"] { left: $col-distance-offset; }
|
&[data-col="0"] { left: $col-distance-offset; }
|
||||||
&[data-col="1"] { left: $col-distance-offset + ($col-distance * 1); }
|
&[data-col="1"] { left: $col-distance-offset + ($col-distance * 1); }
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import './TalentSlot.scss'
|
|||||||
import React, { FC } from 'react'
|
import React, { FC } from 'react'
|
||||||
import { Icon } from './Icon'
|
import { Icon } from './Icon'
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
import { spells } from '../data/spells'
|
|
||||||
import { Map } from 'immutable';
|
import { Map } from 'immutable';
|
||||||
import { getPointsInSpec, calcMeetsRequirements } from '../lib/tree';
|
import { getPointsInSpec, calcMeetsRequirements } from '../lib/tree';
|
||||||
|
|
||||||
@@ -14,8 +13,13 @@ interface Props {
|
|||||||
knownTalents: Map<number, number>
|
knownTalents: Map<number, number>
|
||||||
/** Disabled override */
|
/** Disabled override */
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
onClick?: (e: any) => void
|
onClick?: (talentId: number) => void
|
||||||
onRightClick?: (e: any) => void
|
onRightClick?: (talentId: number) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultProps: Partial<Props> = {
|
||||||
|
onClick: () => undefined,
|
||||||
|
onRightClick: () => undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
const isAvailable = (talent: TalentData, specId: number, knownTalents: Map<number, number>): boolean => {
|
const isAvailable = (talent: TalentData, specId: number, knownTalents: Map<number, number>): boolean => {
|
||||||
@@ -39,7 +43,7 @@ export const TalentSlot: FC<Props> = (props) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const handleContextMenu = (e) => {
|
const handleContextMenu = (e) => {
|
||||||
if (props.onRightClick) props.onRightClick(e)
|
if (props.onRightClick) props.onRightClick(talent.id)
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -50,7 +54,7 @@ export const TalentSlot: FC<Props> = (props) => {
|
|||||||
title={talent.ranks[0].toString()}
|
title={talent.ranks[0].toString()}
|
||||||
data-row={talent.row}
|
data-row={talent.row}
|
||||||
data-col={talent.col}
|
data-col={talent.col}
|
||||||
onClick={!disabled ? props.onClick : () => {}}
|
onClick={!disabled ? () => props.onClick(talent.id) : () => {}}
|
||||||
onContextMenu={handleContextMenu}
|
onContextMenu={handleContextMenu}
|
||||||
>
|
>
|
||||||
<Icon name={talent.icon} />
|
<Icon name={talent.icon} />
|
||||||
@@ -62,4 +66,6 @@ export const TalentSlot: FC<Props> = (props) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
(TalentSlot as any).whyDidYouRender = true
|
TalentSlot.defaultProps = defaultProps
|
||||||
|
|
||||||
|
// ;(TalentSlot as any).whyDidYouRender = true
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { MouseEvent, useCallback } from 'react'
|
import React, { useCallback } from 'react'
|
||||||
import { Map } from 'immutable'
|
import { Map } from 'immutable'
|
||||||
import { TalentSlot } from './TalentSlot';
|
import { TalentSlot } from './TalentSlot';
|
||||||
import { getPointsInSpec } from '../lib/tree';
|
import { getPointsInSpec } from '../lib/tree';
|
||||||
@@ -14,32 +14,40 @@ interface Props {
|
|||||||
export const TalentTree: React.FC<Props> = ({ specId, knownTalents, availablePoints, onTalentPress }) => {
|
export const TalentTree: React.FC<Props> = ({ specId, knownTalents, availablePoints, onTalentPress }) => {
|
||||||
const talents = Object.values(talentsBySpec[specId])
|
const talents = Object.values(talentsBySpec[specId])
|
||||||
|
|
||||||
const handleTalentPress = useCallback((talentId: number, modifier: 1 | -1) => {
|
const handleClick = useCallback(
|
||||||
return (e: MouseEvent) => {
|
(talentId) => onTalentPress(specId, talentId, 1),
|
||||||
onTalentPress(specId, talentId, modifier)
|
[specId, onTalentPress]
|
||||||
}
|
)
|
||||||
}, [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")`
|
backgroundImage: `url("https://wow.zamimg.com/images/wow/talents/backgrounds/classic/${specId}.jpg")`
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="tree" style={style}>
|
<div className="tree">
|
||||||
<h2>{specNames[specId]} ({getPointsInSpec(specId, knownTalents)})</h2>
|
<div className="tree__header">
|
||||||
{talents.map((talent, index) =>
|
<h3>{specNames[specId]} ({getPointsInSpec(specId, knownTalents)})</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="tree__body" style={bodyStyle}>
|
||||||
|
{talents.map((talent) =>
|
||||||
<TalentSlot
|
<TalentSlot
|
||||||
key={talent.id}
|
key={talent.id}
|
||||||
specId={specId}
|
specId={specId}
|
||||||
talent={talent}
|
talent={talent}
|
||||||
availablePoints={availablePoints}
|
availablePoints={availablePoints}
|
||||||
knownTalents={knownTalents}
|
knownTalents={knownTalents}
|
||||||
onClick={handleTalentPress(talent.id, 1)}
|
onClick={handleClick}
|
||||||
onRightClick={handleTalentPress(talent.id, -1)}
|
onRightClick={handleRightClick}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
(TalentTree as any).whyDidYouRender = true
|
;(TalentTree as any).whyDidYouRender = true
|
||||||
+1
-1
@@ -5,7 +5,7 @@ import App from './App';
|
|||||||
import * as serviceWorker from './serviceWorker';
|
import * as serviceWorker from './serviceWorker';
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
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)
|
whyDidYouRender(React)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -95,7 +95,7 @@ export function parsePointString(str: string): List<List<number>> {
|
|||||||
const list: Array<number[]> = []
|
const list: Array<number[]> = []
|
||||||
const trees = str.split('-')
|
const trees = str.split('-')
|
||||||
|
|
||||||
trees.map((stringForTree, index) => {
|
trees.forEach((stringForTree, index) => {
|
||||||
const points = stringForTree.split('').map(a => parseInt(a, 10))
|
const points = stringForTree.split('').map(a => parseInt(a, 10))
|
||||||
list[index] = points
|
list[index] = points
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user