DEV Community

Kidus Adugna
Kidus Adugna

Posted on

Gotchas when learning SolidJS with (p)react background

I recently took on a new project and since I was following solid.js for a while, I thought I'd give it a try. And to reduce the time needed to develop the project I decided to port components from another project I built previously, using preact.

I replaced useState with createSignal or createStore, useEffect with createEffect, etc., with their appropriate syntax (no dependencies for createEffect etc). Since solid shares a lot with react, I thought these fixes were enough. Boy was I surprised. I'll list my findings below. I might be wrong on some details so feel free to correct me.

Solid doesn't like custom hooks exported as default

I had a custom hook to show a loader, whose state is controlled in a context.

// loader.jsx
export default function useLoading() {
    ...
    return () => {...}
}

// component.jsx
import useLoading from './loader.jsx'
...
const loading = useLoading()
loading(true)
...
loading(false)
Enter fullscreen mode Exit fullscreen mode

And the loader doesn't show. Then I copied over the hook definition to component.jsx and it worked. So I tried converting it to a named export, and it worked.

Using state outside of returned JSX is not reactive.

I think this is because components only run once, but
it took me hours to get this:

...
return state.loading && <Loader />
Enter fullscreen mode Exit fullscreen mode

What do you think happens when state.loading changes? Nothing. All changes must be inside the JSX for the component to be reactive.

...
return <>
  {state.loading && <Loader />}
</>
Enter fullscreen mode Exit fullscreen mode

Using For as a top-level return value with a changing list creates an infinite loop

Even when the For is inside a top-level fragment.

...
return <For each={state.list}>
  {...}
</For>
Enter fullscreen mode Exit fullscreen mode

Or

return <>
  <For each={state.list}>
    {...}
  </For>
</>
Enter fullscreen mode Exit fullscreen mode

Both of these create an infinite loop. It must be at least one level deep.

return <div>
  <For each={state.list}>
    {...}
  </For></div>
</div>
Enter fullscreen mode Exit fullscreen mode

As I said in the beginning, all of these may be due to my wrong understanding, but they did take a lot of hours for me to find, and I wanted to share them here. Thanks for reading.

Top comments (4)

Collapse
 
ryansolid profile image
Ryan Carniato • Edited

Thanks for the feedback. Only the second issue is actually by design for Solid.

The first issue is a limit to component identification heuristic for Hot Module Loading found in the Vite starter(github.com/solidjs/solid-refresh). We need to do better here. It's basically thinks the hook is a component when you put it as the default export in a .tsx/.jsx file. This is good to point out and will be improved in the future.

I am not aware of the 3rd issue. It sounds like a bug. Maybe also due to Hot Module Loading. I've never seen but maybe just by chance but works fine in things like the playground: https://playground.solidjs.com/?hash=1947242128&version=1.2.5. Feel free to report any bugs you find on github or drop them in the discord.

Collapse
 
k1dv5 profile image
Kidus Adugna

Thank you Ryan, that clears it up. I'll investigate the third point further, under dev server and built. Thanks for the feedback!

Collapse
 
johncarroll profile image
John Carroll • Edited

All changes must be inside the JSX for the component to be reactive.

I believe this is incorrect. Changes must be inside a reactive context for them to be reactive. JSX works, but I think the following does as well:

return createMemo(() => state.loading && <Loader />);
Enter fullscreen mode Exit fullscreen mode

I had some trouble with this at first as well. Someone (might have been Ryan) pointed out to me that, in React, everything in a component re-runs on every change unless you wrap it in a context that prevents it from re-running (e.g. put code inside useMemo()). In Solid, nothing in a component re-runs unless you wrap it in a context that tells it to re-run (e.g. by using createMemo() or by using a signal inside JSX).

Collapse
 
krischnagabriel profile image
Krischna Gabriel

I can confirm that is is false. Also a call to a memo MUST be inside a JSX-Component.