Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: preactjs/preact-render-to-string
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v5.1.21
Choose a base ref
...
head repository: preactjs/preact-render-to-string
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 99925a0c11eb8edaceea2dc15f899608edafb853
Choose a head ref
  • 2 commits
  • 4 files changed
  • 3 contributors

Commits on Apr 29, 2022

  1. Implement hook state settling (#219)

    * Implement hook state settling (fixes #218)
    
    * Create chilly-ligers-agree.md
    developit authored Apr 29, 2022

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    250c15f View commit details
  2. Version Packages (#220)

    Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
    github-actions[bot] and github-actions[bot] authored Apr 29, 2022

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    99925a0 View commit details
Showing with 73 additions and 8 deletions.
  1. +6 −0 CHANGELOG.md
  2. +1 −1 package.json
  3. +23 −6 src/index.js
  4. +43 −1 test/render.test.js
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# preact-render-to-string

## 5.2.0

### Minor Changes

- [#219](https://github.com/preactjs/preact-render-to-string/pull/219) [`250c15f`](https://github.com/preactjs/preact-render-to-string/commit/250c15fbc01e28c3934689e2a846e441709d829f) Thanks [@developit](https://github.com/developit)! - Implement hook state settling. Setting hook state during the execution of a function component (eg: in `useMemo`) will now re-render the component and use the final result. Previously, these updates were dropped.

## 5.1.21

### Patch Changes
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "preact-render-to-string",
"amdName": "preactRenderToString",
"version": "5.1.21",
"version": "5.2.0",
"description": "Render JSX to an HTML string, with support for Preact components.",
"main": "dist/index.js",
"umd:main": "dist/index.js",
29 changes: 23 additions & 6 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -19,7 +19,9 @@ const VOID_ELEMENTS = /^(area|base|br|col|embed|hr|img|input|link|meta|param|sou

const UNSAFE_NAME = /[\s\n\\/='"\0<>]/;

const noop = () => {};
function markAsDirty() {
this.__d = true;
}

/** Render Preact JSX + Components to an HTML string.
* @name render
@@ -124,8 +126,9 @@ function _renderToString(vnode, context, opts, inner, isSvgMode, selectValue) {
context,
props: vnode.props,
// silently drop state updates
setState: noop,
forceUpdate: noop,
setState: markAsDirty,
forceUpdate: markAsDirty,
__d: true,
// hooks
__h: []
});
@@ -134,7 +137,7 @@ function _renderToString(vnode, context, opts, inner, isSvgMode, selectValue) {
if (options.__b) options.__b(vnode);

// options._render
if (options.__r) options.__r(vnode);
let renderHook = options.__r;

if (
!nodeName.prototype ||
@@ -151,8 +154,20 @@ function _renderToString(vnode, context, opts, inner, isSvgMode, selectValue) {
: cxType.__
: context;

// stateless functional components
rendered = nodeName.call(vnode.__c, props, cctx);
// If a hook invokes setState() to invalidate the component during rendering,
// re-render it up to 25 times to allow "settling" of memoized states.
// Note:
// This will need to be updated for Preact 11 to use internal.flags rather than component._dirty:
// https://github.com/preactjs/preact/blob/d4ca6fdb19bc715e49fd144e69f7296b2f4daa40/src/diff/component.js#L35-L44
let count = 0;
while (c.__d && count++ < 25) {
c.__d = false;

if (renderHook) renderHook(vnode);

// stateless functional components
rendered = nodeName.call(vnode.__c, props, cctx);
}
} else {
// class-based components
let cxType = nodeName.contextType;
@@ -195,6 +210,8 @@ function _renderToString(vnode, context, opts, inner, isSvgMode, selectValue) {
: c.state;
}

if (renderHook) renderHook(vnode);

rendered = c.render(c.props, c.state, c.context);
}

44 changes: 43 additions & 1 deletion test/render.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { render, shallowRender } from '../src';
import { h, Component, createContext, Fragment, options } from 'preact';
import { useState, useContext, useEffect, useLayoutEffect } from 'preact/hooks';
import {
useState,
useContext,
useEffect,
useLayoutEffect,
useMemo
} from 'preact/hooks';
import { expect } from 'chai';
import { spy, stub, match } from 'sinon';

@@ -1052,12 +1058,48 @@ describe('render', () => {
});

it('should work with useState', () => {
let renders = 0;

function Foo() {
renders++;
let [v] = useState(0);
return <div>{v}</div>;
}

expect(render(<Foo />)).to.equal('<div>0</div>');
expect(renders).to.equal(1);
});

it('should re-render when useState setter is called during rendering', () => {
let renders = 0;

function Foo() {
renders++;
let [v, setV] = useState(0);
useMemo(() => {
setV(1);
}, []);
return <div>{v}</div>;
}

expect(render(<Foo />)).to.equal('<div>1</div>');
expect(renders).to.equal(2);
});

it('should re-render up to 25 times to allow useState settling', () => {
let renders = 0;

function Foo() {
renders++;
let [v, setV] = useState(0);
if (v < 30) {
setV(v + 1);
}
return <div>{v}</div>;
}

expect(render(<Foo />)).to.equal('<div>24</div>');
expect(renders).to.equal(25);
});

it('should not trigger useEffect callbacks', () => {