javascriptsvelte

Svelte: update parent state from child


In react I can do something like:

App.jsx

const App = () => {
  const [state, setState] = useState("old value")

  return (
    <>
      <ChildComponent setState={setState} />
    </>
  )
}

ChildComponent.jsx

const ChildComponent = ({ setState }) => {
  const changeState = () => setState("new value")

  return (
    <div>
      <button onClick={changeState}>Click</button>
    </div>
  )
}

Then the parent state will be updated.

I don't know how to do the same in Svelte...

I have this:

index.svelte

<script>
  import { ChildComponent } from "@components"

  let state = "old value"
</script>

<main>
  <ChildComponent {state} />
</main>

ChildComponent.svelte

<script>
  export let state

  const changeState = () => {
    // I need to do something like:
    state = "new value"
  }
 </script>

<div>
  <button on:click={changeState}>Click</button>
</div>

And see the new value reflected in the parent.

I wanna to do it without use stores... I don't know if it's possible.

Maybe store is the only way to proceed.

I'm ears


Solution

  • There are two ways of doing this in Svelte:

    With two-way binding

    This will create a sort of connection between parent and child, where updating one will automatically update the other.

    <!-- Parent.svelte -->
    <script>
     import Child from './Child.svelte';
     let state = "initial";
    </script>
    
    <Child bind:state />
    
    <!-- Child.svelte -->
    <script>
      export let state;
     function changeState() {
       state = "new value";
    </script>
    
    <button on:click={changeState}>Click</button>
    

    Using events

    Just like props go down, events are used to pass informaton up. This can be used if you don't want a strict equivalence between the two states and is a touch more versatile (but also more verbose).

    <!-- Parent.svelte -->
    <script>
      import Child from './Child.svelte';
      let state = "initial"
     
      function handleChange(ev) {
        state = ev.detail.state
      }
    </script>
    
    <Child {state} on:change={handleChange} />
    
    
    <!-- Child.svelte -->
    <script>
      import { createEventDispatcher } from 'svelte';
      export let state
    
      const dispatch = createEventDispatcher()
    
      function changeState() {
        // first argument is the event name
        // second is an object placed in ev.detail
        dispatch('change', { state: "new value" });
      }
    </script>
    
    <button on:click={changeState}>Click</button>
    

    Which of these to use is up to you and likely depends on the situation, but both benefit that they do not actively rely on what is happening outside the component. If nobody used bind the component doesn't care, it still works. If nobody is listening to this event, again the component doesn't care and keeps on working. Contrast this to the situation where no function is passed, or the function has the wrong signature, suddenly your component is dependent on something that it cannot control which is not a good pattern.