Prevent unnecessary re-renders
This commit is contained in:
@@ -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<number, number>()
|
||||
const EMPTY_TALENTS = Map<number, number>()
|
||||
|
||||
// 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<Props> {
|
||||
static whyDidYouRender = true
|
||||
|
||||
export const Calculator: React.FC<Props> = ({ 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 (
|
||||
<div className="calculator">
|
||||
<div className="trees">
|
||||
{classData.specs.map((specId) => (
|
||||
<TalentTree
|
||||
key={specId}
|
||||
specId={specId}
|
||||
availablePoints={availablePoints}
|
||||
knownTalents={knownTalents}
|
||||
onTalentPress={this.handleTalentPress}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="calculator__points">
|
||||
Points: {availablePoints}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [])
|
||||
|
||||
// Reset known talents when switching class
|
||||
useEffect(() => {
|
||||
setKnownTalents(initMap)
|
||||
}, [selectedClass])
|
||||
|
||||
const classData = classByName[selectedClass]
|
||||
const availablePoints = calcAvailablePoints(knownTalents)
|
||||
|
||||
return (
|
||||
<div className="calculator">
|
||||
<div className="trees">
|
||||
{classData.specs.map((specId) => (
|
||||
<TalentTree
|
||||
key={specId}
|
||||
specId={specId}
|
||||
availablePoints={availablePoints}
|
||||
knownTalents={knownTalents}
|
||||
onTalentPress={handleTalentPress}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="calculator__points">
|
||||
Points: {availablePoints}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
(Calculator as any).whyDidYouRender = true
|
||||
@@ -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<Props> = () => {
|
||||
return (
|
||||
<div className="">
|
||||
Pick your class
|
||||
</div>
|
||||
<ul className="class-picker">
|
||||
{Object.values(classByName).map((c) =>
|
||||
<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 { 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 <ul>
|
||||
<li><Link to="/warlock">Warlock</Link></li>
|
||||
<li><Link to="/paladin">Paladin</Link></li>
|
||||
</ul>
|
||||
}
|
||||
export class IndexRoute extends React.PureComponent<Props> {
|
||||
static whyDidYouRender = true
|
||||
|
||||
export const IndexRoute: React.FC<Props> = ({ 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 (
|
||||
<div className="index">
|
||||
<ClassPicker />
|
||||
|
||||
{selectedClass &&
|
||||
<Calculator
|
||||
selectedClass={selectedClass}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="index">
|
||||
<ClassPicker />
|
||||
|
||||
{selectedClass &&
|
||||
<Calculator selectedClass={selectedClass} />
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -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); }
|
||||
|
||||
@@ -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<number, number>
|
||||
/** Disabled override */
|
||||
disabled?: boolean
|
||||
onClick?: (e: any) => void
|
||||
onRightClick?: (e: any) => void
|
||||
onClick?: (talentId: number) => void
|
||||
onRightClick?: (talentId: number) => void
|
||||
}
|
||||
|
||||
const defaultProps: Partial<Props> = {
|
||||
onClick: () => undefined,
|
||||
onRightClick: () => undefined
|
||||
}
|
||||
|
||||
const isAvailable = (talent: TalentData, specId: number, knownTalents: Map<number, number>): boolean => {
|
||||
@@ -39,7 +43,7 @@ export const TalentSlot: FC<Props> = (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> = (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}
|
||||
>
|
||||
<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 { TalentSlot } from './TalentSlot';
|
||||
import { getPointsInSpec } from '../lib/tree';
|
||||
@@ -14,32 +14,40 @@ interface Props {
|
||||
export const TalentTree: React.FC<Props> = ({ 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 (
|
||||
<div className="tree" style={style}>
|
||||
<h2>{specNames[specId]} ({getPointsInSpec(specId, knownTalents)})</h2>
|
||||
{talents.map((talent, index) =>
|
||||
<TalentSlot
|
||||
key={talent.id}
|
||||
specId={specId}
|
||||
talent={talent}
|
||||
availablePoints={availablePoints}
|
||||
knownTalents={knownTalents}
|
||||
onClick={handleTalentPress(talent.id, 1)}
|
||||
onRightClick={handleTalentPress(talent.id, -1)}
|
||||
/>
|
||||
)}
|
||||
<div className="tree">
|
||||
<div className="tree__header">
|
||||
<h3>{specNames[specId]} ({getPointsInSpec(specId, knownTalents)})</h3>
|
||||
</div>
|
||||
|
||||
<div className="tree__body" style={bodyStyle}>
|
||||
{talents.map((talent) =>
|
||||
<TalentSlot
|
||||
key={talent.id}
|
||||
specId={specId}
|
||||
talent={talent}
|
||||
availablePoints={availablePoints}
|
||||
knownTalents={knownTalents}
|
||||
onClick={handleClick}
|
||||
onRightClick={handleRightClick}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
(TalentTree as any).whyDidYouRender = true
|
||||
;(TalentTree as any).whyDidYouRender = true
|
||||
Reference in New Issue
Block a user