Home > Back-end >  How do I set the background color of a number based on whether it goes up or down?
How do I set the background color of a number based on whether it goes up or down?

Time:01-11

I'm trying to catch a state change for a number. I've got a component that renders a number. When the value changes, I want to set the background to be green or red, depending on whether it moved up or down.

I'm actually not sure this is possible to do in functional component. Am I missing something?

Here's my latest (failed) attempt.

import React, { useState } from 'react'

const FlashNumber = ({ value }) => {
  const [prevValue, setPrevValue] = useState(null)
  const [bgColor, setBgColor] = useState('white')

  React.useEffect(() => {
    if (prevValue !== null) {
      if (value > prevValue) {
        setBgColor('green')
        setTimeout(() => {
          setBgColor('white')
        }, 1000)
      } else if (value < prevValue) {
        setBgColor('red')
        setTimeout(() => {
          setBgColor('white')
        }, 1000)
      }
    }
    setPrevValue(value)
  }, [value, prevValue])

  return <div style={{ backgroundColor: bgColor }}>{value}</div>
}

export default FlashNumber

CodePudding user response:

useEffect can return a cleanup function, in that way you will be able to stop "previously set" timeout callbacks from execution.

Codesandbox: https://codesandbox.io/s/cool-dream-inkw4u?file=/src/App.js

const { useMemo, useState, useRef, useEffect } = React;

function App() {
  const [val, setVal] = useState(0);
  return (
    <div className="App">
      <button onClick={() => setVal((x) => x   1)}> </button>
      <button onClick={() => setVal((x) => x - 1)}>-</button>
      <FlashNumber value={val} />
    </div>
  );
}

const FlashNumber = ({ value }) => {
  const prevValueRef = useRef(value);
  const [bgColor, setBgColor] = useState("white");

  useEffect(() => {
    if (value > prevValueRef.current) {
      setBgColor("green");
    } else if (value < prevValueRef.current) {
      setBgColor("red");
    }
    prevValueRef.current = value;
    const timeout = setTimeout(() => {
      setBgColor("white");
    }, 1000);
    return () => clearTimeout(timeout);
  }, [value]);

  return <div style={{ backgroundColor: bgColor }}>{value}</div>;
};

// v18.x 
ReactDOM.createRoot(
    document.getElementById("root")
).render(
    <App />
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script>

<div id="root"></div>

CodePudding user response:

I would probably try to do most of the work in a css animation and keep the timeout hassle out of the component.

import classnames from "clsx"; // you can replace this with a bit of ternary gymnastics

const FlashNumber = ({ value }) => {
  const prevValue = usePrevious(value);
  return (
    <div className={classnames(
      value > prevValue && 'increasing',
      value < prevValue && 'decreasing',
    )}>
      {value}
    </div>
  );
};
const usePrevious = (value) => {
  const ref = useRef();
  useEffect(() => { ref.current = value; }, [value]);
  return ref.current;
}
@keyframes flash-red {
  0%   { background-color: red; }
  100% { background-color: white; }
}

@keyframes flash-green {
  0%   { background-color: green; }
  100% { background-color: white; }
}

.increasing {
  animation: flash-green 1s linear;
  animation-fill-mode: forwards;
}

.decreasing {
  animation: flash-red 1s linear;
  animation-fill-mode: forwards;
}

Note: this flashes only when the direction is changed. If you want to flash whenever a click happens this needs some more tweaking.

  • Related