Headless node not running springs since v9
🐛 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 }
@joshuaellis Do you have any tips/suggestions to figure this out?
Can I get a bit more context, is this running in a test environment?
No need, just run the script above and you can reproduce... But yeah the actual use is in testing
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.
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.
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)
);
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.
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.