How to pass props to a Svelte component

How to pass props to a Svelte component

Props are the lifeblood of component communication in React. Without them, you end up with isolated islands of UI that can’t talk. The basic syntax for passing props is deceptively simple, but good understanding here saves a lot of future headaches.

At its core, passing props is just a matter of adding attributes to JSX elements when you instantiate a component. Consider a simple functional component that expects a message:

function Greeting(props) {
  return <h1>{props.message}</h1>;
}

When you use Greeting, you pass the message in the JSX tag’s attributes:

<Greeting message="Hello, world!" />

This seems trivial, but note what’s happening under the hood. JSX attributes aren’t just arbitrary syntax—they compile down to JavaScript objects where keys are prop names and values are the prop values. So the above expands roughly to this:

React.createElement(Greeting, { message: "Hello, world!" });

Because of this, you can pass more than just strings. Any JavaScript expression inside curly braces is evaluated and passed as the prop value.

const name = "Alice";

<Greeting message={"Hi, " + name} />

Here, the expression "Hi, " + name evaluates to "Hi, Alice" before being handed down as a prop.

Passing numbers, booleans, arrays, objects, or functions works exactly the same. For example, callbacks become effortless:

function Button(props) {
  return <button onClick={props.onClick}>Click me</button>;
}

function Parent() {
  const handleClick = () => alert("Clicked!");

  return <Button onClick={handleClick} />;
}

Note that prop names follow a naming convention within React: typically camelCase rather than HTML attribute naming. But fundamentally, they are just keys in a props object. That means you can pass as many or as few as you want, and access them inside components however it makes sense.

Another thing worth emphasizing is how JSX spreads props, which can be a handy Pythonic aid for avoiding repetition:

const extraProps = {
  disabled: false,
  type: "button",
};

<Button {...extraProps} onClick={handleClick} />

The spread syntax spies the properties of the extraProps object and passes them individually, just as if you’d written them out one by one in JSX.

Of course, props can be destructured right in the function signature or inside the function body, tidying syntax and improving readability:

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

That’s especially clean when passing multiple props:

function Profile({ name, age, location }) {
  return (
    <div>
      <h2>{name}</h2>
      <p>Age: {age}</p>
      <p>Location: {location}</p>
    </div>
  );
}

No magic here – just plain JavaScript, but with the added benefit that it aligns naturally with how props are passed down and consumed.

One last bit before moving on: declaring PropTypes used to be the norm to catch prop-passing mistakes early. Though less common with TypeScript’s rise, it remains a helpful pattern for plain JS projects:

import PropTypes from 'prop-types';

Greeting.propTypes = {
  message: PropTypes.string.isRequired,
};

This doesn’t affect how you pass props, just how you validate them at runtime to catch bugs sooner rather than later. If you try passing a number to message here, you’ll get a helpful warning in the console.

Next up will be how props’ values behave once passed, and how to manage default values and reactivity to changing inputs—because prop passing isn’t a one-and-done operation but can evolve over time as your app state changes, triggering re-renders that update props accordingly. This dynamic nature is what turns basic prop passing into a powerful state management paradigm.

Let’s dig into how to keep props reactive and use default values smartly so you don’t find yourself bandaging your components later on with defensive code or complicated hooks.

Managing prop reactivity and default values

Props are immutable from the child component’s perspective. This means you should never try to modify props directly within the component. Instead, treat them as read-only inputs that reflect the parent’s current state or configuration. React’s reactivity model hinges on props being updated externally; when a parent re-renders and passes new values, React calls your component function again with the updated props.

Consider this example where a parent manages a count and passes it down:

function CounterDisplay({ count }) {
  return <div>Count: {count}</div>;
}

function Counter() {
  const [count, setCount] = React.useState(0);

  return (
    <div>
      <CounterDisplay count={count} />
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

Every time you click the increment button, the parent’s local count state updates, triggers a re-render, and passes the new value into CounterDisplay as a fresh prop. Because of this, the child updates automatically without any direct prop modification.

Sometimes you want props to have default values when a parent doesn’t provide them. The idiomatic way to handle this in functional components is to provide defaults via function parameter destructuring:

function Greeting({ message = "Hello, stranger!" }) {
  return <h1>{message}</h1>;
}

If the parent passes nothing or undefined for message, it gracefully falls back to "Hello, stranger!". This is cleaner and faster than checking inside the function body or using defaultProps, which React now considers legacy for functional components.

However, defaultProps still works for class components or in certain interoperability scenarios:

class Greeting extends React.Component {
  render() {
    return <h1>{this.props.message}</h1>;
  }
}

Greeting.defaultProps = {
  message: "Hello, stranger!",
};

When a parent passes null explicitly, neither destructuring defaults nor defaultProps will replace it, because null is a valid deliberate value distinct from undefined. If you need to treat null as missing, you’d implement your own fallback logic inside the component.

When props change, React’s reconciliation forces a re-render with updated props. You can react to these changes in class components via lifecycle methods, or in functional components by using hooks like useEffect that depend on props:

function StatusDisplay({ status }) {
  React.useEffect(() => {
    console.log("Status changed to:", status);
  }, [status]);

  return <div>Current status: {status}</div>;
}

Using useEffect with a prop dependency array ensures side effects run only when the relevant prop changes, not on every render.

If you need to derive internal state from props, be very cautious. Directly copying props into state is often an antipattern, leading to stale or unsynchronized values. Instead, consider controlled components or effects that update state as props change:

function EditableName({ name: initialName }) {
  const [name, setName] = React.useState(initialName);

  React.useEffect(() => {
    setName(initialName);
  }, [initialName]);

  return (
    <input
      value={name}
      onChange={e => setName(e.target.value)}
    />
  );
}

Here, EditableName listens for changes to its initialName prop and updates its internal state accordingly. Without the effect, if the parent passed a new name while the user was editing, the component would not update properly.

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *