How to reference the leaflet layer of a react-leaflet component? - reactjs

I used react-leaflet to visualize a quite long path on a map. Users can select from a list and I would like to have different color for the selected path. Changing the state and rendering again is too slow, I am looking for a faster solution.
Leaflet path elements have setStyle() method, so my first idea was using it instead of rendering again. But how to reference the leaflet layer?
class MyPathComponent extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
if (nextProps.selected){
this.setState({selected: true});
LEAFLET_POLYLINE.setStyle({
color: 'red'
});
}
return false;
}
render() {
return(
<Polyline polylines={this.props.path} />
);
}
}
So what should I write instead of LEAFLET_POLYLINE in this code?

Components in react-leaflet have a property called leafletElement. I believe you can do something like this:
class MyPathComponent extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
if (nextProps.selected){
this.setState({selected: true});
this.refs.polyline.leafletElement.setStyle({
color: 'red'
});
}
return false;
}
render() {
return(
<Polyline ref="polyline" polylines={this.props.path} />
);
}
}
Two things to note:
I haven't tested this code, so it may need some small tweaks.
Using a string for "ref" is considered legacy in React, so you'll probably want to do something slightly different (see here). The leafletElement is the important part here.
Instead of the code above, it may be better to just extend the Polyline component for your custom component (limited docs here):
import { Polyline } from 'react-leaflet';
class MyPathComponent extends Polyline {
shouldComponentUpdate(nextProps, nextState) {
if (nextProps.selected){
this.setState({selected: true});
this.leafletElement.setStyle({
color: 'red'
});
}
return false;
}
}
Let me know if any of this works out for you.

Full example using React callback ref and adding to #Eric's answer above:
export default class MyMap extends Component {
leafletMap = null;
componentDidMount() {
console.debug(this.leafletMap);
}
setLeafletMapRef = map => (this.leafletMap = map && map.leafletElement);
render() {
return (
<Map
ref={this.setLeafletMapRef}
>
<TileLayer
attribution="Powered by <a href="https://www.esri.com">Esri</a>"
url="http://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}"
/>
</Map>
);
}
}

Related

How to get the DOM node from a Class Component ref with the React.createRef() API

I have these two components:
import { findDOMNode } from 'react-dom';
class Items extends Component {
constructor(props) {
super(props);
this.ref = React.createRef();
this.selectedItemRef = React.createRef();
}
componentDidMount() {
if (this.props.selectedItem) {
this.scrollToItem();
}
}
componentWillReceiveProps(nextProps) {
if (this.props.selectedItem !== nextProps.selectedItem) {
this.scrollToItem();
}
}
scrollToItem() {
const itemsRef = this.ref.current;
const itemRef = findDOMNode(this.selectedItemRef.current);
// Do scroll stuff here
}
render() {
return (
<div ref={this.ref}>
{this.props.items.map((item, index) => {
const itemProps = {
onClick: () => this.props.setSelectedItem(item.id)
};
if (item.id === this.props.selectedItem) {
itemProps.ref = this.selectedItemRef;
}
return <Item {...itemProps} />;
})}
</div>
);
}
}
Items.propTypes = {
items: PropTypes.array,
selectedItem: PropTypes.number,
setSelectedItem: PropTypes.func
};
and
class Item extends Component {
render() {
return (
<div onClick={() => this.props.onClick()}>item</div>
);
}
}
Item.propTypes = {
onClick: PropTypes.func
};
What is the proper way to get the DOM node of this.selectedItemRef in Items::scrollToItem()?
The React docs discourage the use of findDOMNode(), but is there any other way? Should I create the ref in Item instead? If so, how do I access the ref in Items::componentDidMount()?
Thanks
I think what you want is current e.g. this.selectedItemRef.current
It's documented on an example on this page:
https://reactjs.org/docs/refs-and-the-dom.html
And just to be safe I also tried it out on a js fiddle and it works as expected! https://jsfiddle.net/n5u2wwjg/195724/
If you want to get the DOM node for a React Component I think the preferred way of dealing with this is to get the child component to do the heavy lifting. So if you want to call focus on an input inside a component, for example, you’d get the component to set up the ref and call the method on the component, eg
this.myComponentRef.focusInput()
and then the componentRef would have a method called focusInput that then calls focus on the input.
If you don't want to do this then you can hack around using findDOMNode and I suppose that's why it's discouraged!
(Edited because I realized after answering you already knew about current and wanted to know about react components. Super sorry about that!)

Do react render props cause remounting of the child components?

