solidjs-community / solid-motionone

A tiny, performant animation library for SolidJS
MIT License
112 stars 3 forks source link

[BUG] Out of the box, cannot access lexical declaration 'root' before initialization #10

Open PauMAVA opened 2 months ago

PauMAVA commented 2 months ago

Hello!

I was trying to use this library in my SolidJS library project and I have found a problem. I tried to create a <Motion.div> element like this:

const appliedAnimation = {
    unmount: {
      opacity: 0,
    },
    mount: {
      opacity: 1,
    },
}
let [open, setOpen] = createSignal(true);
...
return (
    <>
      {open() && (
        <Motion.div
          {...rest}
          class={classes}
          initial={appliedAnimation.unmount}
          exit={appliedAnimation.unmount}
          animate={open() ? appliedAnimation.mount : appliedAnimation.unmount}
          transition={{ duration: 0.5 }}
        >
          {icon && iconTemplate}
          <span class={contentClasses}>{value}</span>
          {onClose && !action && <span>TODO</span>}
          {action || null}
        </Motion.div>
      )}
    </>
)

When I try to visualize the component I get the following error:

Uncaught ReferenceError: can't access lexical declaration 'root' before initialization
<anonymous> index.js:5450
createAndBindMotionState index.js:5434
runComputation index.js:184
updateComputation index.js:174
createEffect index.js:64
createAndBindMotionState index.js:5432
MotionComponent index.js:5450

Digging a little bit into solid-motionone I found that the conflicting line is inside the MotionComponent: https://github.com/solidjs-community/solid-motionone/blob/ba9005ffe20ae3b4327739c0e83ff0f1623cdb03/src/motion.tsx#L35-L45

As it can be seen the root variable is declared after it's reference into the arrow function? For me the transpiled code appears as:

var MotionComponent = (props) => {
  const [options, , attrs] = splitProps(props, OPTION_KEYS, ATTR_KEYS);
  const [state, style2] = createAndBindMotionState(() => root, () => ({
    ...options
  }), useContext(PresenceContext), useContext(ParentContext));
  let root;
  return createComponent(ParentContext.Provider, {
  ...

I'm quite new to solid-js, sorry if I'm missing something obvious here. I also tried to but the let root before the createAndBindMotionState call, and then the error is Animation state must be mounted with valid Element.

Finally here's a little bit of info about versions:

Thanks in advance! 😁

thetarnav commented 2 months ago

The code is correct you can use variables in callbacks that are declared below because it all depends on the order of execution if the callback is ran after the variable has been declared, it's all good Here, the ref needs to be assigned first before it can even be used so the placement of the variable in the component shouldn't matter Changing the order won't fix the code, just change the error message But what's weird is that the callback is being called immediately As you can see the element callback is ran in an effect, which should happen after the elements have been created and ref declared and assigned with the html element. Effects in solid are scheduled to run at the end of a internal queue, which is created by effects, roots, render calls, etc. If the effect executes immediately, it means that there in no such queue that is can attach to, it has to run as soon as it's created. If you don't have any other issues in you app, then my guess would be that your app and solid-motionone are using two different versions of solid at the same time, and the effects queue that solid in your app creates, is not the same that solid from solid-motion one expects. Thats just a quess but something like that is fairly common. Can you make sure that is not the case or provide a minimal repro to try out? You can confirm that this is an issue by doing something like this:

// somewhere in your app and in solid-motionone
import {createSignal} from "solid-js"
if (!window.createSignal) {
    window.createSignal = createSignal
} else {
    console.log(window.createSignal === createSignal ? "Good" : "Bad")
}
greypants commented 1 month ago

I've got the same issue. I confirmed both solid-motionone and the app are using the same version of solid-js using the method above.

I'm working on a component library, and the code runs fine in my demo app, but fails in another app. I'll post if I figure something out.

    "solid-js": "1.9.1",
    "solid-motionone": "1.0.2",
greypants commented 1 month ago

Figured out our root issue. The app with the failure had some weird setup logic around the initial render that was causing this Solid warning to log:

computations created outside a `createRoot` or `render` will never be disposed

This stack overflow post helped explain it.

Once I addressed the Solid warning, the above referenced issue went away.

greypants commented 1 month ago

It might be nice to add a check in the code for root to be defined, and to throw a more helpful error if it's missing.

thetarnav commented 4 weeks ago

I wouldn't mind having some nice error message that links to this issue during development

PauMAVA commented 3 weeks ago

If you don't have any other issues in you app, then my guess would be that your app and solid-motionone are using two different versions of solid at the same time, and the effects queue that solid in your app creates, is not the same that solid from solid-motion one expects. Thats just a quess but something like that is fairly common.

Indeed, this was the problem I was programming a component library and using solid-motionone on my playground project as well as in the component library project. This caused two different versions of solidjs to be running.

I wouldn't mind having some nice error message that links to this issue during development

This would be nice though, since I spent couple of hours trying to find the root cause of the problem. I totally understand this is not a solid-motionone issue but it would be a niceity plus. Maybe we could add the same check you advised me to use:

function checkDupedSolid() {
    if (!window._smoCheck ) {
    window._smoCheck = createSignal
    } else {
    if (window._smoCheck !== createSignal) {
            console.warn('There is more than one instance of solid running. This may cause errors in the use of this library.')
        }
        window._smoCheck = undefined;
    }
}

checkDupedSolid();

Thanks!