javascript-today

Components

React Components

Components are the building blocks of React applications. They let you split the UI into independent, reusable pieces.

What is a Component?

A component is a JavaScript function that returns JSX:

function Welcome() {
  return <h1>Hello, React!</h1>;
}

That’s it! A component is just a function that returns what to display.

Function Components

The modern way to create components (React 16.8+):

function Greeting() {
  return <h1>Hello!</h1>;
}

// Arrow function version
const Greeting = () => {
  return <h1>Hello!</h1>;
};

// Implicit return (single expression)
const Greeting = () => <h1>Hello!</h1>;

Component Names

Must start with a capital letter:

// ✅ Right - capital letter
function MyComponent() { }
const UserCard = () => { };

// ❌ Wrong - lowercase (React treats as HTML tag)
function myComponent() { }
const userCard = () => { };

Using Components

Use components like HTML tags:

function Welcome() {
  return <h1>Welcome!</h1>;
}

function App() {
  return (
    <div>
      <Welcome />
      <Welcome />
      <Welcome />
    </div>
  );
}

Self-closing if no children:

<Welcome />

With children:

<Welcome>
  <p>This is a child</p>
</Welcome>

Composing Components

Build complex UIs by nesting components:

function Header() {
  return (
    <header>
      <Logo />
      <Navigation />
    </header>
  );
}

function Logo() {
  return <img src="/logo.png" alt="Logo" />;
}

function Navigation() {
  return (
    <nav>
      <a href="/">Home</a>
      <a href="/about">About</a>
    </nav>
  );
}

function App() {
  return (
    <div>
      <Header />
      <MainContent />
      <Footer />
    </div>
  );
}

Component Organization

One component per file (recommended):

// components/Button.jsx
export default function Button() {
  return <button>Click me</button>;
}

// App.jsx
import Button from './components/Button';

function App() {
  return <Button />;
}

Multiple related components:

// components/UserProfile.jsx
function Avatar({ src }) {
  return <img src={src} className="avatar" />;
}

function Bio({ text }) {
  return <p className="bio">{text}</p>;
}

export default function UserProfile({ user }) {
  return (
    <div className="profile">
      <Avatar src={user.avatar} />
      <h2>{user.name}</h2>
      <Bio text={user.bio} />
    </div>
  );
}

Extracting Components

When to extract a component:

1. Reusability:

// Before
function UserList() {
  return (
    <div>
      <div className="user-card">
        <img src="/user1.jpg" />
        <h3>Alice</h3>
      </div>
      <div className="user-card">
        <img src="/user2.jpg" />
        <h3>Bob</h3>
      </div>
    </div>
  );
}

// After - reusable component
function UserCard({ image, name }) {
  return (
    <div className="user-card">
      <img src={image} />
      <h3>{name}</h3>
    </div>
  );
}

function UserList() {
  return (
    <div>
      <UserCard image="/user1.jpg" name="Alice" />
      <UserCard image="/user2.jpg" name="Bob" />
    </div>
  );
}

2. Complexity:

// Before - too complex
function Dashboard() {
  return (
    <div>
      <div className="header">
        <img src="/logo.png" />
        <nav>{/* 20 lines of navigation */}</nav>
        <div>{/* User menu code */}</div>
      </div>
      <div className="sidebar">{/* Sidebar code */}</div>
      <main>{/* Main content */}</main>
    </div>
  );
}

// After - extracted for clarity
function Dashboard() {
  return (
    <div>
      <DashboardHeader />
      <Sidebar />
      <MainContent />
    </div>
  );
}

Pure Components

Components should be pure functions - same props → same output:

// ✅ Pure - predictable
function Greeting({ name }) {
  return <h1>Hello, {name}!</h1>;
}

// ❌ Impure - uses external variable
let counter = 0;
function Counter() {
  counter++;  // Side effect!
  return <div>{counter}</div>;
}

// ❌ Impure - modifies prop
function User({ user }) {
  user.visited = true;  // Mutation!
  return <div>{user.name}</div>;
}

Component Patterns

1. Container/Presentation Pattern:

// Presentation - just displays data
function UserCard({ name, email, avatar }) {
  return (
    <div className="user-card">
      <img src={avatar} alt={name} />
      <h3>{name}</h3>
      <p>{email}</p>
    </div>
  );
}

// Container - fetches/manages data
function UserCardContainer({ userId }) {
  const [user, setUser] = useState(null);
  
  useEffect(() => {
    fetchUser(userId).then(setUser);
  }, [userId]);
  
  if (!user) return <div>Loading...</div>;
  
  return <UserCard {...user} />;
}

2. Compound Components:

function Card({ children }) {
  return <div className="card">{children}</div>;
}

Card.Header = function CardHeader({ children }) {
  return <div className="card-header">{children}</div>;
};

Card.Body = function CardBody({ children }) {
  return <div className="card-body">{children}</div>;
};

// Usage
<Card>
  <Card.Header>
    <h3>Title</h3>
  </Card.Header>
  <Card.Body>
    <p>Content</p>
  </Card.Body>
