refactor: proper memo

This commit is contained in:
cha0s 2020-06-21 20:37:38 -05:00
parent 622f32015f
commit 597de89826
5 changed files with 123 additions and 122 deletions

View File

@ -19,7 +19,7 @@ const EntityComponent = (props) => {
if (!json) { if (!json) {
dispatch(fetchEntity(uri)); dispatch(fetchEntity(uri));
} }
}, [json, uri]); }, [dispatch, json, uri]);
if (!json) { if (!json) {
return null; return null;
} }

View File

@ -0,0 +1,87 @@
import {mapObject} from '@avocado/core';
import {lookupTrait} from '@avocado/entity';
import {all as allTraitComponents} from '@avocado/entity/trait/trait-components.scwp';
import React, {useMemo} from 'react';
import {useDispatch} from 'react-redux';
import Value from '~/client/value';
import {setTraitProperty} from './state';
let TraitComponents;
const ensureTraitComponents = () => {
if (!TraitComponents) {
TraitComponents = Object.values(allTraitComponents()).reduce((r, M) => {
const {default: TraitComponent} = M;
return {...r, [TraitComponent.type]: TraitComponent};
}, {});
}
};
const makePane = (context, dispatch, target, type) => (trait) => {
ensureTraitComponents();
const {params: paramsRaw, state: stateRaw} = trait;
const {[type]: TraitComponent} = TraitComponents;
const Trait = lookupTrait(type);
const params = Trait ? {...Trait.defaultParams(), ...paramsRaw} : paramsRaw;
const state = Trait ? {...Trait.defaultState(), ...stateRaw} : stateRaw;
if (TraitComponent) {
return <TraitComponent params={params} state={state} />;
}
const paramsDescription = Trait?.describeParams() || {};
const stateDescription = Trait?.describeState() || {};
const renderComponents = (traitHalf, description, values) => (
// eslint-disable-next-line no-shadow
Object.values(mapObject(description, (description, key) => {
const {label, options} = description;
return (
<label key={key}>
<span className="text">
{label}
:
</span>
<div className="invisible-separator" />
<Value.Component
context={context}
onChange={(value) => dispatch(setTraitProperty({
target,
trait: type,
type: traitHalf,
key,
value,
}))}
options={options}
value={values[key]}
/>
</label>
);
}))
);
return (
<form className="trait-component-builtin">
{renderComponents('params', paramsDescription, params)}
{renderComponents('state', stateDescription, state)}
</form>
);
};
const TraitPane = (props) => {
const {
context,
target,
trait,
type,
} = props;
const dispatch = useDispatch();
// TODO: dependency on context is too noisy
const renderTraitPane = useMemo(
() => makePane(context, dispatch, target, type),
[context, dispatch, target, type],
);
return useMemo(
() => renderTraitPane(trait),
[renderTraitPane, trait],
);
};
export default TraitPane;

View File

@ -0,0 +1,14 @@
import React from 'react';
const TraitTab = (props) => {
const {type} = props;
return (
<span className="wrapper">
<span className="icon" />
{type}
<span className="close"></span>
</span>
);
};
export default TraitTab;

View File

@ -1,24 +1,16 @@
import {compose, mapObject} from '@avocado/core'; import {compose} from '@avocado/core';
import {all as allTraitComponents} from '@avocado/entity/trait/trait-components.scwp';
import {lookupTrait} from '@avocado/entity';
import contempo from 'contempo'; import contempo from 'contempo';
import {produce} from 'immer';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, {useEffect, useState} from 'react'; import React, {useState} from 'react';
// import ReactMarkdown from 'react-markdown';
import {useDispatch} from 'react-redux';
import { import {
Tab, Tab,
Tabs, Tabs,
TabList, TabList,
TabPanel, TabPanel,
} from 'react-tabs'; } from 'react-tabs';
import {createSelector} from '@reduxjs/toolkit';
import {deregisterHooks, registerHooks} from 'scwp';
import Value from '~/client/value'; import TraitTab from './trait-tab';
import TraitPane from './trait-pane';
import {setTraitProperty} from './state';
const SCROLL_MAG = 80; const SCROLL_MAG = 80;
@ -37,115 +29,18 @@ const decorate = compose(
// ) // )
// ); // );
let TraitComponents;
const ensureTraitComponents = () => {
if (!TraitComponents) {
TraitComponents = Object.values(allTraitComponents()).reduce((r, M) => {
const {default: TraitComponent} = M;
return {...r, [TraitComponent.type]: TraitComponent};
}, {});
}
};
const makeTabSelector = (context, dispatch, target, type) => createSelector(
(_) => _,
(trait) => {
ensureTraitComponents();
const {params: paramsRaw, state: stateRaw} = trait;
const {[type]: TraitComponent} = TraitComponents;
const Trait = lookupTrait(type);
const params = Trait ? {...Trait.defaultParams(), ...paramsRaw} : paramsRaw;
const state = Trait ? {...Trait.defaultState(), ...stateRaw} : stateRaw;
const paramsDescription = Trait?.describeParams() || {};
const stateDescription = Trait?.describeState() || {};
const renderComponents = (traitHalf, description, values) => (
// eslint-disable-next-line no-shadow
Object.values(mapObject(description, (description, key) => {
const {label, options} = description;
return (
<label key={key}>
<span className="text">
{label}
:
</span>
<div className="invisible-separator" />
<Value.Component
context={context}
onChange={(value) => dispatch(setTraitProperty({
target,
trait: type,
type: traitHalf,
key,
value,
}))}
options={options}
value={values[key]}
/>
</label>
);
}))
);
return TraitComponent
? <TraitComponent params={params} state={state} />
: (
<form className="trait-component-builtin">
{renderComponents('params', paramsDescription, params)}
{renderComponents('state', stateDescription, state)}
</form>
);
},
);
const Traits = (props) => { const Traits = (props) => {
const {context, target, traits} = props; const {context, target, traits} = props;
const dispatch = useDispatch();
const [traitForms, setTraitForms] = useState({});
const [mts, setMts] = useState();
useEffect(() => {
if (!mts) {
setMts(() => (type) => makeTabSelector(context, dispatch, target, type));
}
}, [mts, setMts]);
useEffect(() => {
if (!mts) {
return;
}
const entries = Object.entries(traits);
setTraitForms(produce(traitForms, (draft) => {
for (let i = 0; i < entries.length; i++) {
const [type] = entries[i];
draft[type] = mts(type);
}
}));
const M = {
autoreg$accept: (type, M2) => {
if ('trait' === type) {
const {default: Trait} = M2;
setTraitForms(produce(traitForms, (draft) => {
draft[Trait.type()] = mts(Trait.type());
}));
}
},
};
registerHooks(M, __filename);
return () => deregisterHooks(M);
}, [mts, traits]);
const [tabIndex, setTabIndex] = useState(0); const [tabIndex, setTabIndex] = useState(0);
let listRef; let listRef;
const entries = Object.entries(traits); const entries = Object.entries(traits);
const tabs = [];
for (let i = 0; i < entries.length; i++) {
const [type, trait] = entries[i];
if (type in traitForms) {
tabs.push([type, traitForms[type](trait)]);
}
}
return ( return (
<div className="traits"> <div className="traits">
<Tabs <Tabs
domRef={(node) => { domRef={(node) => {
listRef = node && node.querySelector('ul'); listRef = node && node.querySelector('ul');
}} }}
forceRenderTabPanel
selectedIndex={tabIndex} selectedIndex={tabIndex}
onSelect={(index) => setTabIndex(index)} onSelect={(index) => setTabIndex(index)}
> >
@ -157,21 +52,21 @@ const Traits = (props) => {
} }
}} }}
> >
{tabs.map(([type]) => ( {entries.map(([type]) => (
<Tab key={type}> <Tab key={type}>
<span className="wrapper"> <TraitTab key={type} type={type} />
<span className="icon" />
{type}
<span className="close"></span>
</span>
</Tab> </Tab>
))} ))}
</TabList> </TabList>
{tabs.map(([type, content]) => ( {entries.map(([type, trait]) => (
<TabPanel <TabPanel key={type}>
key={type} <TraitPane
> context={context}
{content} key={type}
target={target}
trait={trait}
type={type}
/>
</TabPanel> </TabPanel>
))} ))}
</Tabs> </Tabs>

View File

@ -5,7 +5,12 @@
@extend %tabs; @extend %tabs;
} }
.react-tabs__tab-panel {
display: none;
}
.react-tabs__tab-panel--selected { .react-tabs__tab-panel--selected {
display: block;
height: calc(100vh - 2.7em); height: calc(100vh - 2.7em);
overflow-y: auto; overflow-y: auto;
} }