The Problem with react-baidu-maps and the Abuse of PropTypes
How using cloneElement and magic propTypes can prevent a component from being really composable.
In my last post, we learned how to render some Baidu Maps in a simple way to avoid the Google Maps block in China. We reviewed and tried react-baidu-maps, a React library to render Baidu Maps. In those examples, we used the components exported by the library, like BaiduMap
or Marker
.
One of our projects is a React based framework that provides different components, including maps. Instead of exposing the react-baidu-maps components, we opted to wrap them and provide an abstraction to the user. This way we encapsulate the library internals controlling which props we want to expose to the user. We can even set some defaultProps
to ensure a map will always be rendered even if some props are missing.
Our component library
Our custom Baidu Map implementation is defined in the following snippet. We wanted to control which props were available and their default values:
As it is defined in the PropTypes
, only the Baidu Maps API Key prop is required. A line like <BaiduMap apiKey="MY_API_KEY" />
will be enough to render a Baidu Map.
Once we achieved this we were drawn to do the same with the other components we wanted to expose. For example, our own marker component:
We exposed some props and set some default values as well. But when we wanted to try out our map… Oh no! The map was not rendering and we found some errors on the console.
The problem with react-baidu-maps
We faced the same problem when we tried to wrap other react-baidu-maps components like InfoWindow
or MarkerClusterer
. As it is shown in the console, the error is thrown in the library Marker
component code. So the logical next move was to review the file to understand what that Cannot read property 'addOverlay' of undefined
meant.
Debugging the code we found that the error was thrown in the this.props[MAP].addOverlay(this.marker);
line in thecomponentDidMount
method:
As we can see in the snippet above, Marker
has two propTypes
objects [MAP]
and [MARKER_CLUSTERER]
, two named constants imported from “utils/constants.js”:
So…the secret is out. Some overlay components like InfoWindow
or Marker
must have props with these funny names. Searching for the uses of these constants in the library, we found the BaiduMap
component:
The renderChildren
method checks if each of the Map children contains a [MAP]
propType
(also known as SECRET_MAP_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
). In that case, it injects a map
element in the mentioned SECRET_MAP_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
prop. This checking is necessary to ensure that a compatible element, like a Marker
or InfoWindow
, is being cloned with React cloneElement
. Here is where our problem originated: our custom components did not have a propType
with that name.
In the Marker case, if no prop called SECRET_MAP_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
is received that prop will be undefined. Thus, the previously mentioned line this.props[MAP].addOverlay(this.marker)
in the componentDidMount
method will throw an error.
A solution
In an ideal world, the react-baidu-maps would use the React Context API instead of using cloneElement
and filtering the children using magic propTypes. Sadly, it is not the case, so we have to play by the library rules and adapt our code to this.
We need to add the required propTypes
to our custom components. We can create our own “utils/constants.js” to hide the funny naming, or directly add the constants like in the following examples:
As we can see in the react-baidu-maps InfoWindow
source code, this overlay component expects two props [MAP]
and [MARKER]
, so we need to add the corresponding two propTypes
:
Once we export our custom components, we can simply build an example like the following one:
This way we can successfully render a Baidu Map without any errors:
Wrapping up
react-baidu-maps is a library designed to be used through its exported components. In some cases, you might need to override its behavior or you want to hide the library internals from the user. Due to the library internals, based on filtering the children using magic propTypes
and cloning elements, you will need to add some specifics propTypes
to your custom components. This is a messy approach but it will avoid some errors, headaches and you will get your Baidu Maps running. There are other APIs, like the Context API, that let components pass data to deeply nested descendants, that will adapt better to this scenario and are composable-friendly solutions.