Search code examples
javascriptreactjshigher-order-componentsrecompose

Higher Order Component: Default Props


I have this code and trying to wrap a HOC with a react Class. What I want in the first place is to overwrite one part of the default params. So here is my code (you don't need to read the whole code, the question its just about the defaultProps in the HOC)

First the Component:

import React from 'react';
// recompose
import compose from 'recompose/compose';
import defaultProps from 'recompose/defaultProps';
import withState from 'recompose/withState';
import withHandlers from 'recompose/withHandlers';
import withProps from 'recompose/withProps';
// utils
import { themr } from 'react-css-themr';
import { suslovkaCoords, generateMarkers } from './utils/fakeData';
// controls
import CanvasHoverMap from './controls/Map';
import HoveredTooltipMarker from './controls/Markers/HoveredTooltipMarker';
import TooltipMarker from './controls/Markers/TooltipMarker';
// components
import RoomTooltip from '../RoomTooltip/RoomTooltip';
// styles
import styles from './map.sass';
import mapStyles from './controls/Map/mapSytles';

const MARKERS_COUNT = 3000;

export const map = ({
  theme,
  style,
  options,
  markerHoverDistance,
  markers,
  renderMarkers,
  renderMarker,
  mapParams: {
    zoom,
    center,
  },
  setMapParams,
  onMapParamsChange,
  selectedMarker,
  setSelectedMarker,
  isMobile,
  refresh,
}) => (
  <div className={theme.component}>
    <CanvasHoverMap
      // flex: 1 here
      style={style}
      // google map options https://developers.google.com/maps/documentation/javascript/controls#ControlOptions
      options={options}
      // see CanvasMap onMouseMove, distance at which algorithm decides that marker is hovered
      markerHoverDistance={markerHoverDistance}
      // google-map-react props
      center={center}
      zoom={zoom}
      onChange={onMapParamsChange}
      // canvas markers, and render functions
      markers={markers}
      // render markers at canvas
      renderMarkers={renderMarkers}
      // render hovered marker as is
      renderMarker={renderMarker}
      // to force redraw just pass a new empty object to refresh for example
      refresh={refresh}
      // selected marker always visible over canvas (+ tooltip)
      selectedMarker={selectedMarker}
      setSelectedMarker={setSelectedMarker}
      // mobile-detect
      isMobile={isMobile}
      setMapParams={setMapParams}
    />
  </div>
);

Now the HOC:

  export const mapHOC = compose(
  themr('map', styles),
  defaultProps({
    options: {
      scrollwheel: true,
      zoomControl: true,
      zoomControlOptions: {
        position: 1, // google.maps.ControlPosition.LEFT_TOP
      },
      minZoom: 3,
      zoom: 10,
      maxZoom: 18,
      // disableDoubleClickZoom: true,
      styles: mapStyles,
    },
    style: {
      flex: 1,
    },
    hoverDistance: 15,
    markerHoverDistance: 15,
    markers: generateMarkers(MARKERS_COUNT, 0.0003),
    }),
  withState('mapParams', 'setMapParams', { center: suslovkaCoords, zoom: 8 }),
  withState('selectedMarker', 'setSelectedMarker', null),
  withProps(({ selectedMarker }) => ({
    isSelected: (marker) => selectedMarker
      ? selectedMarker.id === marker.id
      : false,
  })),
  withHandlers({
    onMapParamsChange: ({ setMapParams }) => ({ center, zoom, bounds }) => {
      setMapParams({ center, zoom, bounds });
      console.log('setMapParams', { center, zoom });
    },
    renderMarker: ({ theme, setSelectedMarker, isSelected, isMobile }) => (marker) => {
      const tooltipMarkerProps = {
        key: marker.id,
        theme: {theme},
        themeNamespace: 'tooltipMarker',
        initialScale: 1,
        defaultScale: 1,
        hoveredScale: 1.3,
        tooltipContent: <RoomTooltip marker={marker} />,
        paddingOffset: 10, // used for tooltip position
        tooltipContentHeight: 240, // no need to be exact, used for tooltip position
        tooltipContentWidth: 200, // no need to be exact, used for tooltip position
        setSelectedMarker: setSelectedMarker,
        selected: isSelected(marker),
        marker: marker,
        ...marker,
      };
      return isMobile
        ? <TooltipMarker {...tooltipMarkerProps } />
        : <HoveredTooltipMarker {...tooltipMarkerProps} />;
    },
    // be sure in current implementation markers is tile markers, not all markers.
    // tiling is used as it allows some perf optimizations not used here
    renderMarkers: () => ({ ctx, markers, tileSize }) => {
      ctx.clearRect(0, 0, tileSize, tileSize);
      const radius = 5;
      markers.forEach(({ /* id, */ x, y }) => {
        // just circles here but can be images, use id or other marker props to render
        ctx.fillStyle = 'rgba(0, 0, 0, 0.1)';
        ctx.beginPath();
        ctx.arc(x, y, radius + 3, 0, Math.PI * 2, true);
        ctx.closePath();
        ctx.fill();

        ctx.fillStyle = 'white';
        ctx.beginPath();
        ctx.arc(x, y, radius + 2, 0, Math.PI * 2, true);
        ctx.closePath();
        ctx.fill();

        ctx.fillStyle = '#00b92a';
        ctx.beginPath();
        ctx.arc(x, y, radius, 0, Math.PI * 2, true);
        ctx.closePath();
        ctx.fill();
      });
    },
  }),
);

I would like to remove "markers: generateMarkers(MARKERS_COUNT, 0.0003)" from defaultProps and give the markers from the outside.

What I tried so far:

const newmap = mapHOC(map);
class MapWithState extends React.Component {
    constructor(props) {
        super(props);
    }

    render() {
        const markers = generateMarkers(MARKERS_COUNT, 0.0003);
        return (<newmap markers={markers} />);
    }
}
export default MapWithState;
//export default  mapHOC(map);

Any idea how to do something like this? I hope that it can be done without much trouble. Thanks!


Solution

  • React merge Props when a component is being created.

    Mean, when a component(JSX) is being instantiated it creates a public data and private data to it i.e, state and props respectively.

    When creating props to a component it takes all the argument props (from callee) and also considers if any default props are declared in that class which it can fill in.

    Something like Object.assign({}, { ...defaultProps }, { ...userProps }) where defaultProps will be overrided with userProps. If userProps are not availble then defaultProps would be considered.

    So, in your case you need to delete a default prop, <newmap markers={markers} /> this should work.