1

I have literally tried for a few hours to replicate a clickable ticker, much like they have at the very top of this site: https://www.thebay.com/ I'm confused about what triggers useEffect and long story short, I can't come up with a solution that keeps the ticker moving AND also gives the option of clicking forward/backwards via arrows. Clicking the arrow should not permanently pause the ticker.

function Ticker() {
    const [tickerDisplay, setTickerDisplay] = useState('Free In-store Pickup')
    const [tickerIndex, setTickerIndex] = useState(0)
    const [arrowClicked, setArrowClicked] = useState(false)
    const notices = [
        'Easy Returns within 30 Days of Purchase',
        'Free Shipping on $99+ Orders',
        'Free In-store Pickup',
    ]
    const handleClick = (side) => {

        setArrowClicked(true)
        switch (side) {
            case 'left':
                setTickerIndex(
                    tickerIndex === 0 ? notices.length - 1 : tickerIndex - 1
                )
                break

            case 'right':
                setTickerIndex(
                    tickerIndex === notices.length - 1 ? 0 : tickerIndex + 1
                )
                break

            default:
                console.log('something went wrong')
                break
        }
    }
    useEffect(() => {

        if (arrowClicked) {
            setTickerDisplay(notices[tickerIndex])
            setTickerIndex(
                tickerIndex === notices.length - 1 ? 0 : tickerIndex + 1
            )
            setArrowClicked(false)
            return
        }
        setTimeout(() => {
            setTickerDisplay(notices[tickerIndex])
            setTickerIndex(
                tickerIndex === notices.length - 1 ? 0 : tickerIndex + 1
            )
            console.log('This will run every 6 seconds!')
        }, 6000)
    }, [tickerIndex, notices, tickerDisplay, arrowClicked])

    return (
        <IconContext.Provider value={{ className: 'ticker-icons-provider' }}>
            <div className='ticker'>
                <FaChevronLeft onClick={() => handleClick('left')} />
                <div className='ticker_msg-wrapper'>{tickerDisplay}</div>
                <FaChevronRight onClick={() => handleClick('right')} />
            </div>
        </IconContext.Provider>
    )
}

export default Ticker

What is the best way to code this component?

skyboyer
  • 22,209
  • 7
  • 57
  • 64
algojedi
  • 57
  • 1
  • 4

1 Answers1

0

This is not a work of art and probably some things could've been done better.

Hope that suits you.

const { useRef, useState, useEffect } = React;

const getItems = () => Promise.resolve(['All of our questions are now open', 'Answers extended: 72 hours after questions open', 'Post a question or get an answer', 'Free badges on 20k points'])

const Ticker = ({onPrevious, onNext, items, currentIndex}) => {
  const ref = useRef(null);
  const [size, setSize] = useState({
    width: 0,
    widthPx: '0px',
    height: 0,
    heightPx: '0px'
  })
  
  useEffect(() => {
    if(ref && ref.current) {
      const {width, height} = ref.current.getBoundingClientRect();
      setSize({
        width,
        widthPx: `${width}px`,
        height,
        height: `${height}px`
      })
    }
  }, [ref]);
  
  const calculateStyleForItem = (index) => {
    return {
      width: size.width,
      transform: `translateX(${0}px)`
    }
  }
  
  const calculateStyleForContainer = () => {
    return {
      width: `${size.width * (items.length + 1)}px`,
      transform: `translateX(${-currentIndex * size.width + 2 * size.width}px)`
    }
  }

  return <div ref={ref} className="ticker">
    <div style={{width: size.widthPx, height: size.heightPx}} className="ticker__foreground">
      <div onClick={onPrevious} className="arrow">{'<'}</div>
      <div onClick={onNext} className="arrow">{'>'}</div>
    </div>
    <div>
      <div style={calculateStyleForContainer()} className="ticker__values">
        {items.map((value, index) => <div key={index} style={calculateStyleForItem(index)}className="ticker__value">{value}</div>)}
      </div>
    </div>
  </div>
}

const App = () => {
  const [items, setItems] = useState([]);
  const [currentIndex, setCurrentIndex] = useState(0);
  const [clicked, setClicked] = useState(false);

  useEffect(() => {
    let isUnmounted = false;
    
    getItems()
      .then(items => {
      if(isUnmounted) {
        return
      }
      
      setItems(items);
    })
      
    return () => {
      isUnmounted = true;
    }
  }, [])
  
  useEffect(() => {
    if(!items.length) {
      return () => {
        
      }
    }
    
    let handle = null;
    
    const loop = () => {
      if(!clicked) {
        onNext(null);
      }
    
      setClicked(false);
      handle = setTimeout(loop, 2000);
    }
    
    handle = setTimeout(loop, 2000);
    
    return () => {
      clearTimeout(handle);
    }
  
  }, [items, clicked])
  
  const onPrevious = () => {
    setClicked(true);
    setCurrentIndex(index => (index - 1) > -1 ? index - 1 : items.length - 1)
  }
  
  const onNext = (programmatically) => {
    if(programmatically) {
      setClicked(programmatically);
    }

    setCurrentIndex(index => (index + 1) % items.length)
  }
  
  return <div>
    {items.length ? <Ticker onPrevious={onPrevious} onNext={onNext} currentIndex={currentIndex} items={items}/> : 'Loading'}
  </div>
}

ReactDOM.render(
    <App />,
    document.getElementById('root')
  );
html, body {
  box-sizing: border-box;
  margin: 0;
}

.ticker {
  display: flex;
  justify-content: center;
  align-items: center;
  background: black;
  font-size: 1rem;
  color: white;
  font-weight: bold;
  padding: 1rem;
  overflow: hidden;
}

.ticker__foreground {
  position: absolute;
  z-index: 1;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.ticker__values {
  transition: all .3s ease-in;
}

.ticker__value {
  
  text-align: center;
  display: inline-block;
  vertical-align: middle;
  float: none;
}

.arrow {
  font-size: 1.5rem;
  cursor: pointer;
  padding: 1rem;
}
<script src="https://unpkg.com/react/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<div id="root"></div>
Józef Podlecki
  • 10,453
  • 5
  • 24
  • 50