React Higher Order Elements - how far can one go with passing props - javascript

am refactorizing huge project in which
some components look very similar
there s looots of files (very high level of components vs files granulation)
I've thought and searched for ways to handle this issue here and there and found this great article about Higher Order Components (HOC) - basically being components that wrap another components.
https://medium.com/#franleplant/react-higher-order-components-in-depth-cf9032ee6c3e#.8vr464t20
I will now give you (A) example of two out of eight similar components types that I need to handle, than (B) I will paste the code I came up with that unifies those eight files into one. Finally (C) will paste example of usage of that unified component.
I will try to be consistent and naming will be not domain driven (I can't post project details here) but same names in different code excerpts below will always point to same components and data. Otherwise I will point it.
(A) - similar components types
1. TabA - Simple one
export default class TabA extends Component {
render() {
return (
<PageWrapper>
<Grid>
<GridItem xsSize="3">
<SmartComponent something={this.props.something }/>
</GridItem>
<GridItem xsSize="9">
<Tabs
permalink = { this.props.permalink }
history={ this.props.history }
activeTab={ Paths.somePath }
/>
<TabAContent
data={ this.props.data }
name={ this.props.name }
someValue={ this.props.someValue }
/>
</GridItem>
</Grid>
</PageWrapper>
);
}
}
Notice that SomeComponentA does not take any children in. Also there is no conditional rendering of any kind here.
2. TabB - More complex one
Similarly here, notice that renderSomeData method conditionally renders SmartComponentToBeConditionallyRendered and also SomeComponentB takes children in from the props.
export default class TabB extends Component {
renderSomeData() {
let someData = {
header: "Header text",
searchPlaceHolder: 'Search (name)',
buttonCaption: 'button caption'
};
return (
<SmartComponentToBeConditionallyRendered
type={ 'some_type' }
permalink={ this.props.permalink }
data={ someData }
/>
)
}
render() {
let { data } = this.props;
return (
<div>
<PageWrapper>
<Grid>
<GridItem xsSize="3">
<SmartComponent something={this.props.something}/>
</GridItem>
<GridItem xsSize="9">
<Tabs
permalink = { this.props.permalink }
history = { this.props.history }
activeTab = { Paths.somePage }
/>
<TabBContent data = { data }>
{this.props.children}
</TabBContent>
</GridItem>
</Grid>
</PageWrapper>
{
this.context.hasPermission('somePermission') ?
this.renderSomeData() :
null
}
</div>
)
}
static contextTypes = {
hasPermission: React.PropTypes.func.isRequired
}
}
Those eight components I've wrote about at the beginning - they all represent one of three possibilities.
Two pictured above and possibility C, but differences in C are just another conditionally rendered components so not worth mentioning cause it will finally come down to passing more flags in props.
Those two components above - they differ in:
kind of SomeComponentX - in place of X there may A, B but also C, D, E etc. in every of those eight similar components. Each of SomeComponentX take in different props as well.
Paths.VALUE_HERE
if SomeComponentX takes in any children or not
if they conditionally render data from renderSomeData method
if YES - someData defined inside the method varies as well
permalink
some_type
(B) What I came up with
let availablePartials = {
PartialA: PartialA,
PartialB: PartialB,
PartialC: PartialC
}
export default class GenericTab extends Component {
renderSomeData() {
return (
<SomeData
type = { this.props.type }
permalink = { this.props.permalink }
data = { this.props.someData } //PASSED FROM PROPS NOW
/>
);
}
render() {
let tabContent = React.createElement(
availablePartials[this.props.partialView.name],
this.props.partialView.props,
this.props.renderChildren ? this.props.children : null
);
return (
<div>
<PageWrapper>
<Grid>
<GridItem xsSize="3">
<SmartComponent something = { this.props.permalink }/>
</GridItem>
<GridItem xsSize = "9">
<Tabs
permalink = { this.props.permalink }
history = { this.props.history }
activeTab = { this.props.activeTab }
/>
{ tabContent }
</GridItem>
</Grid>
</PageWrapper>
{
this.context.hasPermission(this.props.requiredPermission) && this.props.dataForSomeDataMethod ?
this.renderSomeData()
: null
}
</div>
)
}
static contextTypes = {
hasPermission: React.PropTypes.func.isRequired
}
};
CityPageTab.propTypes = {
permalink: PropTypes.string,
dataForSomeDataMethod: PropTypes.object,
type: PropTypes.string,
activeTab: PropTypes.string,
renderChildren: PropTypes.bool,
partialView: PropTypes.object,
requiredPermission: PropTypes.string
};
Basically EVERYTHING is constructed from props. The only part I don't like is availablePartials[this.props.partialView.name].
It requires developers to keep the state of availablePartials object consistent and tangles it a bit. Not nice solution but still it is best I came up with so far.
(C) New GenericTab usage example
componentThatUseGenericTabRenderMethod() {
let { valueA, valueB, valueC, history } = this.props;
let someData = {
header: 'header text',
searchPlaceHolder: 'Search (name)',
buttonCaption: 'buttonCaption'
}
return (
<GenericTab
partialView = {{
name: 'PartialA',
props: {
A: valueA,
B: valueB,
C: valueC,
history: history,
permalink: this.props.params.permalink
}
}}
permalink = { this.props.params.permalink }
activeTab = { Paths.somePath }
someData = { someData }
type = { 'SOME_TYPE' }
renderChildren = { false }
requiredPermission = { 'some_required_permision' }
/>
);
}
So that is that. Usage got bit more complex, but I got rid of seven files (and getting rid of files is main objective as there is too many of them) and am going to further push it in similar manner - generic one.
Thing with genericity - it is more difficult to use but saves lots of space.
Project utilises Redux so dont be too concerned about passing props down the tree. They always only come from some SmartParentComponent that renders GenericTab
Below is the visualisation of how it looks on the page.
GenericTab is responsible for rendering Tabs and TabContent parts.
Yes I know it is shitty solution, but am not responsible for architecture of it. There are so many things to be refactorized here and what am asking about is just a step in a journey. So please lets focus on the question asked and not other things that are so wrong with this code. I know.:)
Guess I could make an article out of it but I don't really have blog to do it:).
Please tell me what you think, propose upgrades, different ways of handling this problem etc.

