Appearance
Chapter 2 - Your First Component
The Items List
Let's now pretend we have been giving requirements for our app to have a component that displays a list of "items". We will keep this simple initially and as we move towards more advanced chapter expand on it to show how we can better structure our application to support:
- Quick prototyping and development using mocked data
- Component Organization
- Unit Testing
- State Management
- Internationalization support so we can render our user interface using different languages
- Localization for number and date formatting for different cultures
ItemsList Component Requirements
Your initial version of the ItemsList component, will have to implement the following requirements (later, in more advanced chapters, we will expand on these as we get into more advanced topics):
The component will display a list of items
An item will have 3 properties:
- id
- name
- selected
The item name will be displayed to the user
The user should be able to select/deselect one or more item
An icon will be shown next to the name to indicate if the item is selected
ItemsList Component Code
Within the src/components directory, create a sub-directory called items. Within this folder add a new file called ItemsList.component.tsx[1]
Your directory structure will now look like this:
Within the ItemsList.component.tsx file, paste the following code:
tsx
// example using const of type React.FC:
import React from 'react'
export const ItemsListComponent: React.FC<{
items: any[]
}> = (props) => {
return (
<div>
<h3>Items:</h3>
<ul>
{
props.items.map((item, index) => <li key={index}>{item.name}</li>
)
}
</ul>
</div>
)
}
A few things to notice here. There are different ways to create a React component. You could just return a function, a class, or like in the example above, a const of type React.FC[2]
NOTE: Deciding whether to use a function or a class might be a matter of personal preference, or just abiding the coding standard you have defined with your team in your organization. If you google React.FC vs class
you'll get several blogs/articles where it seems the majority of developers prefer pure function or classes, rather than React.FC. Going forward, I'll try to use classes or functions throughout the book and avoid React.FC (but there might be cases where I use any of the three)
When using a const of type React.FC, you will need to return the component html wrapped with parentheses. If using a class, you will need to implement a render function that returns the html.
For example, using the class syntax, the above component can be re-written as:
tsx
// example using class extending component
import React from 'react'
export class ItemsListComponent extends React.Component<{
items: any[]
}> {
constructor(props: {
items: any[]
}) {
super(props)
}
render(): React.ReactNode {
const { items } = this.props
return <div>
<h3>Items:</h3>
<ul>
{
items.map((item: any, index: number) => <li key={index}>{item.name}</li>)
}
</ul>
</div>
}
}
Please go ahead and replace the code with the above using the class syntax. Then save and verify everything still renders as before without error in the browser console.
Here is what we are doing int he component code above:
For our html, we are returning a <div> element containing:
- a <h3> element with hard-coded text just saying "Items:"
- a <ul> element with some code that will render all our items as <li> elements.
We use the JavaScript Array native map method to loop through the Items array and return an <li> element for each item in the array. The <li> element will display the item name in the browser. Note how we have to also specify the key attribute which is required to be unique within a list rendered by React. Here we leverage the fact the the map method returns the index of the item in the array as the second argument to our handler function (index). The index is good enough for now to use for the key attribute.
Note that with map you can either inline the return expression, thus not needing the keyword return:
tsx
items.map((item, index) => <li key={index}>{item.name}</li>)
Or you could use {} (curly braces) for the function body, and use the return keyword in this case:
tsx
items.map((item, index) => {
return <li key={index}>{item.name}</li>
})
Which syntax you use is up to your preference. However, remember that in a team, especialy in a large organization, there will be coding standards that will dictacte how you consistently write code. You should always abide the standard that you and your team have agreeed upon.
Note also that we declared the items property as an array of any[3] for now (later we'll replace any with an interface we'll create):
typescript
...
{
items: any[] // avoid using "any", in later chapters we'll replace with a TS interface
}
...
Main App View
Open the src/App.tsx file. Replace the entire existing code with this:
tsx
//file: src/App.tsx
// component:
function App() {
return (
<div>
...
</div>
);
}
export default App
Let's start by adding at the top an import to reference our ItemsList.component.tsx:
tsx
//file: src/App.tsx
markua-start-insert
// import reference to your ItemsList component:
import { ItemsListComponent } from './components/items/ItemsList.component'
markua-end-insert
...
For now, quickly mock some data for our list of items that we will feed to our ItemsListComponent. For this we instantiate a local variable called items and initialize it with some hard-coded data[4].
We do this before the App function declaration:
tsx
//file: src/App.tsx
// import reference to your ItemsList component:
import { ItemsListComponent } from './components/items/ItemsList.component'
markua-start-insert
// mock data:
const items: any[] = [{
id: 1,
name: 'Item 1'
}, {
id: 2,
name: 'Item 2'
}, {
id: 3,
name: 'Item 3'
}]
markua-end-insert
// component:
function App() {
...
Finally, we modify the content inside the <div> (within the return statement). Let just add our ItemsListComponent and pass the hard-coded items data to it through its property items.
The complete code within the App.tsx file should now look like this:
tsx
//file: src/App.tsx
// import reference to your ItemsList component:
import { ItemsListComponent } from './components/items/ItemsList.component'
// mock data:
const items: any[] = [{
id: 1,
name: 'Item 1'
}, {
id: 2,
name: 'Item 2'
}, {
id: 3,
name: 'Item 3'
}]
// component:
function App() {
return (
<div>
markua-start-insert
<ItemsListComponent items={items}/>
markua-end-insert
</div>
);
}
export default App
Update src/main.tsx by removing or commenting the imported index.css file:
tsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
// import './index.css' // <-- comment out or remove this line
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<React.StrictMode>
<App />
</React.StrictMode>
)
Save the file. The web browser will refresh and display our preliminary items list being rendered more or less like this:
Chapter 2 Recap
What We Learned
- How to create a basic component that displays a list of items
- How to consume that component from another component or view
Observations
- The items property within the ItemsList.component.tsx is declared as an array of type any
- The App.tsx view contains hard-coded data (items) which is also declared as an array of any
- This means we are not leveraging strong-type checking at development time using TypeScript interfaces/models/types
Based on these observations, there are a few improvements that we will make in the next chapters:
Improvements
- Create a TypeScript interface called ItemInterface for enforcing type checking at development time for our items data
- Update our code so it uses the new ItemInterface interface
We are following a file naming convention where higher level components' names are pascal-case and follow this format [ComponentName].component.tsx - Reference: Naming Conventions section at the end of this book ↩︎
React.FC ↩︎
With 'any', TypeScript does not enforce type-checking on a property or variable. However, this is considered a bad practice as we lose the main benefit of TypeScript. There might be exceptions to this rule when using older 3rd party packages/libraries/plugins that do not offer type definitions. However, even in those cases it would be strongly recommended to provide interfaces and types so that you can still avoid using 'any'. ↩︎
Note: using hard-coded data is a bad practice and here we are only doing it to first illustrate how things flow, and later in the next chapters will remove in favor of best practices and patterns (see Chapter 5) ↩︎