react-spring icon indicating copy to clipboard operation
react-spring copied to clipboard

Headless node not running springs since v9

Open perrin4869 opened this issue 4 years ago • 7 comments

🐛 Bug Report

Upgrading to v9, I found that my tests broke due to react-spring no longer running the spring using api.start. In version 8 I could observe the spring using onFrame, but on version 9, onChange does no longer log out any changes.

To Reproduce

Use the following package.json:

{
  "name": "react-spring-v9-node",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@react-spring/web": "^9.2.4",
    "enzyme": "^3.11.0",
    "enzyme-adapter-react-16": "^1.15.6",
    "jsdom-global": "^3.0.2",
    "raf": "^3.4.1",
    "react": "^16.14.0",
    "react-dom": "^16.14.0",
    "react-spring": "^8.0.27"
  }
}

Then run node index.js, where node --version is v14.17.1 and index.js is:

const raf = require("raf");
require('jsdom-global')();

const { createElement } = require("react");
const { useSpring } = require("react-spring");
const { useSpring: useSpringV9 } = require("@react-spring/web");

const { configure, mount } = require('enzyme');
const Adapter = require('enzyme-adapter-react-16');

raf.polyfill(window);

configure({ adapter: new Adapter() });

const Animated = () => {
  const [props, api] = useSpring(() => ({ animated: 0 }));

  api({ to: { animated: 100 }, onFrame: console.log.bind(console, "log: ") });

  return null;
};

const AnimatedV9 = () => {
  const [props, api] = useSpringV9(() => ({ animated: 0 }));

  api.start({ to: { animated: 100 }, onChange: console.log.bind(console, "log: ") });

  return null;
};

mount(
  // createElement(Animated)
  createElement(AnimatedV9)
);

Expected behavior

The script above will exit upon execution. However, replacing createElement(AnimatedV9) with createElement(Animated) (which uses react-spring@8) will log out the spring values:

❯ node index.js
log:  { animated: 0 }
log:  { animated: 0.8644118349348153 }
log:  { animated: 4.807635439283771 }
log:  { animated: 10.807303068953578 }
log:  { animated: 18.87783916431733 }
log:  { animated: 26.526303182153942 }
log:  { animated: 34.64964740611867 }
log:  { animated: 42.00589749671479 }
log:  { animated: 49.32532416808604 }
log:  { animated: 55.651362830058844 }
log:  { animated: 61.7306036548083 }
log:  { animated: 67.14454407654506 }
log:  { animated: 71.9170083323622 }
log:  { animated: 75.85920723371791 }
log:  { animated: 79.51145062257827 }
log:  { animated: 82.49139913344797 }
log:  { animated: 85.22335901874538 }
log:  { animated: 87.5595800999991 }
log:  { animated: 89.54956494933978 }
log:  { animated: 91.0542828968757 }
log:  { animated: 92.59072657256282 }
log:  { animated: 93.67658011035464 }
log:  { animated: 94.83546182399901 }
log:  { animated: 95.65089276487488 }
log:  { animated: 96.34178101721983 }
log:  { animated: 96.95955334879957 }
log:  { animated: 97.4479544590839 }
log:  { animated: 97.90657077706825 }
log:  { animated: 98.2461428371077 }
log:  { animated: 98.54805845242963 }
log:  { animated: 98.78549732441688 }
log:  { animated: 98.99614430688358 }
log:  { animated: 99.16146829682802 }
log:  { animated: 99.31564274853503 }
log:  { animated: 99.42906498177014 }
log:  { animated: 99.52932985421667 }
log:  { animated: 99.60775838850813 }
log:  { animated: 99.67698982170593 }
log:  { animated: 99.73413674166994 }
log:  { animated: 99.78127797024494 }
log:  { animated: 99.81805779855361 }
log:  { animated: 99.84871024710793 }
log:  { animated: 99.87711934259488 }
log:  { animated: 99.8978974402699 }
log:  { animated: 99.91616829081985 }
log:  { animated: 99.93119342212317 }
log:  { animated: 99.94288275755817 }
log:  { animated: 99.9531489640262 }
log:  { animated: 99.96112994672845 }
log:  { animated: 99.96813432402656 }
log:  { animated: 99.97357588225063 }
log:  { animated: 99.97860092797345 }
log:  { animated: 99.98226348853653 }
log:  { animated: 99.98547392557019 }
log:  { animated: 99.98796512291308 }
log:  { animated: 99.99026301078469 }
log:  { animated: 99.99193601880665 }
log:  { animated: 99.9933227082189 }
log:  { animated: 99.99453673961659 }
log:  { animated: 99.99553082691293 }
log:  { animated: 99.99634464450102 }
log:  { animated: 99.99697515161799 }
log:  { animated: 99.99749724369704 }
log:  { animated: 99.99797800360793 }
log:  { animated: 99.99832745466344 }
log:  { animated: 99.99863299961548 }
log:  { animated: 99.99886952285156 }
log:  { animated: 99.99907626734945 }
log:  { animated: 100 }

perrin4869 avatar Sep 26 '21 11:09 perrin4869

@joshuaellis Do you have any tips/suggestions to figure this out?

perrin4869 avatar Oct 04 '21 04:10 perrin4869

Can I get a bit more context, is this running in a test environment?

joshuaellis avatar Oct 07 '21 14:10 joshuaellis

No need, just run the script above and you can reproduce... But yeah the actual use is in testing

perrin4869 avatar Oct 07 '21 15:10 perrin4869

It helps me to understand what you're doing and why so I can offer suggestions before I get to sitting down and looking at the issue.

So you're polyfilling raf, does this equate to true – typeof window != 'undefined'? If a spring isn't running it's probably because of the rafz package we have.

joshuaellis avatar Oct 07 '21 15:10 joshuaellis

So to add to this I would try:

import { raf as rafz } from '@react-spring/shared'
const raf = require("raf");

rafz.use(raf)

see if that works.

joshuaellis avatar Oct 07 '21 16:10 joshuaellis

Yeah, I am polyfilling window using jsdom, so typeof window != undefined. I tried adding the suggestion you made, but I could not observe any difference:

const raf = require("raf");
const { raf: rafz } = require("@react-spring/shared");
require('jsdom-global')();

const { createElement } = require("react");
const { useSpring } = require("react-spring");
const { useSpring: useSpringV9 } = require("@react-spring/web");

const { configure, mount } = require('enzyme');
const Adapter = require('enzyme-adapter-react-16');

rafz.use(raf);
raf.polyfill(window);

configure({ adapter: new Adapter() });

const Animated = () => {
  const [props, api] = useSpring(() => ({ animated: 0 }));

  api({ to: { animated: 100 }, onFrame: console.log.bind(console, "log: ") });

  return null;
};

const AnimatedV9 = () => {
  const [props, api] = useSpringV9(() => ({ animated: 0 }));

  api.start({ to: { animated: 100 }, onChange: console.log.bind(console, "log: ") });

  return null;
};

mount(
  // createElement(Animated)
  createElement(AnimatedV9)
);

perrin4869 avatar Oct 07 '21 17:10 perrin4869

OK, finally sat down to dive into this issue. I could fix it by wrapping api.start in useLayoutEffect in the V9 version of react-spring. Turns out that in v9 calling api.start synchronously doesn't seem to be supported, I am not sure if this is by design or if it's documented. Feel free to close this if no action is to take place! Thanks.

perrin4869 avatar Jun 08 '22 04:06 perrin4869

Picking this up again, this feels rather niche and i'm not sure it's a pattern we want to advocate as the animation would run on every render. I'm therefore going to close this.

joshuaellis avatar Apr 09 '23 08:04 joshuaellis