Home > Blockchain >  update svelte reference when a prop change
update svelte reference when a prop change

Time:12-17

I created an svg text and I need to create a responsive rectangle outside the text using a svg (no div).

Since the text lenght may change based on window size, also the rect width should change.

Here a working example and below my code:

App.svelte:

<script>
    import Wrapper from './Wrapper.svelte'
    
    let w = 0
    $: h = Math.min(w, 300)
    
    function getTileLabel(w) {
    if (w < 200) {
      return (d) => d.name
    }
    return (d) => `Long label ${d.name}`
  }
    
    const data = [{name: 'a', y: 0}, {name: 'bbbbb', y: 50}, {name: 'ccc', y: 100}]
    $: getLabel = getTileLabel(w)
</script>

<div bind:clientWidth={w}>
    <Wrapper
        w={w}
        h={h}
        {data}
        {getLabel}
    />
</div>

Wrapper.svelte:

<script>
    import Container from './Container.svelte'
    
  export let w
    export let h
  export let data
  export let getLabel
</script>

<svg x={0} y={0} width={w} height={h}>
  {#each data as d}
    <Container {d} label={getLabel(d)} />
  {/each}
</svg>

<style>
    svg {
        border: 2px solid black;
    }
</style>

Container.svelte:

<script>
    export let d
  export let label

  let textElement

  $: textElementBB = textElement && textElement.getBoundingClientRect()

  $: x0 = 0
  $: y0 = d.y
  $: width = d.width
  $: height = d.height
  $: borderWidth = textElementBB?.width
  $: borderHeight = textElementBB?.height
  $: borderX = x0 
  $: borderY = y0 
  $: textX = borderX
  $: textY = borderY
</script>

<rect x={borderX} y={borderY} width={borderWidth} height={borderHeight} stroke="tomato" stroke-width={2} fill="none" />
<text
            bind:this={textElement}
            fill="black"
            x={textX}
            y={textY}
            alignment-baseline="hanging"
            >
    {label}
</text>

The text becomes smaller if you resize the window but the rect width does not change. Why? I think the problem is because the reference of the text node does not change when label change.

So I think the problem is here:

  let textElement

am I right? How can I solve? Is there something like an useEffect of React for Svelte?

CodePudding user response:

Reactivity is based on assignments and events (when using bindings). Here you are only referencing the element which is not reassigned.

There are two options I can think of, both of which drop the use of getBoundingClientRect:

CodePudding user response:

A useEffect-ish function would look something like

<script>

  let borderWidth = 0
  let borderHeight = 0
  let textElement
     
  $: {
    // EVERY line within the object will execute when ANY variable within the object changes. In this case, textElement will change and this object will execute.         

    borderWidth = textElement && textElement.getBoundingClientRect().width
    borderHeight = textElement && textElement.getBoundingClientRect().height
  }

</script>

The React version of this would be

let borderWidth = 0
let borderHeight = 0
let textElement

useEffect(() => {

  borderWidth = textElement && textElement.getBoundingClientRect().width
  borderHeight = textElement && textElement.getBoundingClientRect().height

}, [borderWidth, borderHeight, textElement]

One step further, let's say you only wanted to react when textElement changed and NOT when borderWidth or borderHeight change. For that you would declare a function outside of the object and call it from with the object.

<script>

  let borderWidth = 0
  let borderHeight = 0
  let textElement

  const updateBorderDims = () => {
     borderWidth = textElement && textElement.getBoundingClientRect().width
     borderHeight = textElement && textElement.getBoundingClientRect().height
  }
     
  $: {
     // do something to get the dependancy within the object, here I'm declaring another variable t - this variable isn't passed        
     let t = [textElement]

     updateBorderDims()
  }

</script>
  • Related