Skip to main content

React Components

React allows you to break a UI down to independent, reusable chunks - components.

Example: The following website could be broken into the following components:

  • App, which represents your main application and will be the parent of all other components.
  • Navbar, which will be the navigation bar.
  • MainArticle, which will be the component that renders your main content.
  • NewsletterForm, which is a form that lets a user input their email to receive the weekly newsletter.

I. Create Components

1. JSX

React components can be written in both regular JavaScript and JSX, but JSX makes writing components much more intuitive and readable. JSX is a syntax extension for JavaScript that lets you write HTML-like markup inside a JavaScript file.

React projects defaults to JSX.

  • When using pure JavaScript, we use the React createElement function. This function creates a React element, which is a plain object.
// Without JSX (Pure JavaScript)
function Welcome(props) {
return React.createElement(
'div',
null,
'Hello, ',
props.name
);
}
// With JSX
function Welcome(props) {
return (
<div>
Hello, {props.name}
</div>
);
}

Under the hood, JSX actually gets converted into regular JavaScript (like the first example) during the build process. This conversion is handled by a tool called Babel.

When you create a new React project (whether with Vite or another tool), it automatically includes Babel.

  • Babel knows how to handle JSX regardless of the file extension. This is why you'll see both .js and .jsx files containing JSX code in React projects.
  • Using .jsx for files containing JSX has become a common convention because it makes it immediately clear that the file contains JSX code.

JSX Syntax and Rules

Resources

1. Return a single root element

A component returns a single root element. To return multiple elements in a component, we can either:

**Example: **
  • Correct Syntax:
function App() {
// Could replace <></> with <div></div>
return (
<>
<h1>Example h1</h1>
<h2>Example h2</h2>
</>
);
}
  • Incorrect Syntax:
function App() {
return (
<h1>Example h1</h1>
<h2>Example h2</h2>
);
}
2. Close ALL tags

In HTML, many tags are self-closing and self-wrapping. In JSX however, we must explicitly close and wrap these tags.

Example:
  • Correct Syntax:
function App() {
return (
<>
<input />
<li></li>
</>
);
}
  • Incorrect Syntax:
function App() {
return (
<>
<input>
<li>
</>
);
}
3. camelCase attributes

JSX turns into JavaScript, and attributes of elements become keys of JavaScript objects, so you can’t use dashes or reserved words such as class.

Example
  • Correct Syntax:
function App() {
return (
<div className="container">
<svg>
<circle cx="25" cy="75" r="20" stroke="green" strokeWidth="2" />
</svg>
</div>
);
}
  • Incorrect Syntax:
function App() {
return (
<div class="container">
<svg>
<circle cx="25" cy="75" r="20" stroke="green" stroke-width="2" />
</svg>
</div>
);
}

2. React Module System - export & import

React applications are built by combining multiple components, which are typically stored in separate files. To make these components work together, we need to understand JavaScript's module system, which uses import and export statements.

There are two main ways to export components in React:

// Default Export (UserProfile.jsx)
function UserProfile() {
return <div>User Profile Component</div>;
}

export default UserProfile; // Only one default export per file
// Named Export (Button.jsx)
export function PrimaryButton() {
return <button className="primary">Click Me</button>;
}

export function SecondaryButton() {
return <button className="secondary">Click Me</button>;
}

When importing these components, we use corresponding import syntax:

  • File extensions (.jsx or .js) are optional in imports.
// App.jsx
import UserProfile from './UserProfile'; // Default import
import { PrimaryButton, SecondaryButton } from './Button'; // Named imports

function App() {
return (
<div>
<UserProfile />
<PrimaryButton />
<SecondaryButton />
</div>
);
}

The name of the component in React should be capitalized. This is because when JSX is parsed, React uses capitalization to tell the difference between an HTML tag and an instance of a React component. e.g. Button component vs. button tag.

II. Rendering Components

1. JSX and JavaScript Expressions

Resources:

Curly braces {} in React create a "window" into JavaScript from within JSX, allowing you to embed expressions and dynamic values in your markup.

You can use curly braces in several ways:

