Skip to content

@marvinhagemeister marvinhagemeister released this Mar 5, 2019 · 198 commits to master since this release

Preact X Alpha 0 is here!

tl;dr: Preact X is the next major release which comes with a plethora of highly requested features like Fragments, componentDidCatch, createContext, hooks and many compatibility improvements with third-party libraries.

Fragments βœ…

Fragments has been the most requested feature for Preact for a long time and we were very keen on bringing them into Preact! With Preact X they are now finally here πŸŽ‰ Use the new Fragment export in your components to render children elements inline with their parent, without an extra wrapping DOM element. For example:

import { render, Fragment } from "preact";

function Table() {
  return (
    <table>
      <tr>
        <Columns />
      </tr>
    </table>
  );
}

function Columns() {
  return (
    <Fragment>
      <td>Hello</td>
      <td>World</td>
    </Fragment>
  );
}

render(<Table />, document.body);

// Resulting DOM:
<table>
  <tr>
    <td>Hello</td>
    <td>World</td>
  </tr>
</table>

You can also return arrays from your components:

function Columns() {
  return [
    <td>Hello</td>
    <td>World</td>
  ];
}

Don't forget to add keys to Fragments if you create them in a loop:

function Glossary(props) {
  return (
    <dl>
      {props.items.map(item => (
        // Without the `key`, Preact can't efficiently add,
        // remove, remove new elements as the list changes
        <Fragment key={item.id}>
          <dt>{item.term}</dt>
          <dd>{item.description}</dd>
        </Fragment>
      ))}
    </dl>
  );
}

Fragments are a major new feature of Preact X that largely motivated the rewrite
from Preact 8. We could really use a lot of help testing and validating our
Fragment implementation. If you find something that doesn't seem right, we would
really appreciate reporting a minimally reproducible example to help us improve
our implementation and messaging around Fragments.

componentDidCatch βœ…

We all wish errors wouldn't exists in Web-Apps but sometimes they do happen. With componentDidCatch all errors that happen inside render or another lifecycle method can be caught. This can be used to display user-friendly error messages, or write a log entry to an external service in case something goes wrong.

class Foo extends Component {
  state = { error: false };

  componentDidCatch(err) {
    logErrorToBackend(err);
    this.setState({Β error: true });
  }

  render() {
    // If an error happens somewhere down the tree
    // we display a nice error message.
    if (this.state.error) {
      return <div class="error">Something went wrong...</div>;
    }

    return <Bar />;
  }
}

Because setting a fallback content in an error case is so common we made this even nicer by adding for getDerivedStateFromError which calls setState automatically under the hood.

class Foo extends Component {
  state = { error: false };

  static getDerivedStateFromError(err) {
    // return argument for setState
    return { error: true } 
  }

  render() {
    // If an error happens somewhere down the tree
    // we display a nice error message.
    if (this.state.error) {
      return <div class="error">Whoops! This should not have happened</div>;
    }

    return <Bar />;
  }
}

Hooks

Preact now supports Hooks. React has some phenomenal documentation on hooks that's worth a read, particularly if you're getting started with them for the first time. In Preact, you import hooks from preact/hooks. If you're using a recent version of most bundlers, any hook functions you don't use won't be included in your application.

Here's what hooks look like in Preact:

import { h, render } from 'preact';
import { useState } from 'preact/hooks';

function Counter() {
  const [count, setCount] = useState(0);
                            // ^ default state value
  return (
    <div class="counter">
      Current count: {count}
      <button onClick={() => setCount(count + 1}}> +1 </button>
      <button onClick={() => setCount(count - 1}}> -1 </button>
    </div>
  );
}

render(<Counter />, document.body);

createContext βœ…

The createContext-API is a true successor for getChildContext(). Whereas getChildContext is fine when you're absolutely sure to never change a value, it falls apart as soon as a component in-between the provider and consumer blocks an update via shouldComponentUpdate when it returns false. With the new context API this problem is now a thing of the past. It is a true pub/sub solution to deliver updates deep down the tree.

import { createContext }Β from "preact";

const Theme = createContext("red");

function Button() {
  return <Theme.Consumer>
    {value => <button style={{ color: value }}>click</button>}
  </Theme.Consumer>;
}

function App() {
  return <Theme.Provider value="blue">
    <Button />
  </Theme.Provider>;
}

CSS Custom Properties βœ…

Sometimes it's the little things that make a huge difference. With the recent advancements in CSS you can leverage variables for styling:

function Foo(props) {
  return <div> style={{ --theme-color: "blue" }}>{props.children}</div>;
}

Devtools Adapter

To be able to support all the recent advancements in the excellent react-devtools extension we knew we had to redo our devtools adapter. In previous version we disguised Preact as React v15 to connect to them but this was getting more and more difficult with features added in later releases like the Profiler tab.