When dealing with such architecture (i.e, tabs in your case), you basically don't want to hide the architecture under the hood, because in this case your ending up adding more and more properties with each new case you want to handle.
Instead, you wan't to let react handles the nested structure since it's where react really shines. That let you write something very generic by handling the built in children props. You typically want to write something like :
const PageWithTabs = (props) => (
<Tabs defaultActive={'targaryens-panel'}>
<TabBar>
<TabLink href="#starks-panel">{'Starks'}</TabLink>
<TabLink href="#lannisters-panel">{'Lannisters'}</TabLink>
<TabLink href="#targaryens-panel">{'Targaryens'}</TabLink>
</TabBar>
<TabPanel id="starks-panel">
<ul style={{ listStyleType: 'none' }}>
<li>Eddard</li>
<li>Catelyn</li>
<li>Robb</li>
<li>Sansa</li>
<li>Brandon</li>
<li>Arya</li>
<li>Rickon</li>
</ul>
</TabPanel>
<TabPanel id="lannisters-panel">
<ul style={{ listStyleType: 'none' }}>
<li>Tywin</li>
<li>Cersei</li>
<li>Jamie</li>
<li>Tyrion</li>
</ul>
</TabPanel>
<TabPanel id="targaryens-panel">
<ul style={{ listStyleType: 'none' }}>
<li>Viserys</li>
<li>Daenerys</li>
</ul>
</TabPanel>
</Tabs>
)
The point here is that you don't have to "predict" all the things that might appear under each TabPanel, simply let the developper put whatever he wants ! BUT me need some logic to handle the "go to tab" sort of things.
React provides some very handy utilities methods to dynamically clone elements, map over elements, and render element whether its type is the one you expect or not (in our case, we expect TabBar or TabPanel type, nothing prevent the developper to put any other components than this two but nothing prevent him neither to put any built in <table> html element inside of <a> tag or something weird like that).
Here is a little implementation with Material Design Lite, it's not perfect but you should get the point :
class Tabs extends React.Component {
constructor(props) {
super(props),
this.state = {
activeTabId: props.defaultActive
}
}
tabClickHandlerFactory(id) {
return (e) => {
e.preventDefault()
this.setState({
activeTabId: id
})
}
}
getPanelIdFromLink(href) {
return href.split('#')[1]
}
render() {
const self = this
return (
<div className='mdl-tabs is-upgraded' {...self.props}>
{React.Children.map(self.props.children, (child, index) => {
if (child.type == TabBar) {
return React.cloneElement(child, {}, React.Children.map(child.props.children, (link) => {
const id = self.getPanelIdFromLink(link.props.href)
return (
React.cloneElement(link, {
onClick: self.tabClickHandlerFactory(id),
active: self.state.activeTabId === id
})
)
}))
}
if (child.type == TabPanel) {
const { id } = child.props
const active = self.state.activeTabId === id
return active && React.cloneElement(child, { active: true })
}
})}
</div>
)
}
}
Tabs.propTypes = {
defaultActive: React.PropTypes.string.isRequired,
}
const TabBar = (props) => <div className='mdl-tabs__tab-bar' {...props}>{props.children}</div>
const TabLink = ({ active, ...props }) => {
return (
<a className={`mdl-tabs__tab${active ? ' is-active' : ''}`} {...props}>{props.children}</a>
)
}
const TabPanel = ({ active, ...props }) => (
<div className={`mdl-tabs__panel${active ? ' is-active' : ''}`} {...props}>{props.children}</div>
)
const PageWithTabs = (props) => (
<Tabs defaultActive={'targaryens-panel'}>
<TabBar>
<TabLink href="#starks-panel">{'Starks'}</TabLink>
<TabLink href="#lannisters-panel">{'Lannisters'}</TabLink>
<TabLink href="#targaryens-panel">{'Targaryens'}</TabLink>
</TabBar>
<TabPanel id="starks-panel">
<ul style={{ listStyleType: 'none' }}>
<li>Eddard</li>
<li>Catelyn</li>
<li>Robb</li>
<li>Sansa</li>
<li>Brandon</li>
<li>Arya</li>
<li>Rickon</li>
</ul>
</TabPanel>
<TabPanel id="lannisters-panel">
<ul style={{ listStyleType: 'none' }}>
<li>Tywin</li>
<li>Cersei</li>
<li>Jamie</li>
<li>Tyrion</li>
</ul>
</TabPanel>
<TabPanel id="targaryens-panel">
<ul style={{ listStyleType: 'none' }}>
<li>Viserys</li>
<li>Daenerys</li>
</ul>
</TabPanel>
</Tabs>
)
ReactDOM.render(<PageWithTabs/>, document.getElementById('app'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<link rel="stylesheet" href="https://code.getmdl.io/1.1.3/material.brown-orange.min.css" />
<div id='app'></div>

Related

Why does Ant Designs Tree component render so slow when expanding?

I'm building an React component/EntityBrowser that holds a Tree component from ant.design. It's a prototype going into production soon. It's still based on create-react-app. State is managed through Redux.
I've tried searching for answers using both google and Stackoverflow. The consensus seems to be; whenever the Tree components treeData prop receives too much data, it slows down the rendering process, because each of the TreeNodes needs to be re-rendered every time the Tree is expanded. Since more data means slower rendering for each of the TreeNodes - well.. adding more TreeNodes also means slower rendering by the factor the extra data adds.
I don't know if this is the real reason for the overall performance. I've profiled the component and indeed adding more data results in worse rendering times and same goes for the amount of nodes. I would expect this. I'm just surprised by the rate the Tree performance, both when adding new nodes dynamically, but also when expanding it, deteriorates. It does not require many nodes. After only a few nodes with minimal data, it's already too slow to be usable.
This is the Tree component:
<Tree
rootClassName={dark ? "dark-tree" : ""}
style={{ borderRadius: "0" }}
blockNode={true}
onExpand={onExpand}
onSelect={onSelect}
selectedKeys={selectedKeys}
expandedKeys={expandedKeys}
treeData={treeData}
titleRender={TreeNode}
/>
These are the props handling onExpand and onSelect.
const onExpand = (expandedKeys) => {
dispatch(entityBrowserActions.updateExpandedKeys(expandedKeys));
};
const onSelect = (selectedKeys, { event }) => {
if (handleSelection) handleSelection(selectedKeys);
dispatch(entityBrowserActions.updateSelectedKeys(selectedKeys));
};
The handleSelection call tells the app to show the right "scenario" based on the current entityStructure. We are choosing between them in the EntityBrowser/Tree itself.
The treeData is generated with:
const createTreeData = (entityStructure) => {
return entityStructure.map((item) => {
const itemSelectable = !selectedKeys.includes(item.key);
let title = (<span>{item.name}</span>);
let itemObj = {};
let newName = (e) => onNameChange(e, item.key);
let stopEdit = () => handleStopEdit(item.key);
if (editable && editingEntities[item.key] == true) {
title = (
<Input
value={item.name}
onChange={ newName }
onBlur={ stopEdit }
/>
);
}
if (item.children) {
itemObj = {
title,
key: item.key,
selectable: itemSelectable, // Selectable only if not already selected
checkable: false,
children: createTreeData(item.children),
};
} else {
itemObj = {
title,
key: item.key,
selectable: itemSelectable,
}
}
return itemObj;
});
};
The "design" of the treenodes is added with the titleRender prop:
const TreeNode = (value, record) => {
const handleEdit = () => handleStartEdit(value.key);
const handleClose = () => handleClosePopup(value.key, deletePopoverVisible[value.key]);
const handleDeleteAndClose = () => {
handleDelete(value.key);
handleClosePopup(value.key, false);
}
return (
<div className="tree-title">
{value.title}
{editable ? (
<div style={{}}>
<Button
type="text"
style={{ padding: "2px 4px" }}
onClick={ handleEdit }>
<EditOutlined style={{ fontSize: "1.1rem" }} />
</Button>
<Popover
visible={ deletePopoverVisible[value.key] }
onVisibleChange={ handleClose }
content={
<div style={{ display: "flex", flexDirection: "column" }}>
<Button
type="text"
style={{ color: "red" }}
onClick={ handleDeleteAndClose }>
Delete
</Button>
<Button
type="text"
onClick={ handleClose }>
Cancel
</Button>
</div>
}
trigger="click">
<Button type="text" style={{ padding: "2px 4px" }}>
<DeleteOutlined style={{ fontSize: "1.1rem" }} />
</Button>
</Popover>
</div>
) : null}
</div>
);
};
I've tried to remove all the "unnecessary" props like onSelect, titleRender, blockNode, etc. When doing this it does give a speed up but even if I remove everything but the bare minimum it is still painfully slow. Initially all the function calls happened on props inline and now I've "pulled them out" to try and gain some speed. Got a little bit - but not nearly enough.
I don't know how to proceed here. I'm hoping someone can point me to something super obvious that I'm missing.
Everything and nothing worked.

Hide Child Component Element After onClick using ReactJS

I'm new to React, learning by coding, here i have component A, which has select element with menuItems (all material ui), when user clicks select element and chooses from drop down, right after user has chosen whole component should go display:none, is this possible ? i mean user should not be able to see select element anymore on the page
English is not my mother language, so there might be mistakes.
suggestions/help is appreciated.
component A:
const A: React.FC<AProps> = (props) => {
const handleChange = (e: React.ChangeEvent<{ value: unknown }>) => {
const site = e.target.value as string;
dispatch(changeActiveSite(site));
if (site) {
dispatch(getAnalysers(site));
} else {
dispatch(clearSiteData(site));
}
};
const sites = [
{
ident: "",
name: "None",
},
].concat(sitess);
return (
<React.Fragment>
<FormControl className={classes.formControl}>
<InputLabel id="site-select-input-label">site</InputLabel>
<Select
id="site-select"
value={currentSiteId}
labelId="site-select-input-label"
onChange={(e) => handleChange(e)}
>
{sites.map((site) => {
return (
<MenuItem key={site.ident} value={site.ident}>
{site.name}
</MenuItem>
);
})}
</Select>
</FormControl>
</React.Fragment>
);
};
that component is in component B like this: <div > <A site={site} /> </div>
Let's imagine your MenuItem.js, specifically, its render() and constructor(). You'll want it to be able to be hidden/not-displayed, or visible/displayed. Use a state attribute for hidden to control this, your render will probably look like...
constructor(props) {
super(props);
this.state = {
'hidden':false,
};
}
render () {
if(this.state.hidden) {
return '';
}
return (
<div
onClick={(e) => this.handleOnClick(e)}
>
{this.props.value}
</div>
);
}
Notice I also added a handleOnClick(e) handler up above! That will simply call this.setState({'hidden':true}), so like...
handleOnClick(e) {
this.setState({'hidden':true});
}
I have an answer to a similar question elsewhere, if it might also help: How to set one component's state from another component in React

Toggle only the menu clicked in Reactjs

I am making a menu and submenus using recursion function and I am in the need of help to open only the respective menu and sub menu's..
For button and collapse Reactstrap has been used..
Recursive function that did menu population:
{this.state.menuItems &&
this.state.menuItems.map((item, index) => {
return (
<div key={item.id}>
<Button onClick={this.toggle.bind(this)}> {item.name} </Button>
<Collapse isOpen={this.state.isToggleOpen}>
{this.buildMenu(item.children)}
</Collapse>
</div>
);
})}
And the buildMenu function as follows,
buildMenu(items) {
return (
<ul>
{items &&
items.map(item => (
<li key={item.id}>
<div>
{this.state.isToggleOpen}
<Button onClick={this.toggle.bind(this)}> {item.name} </Button>
<Collapse isOpen={this.state.isToggleOpen}>
{item.children && item.children.length > 0
? this.buildMenu(item.children)
: null}
</Collapse>
</div>
</li>
))}
</ul>
);
}
There is no problem with the code as of now but I am in the need of help to make menu -> submenu -> submenu step by step open and closing respective levels.
Working example: https://codesandbox.io/s/reactstrap-accordion-9epsp
You can take a look at this example that when you click on any menu the whole level of menus gets opened instead of clicked one..
Requirement
If user clicked on menu One, then the submenu (children)
-> One-One
needs to get opened.
And then if user clicked on One-One,
-> One-One-One
-> One - one - two
-> One - one - three
needs to get opened.
Likewise it is nested so after click on any menu/ children their respective next level needs to get opened.
I am new in react and reactstrap way of design , So any help from expertise would be useful for me to proceed and learn how actually it needs to be done.
Instead of using one large component, consider splitting up your component into smaller once. This way you can add state to each menu item to toggle the underlying menu items.
If you want to reset al underlying menu items to their default closed position you should create a new component instance each time you open up a the underlying buttons. By having <MenuItemContainer key={timesOpened} the MenuItemContainer will be assigned a new key when you "open" the MenuItem. Assigning a new key will create a new component instance rather than updating the existing one.
For a detailed explanation I suggest reading You Probably Don't Need Derived State - Recommendation: Fully uncontrolled component with a key.
const loadMenu = () => Promise.resolve([{id:"1",name:"One",children:[{id:"1.1",name:"One - one",children:[{id:"1.1.1",name:"One - one - one"},{id:"1.1.2",name:"One - one - two"},{id:"1.1.3",name:"One - one - three"}]}]},{id:"2",name:"Two",children:[{id:"2.1",name:"Two - one"}]},{id:"3",name:"Three",children:[{id:"3.1",name:"Three - one",children:[{id:"3.1.1",name:"Three - one - one",children:[{id:"3.1.1.1",name:"Three - one - one - one",children:[{id:"3.1.1.1.1",name:"Three - one - one - one - one"}]}]}]}]},{id:"4",name:"Four"},{id:"5",name:"Five",children:[{id:"5.1",name:"Five - one"},{id:"5.2",name:"Five - two"},{id:"5.3",name:"Five - three"},{id:"5.4",name:"Five - four"}]},{id:"6",name:"Six"}]);
const {Component, Fragment} = React;
const {Button, Collapse} = Reactstrap;
class Menu extends Component {
constructor(props) {
super(props);
this.state = {menuItems: []};
}
render() {
const {menuItems} = this.state;
return <MenuItemContainer menuItems={menuItems} />;
}
componentDidMount() {
loadMenu().then(menuItems => this.setState({menuItems}));
}
}
class MenuItemContainer extends Component {
render() {
const {menuItems} = this.props;
if (!menuItems.length) return null;
return <ul>{menuItems.map(this.renderMenuItem)}</ul>;
}
renderMenuItem(menuItem) {
const {id} = menuItem;
return <li key={id}><MenuItem {...menuItem} /></li>;
}
}
MenuItemContainer.defaultProps = {menuItems: []};
class MenuItem extends Component {
constructor(props) {
super(props);
this.state = {isOpen: false, timesOpened: 0};
this.open = this.open.bind(this);
this.close = this.close.bind(this);
}
render() {
const {name, children} = this.props;
const {isOpen, timesOpened} = this.state;
return (
<Fragment>
<Button onClick={isOpen ? this.close : this.open}>{name}</Button>
<Collapse isOpen={isOpen}>
<MenuItemContainer key={timesOpened} menuItems={children} />
</Collapse>
</Fragment>
);
}
open() {
this.setState(({timesOpened}) => ({
isOpen: true,
timesOpened: timesOpened + 1,
}));
}
close() {
this.setState({isOpen: false});
}
}
ReactDOM.render(<Menu />, document.getElementById("root"));
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.4.1/css/bootstrap.min.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/reactstrap/8.4.1/reactstrap.min.js"></script>
<div id="root"></div>
You will want to create an inner component to manage the state at each level.
For example, consider the following functional component (I'll leave it to you to convert to class component):
const MenuButton = ({ name, children }) => {
const [open, setOpen] = useState(false);
const toggle = useCallback(() => setOpen(o => !o), [setOpen]);
return (
<>
<Button onClick={toggle}>{name}</Button>
<Collapse open={open}>{children}</Collapse>
</>
);
};
This component will manage whether to display its children or not. Use it in place of all of your <div><Button/><Collapse/></div> sections, and it will manage the open state for each level.
Keep shared state up at the top, but if you don't need to know whether something is expanded for other logic, keep it localized.
Also, if you do need that info in your parent component, use the predefined object you already have and add an 'open' field to it which defaults to false. Upon clicking, setState on that object to correctly mark the appropriate object to have the parameter of true on open.
Localized state is much cleaner though.
Expanded Example
import React, { Component, useState, useCallback, Fragment } from "react";
import { Collapse, Button } from "reactstrap";
import { loadMenu } from "./service";
const MenuButton = ({ name, children }) => {
const [open, setOpen] = React.useState(false);
const toggle = useCallback(() => setOpen(o => !o), [setOpen]);
return (
<Fragment>
<Button onClick={toggle}>{name}</Button>
<Collapse open={open}>{children}</Collapse>
</Fragment>
);
};
class Hello extends Component {
constructor(props) {
super(props);
this.state = {
currentSelection: "",
menuItems: [],
};
}
componentDidMount() {
loadMenu().then(items => this.setState({ menuItems: items }));
}
buildMenu(items) {
return (
<ul>
{items &&
items.map(item => (
<li key={item.id}>
<MenuButton name={item.name}>
{item.children && item.children.length > 0
? this.buildMenu(item.children)
: null}
</MenuButton>
</li>
))}
</ul>
);
}
render() {
return (
<div>
<h2>Click any of the below option</h2>
{this.state.menuItems &&
this.state.menuItems.map((item, index) => {
return (
<MenuButton name={item.name}>
{this.buildMenu(item.children)}
</MenuButton>
);
})}
</div>
);
}
}
export default Hello;

Skills list is not updated when new skill is added

There is an accordion of Skills and Experiences in background component. When the title on the skills accordion is clicked then the modal will pop up with skill form which when filled and submitted then the modal closes and the list of skill should get update but it is not updating. I have to refresh to see the changes in the skill list. Here is how i have done
class Background extends React.PureComponent {
state = {
show: false,
componentName: null,
activeIndex: 0
};
handleModal = (action, componentName) =>
this.setState(state => ({
show: action,
componentName
}));
render() {
const { show, activeIndex, componentName } = this.state;
return (
<React.Fragment>
<ProfileWrapper>
<Query query={BACKGROUND_QUERY}>
{({
data: { experiences, skills, languages, educations } = {},
loading
}) => {
if (loading) {
return <h1>Loading...</h1>;
} else {
return (
<Grid columns={2}>
<Accordion
index={1}
onToggle={this.handleToggle}
css="max-width: 100%; min-width: 200px;"
>
<Accordion.Title>
<Title>
Skills (
{`${skills !== undefined && skills.edges.length}`})
</Title>
</Accordion.Title>
<Accordion.Content>
<Content>
{skills !== undefined && skills.edges.length > 0 && (
<span>
{skills.edges.map(skill => {
return (
<React.Fragment key={skill["node"].id}>
<span key={skill["node"].id}>
<Chip>{skill["node"].title}</Chip>
</span>
</React.Fragment>
);
})}
</span>
)}
</Content>
</Accordion.Content>
</Accordion>
<Modal
position="centerCenter"
open={show}
onClose={() => this.handleModal(false, null)}
>
<React.Fragment>
{componentName !== null &&
componentName === "experiences" && (
<Experiences experiences={experiences} />
)}
{componentName !== null &&
componentName === "skills" && (
<Skills skills={skills} />
)}
</React.Fragment>
</Modal>
</Grid>
);
}
}}
</Query>
</ProfileWrapper>
</React.Fragment>
);
}
}
export default Background;
const Skills = ({ handleSubmit, ...props }) => {
const formSubmit = async (val, mutation) => {
const {
data: { skill: response }
} = await mutation({
variables: val
});
console.log('response', response);
if (response.success) {
props.closeModal();
toast.success("New Skill Added!");
}
};
return (
<React.Fragment>
<Mutation mutation={CREATE_SKILL}>
{mutation => {
return (
<form onSubmit={handleSubmit(val => formSubmit(val, mutation))}>
<Field name="title" label="Title" component={TextField} />
<Button>
<Button.Primary>Add Skill</Button.Primary>
<Button.Secondary>Cancel</Button.Secondary>
</Button>
</form>
);
}}
</Mutation>
</React.Fragment>
);
};
export default compose(
reduxForm({
form: "skillsProfile",
enableReinitialize: true,
destroyOnUnmount: false
})
)(Skills);
why optimistic ui update is not working here when Query is done in background component?
From the docs:
Sometimes when you perform a mutation, your GraphQL server and your Apollo cache become out of sync. This happens when the update you’re performing depends on data that is already in the cache; for example, deleting and adding items to a list. We need a way to tell Apollo Client to update the query for the list of items. This is where the update function comes in!
There's no way for Apollo to know what sort of operations your server is doing when it executes a mutation (adding one or more rows, deleting a row, etc.) -- all it has to go off is the data that's returned by the mutation. It can match this data against objects that are already in the cache and update them accordingly, but that's it. If there are any fields that were cached and need to be updated as a result of your mutation, you need to explicitly tell Apollo how to do this (side note, these could be fields that return a List of Skills, but they could be literally any other fields that were impacted by the mutation).
Your update function for adding a skill, for example, would look something like this:
update={(cache, { data: { addSkill } }) => {
// assumes addSkill resolves to a single Skill
const { skills, ...rest } = cache.readQuery({ query: BACKGROUND_QUERY });
cache.writeQuery({
query: BACKGROUND_QUERY,
data: { skills: skills.concat([addSkill]), ...rest },
});
}}
See the docs for additional examples. It's also worthwhile noting that when using readQuery or writeQuery, you will need to pass in the appropriate variables if your query takes any.
While you can refetch your queries (for example, by specifying them as part of refetchQueries), it's largely unnecessary for simple updates to the cache and obviously slower than just using update since it requires another round-trip to your server. Additionally, update even works with optimistic updates to your UI.

Specifying complex PropTypes for children

I'm writing a small Toolbar component in React. Here's how it should be used:
<Toolbar>
<ToolbarButtonSearch />
<ToolbarButtonFold />
</Toolbar>
or
<Toolbar>
<ToolbarButtonSearch />
</Toolbar>
or
<Toolbar>
<ToolbarButtonFold />
</Toolbar>
I'd like to be able to specify that the only children Toolbar accepts are one of ToolbarButtonSearch, one of ToolbarButtonFold, or one of each.
Here's what I have now (not including imports):
export default class Toolbar extends React.Component {
static get propTypes() {
const acceptedChildren =
React.PropTypes.oneOfType([
React.PropTypes.instanceOf(ToolbarButtonFold),
React.PropTypes.instanceOf(ToolbarButtonSearch)
]);
return {
children: React.PropTypes.oneOfType([
React.PropTypes.arrayOf(acceptedChildren),
acceptedChildren
]).isRequired
};
}
render() {
return (
<div className="toolbar">
{this.props.children}
</div>
);
}
}
Here's how I'm using it:
<Toolbar>
<ToolbarButtonFold />
</Toolbar>
This results in the following error:
Warning: Failed prop type: Invalid prop 'children' supplied to 'Toolbar'.
I'm not sure what I'm doing wrong. Any insight would be very helpful. Thanks!
Did you tried to do:
customProp: function (props, propName, componentName) {
props.children.forEach(child => {
if (!child instanceof ToolbarButtonSearch && !child instanceof ToolbarButtonFold) {
return new Error(
'Invalid children supplied to' +
' `' + componentName + '`. Validation failed.'
)
}
})
}
Instead of passing components (since they have to be imported into Toolbar, anyway), you could just pass props defining if the specific component should be displayed.
const Toolbar = ({showButtonSearch = false, showButtonFold = false, buttonSearchProps = {}, buttonFoldProps = {}}) => {
return (
<div className='Toolbar'>
{showButtonSearch ? <ButtonSearch {...buttonSearchProps} /> : null}
{showButtonFold ? <ButtonFold {...buttonFoldProps} /> : null}
</div>
)
}
This method would also allow you to pass props for the children as well as any further functionality you need before displaying the children (such as ordering the children a specific way).

Categories

Resources