Knowledge Hub
In this practical guide to SolidJS, you will learn about SolidJS, its setup procedure, and file organization. You will discover how to add components, style, and manage state in a SolidJS app. Furthermore, you will explore how to fetch data from a server, pass data between components and navigate between pages.
You will build a simple movie app that displays lists of movies that allows searching and viewing details. You will utilize Open Movie Database (OMDb) API to get the movie data.
By the end of this article, You'll have an in-depth knowledge of how SolidJS functions and how to use it in rapidly developing effective and fast websites.
SolidJS is a modern declarative JavaScript library for UI component development. It offers a high-performance option for developing dynamic user interfaces because of its simple, effective, and quick design. Because SolidJS uses the reactive programming paradigm, components dynamically update in reaction to changes in their dependencies without the need for explicit data binding or labor-intensive DOM manipulation.
SolidJS's fine-grained reactivity mechanism is one of its standout characteristics. It ensures that the UI always represents the most current state of the application by updating the impacted components immediately whenever a dependent changes. By only updating the UI elements that are affected by changes, this reactive approach is much more efficient than other libraries and frameworks at updating the user interface.
SolidJS has a component-based design where UI components get packaged into independent functional units. Its components are flexible and adaptable since they may contain local state, props, and lifecycle methods. As the components are easily reusable across many sections of an app, it encourages code reuse and maintainability. These components may be constructed using declarative template-based syntax, which makes it simple to design UI elements using familiar HTML-like syntax and combine them with JavaScript logic to manage dynamic behavior.
SolidJS's capacity for modification and extension is another asset. SolidJS's robust and adaptable API makes it possible to customize and integrate it with other frameworks and packages. This library may be adapted to various project needs and development workflows thanks to its support for integration with already-existing tools and technologies. Because of this, SolidJS is a very flexible option for developing modern user interfaces in JavaScript.
SolidJS supports modern framework features such as:
It uses Vite as the principal building tool that conveniently packs your application's JavaScript.
You'll use a Vite template which has the necessary boilerplate code to create a SolidJS project.
Run the npx degit
command to clone the template from the repository solid/js/templates/js
to your project folder in a new terminal window:
npx degit solidjs/templates/js solid-movie
Replace solid-movie
with the relevant name for your application.
If you prefer to use TypeScript, run the following command instead:
npx degit solidjs/templates/ts solid-movie
The /js
template and /ts
template both produce the same results.
When the template has been cloned to your project folder, go to that directory and install the project's required dependencies:
cd solid-movie
npm install
Use the following command to launch the development server after the dependencies are successfully installed:
npm run dev
To access the SolidJS launch page, open your browser at http://localhost:3000/.
Here is a sample file structure for a SolidJS application:
my-solid-app
├── src/
│ ├── components/
│ ├── assets/
│ │ └── favicon.svg
│ ├── App.jsx
│ ├── App.module.css
│ ├── index.css
│ ├── index.jsx
│ └── logo.svg
├── index.html
├── jsconfig.json
├── vite.config.js
└── package.json
src
: you will place your application's source code in this directory. It might have subdirectories to arrange your resources, assets, and other pieces.assets
: static files used by the application, such as images, fonts, and other resources, can be placed in this directory. The assets can be arranged according to the type or function in the application.App.js
: the bootstrapping of the components and any global configurations or providers are done in this file, which also serves as the SolidJS application's entry point.Index.js
: this file is in charge of rendering the primary SolidJS application component, App.js, into the HTML document and attaching it to the DOM.vite.config.js
: it is a configuration file used by Vite, the default build tool suggested for usage with SolidJS.This gives a general understanding of the SolidJS file structure. Although the precise file structures may vary based on the size and complexity of your application, you can arrange your files in a way that makes sense for your particular use case.
In a SolidJS application, components serve as the basic building blocks and encapsulate the UI logic and state for individual sections. Components are defined as reactive functions, which update dynamically as their dependencies change, following the reactive programming paradigm.
Components in SolidJS are described as functions that return a UI template. These functions return JSX or plain HTML and accept reactive state and props as inputs. Functional components are reusable and maintainable due to their simplicity and ease of reasoning.
In the src
directory, create a components
folder that will house the necessary components for the app. Next, create three files for the components Movie-Card.jsx
, Movie-List.jsx
, and Movie-Details
.
Write the following code in the Movie-Card.jsx
file:
const MovieCard = () => {
return (
<div>
<img src="" />
<h2>Title</h2>
<p>Year</p>
<button>See details</button>
</div>
)
}
export default MovieCard
This code snippet defines the functional component MovieCard
. It returns a div element with an image, a title, the year, and a "See details" button. The MovieCard
component is exported at the end of the code so it can be imported and used in different parts of the application.
Now, in the Movie-List.jsx
file:
import MovieCard from "./Movie-Card";
const MovieList = () => {
return (
<>
<header>
<h1>Movie List</h1>
</header>
<MovieCard />
</>
)
}
export default MovieList
The MovieList
component is created. The component displays a movie card after importing the MovieCard component from "./Movie-Card". The title is shown in an h1
element inside the MovieList
component using a header
element.
It is possible to return several elements from a component without needing to wrap them in a container element by using the shorthand syntax of Fragments (<></>). Then the component is exported at the end of the code.
Finally, the Movie-Details.jsx
file:
const MovieDetails = () => {
return (
<div>
Movie Details
</div>
)
}
export default MovieDetails
The MovieCard
component is built with just one div
. It is exported, making it accessible across the app. Now import the components into the root component App.jsx
:
import MovieList from './components/Movie-List';
import MovieDetails from "./components/Movie-Details";
function App() {
return (
<>
<MovieList />
<MovieDetails />
</>
);
}
export default App;
You import the MovieList
and MovieDetails
components from their respective files.
Making interactive and aesthetically pleasing web applications requires styling.
These are just a few of the styling methods offered by SolidJS:
These methods for styling in SolidJS enable you to design well-structured, modular, and dynamic user interfaces.
In this application, you will make use of Solid-bootstrap in order to speed up the development process. Solid Bootstrap is a library of pre-made UI elements for Solid applications built on the Bootstrap framework. The components and style possibilities offered by Solid-Bootstrap are numerous.
To use it in your Solid project, execute the subsequent command to install solid-bootstrap using npm or yarn:
npm install solid-bootstrap
Solid Bootstrap offers pre-built CSS files that you may use to customize your application.
Add the CSS file to your index.html
file:
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC"
crossorigin="anonymous"
/>
Now your SolidJS components can import and utilize the solid-bootstrap
elements.
Proceed to style your components using solid-bootstrap
elements.
Update the MovieCard.jsx
file:
import { Button } from 'solid-bootstrap';
import { Card } from "solid-bootstrap";
const MovieCard = () => {
return (
<Card class='my-2'>
<Card.Img variant="top" src="" />
<Card.Body>
<Card.Title>Title</Card.Title>
<Card.Text>
Year
</Card.Text>
<Button variant="primary">See Details</Button>
</Card.Body>
</Card>
)
}
export default MovieCard
The components for the button and card are imported from solid-bootstrap. An image is displayed using the Card.Img
element, and the variant="top" parameter specifies that the picture should appear at the top of the card. A Card.Title
and a Card.Text
elements are found in the "Card.Body" element and are used to display the movie's title and its release year, respectively. A button is displayed last, and the variant="primary" prop specifies that the button should be blue.
Now update the MovieList
component:
import { Col, Form, Spinner, Row } from "solid-bootstrap";
import MovieCard from "./Movie-Card";
const MovieList = () => {
return (
<>
<header class="text-center">
<h1>Movie List</h1>
</header>
<section>
<Form.Group class="mb-3">
<Form.Control
type="search"
placeholder="Search movie"
/>
</Form.Group>
</section>
<section>
<Row>
<Col>
<MovieCard />
</Col>
</Row>
</section>
</>
)
}
export default MovieList
The Col
, Form
, Spinner
, and Row
elements from the solid-bootstrap
library are imported. There is a search input field and a section has a row of movie cards that are individually presented using the "Col" component in a column.
Then the MovieDetails.jsx
component:
import { Card } from 'solid-bootstrap';
const MovieDetails = () => {
return (
<>
<section>
<h1>Title</h1>
<Card>
<Card.Img variant="top" src="" />
<Card.Body>
<Card.Text>Plot</Card.Text>
<Card.Text>Year</Card.Text>
</Card.Body>
</Card>
<br />
<Card>
<Card.Body>
<Card.Text>Director</Card.Text>
<Card.Text>Stars</Card.Text>
<Card.Text>Rating</Card.Text>
</Card.Body>
</Card>
</section >
</>
)
}
export default MovieDetails
An h1
tag can be found in the component's return statement. It also has two Card
components, the first of which has two Card.Text
components and a Card.Img
component for the display of the movie's plot and its year of release. The director, stars, and rating of the film are displayed using three Card.Text
components found in the second Card
component.
State management is a vital component of developing contemporary web applications, and SolidJS provides a variety of methods for doing so effectively. The reactive state, which enables developers to create and manipulate application states reactively, is the basic idea behind SolidJS's state management system. With a reactive state, there is no need for manual DOM modification because the user interface is updated immediately anytime the underlying state changes.
SolidJS offers robust capabilities for managing states reactively and effectively, whether it be component-level state, props, context, or global state management via the store API.
The state is commonly managed in SolidJS using Signals
, which generates a reactive state value and a setter function that may be used to update the value. Solid's reactivity is built on Signals
. They include dynamic values - when you modify a signal's value, everything that relies on it is updated immediately. The initial value is the parameter used to construct the Signal
, and the return values are an array with two functions a getter and a setter. The first returned value is not the value itself, but rather a getter, a function that returns the current value.
Let's import createSignal
from solid-js
to create a signal and use it in the MovieList
component:
import { createSignal } from "solid-js";
import { Col, Form, Spinner, Row } from "solid-bootstrap";
import MovieCard from "./Movie-Card";
const MovieList = () => {
const [movies, setMovies] = createSignal([]);
const [searchWord, setSearchWord] = createSignal('men');
const [loading, setLoading] = createSignal(false);
return (
<>
<header class="text-center">
<h1>Movie List</h1>
</header>
<section>
<Form.Group class="mb-3" >
<Form.Control
type="search"
placeholder="Search movie"
value={searchWord()}
/>
</Form.Group>
</section>
<section>
{loading() && (
<div class="text-center">
<Spinner animation="border" role="status" variant="primary">
<span class="visually-hidden">Loading...</span>
</Spinner>
</div>
)}
<Row>
<Col>
<MovieCard />
</Col>
</Row>
</section>
</>
)
}
Utilizing the createSignal
function from SolidJS defines three signals
:
movies
: an empty array that will be used to store the list of movies that was fetched from an external API.searchWord
: a string that has the value "men" as its starting value and represents the user's current search query.loading
: a boolean that, by default, is initialized to false and determines whether the component is presently loading data.The value of the search input field is currently set using searchWord()
. Depending on whether the loading
signal is true
, the movie list section conditionally displays a spinner.
In a SolidJS application, fetching data entails sending asynchronous queries to a remote API or server, that are subsequently used to update the user interface (UI). You will use either built-in browser APIs like fetch
or third-party libraries like Axios
since SolidJS lacks built-in data fetching functionality. It's crucial to adhere to best practices for handling errors, handling retries, and controlling the lifecycle of the fetch operation while retrieving data in a SolidJS application.
In the MovieList
component, you will use the fetch
API to retrieve movies from OMDb. To receive an API key that will be required while making API requests, you must register on the website.
Create a ".env" file at the root of your app to keep this API key as an environment variable. The environment variables stored in the .env
file are loaded by Vite by use of dotenv. Add the values in a variable in the .env
file like follows:
VITE_API_KEY=YOUR_API_KEY
The function that will make the API calls will be created in a createEffect
function.
Effects are intended for side effects that read the reactive system but do not write to it. Effects are common ways to make code segments run whenever dependencies change, such as when manually altering the DOM or making API calls.
createEffect
provides a new calculation that runs the supplied function in a tracking scope while continuously tracking its dependencies and rerunning the function whenever the dependencies change.
Updating the MovieList.jsx
component:
import { createSignal, createEffect } from "solid-js";
...
const apiKey = import.meta.env.VITE_API_KEY;
const MovieList = () => {
...
createEffect(() => {
const getMovies = async () => {
setLoading(true)
const res = await fetch(`http://www.omdbapi.com/?s=${searchWord()}&page=1&type=movie&apikey=${apiKey}`)
const data = await res.json();
setMovies(data.Search)
setLoading(false)
}
getMovies();
})
...
}
First, import the createEffect
to use it. Environment variables declared in .env
files at build time are used to define the apiKey
constant. It guarantees that the API key is kept private and hidden from view in the client-side code.
The effect gets triggered when the component renders and runs the asynchronous function getMovies()
. Following that, a GET
request is sent with the search criteria and API key supplied to the OMDB API. The res.json()
method is used to parse the response data, and the output is converted into a JavaScript object. The setMovies()
function stores the search array in the component's state. In order to signal that the process is complete, setLoading(false)
is called at the end.
Control flow is a crucial idea in programming that enables programmers to execute code only when specific conditions are met or to carry out repetitive activities. Reactive programming techniques and specific directives for conditional rendering and iteration are used in SolidJS to accomplish control flow. These capabilities offer a powerful and adaptable method for handling conditional logic and dynamic rendering in SolidJS applications.
The for
directive in SolidJS allows you to iterate over arrays or other iterable data and dynamically render items for each entry in the array.
To use the for
directive, you have to import it first in the MovieList.jsx
component:
import { createSignal, createEffect, For } from "solid-js";
const MovieList = () => {
...
return (
...
{movies() && movies().length > 0 ? (
<Row>
<For each={movies()}>{(movie) =>
<Col sm={12} md={3}>
<MovieCard key={movie.imdbID} movie={movie} />
</Col>
}
</For>
</Row>
) : (
<p>No movies found for the given search query.</p>
)}
...
)
}
...
The code determines whether movies()
is truthy (not null, undefined, or empty) and checks whether the length of the movies()
array is greater than zero. If both requirements are satisfied, it renders a Row
component with a loop using the For
directive from SolidJS.
It renders a MovieCard
component wrapped in a Col
component with given column widths for various screen sizes (sm: 12 columns for small screens, md: 3 columns for medium screens) for each movie object in the movies()
array. Employing conditional rendering, a p
tag is used to display a message if the movies
array is empty.
Props are used to facilitate data flow between components. Props are values or objects that are sent from a parent component to a child component. They can be defined in a child component and used to access data coming from the parent component.
In the MoiveList
component, you will pass a movie item as a prop to the MovieCard
component:
...
<MovieCard key={movie.imdbID} movie={movie} />
...
For efficient rendering in SolidJS, the key
prop is set to movie.imdbID
to provide a distinct identity for each MovieCard
component in the loop.
Then proceed to the MovieCard
component to receive the prop and utilize it.
...
const MovieCard = (props) => {
const { imdbID, Title, Year, Poster } = props.movie;
return (
<Card class='my-2'>
<Card.Img variant="top" src={Poster} />
<Card.Body>
<Card.Title>{Title}</Card.Title>
<Card.Text>
{Year}
</Card.Text>
<Button variant="primary">See Details</Button>
</Card.Body>
</Card>
)
}
...
This component receives a prop named movie
, which is an object that includes information about a movie, such as a title
, year
, and poster
.
The component then utilizes object destructuring to extract these properties and use them in the Bootstrap Card component.
This is what your app will look like:
SolidJS uses Solid Router to implement routing and navigation. Solid Router is a multipurpose router for SolidJS. Iit functions whether rendering is on the client or the server. It draws on and integrates the React Router and Ember Router principles. Route definitions can be made explicitly in the JSX template for your app, but you can also pass your route information as an object. It allows hierarchical routing, allowing navigation to modify only a portion of a component rather than entirely replacing it. You can utilize it with suspense, resources, and lazy components because it supports all of Solid's SSR methods and has Solid's transitions built in.
Installing a solid-router
is required in order to use it. It may be installed using a package manager like npm or yarn.
npm install @solidjs/router
Now you can proceed to set up the application's routes.
Open the src/index.jsx
file, import and wrap your root component within the Router
component:
import { Router } from "@solidjs/router";
...
render(() =>
<Router>
<App />
</Router>
, root);
The Router
gives you a context that enables you to display the routes throughout the app. Now, open the src/App.jsx
file to configure app routes:
import { Routes, Route, A } from "@solidjs/router"
import { Navbar, Container } from 'solid-bootstrap';
import MovieList from './components/Movie-List';
import MovieDetails from "./components/Movie-Details";
function App() {
return (
<>
<Container>
<Navbar bg="light" expand="lg">
<Navbar.Brand>
<A href="/">Solid-Movie</A>
</Navbar.Brand>
</Navbar>
<Routes>
<Route path="/" component={MovieList} />
<Route path="/movie-details/:id" component={MovieDetails} />
</Routes>
</Container>
</>
);
}
export default App;
First, The Routes
and Route
components from the @solidjs/router
library are imported.
This code leverages the Solid Bootstrap Container
component to enclose and add padding to the application's content. A navigation bar is made using the Navbar
component from the same package. The Routes
component defines the routes, while the Route
component specifies the path and the associated component to render. To make sure that the navigation is handled by the router, @solidjs/router supplies the A
component that should be used in place of the standard anchor tag.
The root path /
is the first route specified, and it renders the MovieList
component. The /movie-details/:id
path, which is the second declared route, renders the MovieDetails component. The colon (:) in the path denotes that a dynamic parameter (the movie id) will be passed.
You have to set up the "See details" button to navigate to the MovieDetails
page.
...
import { A } from "@solidjs/router";
...
const MovieCard = (props) => {
...
<A class='text-white text-decoration-none' href={`/movie-details/${imdbID}`}>
See Details
</A>
...
}
The link to the movie details page is made using an A
component from the router library with the href property pointing to /movie-details/imdbID
.
Then finishing up on the MovieDetails.jsx
:
import { createSignal, createEffect } from "solid-js";
import { Badge, Card, Spinner } from 'solid-bootstrap';
import { useParams } from "@solidjs/router";
const apiKey = import.meta.env.VITE_API_KEY;
const MovieDetails = () => {
const id = useParams().id;
const [movieDetail, setMovieDetail] = createSignal({});
const [loading, setLoading] = createSignal(false);
createEffect(() => {
const getMovieDetails = async () => {
setLoading(true)
const res = await fetch(`https://www.omdbapi.com/?i=${id}&apikey=${apiKey}`)
const data = await res.json();
setMovieDetail(data);
setLoading(false)
}
getMovieDetails();
})
return (
<>
<section>
{loading() && (
<div class="text-center">
<Spinner animation="border" role="status" variant="primary">
<span class="visually-hidden">Loading...</span>
</Spinner>
</div>
)}
{movieDetail() && (
<>
<h1>{movieDetail().Title}</h1>
<Card>
<Card.Img class="img-fluid" variant="top" src={movieDetail().Poster} />
<Card.Body>
{movieDetail().Genre && (
movieDetail().Genre.split(", ").map((genre, index) => (
<Badge key={index} bg="secondary" class="me-1">{genre}</Badge>
))
)}
<Card.Text>
{movieDetail().Plot}
</Card.Text>
<Card.Text>
{movieDetail().Year}
</Card.Text>
</Card.Body>
</Card>
<br />
<Card>
<Card.Body>
<Card.Text><b>Director:</b> {movieDetail().Director}</Card.Text>
<Card.Text>
<b>Stars: </b>
{movieDetail().Actors && (
movieDetail().Actors.split(", ").map((actor, index) => (
<span key={index}>
{actor}
{index !== movieDetail().Actors.split(", ").length - 1 && ", "}
</span>
))
)}
</Card.Text>
<Card.Text><b>Rating:</b> {movieDetail().imdbRating}</Card.Text>
</Card.Body>
</Card>
</>
)}
</section>
</>
)
}
export default MovieDetails
The component imports the Badge
, Card
, and Spinner
components from Solid Bootstrap as well as the createSignal
and createEffect
utilities from the SolidJS library. Additionally, the useParams
method from the @solidjs/router
package is imported to take the id parameter out of the URL. useParams
obtains a reactive, store-like object that contains the Route's current route path parameters.
The component specifies the movieDetail
and loading
signals. loading
is a boolean that is set to true
while the API request is being made and set to false
when the request is finished. movieDetail
stores an object containing the details of the movie that were fetched from the API.
When the component gets rendered, the effect specified by the component is executed. The effect sends an asynchronous request to the OMDb API to retrieve the movie information associated with the URL's extracted id parameter. The loading
signal is set to false
and the movieDetail
signal is updated with the response data when the API response is received.
While the API request is being processed, the component displays a loading spinner. Once the API response has been received, the component displays the movie details.
The movie-details
page now looks like this:
In a SolidJS project, handling events involves capturing user interactions like button clicks, form submissions, and keyboard events and reacting to them with the required logic. SolidJS offers event bindings that capture several types of events, including onClick
, onChange
, onSubmit
, and others, which may be incorporated as attributes on JSX elements. With the aid of these event bindings, you can affix event handlers to specific elements.
Update the MovieList.jsx
:
...
const MovieList = () => {
...
const handleSearch = (event) => {
setSearchWord(event.target.value);
}
return (
...
<section>
<Form.Group class="mb-3" >
<Form.Control
type="search"
placeholder="Search movie"
value={searchWord()}
onChange={(event) => handleSearch(event)}
/>
</Form.Group>
</section>
...
)
}
...
The handleSearch
function serves as an event handler for the search input field's onChange
event, which modifies the searchWord
signal to reflect the most recent value that the user has entered. When the function is being executed, the effect automatically subscribes to any signals that are read and reruns whenever any of them change.
SolidJS is created to be performant and versatile. It includes a variety of optimization approaches to aid in enhancing your application's speed and effectiveness. Here are some best recommendations for improving SolidJS applications:
lazy
function to just load components or modules as needed.key
property - the ability of SolidJS to effectively update the DOM depends on the ability to uniquely identify elements in a list using the key
property. It's crucial to avoid using the index as the key and use a reliable, distinct key for each element.defer
and async
- until the subsequent render cycle, a function's execution can be postponed using SolidJS' defer
function. To run a function asynchronously, you can employ the async
function.Developers may optimize SolidJS applications to deliver a quick and effective user experience by adhering to these best practices.
SolidJS projects require several processes to build and deploy, including code bundling and compilation as well as optimization, deployment, and monitoring. All these processes are handled by Vite, a tool for building and deploying SolidJS apps. It is an effective tool that can assist developers in streamlining their development processes and creating high-performance SolidJS applications for use in real-world scenarios. With features like hot module replacement, improved production builds, and quick development server startup times, it is a fast and lightweight tool that provides a modern development experience.
When you're prepared to build the application for usage in production, execute the following command:
npm run build
It will start Vite's build process, which bundles the application code into optimal files prepared for production deployment. The dist
directory will be the default location for the stored files.
You can deploy the newly generated files to a hosting platform or production server once the build procedure is complete. It can entail deploying to a Platform as a Service (PaaS), employing a cloud-based hosting provider, or uploading the files to a web server.
After deploying the files, you can serve them using a web server or hosting provider of your choice. The server can be set up to serve the SolidJS application's entry point, the index.html file, and any additional static files generated during the building process.
It is also worth noting that SolidJS has a meta framework, SolidStart, which makes building web applications faster. SolidStart is a feature-rich and flexible framework for creating cutting-edge web apps using SolidJS. Developers looking to develop complex applications fast and effectively without sacrificing maintainability or scalability will find its feature set quite extensive and its API user-friendly. SolidStart helps build web applications in Solid.
In conclusion, SolidJS is a powerful and effective library that empowers programmers to create efficient and user-friendly web applications. You have covered every crucial SolidJS topic in this practical guide, from project setup through component creation, styling, and state management. Moreover, you have examined how to handle events, convey data between components, fetch data from a remote server, and best practices for optimization.
You will learn SolidJS' versatility and power as you continue to explore it and develop more complicated web applications. SolidJS is a valuable addition to your toolkit for web development projects due to its rising popularity and strong community support.
Github repo for the project: https://github.com/bejamas/solid-movie.