I was just wondering if people know if using the "render props" pattern causes excessive mounting/unmounting of the child component.
For example, adapting from the react docs (https://reactjs.org/docs/render-props.html):
<Mouse>
{mouse => (
<ShowMousePosition mouse={mouse}/>
)}
</Mouse>
class ShowMousePosition extends React.Component {
componentDidMount(){
console.log('mounting!')
}
render () {
const {mouse} = this.props
return (
<p>The mouse position is {mouse.x}, {mouse.y}</p>
)
}
}
I know the react docs say:
Using a render prop can negate the advantage that comes from using React.PureComponent if you create the function inside a render method. This is because the shallow prop comparison will always return false for new props, and each render in this case will generate a new value for the render prop.
But, will "mounting!" be called over and over as the user moves the mouse around?
Thanks!
I went ahead and tried to answer my own question using a fiddle. It appears that "mounting!" is not called over and over again:
https://jsfiddle.net/69z2wepo/186690/
Here is the code:
class Hello extends React.Component {
render() {
return <Mouse>
{mouse => (
<ShowMousePosition mouse={mouse}/>
)}
</Mouse>
}
}
class Mouse extends React.Component {
constructor(props) {
super(props);
this.handleMouseMove = this.handleMouseMove.bind(this);
this.state = { x: 0, y: 0 };
}
handleMouseMove(event) {
this.setState({
x: event.clientX,
y: event.clientY
});
}
render() {
return (
<div style={{ height: 800, width: 800 }} onMouseMove={this.handleMouseMove}>
{/*
Instead of providing a static representation of what <Mouse> renders,
use the `render` prop to dynamically determine what to render.
*/}
{this.props.children(this.state)}
</div>
);
}
}
class ShowMousePosition extends React.Component {
componentDidMount(){
console.log('mountin!')
}
render () {
const {mouse} = this.props
return (
<p>The mouse position is {mouse.x}, {mouse.y}</p>
)
}
}
ReactDOM.render(
<Hello name="World" />,
document.getElementById('container')
);
componentDidMount is only called once but componentDidUpdate will be called multiple times along with your render function every time your state/props is changed.

Is this considered mutation from a Higher Order Component?

I was reading the section on Don’t Mutate the Original Component. Use Composition from this link.
https://reactjs.org/docs/higher-order-components.html
I then reviewed a project I'm trying to build. At a high level, this is what my code looks like:
class Wrapper extends Component {
constructor(props) {
this.wrappedComponent = props.wrappedComponent;
}
async componentWillAppear(cb) {
await this.wrappedComponent.prototype.fetchAllData();
/* use Greensock library to do some really fancy animation on the wrapper <Animated.div> */
this.wrappedComponent.prototype.animateContent();
cb();
}
render() {
<Animated.div>
<this.wrappedComponent {...this.props} />
</Animated.div>
}
}
class Home extends Component {
async fetchAllData(){
const [r1,r2] = await Promise.All([
fetch('http://project-api.com/endpoint1'),
fetch('http://project-api.com/endpoint2')
]);
this.setState({r1,r2});
}
animateContent(){
/* Use the GreenSock library to do fancy animation in the contents of <div id="result"> */
}
render() {
if(!this.state)
return <div>Loading...</div>;
return (
<div id="result">
{this.state.r1.contentHTML}
</div>
);
}
}
export default class App extends Component {
render() {
return <Wrapper wrappedComponent={Home} />;
}
}
My questions are:
In my Wrapper.componentWillAppear(), I fire the object methods like this.wrappedComponent.prototype.<methodname>. These object methods can set it's own state or animate the contents of the html in the render function. Is this considered mutating the original component?
If the answer to question 1 is yes, then perhaps I need a better design pattern/approach to do what I'm trying to describe in my code. Which is basically a majority of my components need to fetch their own data (Home.fetchAllData(){then set the state()}), update the view (Home.render()), run some generic animation functions (Wrapper.componentWillAppear(){this.animateFunctionOfSomeKind()}), then run animations specific to itself (Home.animateContent()). So maybe inheritance with abstract methods is better for what I want to do?
I would probably actually write an actual Higher Order Component. Rather than just a component which takes a prop which is a component (which is what you have done in your example). Predominately because I think the way you have implemented it is a bit of a code smell / antipattern.
Something like this, perhaps.
class MyComponent extends React.Component {
constructor() {
super();
this.animateContent = this.animateContent.bind(this);
}
componentWillReceiveProps(nextProps) {
if (this.props.r1 !== nextProps.r1) {
this.animateContent();
}
}
componentDidMount() {
// do your fetching and state setting here
}
animateContent() {
// do something
}
render() {
if(!this.props.r1) {
return <div>Loading...</div>;
}
return (
<div id="result">
{this.props.r1.title}
</div>
);
}
}
const myHOC = asyncFn => WrappedComponent => {
return class EnhancedComponent extends React.Component {
async componentDidMount(){
const [r1, r2] = await asyncFn();
this.setState({ r1, r2 })
this.animateContent();
}
animateContent = () => {
// do some animating for the wrapper.
}
render() {
return (<WrappedComponent {...this.props} {...this.state} />)
}
}
}
const anAsyncExample = async () => {
const result = await fetch("https://jsonplaceholder.typicode.com/posts");
return await result.json();
}
const MyEnhancedComponent = myHOC(anAsyncExample)(MyComponent);
Here's a working JSFiddle so you can see it in use:
https://jsfiddle.net/patrickgordon/69z2wepo/96520/
Essentially what I've done here is created a HOC (just a function) which takes an async function and returns another function which takes and a component to wrap. It will call the function and assign the first and second result to state and then pass that as props to the wrapped component. It follows principles from this article: https://medium.com/#franleplant/react-higher-order-components-in-depth-cf9032ee6c3e

Tabs only mount Tab content on the first time it becomes active

I would like to load the tab content only on the first time it becomes active, after that the content stays in the DOM
This is what I have
<Tabs defaultActiveKey={1} animation={false} id="my-tabs" mountOnEnter unmountOnExit>
<Tab eventKey={1}>
<div>content1</div>
</Tab>
<Tab eventKey={2}>
<div>content1</div>
</Tab>
</Tabs>
it works fine, but there is a lag between switching tabs, since the content I have is quite large and I would like to render it only once, on the first time the tab becomes active.
Is there a way to achieve that? I'm using react-bootstrap 0.30.10
UPDATE:
apparently mountOnEnter must be used with animation, otherwise it will not work as intended. I made the change and it works fine now
Old answer:
so I have come up with this wrapping component as follow
class TabsLazyLoad extends Component {
constructor(props) {
super(props);
this.state = this.getInitialState();
this.handleSelect = this.handleSelect.bind(this);
}
getInitialState() {
return {
key: this.props.key || this.props.defaultActiveKey,
rendered: [],
};
}
addRenderedTab(key) {
const newState = _.cloneDeep(this.state);
newState.rendered.push(key);
this.setState(newState);
}
handleSelect(key) {
this.setState({ key });
}
render() {
return (
<Tabs activeKey={this.state.key} onSelect={this.handleSelect} {...this.props}>
{_.map(this.props.children, (tabComponent) => {
if (_.includes(this.state.rendered, tabComponent.props.eventKey)) {
return tabComponent;
}
if (tabComponent.props.eventKey === this.state.key) {
this.addRenderedTab(this.state.key);
}
// if it's not rendered, return an empty tab
const emptyTab = _.cloneDeep(tabComponent);
emptyTab.props.children = null;
return emptyTab;
})}
</Tabs>
);
}
}
TabsLazyLoad.propTypes = Tabs.propTypes;
It seems to be working fine, but I reckon this is a bit hacky, but it's the best I can come up with for now.
It sounds like a good use case for the "Avoid Reconciliation" option that React provides.
Here's a link to the relevant section in the documentation.
Essentially, there's a lifecycle event called shouldComponentUpdate that defaults to true. When you change it to false, it tells React not to run the component through the standard Reconciliation process (i.e. the "diff" checks).
Like with any lifecycle method, you can create a conditional statement for it.
For a component that should be made completely static after its first render, this is really all you need:
class YourComponent extends React.Component {
...
shouldComponentUpdate() {
return false;
}
...
}
However, for a more general use case, you'd want to write a conditional statement based on the props and/or the state of the component:
class YourComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
// Your state
};
}
shouldComponentUpdate(nextProps, nextState) {
// A conditional statement to determine whether
// this component should check for updates or not
}
render () {
return (
<div>
{/* Your JSX*/}
</div>
)
}
I don't use React Boostrap but I guess it's based on the Component design,
example, the rendered content used TabIndex state. Take a closer look at this sample code:
renderActiveTabContent() {
const { children } = this.props
const { activeTabIndex } = this.state
if (children[activeTabIndex]) {
return children[activeTabIndex].props.children
}
}
So the content component render every time Tab state is indexed.
You could use https://github.com/reactjs/react-tabs for your solution other wise take a look of those codes to write a simple one, the Component is rendered once and show/hide state via display: style attribute.
Hope it's help.

React Native: How can I focus on ListView?

I've been working on focusing on ListView component.
Here's my code
class MyListView extends Component {
static propTypes = {
navigator: PropTypes.object.isRequired,
};
componentDidMount() {
this.listView.focus()
}
render() {
return (
<ListView
ref={ref => { this.listView = ref }}
contentContainerStyle={styles.listView}
dataSource={cloneWithRows(listDataSource)}
renderRow={(data) => <MyListViewCell
...
/>}
/>
)
}
}
export default MyListView
but it just returns an error like "this.listView.focus is not a function"
I've searched lots of articles but I couldn't find the solution.
Any ideas?
componentDidMount does not guarantee that ref prop is called before componentDidMount is called. You should change for componentDidMount code to following:
componentDidMount() {
requestAnimationFrame(()=>{
this.listView.focus()
})
}
Also if you decide to use requestAnimationFrame function. It will be better you use it using TimerMixin (see TimerMixin doc). Use react-mixin to implement Mixin in ES6 as suggested in TimerMixin doc.

Resources