1. For displaying variables
function Greeting() {
const name = "Sara";
return <h1>Hello, {name}!</h1>;
}
2. For expressions and calculations
<p>Total: ${price + (price * taxRate)}</p>
3. For component attributes/props:
<img src={imageUrl} alt="Profile" />
<Button disabled={!isVerified}>Edit Profile</Button>
4. For inline styles (using double curly braces):
<p style={{ color: "blue", fontSize: "16px" }}>Styled text</p>
5. For [[#2. Conditional Rendering|conditional rendering]]
<div>
{isLoggedIn ? <WelcomeMessage /> : <LoginButton />}
{hasMessages && <NotificationIcon />}
</div>

2. Rendering Lists

Resources:

a. Basic List Rendering

function App() {
return (
<div>
<h1>Animals: </h1>
<ul>
<li>Lion</li>
<li>Cow</li>
<li>Snake</li>
<li>Lizard</li>
</ul>
</div>
);
}

Instead of manually writing multiple elements like this, we can use JavaScript's map() to render lists. JSX can automatically render arrays of elements

1. Direct Mapping

In this example, both the key and the content of the <li> are using the same value from the animal variable in the map() function.

function App() {
const animals = ["Lion", "Cow", "Snake", "Lizard"];
return (
<div>
<ul>
{animals.map((animal) => <li key={animal}>{animal}</li>)}
</ul>
</div>
);
}
// OUTPUT
// First iteration
<li key="Lion">Lion</li>

// Second iteration
<li key="Cow">Cow</li>

// Third iteration
<li key="Snake">Snake</li>

// Fourth iteration
<li key="Lizard">Lizard</li>

Important Note: When using map() for rendering, each iteration must return a React element. The arrow function above implicitly returns the <li> element.

2. Separate Variable

Creating a separate variable to hold your mapped elements can make your JSX cleaner and allow you to transform your list before rendering.

In this approach, we process the array with map() first, storing the resulting array of React elements in a variable, and then include that variable in our JSX return statement.

function App() {
const animals = ["Lion", "Cow", "Snake", "Lizard"];
const animalsList = animals.map((animal) => <li key={animal}>{animal}</li>);
return (
<div>
<ul>{animalsList}</ul>
</div>
);
}

b. Component-Based List Rendering

Example: The App component is our top-level component (parent). It contains our source data - an array of animals: ["Lion", "Cow", "Snake", "Lizard"].

  • The App component passes this array to the List component through what we call a "[[02. React Components#IV. Props |prop]]" (short for property). When we write <List animals={animals} />, we're passing the animals array as as prop named “animals” to the List component.
// top-level component - parent
function App() {
const animals = ["Lion", "Cow", "Snake", "Lizard"]; // source data

// passes the array to List component
return (
<div>
<h1>Animals: </h1>
<List animals={animals} />
</div>
);
}

The List component receives these props and processes them:

  • It creates the <ul> (unordered list) container
  • It maps over each animal in the array, creating a ListItem component for each one. For each animal, it passes two props:
    • key: A special React prop used for optimization (required when mapping)
    • animal: The actual animal string that we want to display
// This is what props looks like inside the List component
props = {
animals: ["Lion", "Cow", "Snake", "Lizard"]
}
function List(props) {
return (
<ul>
{props.animals.map((animal) => {
return <ListItem key={animal} animal={animal} />;
})}
</ul>
);
}

The ListItem component just takes the animal it received as a prop and displays it inside an <li> (list item) tag.

// This is what props looks like inside the ListItem component
props = {
animal: "Lion",
// Note: key is a special prop that React uses internally
// and isn't actually accessible inside props
}
function ListItem(props) {
return <li>{props.animal}</li>
}

2. Conditional Rendering

Resources

a. Ternary Operator

One way to conditionally render an element is with a ternary operator, using a boolean value to decide what to render.

Example: We are using the String method startsWith to check if the animal starts with the letter L. This method either returns true or false.

function List(props) {
return (
<ul>
{props.animals.map((animal) => {
return animal.startsWith("L") ? <li key={animal}>{animal}</li> : null;
})}
</ul>
);
}

function App() {
const animals = ["Lion", "Cow", "Snake", "Lizard"];

return (
<div>
<h1>Animals: </h1>
<List animals={animals} />
</div>
);
}

b.. && operator

When using && for conditional rendering, don’t put numbers on the left side.

Example: If the result of the startsWith function is true, then it returns the second operand, which is the <li> element, and renders it. Otherwise, if the condition is false it just gets ignored.

function List(props) {
return (
<ul>
{props.animals.map((animal) => {
return animal.startsWith("L") && <li key={animal}>{animal}</li>;
})}
</ul>
);
}

function App() {
const animals = ["Lion", "Cow", "Snake", "Lizard"];

return (
<div>
<h1>Animals: </h1>
<List animals={animals} />
</div>
);
}

c. Conditional Statements

We can also use ifif/else, and switch to conditionally render something.

Example:
  • Check if the animals property exists
  • Check if the animals length is greater than 0
function List(props) {
if (!props.animals) {
return <div>Loading...</div>;
}

if (props.animals.length === 0) {
return <div>There are no animals in the list!</div>;
}

return (
<ul>
{props.animals.map((animal) => {
return <li key={animal}>{animal}</li>;
})}
</ul>
);
}

function App() {
const animals = [];

return (
<div>
<h1>Animals: </h1>
<List animals={animals} />
</div>
);
}

III. Keys

Resource

Keys are special identifiers that React uses to track individual elements in a list. They are not a normal prop that gets passed down to the component to use, it's a special attribute that React uses internally to keep track of elements, similar to how HTML elements have attributes like id or class.

// SYNTAX
<Component key={keyValue} />

// or
<div key={keyValue} />
function BookList() {
const books = [
{ id: "book_1", title: "React 101" },
{ id: "book_2", title: "JavaScript Basics" }
];

return (
<ul>
{books.map(book => (
// The key is like a sticker on the li element
// React uses this internally, but the ListItem component
// never sees or knows about this key
<li key={book.id}>
{/* Props are passed as data to the component */}
<ListItem title={book.title} />
</li>
))}
</ul>
);
}

When React updates this list, it uses those keys to efficiently track which elements need to change.

1. Rules and Best Practices

Keys must be unique among siblings (but can be repeated in different lists). If you are defining data yourself, it is good practice to assign a unique id to each item. You can use the crypto.randomUUID() function to generate a unique id.

// a list of todos, each todo object has a task and an id
const todos = [
{ task: "mow the yard", id: crypto.randomUUID() },
{ task: "Work on Odin Projects", id: crypto.randomUUID() },
{ task: "feed the cat", id: crypto.randomUUID() },
];

function TodoList() {
return (
<ul>
{todos.map((todo) => (
// here we are using the already generated id as the key.
<li key={todo.id}>{todo.task}</li>
))}
</ul>
);
}

2. Anti-pattern

Keys should never be generated on the fly. Using key={Math.random()} or key={crypto.randomUUID()} while rendering the list defeats the purpose of the key. They’re no longer consistent as now a new key will get created for every render of the list.

→ The key should be inferred from the data itself.

const todos = [
{ task: "mow the yard", id: crypto.randomUUID() },
{ task: "Work on Odin Projects", id: crypto.randomUUID() },
{ task: "feed the cat", id: crypto.randomUUID() },
];

function TodoList() {
return (
<ul>
{todos.map((todo) => (
// DON'T do the following i.e. generating keys during render
<li key={crypto.randomUUID()}>{todo.task}</li>
))}
</ul>
);
}

IV. Props

Resource

Props (short for properties) are a way to pass data from parent components to child components in React. They are read-only, meaning child components cannot modify the props they receive.

This data transfer is unidirectional, meaning it flows in only one direction (parent → child). Any changes made to this data will only affect child components using the data, and not parent or sibling components. If you need to modify data, it should be done in the parent component

// Basic syntax for passing props
<ComponentName propName={propValue} />

// Basic syntax for receiving props
function ComponentName(props) {
return <div>{props.propName}</div>;
}

// Alternate syntax using destructuring
function ComponentName({ propName }) {
return <div>{propName}</div>;
}

Props can be of any data type:

<MyComponent 
string="Hello" // String
number={42} // Number
boolean={true} // Boolean
array={[1, 2, 3]} // Array
object={{ key: "value" }} // Object
function={() => {}} // Function
/>

1. Basic Uses of Props

By using props, we account for any number of variations with a single component. This allows one component to become more versatile.

Example:
  • The Button functional component now receives props as a function argument. The individual properties are then referenced within the component via props.propertyName.
  • When rendering the Button components within App, the prop values are defined on each component.
  • Inline styles are dynamically generated and then applied to the button element.
function Button(props) {
const buttonStyle = {
color: props.color,
fontSize: props.fontSize + 'px'
};

return (
<button style={buttonStyle}>{props.text}</button>
);
}

export default function App() {
return (
<div>
<Button text="Click Me!" color="blue" fontSize={12} />
<Button text="Don't Click Me!" color="red" fontSize={12} />
<Button text="Click Me!" color="blue" fontSize={20} />
</div>
);
}

2. Prop Destructuring

A common pattern you will come across in React is prop destructuring. Unpacking your props in the component arguments allows for more concise and readable code.

Prop Structure

Props in React are passed as a single JavaScript object. When you write:

<Button 
text="Click Me!"
color="blue"
fontSize={12}
/>

React internally combines all these attributes into a single props object that looks like:

{
text: 'Click Me!',
color: 'blue',
fontSize = {12}
}

This props object is then passed as the first argument to your function component. That's why you can destructure it with:

function Button({ text, color, fontSize }) {   
// ...
}

Or access the properties using the props parameter:

function InfoSection(props) {
// Access as props.data and props.onTextChange
}
Example:
function Button({ text, color, fontSize }) {
const buttonStyle = {
color: color,
fontSize: fontSize + "px"
};

return <button style={buttonStyle}>{text}</button>;
}

export default function App() {
return (
<div>
<Button text="Click Me!" color="blue" fontSize={12} />
<Button text="Don't Click Me!" color="red" fontSize={12} />
<Button text="Click Me!" color="blue" fontSize={20} />
</div>
);
}

3. Default Props

We can define default parameters to set default values for props. This can help protect your application from undefined values and also avoid code repetition when creating components.

Example: We now only need to supply prop values to Button when rendering within App if they differ from the default values defined in the function parameters.

function Button({ text = "Click Me!", color = "blue", fontSize = 12 }) {
const buttonStyle = {
color: color,
fontSize: fontSize + "px"
};

return <button style={buttonStyle}>{text}</button>;
}

export default function App() {
return (
<div>
<Button />
<Button text="Don't Click Me!" color="red" />
<Button fontSize={20} />
</div>
);
}

You may also come across the use of defaultProps in some codebases. This was traditionally used to set default values for props, particularly in class components. While React now prefers the default parameter approach for function components, understanding defaultProps is still useful, especially when working with class components or older codebases.

function Button({ text, color, fontSize }) {
const buttonStyle = {
color: color,
fontSize: fontSize + "px"
};

return <button style={buttonStyle}>{text}</button>;
}

// defaultProps!!!
Button.defaultProps = {
text: "Click Me!",
color: "blue",
fontSize: 12
};

export default function App() {
return (
<div>
<Button />
<Button text="Don't Click Me!" color="red" />
<Button fontSize={20} />
</div>
);
}

4. Functions as props

In addition to passing variables through to child components as props, you can also pass through functions.

Example:
  • The function handleButtonClick is defined in the parent App component.
  • A reference to this function is passed through as the value for the handleClick prop on the Button component.
  • The function is received in Button and is called on a click event.
function Button({ text = "Click Me!", color = "blue", fontSize = 12, handleClick }) {
const buttonStyle = {
color: color,
fontSize: fontSize + "px"
};

return (
<button onClick={handleClick} style={buttonStyle}>
{text}
</button>
);
}

export default function App() {
const handleButtonClick = () => {
window.location.href = "https://www.google.com";
};

return (
<div>
<Button handleClick={handleButtonClick} />
</div>
);
}

Similar with event handlers, we only pass through a reference to handleButtonClick, not the actual function handleButtonClick() because we don’t want to call the function right away, but call it only when the button is clicked.