skip to Main Content

I have a MUI slider where the value of the slider needs to be inverted based on a boolean variable. My current code inverts it correctly but it also inverts the drag functionality, meaning if I tried to drag my slider towards 0, it would move towards 100 instead. I can’t think of a workaround to correct this. Would appreciate if anyone has any suggestions I could try.

const handleSelectionChange = (e: any) => {
  if (typeof e.target.value === 'string') {
    onChange(e.target.value)
  } else {
    onChange(Number(e.target.value))
  }
}
<Slider
            aria-label='DiceRoll'
            defaultValue={50}
            valueLabelDisplay='auto'
            step={1}
            min={0}
            max={100}
            value={overUnder ? 100 - targetNumber : targetNumber} //overUnder is boolean and targetNumber ranges from 0 to 100.
            marks={marks}
            onChange={handleSelectionChange}
/> 

2

Answers


  1. Chosen as BEST ANSWER

    So after a few hours I've found the solution, it's actually a pretty simple solution, just an additional 3 lines of codes. We just have to introduce another state which only purpose is to display the correct slider Value.

    const handleSelectionChange = (e: any) => {
      if(overUnder){ //defaultValue of overUnder is true
        if (typeof e.target.value === 'string') {
          onChange(e.target.value)
        } else {
          onChange(Number(e.target.value))
        }
      } else {
        if (typeof e.target.value === 'string') {
          onChange(100 - e.target.value)
          setFlippedTargetNumber(e.target.value)
        } else {
          onChange(100 - Number(e.target.value))
          setFlippedTargetNumber(Number(e.target.value))
        }
      }
    }
    
    const [ flippedTargetNumber, setFlippedTargetNumber] = useState<number>(50)
    
    <Slider
            aria-label='DiceRoll'
            defaultValue={50}
            valueLabelDisplay='auto'
            step={1}
            min={0}
            max={100}
            value={overUnder ? targetNumber : flippedTargetNumber} //overUnder is boolean and targetNumber ranges from 0 to 100.
            marks={marks}
            onChange={handleSelectionChange}
    /> 
    

    basically what this does is, when overUnder = true, and when it's true, the slider would just work normally. when it is flipped and overUnder = false, onChange will update targetNumber to inverse [which is needed for other components]. and setFlippedTargetNumber will be used as slider Value instead of targetNumber.

    Hope this helps anyone who is facing a similar problem. :)


  2. Using the valueLabelFormat function, you can simply format the number display instead of the number value itself, preventing the problem.

    Use this instead:

    valueLabelFormat={(v) => (overUnder ? 100 - v : v)}

    Set the value back to normal:

    value={targetNumber}

    const { StrictMode, useState } = React;
    const { createRoot } = ReactDOM;
    
    const App = () => {
    
      const [overUnder, setOverUnder] = useState(false);
      const [targetNumber, setTargetNumber] = useState(42.0);
      
      const handleSelectionChange = (e) => {
        if (typeof e.target.value === "string") {
          setTargetNumber(e.target.value);
        } else {
          setTargetNumber(Number(e.target.value));
        }
      };
      
      const onFlip = () => {
        setOverUnder((s) => !s);
      };
      
      return (
        <div>
          <div>
            <button onClick={onFlip}>Flip</button>
          </div>
          <div>Target Number: {targetNumber}</div>
          <div>Over Under: {`${overUnder}`}</div>
          <MaterialUI.Slider
            aria-label="DiceRoll"
            defaultValue={50}
            valueLabelDisplay="on"
            step={1}
            min={0}
            max={100}
            value={targetNumber}
            valueLabelFormat={(v) => (overUnder ? 100 - v : v)}
            marks={false}
            inverted={true}
            onChange={handleSelectionChange}
            sx={{ pt: "10rem" }}
          />
        </div>
      );
    }
    
    const rootElement = document.getElementById("root");
    const root = createRoot(rootElement);
    
    root.render(
      <StrictMode>
        <App />
      </StrictMode>
    );
    <div id="root"></div>
    <script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
    <script src="https://unpkg.com/@mui/material@latest/umd/material-ui.production.min.js"></script>

    Also a sandbox for reference.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search