Converting a React Component to SolidJS
Tags: react, solidjs, tutorial, webdev
Published: 2022-06-03 11:40:42

Join me in this post as I migrate a React component with hooks to SolidJS.

I recently have been hearing more and more about [SolidJS](https://www.solidjs.com/) and after reading a bit about it and listening to several podcasts with its creator, [Ryan Carniato](https://github.com/ryansolid), I got really excited by what this Framework offers with a tingling feeling on my finger tips urging me to have a go at it.

Not to diss anyone, but it seems to me that React has become this complex state machine, with many patches when SolidJS appears to offer a simple approach which is very intuitive and makes a lot of sense right away. 
In this post I will attempt to take a simple React component, convert it to SolidJS and learn on the path if that’s really the case, and if it really shines where React does not.

The component I’m going to take is my dreadfully “skinny” Pagination component, which resides on my [@pedalboard/components](https://github.com/mbarzeev/pedalboard/tree/master/packages/components) package and looks like this:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m4mu4clli83h54caqdpn.png) 

It uses a Pagination hook which encapsulates the cursor and onChange callback logics. I believe that it is a good candidate to stretch SolidJS limits a bit from the usual “Hello World” examples.
Are we all set? Let’s get to it

***

I first go to the SolidJS docs and see what it takes to get things started. Looking at the “new docs” I’m going for the [JavaScript template](https://docs.solidjs.com/guides/getting-started-with-solid/installing-solid#javascript-template).
“Installing” everything using [degit](https://github.com/Rich-Harris/degit) (a tool for coping git repos by Rich Harris), I ran `yarn start` and I have a SolidJS application ready to go. It actually has the spinning logo much like the Create-React-App (CRA) and as I understand, Ryan is no shy about the inspiration he got from the framework.

For starters I like the fact that unlike CRA there aren’t a ton of configuration files when the project is set. Perhaps it is due to the fact the CRA supports much more features and boilerplate code, but I like the simplicity so far.

My Pagination component origin code can be found [here](https://github.com/mbarzeev/pedalboard/tree/master/packages/components/src/Pagination). So first thing I will do is create my component files structure:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b242mnygaect0qog34pa.png) 

My `Pagination.jsx` component has this code to begin with:

```javascript
const Pagination = () => {
   return <div>Pagination Component</div>;
};
 
export default Pagination;
```

And in the `App.jsx` I will remove all the initial code the scaffold comes with and place my component in there instead:

```javascript
import Pagination from './components/Pagination/Pagination';
 
function App() {
   return (
       <div>
           <Pagination />
       </div>
   );
}
 
export default App;
```

That’s a good start, Vite is truly blazing fast and I get my result in the browser swiftly - a mere text saying “Pagination component”. Moving on.

I’m copying the component content from my React component into the SolidJS one, without the usage of the Pagination hook yet. I just want to see if this compiles well. Here is the code now:

```javascript
const Pagination = (props) => {
   const {cursor, totalPages, goPrev, goNext} = {cursor: 0, totalPages: 10, goPrev: () => {}, goNext: () => {}};
   const buffer = new Array(props.pagesBuffer).fill(0);
   let bufferGap = 0;
   if (totalPages - cursor < buffer.length) {
       bufferGap = totalPages - cursor - buffer.length;
   }
 
   return (
       <div>
           <button onClick={goPrev} disabled={cursor === 0}>
               PREV
           </button>
           {buffer.map((item, index) => {
               const pageCursor = cursor + index + bufferGap;
               const className = pageCursor === cursor ? 'selected' : '';
 
               return pageCursor >= 0 && pageCursor < totalPages ? (
                   <span key={`page-${pageCursor}`} className={className}>
                       {` [${pageCursor}] `}
                   </span>
               ) : null;
           })}
           <button onClick={goNext} disabled={cursor === totalPages - 1}>
               NEXT
           </button>
       </div>
   );
};
 
export default Pagination;
```

```javascript
In our App.jsx code we will add the pagesBuffer, like so:

function App() {
   return (
       <div class={styles.App}>
           <Pagination pagesBuffer={5} />
       </div>
   );
}

And the result looks like this now:
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vuagy8ilx05quem7bjcq.png)

That’s not bad at all, right? No real changes to the code, which I consider some of the immediate advantages of SolidJS if you’re coming from a React background. The syntax stays the same for the most part.

Now we need to take care of what the hook is providing us, which is basically the whole cursor manipulation. Looking at the [hook’s code](https://github.com/mbarzeev/pedalboard/blob/61c43bed140cf0f162670a7b8c7da60fa3d84d15/packages/hooks/src/use-pagination-hook/index.js#L12), how do I migrate it to SolidJS?

I think it would be wise to start with the basic state it has and the methods which manipulate it. This is how the code look like in the origin hook:

```javascript
if (!totalPages) {
       throw new Error(NO_TOTAL_PAGES_ERROR);
   }
 
   const [cursor, setInternalCursor] = useState(initialCursor || 0);
 
   const setCursor = (newCursor) => {
       if (newCursor >= 0 && newCursor < totalPages) {
           setInternalCursor(newCursor);
       }
   };
 
   const goNext = () => {
       const nextCursor = cursor + 1;
       setCursor(nextCursor);
   };
 
   const goPrev = () => {
       const prevCursor = cursor - 1;
       setCursor(prevCursor);
   };
```

I will use SolidJS createSignal in order to create the cursor state. This means that in any place where I have a reference to the `cursor` I will need to change it to be `cursor()`.
I’m also removing the code which uses the hook, and so my SolidJS component looks like this now - 

```javascript
import {createSignal} from 'solid-js';
 
const Pagination = (props) => {
   if (!props.totalPages) {
       throw new Error(NO_TOTAL_PAGES_ERROR);
   }
 
   const [cursor, setInternalCursor] = createSignal(props.initialCursor || 0);
 
   const setCursor = (newCursor) => {
       if (newCursor >= 0 && newCursor < props.totalPages) {
           setInternalCursor(newCursor);
       }
   };
 
   const goNext = () => {
       const nextCursor = cursor() + 1;
       setCursor(nextCursor);
   };
 
   const goPrev = () => {
       const prevCursor = cursor() - 1;
       setCursor(prevCursor);
   };
 
   const buffer = new Array(props.pagesBuffer).fill(0);
   let bufferGap = 0;
   if (props.totalPages - cursor() < buffer.length) {
       bufferGap = props.totalPages - cursor() - buffer.length;
   }
 
   return (
       <div>
           <button onClick={goPrev} disabled={cursor() === 0}>
               PREV
           </button>
           {buffer.map((item, index) => {
               const pageCursor = cursor() + index + bufferGap;
               const className = pageCursor === cursor() ? 'selected' : '';
 
               return pageCursor >= 0 && pageCursor < props.totalPages ? (
                   <span key={`page-${pageCursor}`} className={className}>
                       {` [${pageCursor}] `}
                   </span>
               ) : null;
           })}
           <button onClick={goNext} disabled={cursor() === props.totalPages - 1}>
               NEXT
           </button>
      </div>
   );
};
 
export default Pagination;
```

Let’s also add the CSS for this component so that we can see the current cursor, in `Pagination.css`:

```css
.selected {
   font-weight: bolder;
}
```

And import it to the component as style module

```javascript
import {createSignal} from 'solid-js';
import styles from './Pagination.css';
 
const Pagination = (props) => {
   if (!props.totalPages) {
	. . .
```

And we’re getting there:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cyc8du3yrppcqvj9txw5.png) 

But here is something interesting which represents one of the key differences between React and SolidJS - As you can see I’m calculating the `bufferGap` on each render of the React component so I will not end up with displaying less pages in the buffer than what the component is required to. 
In other words avoid this situation:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jdliw0ut5oool4219dg5.png) 

Where the result we want is this:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9k8v4tm8wt2tzb9b54md.png) 

The value which determines this behavior is the bufferGap and the reason we have this bug now is that SoliJS does not re-runs the component function over and over again forcing the bufferGap to recalculate according to the new state. It calls the component’s function just once.

So to solve that I create a new signal, called “bufferGap” and I use the createEffect SolidJS method to “listen” for changes over the cursor() and calculate the bufferGap accordingly:

```javascript
const [bufferGap, setBufferGap] = createSignal(0);
 
   createEffect(() => {
       let newBufferGap = bufferGap();
       if (props.totalPages - cursor() < buffer.length) {
           newBufferGap = props.totalPages - cursor() - buffer.length;
       }
       setBufferGap(newBufferGap);
   });
```

Notice that I don’t need to put anything in a dependency array - Solid knows to inspect the function body and when it detects a signal in it (like our cursor) it will know to invoke this method again when it changes.
Down the code I’m using my newly created state, like so:

```javascript
const pageCursor = cursor() + index + bufferGap();
```

I could do this with the [Derived State](https://docs.solidjs.com/guides/getting-started-with-solid/adding-interactivity-with-state#derived-state) capability of solid, but in my particular case having it like this ensures that the bufferGap calculation will be called just once for each time the cursor changes.

Moving forward we would like our component to invoke an `onChange` callback when the cursor changes with the new cursor as an argument.

I’m creating another effect which will invoke the onChange callback when ever the cursor changes (I could probably consolidate it with the previous createEffect but I like the separation here better):

```javascript
createEffect(() => {
       props.onChange?.(cursor());
   });
```

And in the application using this component I add the actual callback:

```javascript
<Pagination
               totalPages={10}
               pagesBuffer={5}
               onChange={(newCursor) => console.log('newCursor :>> ', newCursor)}
           />
```

This cannot be any simpler, right?
Yes, but we have an issue here - when the components first renders it calls the onChange callback, though there was no real change, and we solved that issue in the React component using a ref which indicates if the hook is initializing, which then means it does not need to trigger the callback, but how do we solve it here?

turns out there is a great API called "on" for SolidJS which allows to invoke a callback function once a signal has changed. The really cool thing about it is that it can be deferred and not invoke the function when the value is first set.
Here how it will look in the code:

```javascript
createEffect(on(cursor, (value) => props.onChange?.(value), {defer: true}));
```
Thanks @uminer for this great advice!

We’ve reached a fine milestone here. We have a Pagination component in SolidJS which does exactly what our origin React component did, but with a slight difference - 
We don’t have the cursor logic represented as a reusable hook. Can we do that in SolidJS?

Let’s extract it all to a function:

```javascript
function paginationLogic(props) {
   if (!props.totalPages) {
       throw new Error(NO_TOTAL_PAGES_ERROR);
   }
 
   const [cursor, setInternalCursor] = createSignal(props.initialCursor || 0);
 
   const setCursor = (newCursor) => {
       if (newCursor >= 0 && newCursor < props.totalPages) {
           setInternalCursor(newCursor);
       }
   };
 
   const goNext = () => {
       const nextCursor = cursor() + 1;
       setCursor(nextCursor);
   };
 
   const goPrev = () => {
       const prevCursor = cursor() - 1;
       setCursor(prevCursor);
   };
 
   createEffect(on(cursor, (value) => props.onChange?.(value), {defer: true}));
 
   return {
       cursor,
       totalPages: props.totalPages,
       goNext,
       goPrev,
   };
}
```

And our component will use it like so:

```javascript
const Pagination = (props) => {
   const {cursor, totalPages, goNext, goPrev} = paginationLogic(props);
   const buffer = new Array(props.pagesBuffer).fill(0);
 
   const [bufferGap, setBufferGap] = createSignal(0);
 
   createEffect(() => {
       let newBufferGap = bufferGap();
       if (props.totalPages - cursor() < buffer.length) {
           newBufferGap = props.totalPages - cursor() - buffer.length;
       }
       setBufferGap(newBufferGap);
   });
 
   return (
       <div>
           <button onClick={goPrev} disabled={cursor() === 0}>
               PREV
           </button>
           {buffer.map((item, index) => {
               const pageCursor = cursor() + index + bufferGap();
               const className = pageCursor === cursor() ? 'selected' : '';
 
               return pageCursor >= 0 && pageCursor < totalPages ? (
                   <span key={`page-${pageCursor}`} className={className}>
                       {` [${pageCursor}] `}
                   </span>
               ) : null;
           })}
           <button onClick={goNext} disabled={cursor() === totalPages - 1}>
               NEXT
           </button>
       </div>
   );
};
```

This is exactly like a React hook!
I can now take this function, export it as a separated module, and have it reused across my components and applications.
This is freaking awesome!

## Wrapping up
So here we have it - we took a React component which uses a hook and converted it to Solid JS in what appears to be very a intuitive, and above all, simple process
I’m really excited about SolidJS - the fact that it is very small in size, performant while going back to the roots of web development makes it a good candidate for being the next evolution in Frontend development IMO. I know that there are still a lot of aspects which React covers which SolidJS still needs to catch up with, but SolidJS comes with the right approach to matters as I see it.

As always if you have any comments on how this can be done better or questions be sure to leave them in the comments below

*Hey! If you liked what you've just read check out <a href="https://twitter.com/mattibarzeev?ref_src=twsrc%5Etfw" class="twitter-follow-button" data-show-count="false">@mattibarzeev</a><script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> on Twitter* :beers: