react-virtualized share CellMeasurerCache for multiple Grids - react-virtualized

I got few grids side by side, and for first of them i want to calculate row heights dynamically using CellMeasurer, how it's possible to reuse CellMeasurerCache from this grid in another (to synchronize cell heights/widths)?
jsbin example
class App extends React.Component {
constructor(props) {
super(props);
this.leftHeadersCellMeasurerCache = new CellMeasurerCache({
fixedWidth: true,
minHeight: 40
});
}
render() {
return (
<ScrollSync>
{({scrollTop, onScroll}) => (
<div className="row">
<LeftGrid
width={300}
height={500}
scrollTop={scrollTop}
cellMeasurerCache={this.leftHeadersCellMeasurerCache}
/>
<DataGrid
width={400}
height={500}
onScroll={onScroll}
rowHeight={this.leftHeadersCellMeasurerCache.rowHeight}
/>
</div>
)}
</ScrollSync>
)
}
}
PS. Unfortunately cannot use MultiGrid, data on left side is "uneven".

You can't directly share a CellMeasurerCache cache between Grids unless the content of all cells in both Grids are the same (which I doubt is ever the case).
I think you'll want to decorate the CellMeasurerCache in a similar way as MultiGrid does. Your decorator would need to decide when to pass-thru values as-is and when to add a column-offset to avoid clobbering measurements.

Related

How to optimize the rendering of multiple instances of the same element, with the same property value

I have a component based on lit-element that is rendering a combobox with about 200 entries. The rendering of the template is taking about 0.20s.
html`
<select>
${this.list.map((o) => html`
<option id="${index}"
#click="${this.handleClick}">${o}</option>`
)}
</select>`
I'm rendering the same component multiple times, with the same value for the property list. The rendering is taking 0.20s each time, which is quite long if I'm rendering 10 instances of the component. Is there any way to optimize this, specially when knowing that the generated template is the same for every instance of the component ?
You can use an intermediate variable
html`
<select>
${this.options}
</select>`
with a function like this
this.options = this.list.map(
(o) => html`
<option id="${index}" #click="${this.handleClick}">${o}</option>`
);

How to focus a styled component?

I have a styled component:
const StyledComponent = styled.div`
...
`;
and I want to focus it when the component that uses it is mounted:
class someComponent extends React.Component {
componentDidMount() {
this.sc.focus();
}
render() {
return (
<StyledComponent innerRef={(elem) => { this.sc = elem; }}>
...
</StyledComponent>
);
}
}
this technique does not work - is there a solution for this?
You can't focus non-interactive elements without the use of tabIndex ("tabindex" in normal HTML). The "div" element is considered non-interactive (unlike anchor links "a" and button controls "button".)
If you want to be able to focus this element, you'll need to add a tabIndex prop. You can use .attrs if desired to have it be added automatically so you don't need to write the prop every time.
This link
has an example with both React 16 React.createRef() and the callback method.
Have you tried setting the autoFocus attribute, or is that not fit for your use case?
Try passing the autoFocus prop to your StyledComponent like <StyledComponent autoFocus />.

React-Virtualized ScrollSync: How to set an initial scrollTop value?

I'm using the react-virtualized ScrollSync component to sync the scrolling of a couple fixed headers — very similar to the example in the docs.
My question: is it possible to provide an initial scrollTop value to ScrollSync (or its children)? I realize that the better way to do this would be through use of scrollToRow on the Grid component that controls the scroll position — for what it's worth, I am using scrollToColumn for this purpose. But because, vertically, I'm only rendering one very tall cell, scrollToRow doesn't provide the fidelity needed.
I do realize that this is a slightly bastardized use of a grid component, but it all works quite nicely as a horizontal, infinitely loading scroller, while allowing me to re-use an existing component. If I could just set an initial scrollTop, I'd be golden.
Thanks.
Unfortunately this is not currently supported without a bit of a hack.
First, the reason for the hack: Scroll offsets flow in one direction with ScrollSync (main Grid to synchronized Grids). This means that even if ScrollSync accepted default left/top offsets as props- they would get overridden by the first render of the main Grid. I think this is probably the right thing to do to avoid ugliness inside of react-virtualized.
However you could work around it in application code like this if you wanted to:
class YourComponent extends Component {
render () {
// These are arbitrary
const defaultScrollLeft = 300
const defaultScrollTop = 500
return (
<ScrollSync>
{({ clientHeight, clientWidth, onScroll, scrollHeight, scrollLeft, scrollTop, scrollWidth }) => {
if (!this._initialized) {
scrollLeft = defaultScrollLeft
scrollTop = defaultScrollTop
this._initialized = true
}
return (
<div>
<Grid
{...otherSyncedGridProps}
scrollLeft={scrollLeft}
/>
<Grid
{...otherMainGridProps}
onScroll={onScroll}
scrollLeft={defaultScrollLeft}
scrollTop={defaultScrollTop}
/>
</div>
)
}}
</ScrollSync>
)
}
}

Does react-virtualized work with airbnb/enzyme?

Is it possible to use react-virtualized and enzyme together? When I try to use them together I seem to get an empty list of items in the grid.
The 2 should work together, yes. I believe the likely problem is that the react-virtualized component is being given a width or height of 0 which causes it not to render anything. (It only renders enough to fill the "window" it has.)
Assuming you're using the AutoSizer HOC- (most people do)- then one pattern I've found helpful is to export 2 versions of components- one that expects explicit width/height properties and one that wraps the other with an AutoSizer. Pseudo code would be:
import { AutoSizer, VirtualScroll } from 'react-virtualized'
// Use this component for testing purposes so you can explicitly set width/height
export function MyComponent ({
height,
width,
...otherProps
}) {
return (
<VirtualScroll
height={height}
width={width}
{...otherProps}
/>
)
}
// Use this component in your browser where auto-sizing behavior is desired
export default function MyAutoSizedComponent (props) {
return (
<AutoSizer>
({ height, width }) => (
<MyComponent
height={height}
width={width}
{...props}
/>
)
</AutoSizer>
)
}
as of react-virtualized 9.12.0 the Autosizer has defaultWidth and defaultHeight properties.
I found setting those meant enzyme tests ran correctly - rendering the child rows as expected.
<AutoSizer disableHeight defaultWidth={100}>
{({ width }) => (
....
)}
</AutoSizer>
Putting this in my test case worked for me:
import { AutoSizer } from 'react-virtualized';
// ...
it('should do something', function() {
spyOn(AutoSizer.prototype, 'render').and.callFake(function render() {
return (
<div ref={this._setRef}>
{this.props.children({ width: 200, height: 100 })}
</div>
);
});
// do something...
I use Jasmine's spyOn here, but other libraries have their own ways of overwriting functions.
Keep in mind that this is pretty fragile against future changes to the react-virtualized library (this._setRef was just yanked from the source code), and may give you false positives.

Create an interactive SVG component using React

Let's say I have an SVG element with paths for all US states.
<svg>
<g id="nh">
<title>New Hampshire</title>
<path d="m 880.79902,142.42476 0.869,-1.0765 1.09022,..." id="NH" class="state nh" />
</g>
...
</svg>
The SVG data is saved in a separate file with a .svg extension. Say I want to create a React component of that map, with complete control over it so that I can modify the styling of individual states based on some external input.
Using Webpack, as far as I can tell, I have two options for loading the SVG markup: Insert it as raw markup using the raw-loader and create a component using dangerouslySetInnerHTML:
var InlineSvg = React.createClass({
render() {
var svg = require('./' + this.props.name + '.svg');
return <div dangerouslySetInnerHTML={{__html: svg}}></div>;
}
});
or manually convert the markup to valid JSX:
var NewComponent = React.createClass({
render: function() {
return (
<svg>
<g id="nh">
<title>New Hampshire</title>
<path d="m 880.79902,142.42476 0.869,-1.0765 1.09022,..." id="NH" className="state nh" />
</g>
...
</svg>
);
});
Finally, let's say that in addition to the SVG map, there's a simple HTML list of all the states. Whenever a user hovers over a list item, the corresponding SVG path should shift fill color.
Now, what I can't seem to figure out is how to update the React SVG component to reflect the hovered state. Sure, I can reach out into the DOM, select the SVG state by classname and change its color, but that doesn't seem to be the "react" way to do it. A pointing finger would be much appreciated.
PS. I'm using Redux to handle all communication between components.
You need to do two things:
1) Set an event listener on each list item to inform your app of the highlighted item.
<li
onMouseOver={() => this.handleHover('NH')}
onMouseOut={() => this.handleUnhover()}
>
New Hampshire
</li>
2) Capture the data, and propagate it your SVG component.
This is the more complicated part, and it comes down to how you've structured your app.
If your entire app is a single React component, then handleHover would simply update the component state
If your app is divided into multiple components, then handleHover would trigger a callback passed in as a prop
Let's assume the latter. The component methods might look like this:
handleHover(territory) {
this.props.onHighlight(territory);
}
handleUnhover() {
this.props.onHighlight(null);
}
Assuming you have a parent component, which contains both the SVG map and the list, it might look something like this:
class MapWrapper extends React.Component {
constructor(props) {
super(props);
this.state = {
highlighted: null;
};
}
setHighlight(territory) {
this.setState({
highlighted: territory
});
}
render() {
const highlighted = { this.state };
return (
<div>
<MapDiagram highlighted={highlighted} />
<TerritoryList onHighlight={(terr) => this.setHighlight(terr)} />
</div>
);
}
}
The key here is the highlighted state variable. Every time a new hover event occurs, highlighted changes in value. This change triggers a re-render, and the new value is passed onto MapDiagram which can then determine which part of the SVG to highlight.

Resources