Building User Interfaces with Components

What are components?

A component is a function that defines a piece of your user interface. They're the building blocks of a Solid app.

Let's build our first component. This component will display Hello World! inside a <div> element. To do this, we need to create a file called HelloWorld.jsx inside our src folder. Then copy the following contents:

tsx
function HelloWorld() {
return <div>Hello World!</div>;
}
tsx
function HelloWorld() {
return <div>Hello World!</div>;
}

There are a couple of things to note in our first component:

  • Our component name, HelloWorld, is title-cased (sometimes referred to as Pascal case), with Hello and World capitalized. This helps us differentiate components from other JavaScript functions.
  • Our component returns something that looks a lot like HTML, but isn't—it's JSX.

Using a component

Let's get this component on the screen. Export it from HelloWorld.jsx by replacing function with export function. Then, let's replace the boilerplate in App.jsx with code that imports and displays our component:

tsx
import { HelloWorld } from "./HelloWorld";
function App() {
return (
<div>
<h1>Welcome</h1>
<HelloWorld />
</div>
);
}
export default App;
tsx
import { HelloWorld } from "./HelloWorld";
function App() {
return (
<div>
<h1>Welcome</h1>
<HelloWorld />
</div>
);
}
export default App;

We used our component just like any other element: after importing the HelloWorld function, we can now use <HelloWorld />.

By building with components, we're able to separate and reuse code. Components let us create building blocks that we can put together to form new pieces.

What is JSX?

As we learned above, Solid components return JSX. JSX is an HTML-like syntax used in Solid JavaScript code to represent part of the Document Object Model (DOM). In our HelloWorld.jsx example above, we returned the following JSX from our component:

tsx
<div>Hello World!</div>
tsx
<div>Hello World!</div>

This JSX represents a <div> element with the text Hello World! inside. It's just as if we had written it in an HTML document. As a means of showing you the power and flexibility of Solid, let's add some inline styling to our <div>:

tsx
<div style="background-color: #2c4f7c; color: #FFF;">Hello world!</div>
tsx
<div style="background-color: #2c4f7c; color: #FFF;">Hello world!</div>

This will render a div with a blue background color and white text:

Hello world!

Trying to create a stylized div using JavaScript without JSX would be more complex:

ts
const div = document.createElement("div");
div.style["background-color"] = "#2c4f7c";
div.style.color = "#FFF";
ts
const div = document.createElement("div");
div.style["background-color"] = "#2c4f7c";
div.style.color = "#FFF";

JSX + JavaScript

JSX becomes even more powerful when used alongside JavaScript expressions. For example, we can use variables to represent some of the text content in our <div>:

tsx
const name = "Solid";
<div style="background-color: #2c4f7c; color: #FFF;">Hello {name}!</div>;
tsx
const name = "Solid";
<div style="background-color: #2c4f7c; color: #FFF;">Hello {name}!</div>;

Notice how we can add {name} where it used to say World. This JavaScript expression allows us to modify the content of our <div>:

Hello Solid!

We can take this a step further and specify our div's {style} attribute as a JavaScript object:

tsx
const name = "Solid";
const style = { "background-color": "#2c4f7c", color: "#FFF" };
<div style={style}>Hello {name}!</div>;
tsx
const name = "Solid";
const style = { "background-color": "#2c4f7c", color: "#FFF" };
<div style={style}>Hello {name}!</div>;

With JSX, we have code that lets us take advantage of the dynamic nature of JavaScript with a declarative HTML-like syntax.

As we progress through these tutorials, we'll discover how Solid's reactive system combines with JSX to improve the experience of crafting user interfaces.

Let's update our initial HelloWorld component with what we learned in our JSX exploration:

tsx
export function HelloWorld() {
const name = "Solid";
const style = { "background-color": "#2c4f7c", color: "#FFF" };
return <div style={style}>Hello {name}!</div>;
}
tsx
export function HelloWorld() {
const name = "Solid";
const style = { "background-color": "#2c4f7c", color: "#FFF" };
return <div style={style}>Hello {name}!</div>;
}

Using fragments

One rule when using JSX is that a component function must return a single element. For example, the following is valid JSX because there is a single <div /> that contains the rest of the JSX:

tsx
<div>
<h1>Welcome</h1>
<HelloWorld />
</div>
tsx
<div>
<h1>Welcome</h1>
<HelloWorld />
</div>

But the following is not valid JSX because there is no single containing element:

tsx
<h1>Welcome</h1>
<HelloWorld />
tsx
<h1>Welcome</h1>
<HelloWorld />

However, we may not actually want a div surrounding this code when it's rendered to the DOM. JSX fragments give us a way to write valid JSX and also not actually render a containing element to the DOM:

tsx
<>
<h1>Welcome</h1>
<HelloWorld />
</>
tsx
<>
<h1>Welcome</h1>
<HelloWorld />
</>

JSX is compiled

JSX is syntax on top of JavaScript and is not directly part of any JavaScript standard. Therefore, JSX can't be run directly in the browser (or most other JavaScript runtimes). Solid and other frameworks that use JSX require their code to be run through a compiler. If you're using a starter template you don't have to worry about this—everything will just work out of the box.

