Troubleshooting

Of course, my migration did not go flawlessly — things never seem to, do they? I hope yours does, but if it does not, here are the issues I encountered and how I overcame them.

Context changes

My first issue was that changes being broadcast from a Context were no longer being sent to child components. I had the Context set up like this:

<Provider value={this.state}> ... </Provider>

Context Consumers are supposed to re-render whenever the Provider’s value changes. In React, the above line would cause all dependent components to re-render when the Provider’s state changed.

In Preact, this stopped working. I’m assuming this is because Preact does not copy state on setState ; instead, it modifies state in place. Because JS object comparisons are done by reference, no change appears to happen and so the Consumer components are never re-rendered.

To get around this, I’m copying state to a new object whenever it changes:

<Provider value={Object.assign({}, this.state)}> ... </Provider>

If anyone knows a cleaner way, please let me know.

React Developer Tools

I discovered that my React developer tools were no longer working. This is because Preact does not include the developer tools in the main bundle to cut down on unnecessary load time.

To fix this, I just added the following line to index.js to load the debug module if this module is being hot-reloaded by Webpack:

if(module.hot) require('preact/debug')

React Router errors

The last issue I encountered was a problem with react-router-dom . The BrowserRouter component was throwing the following error during render:

Warning: Failed prop type: Invalid prop `children` supplied to `Router`, expected a ReactNode.

For some reason, the <div> element that was the immediate child of my BrowserRouter was not being recognized as a valid component. I tracked down this error to the definition of isValidElement in prop-types :

var REACT_ELEMENT_TYPE = (typeof Symbol === 'function' &&

Symbol.for &&

Symbol.for('react.element')) ||

0xeac7; var isValidElement = function(object) {

return typeof object === 'object' &&

object !== null &&

object.$$typeof === REACT_ELEMENT_TYPE;

};

The <div> fails the isValidElement test because it has no $$typeof property. The only thing I can seem to figure out about $$typeof is that it’s a special property React uses in order to distinguish React-created DOM objects from native DOM objects.

I tried adding a hook to preact to automatically populate the $$typeof on new VNode (virtual nodes) in the DOM, but that just uncovered another host of issues related to differences between the React virtual DOM and Preact’s VDOM.

So, with a heavy heart (it’s 2 kilobytes of code!!), I decided to install preact-compat (2kb!!!) and patch it into my app.

Doing this was fairly simple, just yarn add preact-compat and add 2 dependencies to your resolve.alias configuration in your webpack config:

alias: {

'react': 'react-compat',

'react-dom': 'react-compat'

},

Note: Because I created my app with create-react-app , I had to run yarn eject so that I could edit my webpack config instead of using one from node_modules . You may or may not have to do this first.

After doing this, react-router-dom worked like a charm.

I also went through my project and replaced all my preact imports with preact-compat just to match the documented way of doing it: