It is very sad that after 3 days trying I still fail to integrate Odometer js in Next js. I can't understand where I wrong in my code. Here is my code in CodeSandBox - https://codesandbox.io/s/long-moon-z9zqu
this is code-
export default function Home() {
const [odometerValue, setOdometerValue] = useState(0);
useEffect(() => {
setTimeout(() => {
setOdometerValue(300);
}, 1000);
}, []);
return (
<Odometer
value={odometerValue}
format="(,ddd)"
theme="default"
/>
);
}
The npm pacjage I use - https://www.npmjs.com/package/react-odometerjs
Please see the code and solve the problem who can. It will be very help full for me.
I think the problem is that it's loading the react-odometerjs
library dynamically, so the first time your Home
component gets rendered, the library isn't loaded yet. So it renders the loading
component of the dynamic
options which just shows a 0. Because it's the first render your useEffect
is run and that sets odometerValue
to 300.
A little bit later, the react-odometerjs
library is loaded which causes Home
to rerender. Home renders the Odometer
component, but now this renders the real Odometer
component instead of the loading
component so it will render like an odometer, but the value is set to 300 for the first load, so it just sits at 300.
If you add in a little delay before setting it to 300, then you can see it working. Here's an example:
export default function Home() {
const [odometerValue, setOdometerValue] = useState(0);
useEffect(() => {
setTimeout(() => {
setOdometerValue(300);
}, 1000);
}, []);
return (
<Odometer
value={odometerValue}
format="(,ddd)"
theme="default"
/>
);
}
The problem with using a timer for this is that it can take different amounts of time for the library to actually load. The reason you need to load this library using dynamic
is because you're using nextjs and it's doing Server Side Rendering (SSR) and when it does SSR it's rendering in nodejs which doesn't have the document
or window
globals defined and odometer.js
uses those.
So you have some choices:
setTimeout
thing and get some slightly annoying behavior if the library loads slowly.document
and window
and then you can just use import {Odometer} from 'react-odometerjs'
instead of using dynamic
.Hacky version to wait for library to load before setting odometerValue
:
let loadedCallback = null;
let loaded = false;
const Odometer = dynamic(async () => {
const mod = await import("react-odometerjs");
loaded = true;
if (loadedCallback != null) {
loadedCallback();
}
return mod;
}, {
ssr: false,
loading: () => 0
});
export default function Home() {
const [odometerLoaded, setOdometerLoaded] = useState(loaded);
const [odometerValue, setOdometerValue] = useState(0);
loadedCallback = () => {
setOdometerLoaded(true);
};
useEffect(() => {
if (odometerLoaded) {
setOdometerValue(1);
}
}, [odometerLoaded]);
useEffect(() => {
setOdometerValue(300);
}, [odometerValue]);
return (
<Odometer
value={odometerValue}
format="(,ddd)"
theme="default"
/>
);
}