Note

Remember how we discussed that component function names are title-cased? This is so the compiler understands the difference between native DOM elements (like div) and custom components (like HelloWorld).

Mounting Solid to the DOM

How does our App component make it on to the actual HTML page that we show our users?

To add a Solid application to an HTML document, we first designate a containing element in our HTML and assign it an id. That usually looks like this:

html
<body>
<div id="root"></div>
</body>
html
<body>
<div id="root"></div>
</body>

Then, we call Solid's render function, passing in our App component and this element:

jsx
render(() => <App />, document.getElementById('root'));
jsx
render(() => <App />, document.getElementById('root'));

See how this plays out in our project in the index.html file, which creates the div, and src/index.jsx, which calls that render function:

jsx
import { render } from 'solid-js/web';
import './index.css';
import App from './App';
render(() => <App />, document.getElementById('root'));
jsx
import { render } from 'solid-js/web';
import './index.css';
import App from './App';
render(() => <App />, document.getElementById('root'));

Note

You'll want to make sure the first argument passed to the render function is a function that returns a component () => <Component /> rather than just the component itself.

Composing multiple components together

In most cases, our applications are big enough that we will want to split them into multiple components. Let's move on from our HelloWorld example to build something a little bigger—a virtual bookshelf. Our bookshelf will allow us to add books, mark books as "read", and eventually fetch book titles from other services on the Internet.

Given the intuitive nature of JSX, we can plan out our application a bit. Our main Bookshelf will comprise two top-level components:

  • A <BookList /> component, which lists all the books in our bookshelf
  • An <AddBook /> component, which will allow us to add a book to our bookshelf

While we'll eventually want to make our bookshelf interactive, let's first create non-interactive versions of each component. First, we can make the BookList component inside and list a couple of books alongside their authors:

jsx
export function BookList() {
return (
<ul>
<li>
Code Complete{" "}
<span style={{ "font-style": "italic" }}>(Steve McConnell)</span>
</li>
<li>
The Hobbit{" "}
<span style={{ "font-style": "italic" }}>(J.R.R. Tolkien)</span>
</li>
</ul>
);
}
jsx
export function BookList() {
return (
<ul>
<li>
Code Complete{" "}
<span style={{ "font-style": "italic" }}>(Steve McConnell)</span>
</li>
<li>
The Hobbit{" "}
<span style={{ "font-style": "italic" }}>(J.R.R. Tolkien)</span>
</li>
</ul>
);
}

Next, we create an AddBook component. This will be a form that eventually allows us to add a book to our BookList list.

tsx
export function AddBook() {
return (
<form>
<div>
<label for="title">Book name</label>
<input id="title" />
</div>
<div>
<label for="author">Author</label>
<input id="author" />
</div>
<button type="submit">Add book</button>
</form>
);
}
tsx
export function AddBook() {
return (
<form>
<div>
<label for="title">Book name</label>
<input id="title" />
</div>
<div>
<label for="author">Author</label>
<input id="author" />
</div>
<button type="submit">Add book</button>
</form>
);
}

Finally, we can compose our BookList and AddBook components together. Let's create a Bookshelf component within App.jsx:

jsx
import { BookList } from "./BookList";
import { AddBook } from "./AddBook";
function Bookshelf() {
return (
<div>
<h1>My Bookshelf</h1>
<BookList />
<AddBook />
</div>
)
}
function App() {
return (
<Bookshelf/>
);
}
export default App;
jsx
import { BookList } from "./BookList";
import { AddBook } from "./AddBook";
function Bookshelf() {
return (
<div>
<h1>My Bookshelf</h1>
<BookList />
<AddBook />
</div>
)
}
function App() {
return (
<Bookshelf/>
);
}
export default App;

Component relationships

If a component includes another, we call it a parent component. In our Bookshelf example, the Bookshelf component is the parent of both BookList and AddBook because those two components are within Bookshelf. Accordingly, BookList and AddBook are children of Bookshelf. You may already be familiar with this terminology; it's how we tend to refer to HTML element relationships in our DOM tree.

Passing props between components

Components can communicate with their child components by passing properties, often shortened to props. To demonstrate the power of props, let's customize the bookshelf by adding a name. To do this, add a props parameter to the bookshelf function. Then, add a name attribute when we use the component:

tsx
import { BookList } from "./BookList";
import { AddBook } from "./AddBook";
function Bookshelf(props) {
return (
<div>
<h1>{props.name}'s Bookshelf</h1>
<BookList />
<AddBook />
</div>
);
}
function App() {
return (
<Bookshelf name="solid"/>
);
}
export default App;
tsx
import { BookList } from "./BookList";
import { AddBook } from "./AddBook";
function Bookshelf(props) {
return (
<div>
<h1>{props.name}'s Bookshelf</h1>
<BookList />
<AddBook />
</div>
);
}
function App() {
return (
<Bookshelf name="solid"/>
);
}
export default App;

Component functions only ever take one argument, if any: the props object. The properties on this object are set when we use the component and provide attributes.

Progress!

Thus far, we learned about Solid components, JSX, and built the foundation of a handy bookshelf app. Next, we'll bring our bookshelf to life using Solid's reactive system!