Problem updating a variable when changes on a custom component

Hi everyone,

I'm trying to change a global variable (an array of 4 values) when a custom component (slider with 3 handles) is modified.

I'm using an event handler in the react code:

import React, { useState, useCallback } from 'react';
import { Retool } from '@tryretool/custom-component-support';
import Slider from 'rc-slider';
import 'rc-slider/assets/index.css';
import styled from 'styled-components';
import debounce from 'lodash/debounce';

const MIN = 0;
const MAX = 100;
const TRACK_LABELS = ['Ville', 'Nationale', 'Autoroute', 'Montagne'];

const SliderWrapper = styled.div`
  position: relative;
  touch-action: none;
  padding: 16px;
  background-color: #fff;
  border-radius: 8px;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
  margin: 8px 0;
  max-width: 800px;
`;

const SliderLabel = styled.label`
  color: var(--primary-text, #0d0d0d);
  font-size: 12px;
  font-weight: 600;
  font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
  margin-bottom: 4px;
  display: block;
`;

const StyledSliderContainer = styled.div`
  .rc-slider {
    margin: 20px 0;
  }

  .rc-slider-track {
    background-color: #16a085;
  }

  .rc-slider-track-1 {
    background-color: #3fc380;
  }

  .rc-slider-track-2 {
    background-color: #90c695;
  }

  .rc-slider-handle {
    border: 2px solid #16a085;
    background-color: white;
    width: 18px;
    height: 18px;
    margin-top: -7px;

    &:hover,
    &:focus,
    &:active {
      border-color: #17b261;
      box-shadow: 0 0 0 5px rgba(23, 178, 97, 0.3);
    }
  }
`;

const SliderOutput = styled.output`
  color: var(--retool-slider-output, #0d0d0db3);
  font-size: var(--retool-slider-output-font-size, 12px);
  line-height: var(--retool-slider-output-line-height, 16px);
  font-weight: var(--retool-slider-output-font-weight, 500);
  font-family: var(--retool-slider-output-font-family, Inter, sans-serif);
  display: block;
  margin-top: 40px;
`;

export const mSlider = () => {
  // Retool states (for data persistence)
  const [retoolValuesHandles, setRetoolValuesHandles] = Retool.useStateArray({
    name: "valuesHandles",
    initialValue: [25, 50, 75],
    inspector: "text",
    label: "Handles Values",
  });

  const [retoolRouteProportions, setRetoolRouteProportions] = Retool.useStateArray({
    name: "routeProportions",
    initialValue: [25, 25, 25, 25],
    inspector: "text",
    label: "Route Proportions",
  });

  // Local React states (for smooth UI)
  const [localValuesHandles, setLocalValuesHandles] = useState(retoolValuesHandles);
  const [localRouteProportions, setLocalRouteProportions] = useState(retoolRouteProportions);
  const [labelText] = useState('Répartition du trajet (%)');

  // Debounced function to update Retool state
  const debouncedRetoolUpdate = useCallback(
    debounce((values, proportions) => {
      setRetoolValuesHandles(values);
      setRetoolRouteProportions(proportions);
    }, 200),
    []
  );

  // Add the event callback
  const onProportionsChange = Retool.useEventCallback({
    name: "proportionsChanged"
  });

  const handleSliderChange = (newValues) => {
    // Update local state immediately for smooth UI
    setLocalValuesHandles(newValues);

    const proportions = [
      newValues[0],
      newValues[1] - newValues[0],
      newValues[2] - newValues[1],
      100 - newValues[2]
    ];

    setLocalRouteProportions(proportions);

    // Update Retool state with debounce
    debouncedRetoolUpdate(newValues, proportions);

    // Trigger the event with the new proportions
    onProportionsChange(proportions);
  };



  return (
    <SliderWrapper>
      <SliderLabel htmlFor="custom-slider">{labelText}</SliderLabel>
      <StyledSliderContainer>
        <Slider
          min={MIN}
          max={MAX}
          value={localValuesHandles}  // Use local state here
          onChange={handleSliderChange}
          range
          pushable={false}
          allowCross={false}
        />
      </StyledSliderContainer>
      <SliderOutput>
        Ville : {localRouteProportions[0]}% - Nationale : {localRouteProportions[1]}% -
        Autoroute : {localRouteProportions[2]}% - Montagne : {localRouteProportions[3]}%
      </SliderOutput>
    </SliderWrapper>
  );
};

I then added a run script action when the event is triggered:

It does update, but the problem is that the values of the global variables are updated with the previous values of the multislider.

Let's say the values of the slider are [10, 10, 10, 70].

Then user clicks and changes them to [30, 30, 30, 10].

My global variable is then updated but with the values [10, 10, 10, 70].

It seems however that in the React script, the event callback happens after the values are updated.

Any hint?

Baptiste

1 Like

Hi everyone,

Small up on this message, I'm still stuck there...

Any idea on how to solve this?

Baptiste

Hey @Baptiste_LC! Thanks for reaching out and for your patience, as well.

I'm guessing that this is an issue with the timing of events, as you're executing the event handler callback immediately but debouncing the update of the corresponding state variables. As a result, mSlider_workday.routeProportions is out of date when your event handler runs.

There are likely a few different ways to solve this. You can debounce onProportionsChange, as well, or incorporate it into debouncedRetoolUpdate.

I hope that helps! Let me know if you have any follow-up questions.

Hi Darren,

Makes lots of sense indeed, just fixed it.

Thanks a lot!

Baptiste