</Card>

3. Render Props Pattern:

function DataProvider({ url, render }) {
  const [data, setData] = useState(null);
  
  useEffect(() => {
    fetch(url)
      .then(r => r.json())
      .then(setData);
  }, [url]);
  
  return render(data);
}

// Usage
<DataProvider 
  url="/api/users"
  render={data => data ? <UserList users={data} /> : <Loading />}
/>

Component Lifecycle (Modern)

With hooks, components have three main phases:

1. Mount (component appears):

function Component() {
  useEffect(() => {
    console.log('Component mounted');
  }, []);  // Empty array = run once on mount
  
  return <div>Hello</div>;
}

2. Update (props/state change):

function Component({ prop }) {
  useEffect(() => {
    console.log('Component updated');
  }, [prop]);  // Runs when prop changes
  
  return <div>{prop}</div>;
}

3. Unmount (component removed):

function Component() {
  useEffect(() => {
    return () => {
      console.log('Component unmounted');
    };
  }, []);
  
  return <div>Hello</div>;
}

Component Communication

Parent to Child (Props):

function Parent() {
  const message = "Hello from parent";
  return <Child message={message} />;
}

function Child({ message }) {
  return <div>{message}</div>;
}

Child to Parent (Callback):

function Parent() {
  const handleClick = (data) => {
    console.log('Child clicked:', data);
  };
  
  return <Child onAction={handleClick} />;
}

function Child({ onAction }) {
  return (
    <button onClick={() => onAction('some data')}>
      Click me
    </button>
  );
}

Sibling Communication (Lift State Up):

function Parent() {
  const [shared, setShared] = useState('');
  
  return (
    <>
      <ChildA value={shared} onChange={setShared} />
      <ChildB value={shared} />
    </>
  );
}

Best Practices

1. Keep components small and focused:

// Good - single responsibility
function Avatar({ src, alt }) {
  return <img src={src} alt={alt} className="avatar" />;
}

// Bad - too many responsibilities
function UserSection({ user, posts, comments }) {
  // Hundreds of lines...
}

2. Use descriptive names:

// Good
function UserProfileHeader() { }
function ProductListItem() { }

// Bad
function Component1() { }
function Thing() { }

3. Avoid deep nesting:

// Bad
<Parent>
  <Child1>
    <Child2>
      <Child3>
        <Child4>
          // ...
        </Child4>
      </Child3>
    </Child2>
  </Child1>
</Parent>

// Good - flatten when possible
<Parent>
  <Section1 />
  <Section2 />
  <Section3 />
</Parent>

4. Extract repeated JSX:

// Before
<div>
  <button onClick={handleSave}>Save</button>
  <button onClick={handleCancel}>Cancel</button>
  <button onClick={handleDelete}>Delete</button>
</div>

// After
function ActionButton({ onClick, children }) {
  return <button onClick={onClick}>{children}</button>;
}

<div>
  <ActionButton onClick={handleSave}>Save</ActionButton>
  <ActionButton onClick={handleCancel}>Cancel</ActionButton>
  <ActionButton onClick={handleDelete}>Delete</ActionButton>
</div>

Practical Example: Blog Post

function BlogPost({ post }) {
  return (
    <article className="blog-post">
      <PostHeader 
        title={post.title}
        author={post.author}
        date={post.date}
      />
      <PostContent content={post.content} />
      <PostFooter 
        likes={post.likes}
        comments={post.comments}
      />
    </article>
  );
}

function PostHeader({ title, author, date }) {
  return (
    <header>
      <h1>{title}</h1>
      <AuthorInfo author={author} date={date} />
    </header>
  );
}

function AuthorInfo({ author, date }) {
  return (
    <div className="author-info">
      <Avatar src={author.avatar} />
      <span>{author.name}</span>
      <time>{formatDate(date)}</time>
    </div>
  );
}

function Avatar({ src }) {
  return <img src={src} className="avatar" alt="Avatar" />;
}

function PostContent({ content }) {
  return <div className="content">{content}</div>;
}

function PostFooter({ likes, comments }) {
  return (
    <footer>
      <LikeButton count={likes} />
      <CommentCount count={comments} />
    </footer>
  );
}

Common Mistakes

1. Not starting with capital letter:

// Wrong
const myComponent = () => <div>Hello</div>;
<myComponent />  // Treated as HTML tag!

// Right
const MyComponent = () => <div>Hello</div>;
<MyComponent />

2. Returning multiple elements without wrapper:

// Wrong
function Component() {
  return (
    <h1>Title</h1>
    <p>Text</p>
  );
}

// Right
function Component() {
  return (
    <>
      <h1>Title</h1>
      <p>Text</p>
    </>
  );
}

3. Modifying props:

// Wrong
function Component({ user }) {
  user.name = "Modified";  // Never mutate props!
  return <div>{user.name}</div>;
}

// Right
function Component({ user }) {
  const displayName = user.name.toUpperCase();
  return <div>{displayName}</div>;
}