For Preact X we rewrote our adapter from scratch and can directly hook into our own renderer. This is a lot more straightforward for us and eases feature development greatly. It didn't take long for us to bring the Profiler into Preact πŸ‘

screenshot 2019-03-05 at 01 12 05

Compat lives now in core

Although we were always keen on adding new features and pushing Preact
forward, the preact-compat package didn't receive as much love. Up until now
it has lived in a separate repository making it harder to introduce breaking
changes.

// Preact 8.x
import React from "preact-compat";

// Preact X
import React from "preact/compat";

New compat features

  • forwardRef
  • UNSTABLE_*-Lifecycle hooks
  • memo

Removing old React APIs

To make maintenance easier we dropped all legacy APIs that were available
in React versions prior to v16. This includes the DOM-Factories API,
createClass, string refs and a few more.

Breaking Changes

We were very careful to introduce as few breaking changes as possible. As a user
the most noticable change will be that props.children is not guaranteed to be
an array anymore. This change was necessary to be able to support rendering
components that return an array of children without wrapping them in a
root node. On top of that this fixes quite a few issues with third-party
components that expect props.children to be undefined when no children are
passed around.

The VNode shape has changed

We renamed/moved the following properties:

  • attributes -> props
  • nodeName -> type
  • children -> props.children

The children of a VNode are no longer guaranteed to be a flat array.
props.children could be undefined or it could be a nested array of children.
Pass props.children to the newly exported helper toChildArray to always get
an array back.

import { h, toChildArray } from "preact";

function MyComponent(props) {
	// Always convert props.children to an array
	const children = toChildArray(props.children);
	return <div>I have {children.length} child nodes</div>;
}

Note: toChildArray will flatten and remove non-renderables like null, undefined, true, and false from the children array.

setState no longer modifies state synchronously

In Preact X the state of a component will no longer be mutated synchronously.
This means that reading from this.state right after a setState call will
return the previous values. Instead you should use a callback function to
modify state that depends on the previous values.

this.state = { counter: 0 };

// Preact 8.x
this.setState({ counter: this.state.counter++ });

// Preact X
this.setState(prevState => {
	// Alternatively return `null` here to abort the state update
	return { counter: prevState.counter++ };
});

render() has changed

The root render function (the one you import from preact) has changed. It no
longer returns the newly created DOM element. Its return type is now void. We
made it simpler, by removing the third argument that was used to hydrate the
DOM. Use the hydrate function instead to hydrate a server rendered DOM tree.

When render is called multiple times on the same elements, the trees will be
merged together. It no longer just appends to the DOM. This change will be
very welcomed by new users as it was a frequeuent source confusion πŸŽ‰

import { render, hydrate } from "preact";

const root = document.getElementById("root");

// Render into the DOM
render(<div>foo</div>, root);
// calling `render` a second time will merge the trees like you would expect it to
render(<div>foo<span>bar</span></div>, root);

// Or use `hydrate` if you're making use of server side rendering
hydrate(<div>foo</div>, root);

preact/devtools is part of preact/debug

Our devtools adapter has been moved into our debug package and doesn't require a dedicated import statement anymore. The preact/debug package must be imported before preact to prevent our devtools integration to be overwritten by the default one supplied by the react-devtools extension.

Better support for Tree-Shaking

A lot has changed in the past years in the bundling space. Most bundlers now
offer excellent support for tree-shaking, where unused exports can be dropped
if they are not used. In the past we always exported an additional object as
the default export. But because of the nature of JavaScript it is very hard
to prove that an object property will never be used, they were never removed.

By removing the default export completely, only the code you need will
be included in the bunlde. The rest will be tree-shaken away.

// Preact 8.x
import preact from "preact";

// Preact X
import * as preact from "preact";

// Preferred: Named exports (works in 8.x and Preact X)
import { h, Component } from "preact";

Note: This change doesn't affect preact/compat. It still has both named and a default export to remain compatible with react.

Other breaking changes

  1. Falsy attributes values are no longer removed from the DOM. For some
    attributes (e.g. spellcheck) the values false and '' have different
    meaning so being able to render false is important
  2. The Component constructor no longer initializes state to an empty object. If
    state has not been previously set, it will be set to an empty object the first
    time the component is rendered, after the constructor has been called
  3. A falsy argument passed to setState will not enqueue an update. This change was made to support returning null from the updater function, which allows users to abort an update. If you just want to trigger an update and don't care about the state itself you can call it with an empty object setState({}).

Minor Changes

  1. render(null, container) no longer renders an empty text node but instead renders nothing
  2. We longer support a synchronous options.debounceRendering. The value of options.debounceRendering must asynchronously invoke the passed in callback. It is important that contributors to Preact can consistently reason about what calls to setState, etc. do, and when their effects will be applied. See the links below for some
    further reading on designing asynchronous APIs.

Known Issues

  • Nested Fragments lead to more DOM operations than necessary
  • Hooks state not visible inside the devtools panel
Assets 2
You can’t perform that action at this time.