GAGANDEEP SINGH
GAGANDEEP SINGH

Reputation: 1016

Reactive deceleration in svelte not working as expected

I am trying to create a TextAreaAutosize component which returns a textarea which adjust its height automatically when need to.

I wanted to run onHeightChange function when height changes so i used svelte reactive statements($:) so that whenever change in height, that piece of code run.

App.svelte

<script>
  import TextArea from "./TextAreaAutosize.svelte";
  let val, ref;
</script>

<TextArea 
  bind:value={val}  
  bind:ref={ref} 
  style={"height: 90px; maxHeight=40"} 
  minRows={4}
 />

TextAreaAutosize.svelte

<script context="module">
  import calculateNodeHeight from './calculateNodeHeight.js'
  let uid = 0;
  const noop = () => {};
</script>

<script>
  export let value = '';
  export let style;
  export let useCacheForDOMMeasurements = false;
  export let minRows = -Infinity;
  export let maxRows = +Infinity;
  export let onHeightChange = noop;
  export let ref = null;

  let refConfig = {
    _uid: uid++
  }

  let state = {
    height: 0,
    minHeight: -Infinity,
    maxHeight: +Infinity
  }

  // runs when change in height
  $: if(state.height){

    //block 1

    console.log('height changed')
    onHeightChange(refConfig)

  }

  // resizes element on initial mount or whenever change in value
  $:if(ref || value){

     //block 2

    _resizeComponent()

  }

  // function for resizing textarea when required
  function _resizeComponent(){
    if(!ref){
      return;
    }

    const refHeight = calculateNodeHeight(
      ref,
      refConfig._uid,
      useCacheForDOMMeasurements,
      minRows,
      maxRows
    );

    const {
      height,
      minHeight,
      maxHeight,
      rowCount,
      valueRowCount
    } = refHeight;

    refConfig.rowCount = rowCount;
    refConfig.valueRowCount = rowCount;

    if(
      state.height !== height ||
      state.maxHeight !== maxHeight ||
      state.minHeight !== minHeight
    ){
      state = {height, minHeight, maxHeight}
    }
  }

</script>

<textarea 
  bind:value={value} 
  bind:this={ref} 
  style={`${style};height: ${state.height}px;`}
  on:click
  on:change  
  class={$$props.class}
/>

In this component the block 1 should run whenever the state.height changes. but it doesn't run at all. But when i place it after the block2 it runs whenever there is change in state.height

when value of textarea change block2 run and calls _resizeComponent which then update state with new height if needed. which should trigger block1 if height change. but it triggers that block only if i place it after block2

i am not able to understand why this behaviour in my code. Statements in block1 should run whenever change in state.height irrespective of order of reactive statements

Upvotes: 2

Views: 1309

Answers (1)

Rich Harris
Rich Harris

Reputation: 29615

Svelte can't determine, from analysing that code, that the second block could affect the value of state. So it doesn't reorder the blocks to run the second one first. (It can't just keep re-running the reactive blocks if there are unexpected changes at the end; that way lies infinite loops.)

If instead of just _resizeComponent() it was state = _resizeComponent(), and the function was changed accordingly, that would change. Alternatively, just reorder the blocks manually.

Incidentally, there's an easier way to get an auto-resizing textarea: https://svelte.dev/repl/40f4c7846e6f4052927ff5f9c5271b66?version=3.6.8

Upvotes: 3

Related Questions