From a471cd0c3b2ba8210ea482daf0223e595393be53 Mon Sep 17 00:00:00 2001 From: andrico Date: Thu, 18 Jul 2019 00:35:29 +0100 Subject: [PATCH] split context in two --- src/components/CalculateNodeCount.tsx | 4 +- src/components/SkillNode.tsx | 10 +- src/components/SkillTree.tsx | 5 +- src/components/SkillTreeGroup.tsx | 4 +- src/components/SkillTreeSegment.tsx | 11 +- .../__tests__/CalculateNodeCount.test.tsx | 15 ++- src/components/__tests__/SkillTree.test.tsx | 9 +- .../{SkillContext.tsx => SkillAppContext.tsx} | 86 +++++++------- src/context/SkillTreeContext.tsx | 108 ++++++++++++++++++ src/index.tsx | 2 +- 10 files changed, 187 insertions(+), 67 deletions(-) rename src/context/{SkillContext.tsx => SkillAppContext.tsx} (63%) create mode 100644 src/context/SkillTreeContext.tsx diff --git a/src/components/CalculateNodeCount.tsx b/src/components/CalculateNodeCount.tsx index d284a75..fec634c 100644 --- a/src/components/CalculateNodeCount.tsx +++ b/src/components/CalculateNodeCount.tsx @@ -1,6 +1,6 @@ import { useContext, useEffect } from 'react'; import { Skill } from 'models'; -import SkillContext from '../context/SkillContext'; +import SkillTreeContext from '../context/SkillTreeContext'; interface Props { data: Skill[]; @@ -17,7 +17,7 @@ function calculateNodeCount(data: Skill[]): number { } function CalculateNodeCount({ data }: Props) { - const { addToSkillCount } = useContext(SkillContext); + const { addToSkillCount } = useContext(SkillTreeContext); useEffect(() => { const count = calculateNodeCount(data); diff --git a/src/components/SkillNode.tsx b/src/components/SkillNode.tsx index 12dbb3b..9b5d928 100644 --- a/src/components/SkillNode.tsx +++ b/src/components/SkillNode.tsx @@ -1,8 +1,8 @@ import * as React from 'react'; import classnames from 'classnames'; -import { throttle, Cancelable, isEmpty } from 'lodash'; +import { throttle, Cancelable } from 'lodash'; import Tippy from '@tippy.js/react'; -import SkillContext from '../context/SkillContext'; +import SkillTreeContext from '../context/SkillTreeContext'; import { LOCKED_STATE, UNLOCKED_STATE, SELECTED_STATE } from './constants'; import SkillTreeSegment from './SkillTreeSegment'; import TooltipContent from './ui/TooltipContent'; @@ -19,7 +19,7 @@ interface State { } class SkillNode extends React.Component { - static contextType = SkillContext; + static contextType = SkillTreeContext; private skillNodeRef: React.RefObject; private throttledResize: VoidFunction & Cancelable; private childWidth: number = 0; @@ -90,10 +90,6 @@ class SkillNode extends React.Component { this.calculateOverlayWidth(); window.addEventListener('resize', this.throttledResize); - - if (isEmpty(this.context.skills)) { - return this.updateState(UNLOCKED_STATE); - } } componentWillUnmount() { diff --git a/src/components/SkillTree.tsx b/src/components/SkillTree.tsx index 9c5afc7..f28cd22 100644 --- a/src/components/SkillTree.tsx +++ b/src/components/SkillTree.tsx @@ -4,6 +4,7 @@ import { Skill } from '../models'; import SkillTreeSegment from './SkillTreeSegment'; import HSeparator from './ui/HSeparator'; import CalculateTotalNodes from './CalculateNodeCount'; +import { SkillTreeProvider } from '../context/SkillTreeContext'; interface Props { data: Skill[]; @@ -31,7 +32,7 @@ function SkillTree({ data, title }: Props) { }, []); return ( - +

{title}

@@ -50,7 +51,7 @@ function SkillTree({ data, title }: Props) { })}
-
+ ); } diff --git a/src/components/SkillTreeGroup.tsx b/src/components/SkillTreeGroup.tsx index 25098d1..671bea7 100644 --- a/src/components/SkillTreeGroup.tsx +++ b/src/components/SkillTreeGroup.tsx @@ -1,5 +1,5 @@ import React, { useContext } from 'react'; -import SkillContext from '../context/SkillContext'; +import SkillAppContext from '../context/SkillAppContext'; export interface TreeData { skillCount: number; @@ -13,7 +13,7 @@ interface Props { function SkillTreeGroup(props: Props) { const { skillCount, selectedSkillCount, resetSkills } = useContext( - SkillContext + SkillAppContext ); const treeData = { diff --git a/src/components/SkillTreeSegment.tsx b/src/components/SkillTreeSegment.tsx index 91dbe23..ce9c417 100644 --- a/src/components/SkillTreeSegment.tsx +++ b/src/components/SkillTreeSegment.tsx @@ -1,10 +1,10 @@ import React, { useRef, useEffect, useState, useContext } from 'react'; -import { throttle } from 'lodash'; +import { throttle, isEmpty } from 'lodash'; import SkillNode from './SkillNode'; import SkillEdge from './SkillEdge'; import { Skill, ParentPosition, ChildPosition, NodeState } from '../models'; import { Nullable } from '../models/utils'; -import SkillContext from '../context/SkillContext'; +import SkillTreeContext from '../context/SkillTreeContext'; import { SELECTED_STATE, LOCKED_STATE, UNLOCKED_STATE } from './constants'; interface Props { @@ -27,8 +27,9 @@ const SkillTreeSegment = React.memo(function({ }: Props) { const [childPosition, setChildPosition] = useState(defaultParentPosition); const { skills, updateSkillState, decrementSelectedSkillCount } = useContext( - SkillContext + SkillTreeContext ); + const skillNodeRef: React.MutableRefObject> = useRef( null ); @@ -75,6 +76,10 @@ const SkillTreeSegment = React.memo(function({ window.addEventListener('resize', throttle(calculatePosition, 250)); calculatePosition(); + if (isEmpty(skills)) { + return updateSkillState(skill.id, UNLOCKED_STATE); + } + return function cleanup() { window.removeEventListener('resize', throttle(calculatePosition)); }; diff --git a/src/components/__tests__/CalculateNodeCount.test.tsx b/src/components/__tests__/CalculateNodeCount.test.tsx index ec20de0..418058b 100644 --- a/src/components/__tests__/CalculateNodeCount.test.tsx +++ b/src/components/__tests__/CalculateNodeCount.test.tsx @@ -2,7 +2,8 @@ import React, { useContext } from 'react'; import { render } from '@testing-library/react'; import CalculateSkillNodes from '../CalculateNodeCount'; import { Skill } from 'models'; -import SkillContext from '../../context/SkillContext'; +import SkillAppContext from '../../context/SkillAppContext'; +import { SkillTreeProvider } from '../../context/SkillTreeContext'; import { legsPullData, legsPushData, @@ -13,7 +14,7 @@ interface GetDummyCounterProps { } function GetDummyCounter({ children }: GetDummyCounterProps) { - const { skillCount } = useContext(SkillContext); + const { skillCount } = useContext(SkillAppContext); return children(skillCount); } @@ -29,7 +30,11 @@ function renderComponent(data: Skill[]) { {skillCount => { counter = skillCount; - return ; + return ( + + > + + ); }} ); @@ -47,14 +52,14 @@ describe('CalculateSkillNodes component', () => { expect(getCounter).toBe(0); }); - xit('should correctly calculate a single branch tree', async () => { + it('should correctly calculate a single branch tree', async () => { const { getCounter, debug } = renderComponent(legsPullData); debug(); expect(getCounter).toBe(6); }); - xit('should correctly calculate a tree with multiples branches', () => { + it('should correctly calculate a tree with multiples branches', () => { const { getCounter } = renderComponent(legsPushData); expect(getCounter).toBe(6); diff --git a/src/components/__tests__/SkillTree.test.tsx b/src/components/__tests__/SkillTree.test.tsx index e9dba8f..0b3dbec 100644 --- a/src/components/__tests__/SkillTree.test.tsx +++ b/src/components/__tests__/SkillTree.test.tsx @@ -2,8 +2,9 @@ import React from 'react'; import { render, fireEvent, act } from '@testing-library/react'; import SkillTree from '../SkillTree'; import MockLocalStorage from '../__mocks__/mockLocalStorage'; -import { SkillProvider } from '../../context/SkillContext'; +import { SkillProvider } from '../../context/SkillAppContext'; import SkillTreeGroup from '../../components/SkillTreeGroup'; +import { SkillTreeProvider } from '../../context/SkillTreeContext'; const mockSkillTreeData = [ { @@ -56,7 +57,11 @@ function renderComponent() { {treeData => { selectedSkillCount = treeData.selectedSkillCount; resetSkills = treeData.resetSkills; - return ; + return ( + + + + ); }} diff --git a/src/context/SkillContext.tsx b/src/context/SkillAppContext.tsx similarity index 63% rename from src/context/SkillContext.tsx rename to src/context/SkillAppContext.tsx index 913bb96..a9243a8 100644 --- a/src/context/SkillContext.tsx +++ b/src/context/SkillAppContext.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { ContextStorage, NodeState } from '../models'; import { Dictionary } from '../models/utils'; -import { SELECTED_STATE } from '../components/constants'; +// import { SELECTED_STATE } from '../components/constants'; type Props = typeof SkillProvider.defaultProps & { appId: string; @@ -11,33 +11,35 @@ type DefaultProps = { storage: ContextStorage; }; +type Skills = Dictionary; + interface State { - skills: Skills; + globalSkills: Dictionary; skillCount: number; selectedSkillCount: number; } -export interface ISkillContext { - skills: Skills; +export interface ISkillAppContext { + appId: string; skillCount: number; selectedSkillCount: number; - updateSkillState: (key: string, updatedState: NodeState) => void; + updateSkillState: (treeId: string, updatedState: Skills) => void; incrementSelectedSkillCount: VoidFunction; decrementSelectedSkillCount: VoidFunction; addToSkillCount: (number: number) => void; + addToSelectedSkillCount: (number: number) => void; resetSkills: VoidFunction; } -type Skills = Dictionary; - -const SkillContext = React.createContext({ - skills: {}, +const SkillAppContext = React.createContext({ + appId: '', skillCount: 0, selectedSkillCount: 0, updateSkillState: () => undefined, incrementSelectedSkillCount: () => undefined, decrementSelectedSkillCount: () => undefined, addToSkillCount: () => undefined, + addToSelectedSkillCount: () => undefined, resetSkills: () => undefined, }); @@ -49,20 +51,12 @@ export class SkillProvider extends React.Component { constructor(props: Props) { super(props); - const storedSkills: Skills = + const storedSkills: Dictionary = JSON.parse(props.storage.getItem(`skills-${props.appId}`)!) || {}; - const selectedSkillCount = Object.keys(storedSkills).reduce((acc, i) => { - if (storedSkills[i] === SELECTED_STATE) { - return acc + 1; - } - - return acc; - }, 0); - this.state = { - selectedSkillCount, - skills: storedSkills, + selectedSkillCount: 0, + globalSkills: storedSkills, skillCount: 0, }; } @@ -75,12 +69,19 @@ export class SkillProvider extends React.Component { window.removeEventListener('beforeunload', this.writeToStorage); } + // refactor the increment items to use just one method. addToSkillCount = (number: number): void => { this.setState(({ skillCount }) => ({ skillCount: skillCount + number, })); }; + addToSelectedSkillCount = (number: number): void => { + this.setState(({ selectedSkillCount }) => ({ + selectedSkillCount: selectedSkillCount + number, + })); + }; + incrementSelectedSkillCount = (): void => { return this.setState(({ selectedSkillCount }) => { return { @@ -98,31 +99,29 @@ export class SkillProvider extends React.Component { }; resetSkills = () => { - return this.setState(prevState => { - const { skills } = prevState; - let resettedSkills = { ...skills }; - const skillKeys = Object.keys(resettedSkills); - - skillKeys.map(key => { - resettedSkills[key] = 'locked'; - }); - - return { - skills: resettedSkills, - selectedSkillCount: 0, - }; - }); + // return this.setState(prevState => { + // const { globalSkills } = prevState; + // let resettedSkills = { ...globalSkills }; + // const skillKeys = Object.keys(resettedSkills); + // skillKeys.map(key => { + // resettedSkills[key] = 'locked'; + // }); + // return { + // skills: resettedSkills, + // selectedSkillCount: 0, + // }; + // }); }; - updateSkillState = (key: string, updatedState: NodeState): void => { + updateSkillState = (treeId: string, updatedState: Skills): void => { this.setState((prevState: State) => { const updatedSkills = { - ...prevState.skills, - [key]: updatedState, + ...prevState.globalSkills, + [treeId]: updatedState, }; return { - skills: updatedSkills, + globalSkills: updatedSkills, }; }); }; @@ -130,17 +129,18 @@ export class SkillProvider extends React.Component { writeToStorage = () => { this.props.storage.setItem( `skills-${this.props.appId}`, - JSON.stringify(this.state.skills) + JSON.stringify(this.state.globalSkills) ); }; render() { return ( - { }} > {this.props.children} - + ); } } -export default SkillContext; +export default SkillAppContext; diff --git a/src/context/SkillTreeContext.tsx b/src/context/SkillTreeContext.tsx new file mode 100644 index 0000000..2ea1a0f --- /dev/null +++ b/src/context/SkillTreeContext.tsx @@ -0,0 +1,108 @@ +import React from 'react'; +import { isEmpty } from 'lodash'; +import { NodeState, ContextStorage } from '../models'; +import { Dictionary } from '../models/utils'; +import SkillAppContext, { + SkillProvider, + ISkillAppContext, +} from './SkillAppContext'; +import { SELECTED_STATE } from '../components/constants'; + +type Props = typeof SkillProvider.defaultProps & { + treeId: string; +}; + +type DefaultProps = { + storage: ContextStorage; +}; + +type Skills = Dictionary; + +interface State { + skills: Skills; +} + +export interface ISkillTreeContext { + skills: Skills; + updateSkillState: (key: string, updatedState: NodeState) => void; + incrementSelectedSkillCount: VoidFunction; + decrementSelectedSkillCount: VoidFunction; + addToSkillCount: (count: number) => void; +} + +const SkillTreeContext = React.createContext({ + skills: {}, + updateSkillState: () => undefined, + incrementSelectedSkillCount: () => undefined, + decrementSelectedSkillCount: () => undefined, + addToSkillCount: () => undefined, +}); + +export class SkillTreeProvider extends React.Component { + static contextType = SkillAppContext; + static defaultProps: DefaultProps = { + storage: localStorage, + }; + + constructor(props: Props, context: ISkillAppContext) { + super(props, context); + + const allStoredSkills: Dictionary = + JSON.parse(props.storage.getItem(`skills-${context.appId}`)!) || {}; + + if (isEmpty(allStoredSkills)) { + } + + const treeSkills = allStoredSkills[props.treeId] || {}; + const selectedSkillCount = Object.keys(treeSkills).reduce((acc, i) => { + if (treeSkills[i] === SELECTED_STATE) { + return acc + 1; + } + + return acc; + }, 0); + + context.addToSelectedSkillCount(selectedSkillCount); + + this.state = { + skills: treeSkills, + }; + } + + addToSkillCount = (count: number) => { + return this.context.addToSkillCount(count); + }; + + updateSkillState = (key: string, updatedState: NodeState) => { + return this.setState(prevState => { + const updatedSkills = { + ...prevState.skills, + [key]: updatedState, + }; + + this.context.updateSkillState(this.props.treeId, updatedSkills); + + return { + skills: updatedSkills, + }; + }); + }; + + render() { + return ( + + {this.props.children} + + ); + } +} + +export default SkillTreeContext; diff --git a/src/index.tsx b/src/index.tsx index a034453..f8cb9db 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,4 +1,4 @@ export { default as SkillTreeGroup } from './components/SkillTreeGroup'; export { default as SkillTree } from './components/SkillTree'; -export { SkillProvider } from './context/SkillContext'; +export { SkillProvider } from './context/SkillAppContext'; export { Skill as SkillType } from './models/index';