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.