Continuing in the previous theme of “What’s the Svelte way to handle X as I did in React?”, we’re going to look at basic props patterns in the React and Svelte JavaScript frameworks.
import {useState} from 'react';
export const ToggleValue = () => {
const [bool, setBool] = useState(false);
const toggleBool = () => setBool(!bool);
return (
<>
<div>{bool.toString()}</div>
<button onClick={toggleBool}>Toggle Bool</button>
</>
)
}
I’ve selected this example from our previous article to show a simple value toggling pattern. In React, the very convenient useState hook returns the value along with the function to update it. We can set the initial value to false by providing a value for the argument to useState.
<script>
let bool = false;
const toggleBool = () => bool = !bool;
</script>
<div>Bool: {bool}</div>
<button on:click={toggleBool}>Toggle Bool</button>
With Svelte, we use the vanilla JS let declaration to initialize the variable. Since let does not need an assigned value upon initialization, we can consider setting the false value as virtually the same as passing React’s useState an initial value. Unlike React, we have to handle changing the value on our own. Svelte does not provide anything like the setState function back to us. That means we need to define our toggleBool function, which will assign a new value to the bool variable when invoked.
export const ToggleValueChild = ({boolValue = true}) => {
return (
<div>
This is the value I passed to the child: {boolValue.toString()}
</div>
)
}
import {useState} from 'react';
import {ToggleValueChild} from './ToggleValueChild';
export const ToggleValue = () => {
const [bool, setBool] = useState(false);
const toggleBool = () => setBool(!bool);
return (
<>
<button onClick={toggleBool}>Toggle Bool</button>
<br />
<ToggleValueChild boolValue={bool}/>
</>
)
}
The code sample above is a simple example of providing a prop to a component. We took the last example and added a nested component whose job is to display the bool value we were toggling before. We want to render the new boolean display component with data from the parent component. We can pass in our bool value in a prop, which we have to name. We’ve called ours boolValue. In our child component, we expose the boolValue prop so we can use it.
I’ve chosen to destructure in the function definition. You could define the props parameter if you so prefer, and use props in the div like {props.boolValue.toString()}. Since we’re destructuring, we’ve also provided a default value of true to our boolValue prop. If we don’t provide a value from the parent component, then the child will set the boolValue variable to true.
<script>
import ToggleValueChild from './Child.svelte';
let bool = false;
const toggleBool = () => bool = !bool;
</script>
<button on:click={toggleBool}>Toggle Bool</button>
<br>
<ToggleValueChild boolValue={bool}/>
<!-- <ToggleValueChild {bool}/> -->
<script>
// Assigning a value here acts as a default if no value is provided through props
// We can also just do - export let boolValue
// but that runs the risk of defaulting to undefined if we fail to pass in a prop value
export let boolValue = true;
// export let bool;
</script>
<div>This is the value I passed to the child: {boolValue}</div>
The Svelte version is conceptually similar to the React version, allowing for the differences in syntax of course. We import our child component and pass it a named prop, which is set to the value we want to pass down. In the child component, we have the most significant difference from React’s syntax; the export let boolValue; line. Svelte’s syntax for exposing a component’s variables to parent components is by using the export keyword. You’ll notice that we do not need to export the child component as we do in React. The Svelte child component can be imported without having to use the export keyword. Additionally, we can provide our default value by assigning a value to the variable we’re exposing, as we’ve done by assigning the child’s boolValue to true. If the parent does not provide a boolValue prop, the child will use the value we’ve assigned in the variable initialization.
You may have noticed a few comments in the Svelte components. Svelte allows for a prop shorthand of {propValue} when the variable name is the same between parent and child. In this case, if the child component has a variable named boolValue and the parent does too, you can pass the prop as just {boolValue} which will be shorthand for boolValue={boolValue}.
import {useState} from 'react';
import {ToggleValueChild} from './ToggleValueChild2';
export const ToggleValue = () => {
const [bool, setBool] = useState(false);
const toggleBool = () => setBool(!bool);
return (
<>
<div>This is in the parent component.</div>
<ToggleValueChild boolValue={bool} toggleBool={toggleBool} />
</>
)
}
import {useState} from 'react';
import {ToggleValueChild} from './ToggleValueChild2';
export const ToggleValue = () => {
const [bool, setBool] = useState(false);
const toggleBool = () => setBool(!bool);
return (
<>
<div>This is in the parent component.</div>
<ToggleValueChild boolValue={bool} toggleBool={toggleBool} />
</>
)
}
We want to let the child component invoke the parent’s setState function, so we pass it down like anything else. Then we can have our onClick handler invoke the function, and it works like when the button was in the parent.
<script>
import ToggleValueChild from './Child.svelte';
let bool = false;
const toggleBool = () => {
bool = !bool
}
</script>
<ToggleValueChild {bool} {toggleBool}/>
<script>
export let bool;
export let toggleBool;
</script>
<button on:click={toggleBool}>
Toggle
</button>
<div>{bool}</div>
If we wanted to mimic how we handled this situation in React, we could do that easily by just providing the function we wish to invoke as a prop to the child component. Nothing much more to it than that.
With that in mind, we’ll refactor this functionality to follow the standard approach.
<script>
import ToggleValueChild from './Child.svelte';
let bool = false;
const toggleBool = () => {
bool = !bool
}
</script>
<ToggleValueChild {bool} on:toggleBool={toggleBool}/>
<script>
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
const toggleBool = () => dispatch('toggleBool');
export let bool = true;
</script>
<button on:click={toggleBool}>
Toggle Bool
</button>
<div>{bool}</div>
Svelte prefers to have the child emit an event to which the parent listens. There is a bit of boilerplate with importing createEventDispatcher, declaring a separate dispatch variable, and finally declaring the particular event’s function. Once we’ve set that up, our child component can dispatch the event when we click the button. In our parent component, then we need to listen for the event and then do something when it comes through. Luckily Svelte makes that pretty simple. We use the on: syntax for the button, but now we use the event’s name, so on:toggleBool. Then we give it the function we want invoked when that event happens, so we end up with on:toggleBool={toggleBool}. In plain English this says “When I see the toggleBool event, invoke this function”.
Conclusion
Handling props and parent-child data passing is integral to any component-based app. While the basic idea is similar between React and Svelte, there is a definite deviation between handling props. In particular, the last example of calling a function from the parent illustrates how even the basic patterns in React might be very different in Svelte.