0

I have a react App that passes to a SlotMachine component when it senses an API hit (using Pusher for publish-subscribe).

It should only rotate() once per API hit, however, as you can see in my output it invokes rotate() multiple times and says that props have changed before each time. BUT the props look the same in the console and there are not more API hits after the initial one (I'm triggering using Postman).

Any reason for my props to change so much? Something I don't understand about hooks or props? Also, why does my rotate function constantly get called except after the first API hit?

App:

import React, { useState, useEffect } from 'react'
import { render } from 'react-dom'
import Pusher from 'pusher-js'
import SlotMachine from './components/SlotMachine'

const App = () => {
  const [ slots, setSlots ] = useState([]);
  const [ apiHit, setApiHit ] = useState(false);

  const pusher = new Pusher('XXXXXXX', {
         cluster: 'XXX',
         encrypted: true
       });

  const channel = pusher.subscribe('my-channel');
     channel.bind('my-event', data => {
       console.log(`Got data from pusher ${data}`);
       setSlots(data);
       setApiHit(true);
     });

  return(
    <div>
      <SlotMachine slots={slots} apiHit={apiHit}/>
    </div>
  );
};

render(<App />, document.getElementById('root'));

Slots Machine:

 import React, { useEffect, useRef, useState } from 'react'
    import './slots.css'

    function rnd(min, max) {
      return Math.floor(Math.random() * (max - min)) + min
    }

    const SlotMachine = (props) => {
        const ringElements = useRef();
        const [manual, setManual] = useState(true);
        const [rings, updateRings] = useState([]);
        const [ slots, setSlots ] = useState([]);

        const slotMap = {
          0: 72,
          1: 144,
          2: 216,
          3: 288,
          4: 360
      };

        useEffect(()=>{
          console.log(' --- props change --- ');
          console.log(props);
          setSlots( slots => props.slots );
          if(props.slots !== undefined && props.apiHit === true ) {
            setManual(manual => false);
            rotate();
            setManual(true);
          }
        }, [props.slots, props.apiHit]);

        //need to store previous to get accurate measure
        const rotate = () => {
          console.log(`---ROTATE---`);
            rings.forEach((el, i) => {
              let obj = el; //should be an obj
              console.log(obj);
              let prev = null;
              if (obj.prev !== null) {  prev = obj.prev; } //update prev if not first time
              console.log(`Prev is ${prev}`);
              let element = obj.ringElement; //should be the ring itself
              //let obj  = rings[i];
             console.log(element);
              //get the slots it needs to move to
              let move = manual && !props.apiHit ? slotMap[rnd(0,4)] : slotMap[slots[i]];
                console.log(`move: ${move}`);
              let diff = Math.abs(prev - move);
              console.log(`The diff is ${diff}`);
              //take the previous and the move and figure out "difference", be sure to add that difference to the spin
              let res = move + (360 * rnd(3,6));
                //let res = move //+ (360 * rnd(3,6)); //randomly spin should end up back at same spot no matter what
                console.log(`rotateX(${res}deg) for ring${i}`);
                element.style.transform = `rotateX(${res}deg)`;
              obj.prev = move;
                //rings[i] = obj;// update proper object in state
              rings[i] = obj;
              updateRings( rings  );
            });
         }

    //when first mounts
        useEffect(() => {
          console.log('component did mount');
            Array.from(ringElements.current.children).forEach((el, i) => {
              console.log(`elements in ring: ${el}`);
              let obj = {}
              obj.ringElement = el
              obj.prev = null
              updateRings(rings => [...rings, obj])
            //  updateRings(rings => [...rings, el]);
            });
          }, []);

        return(
          <div>
            <div className="slots">
            <div className="rings" ref={ringElements}>
              <div className="ring">
                <div className="slot" id="0">0</div>
                <div className="slot" id="1">1</div>
                <div className="slot" id="2">2</div>
                <div className="slot" id="3">3</div>
                <div className="slot" id="4">4</div>
              </div>
              <div className="ring">
              <div className="slot" id="0">0</div>
              <div className="slot" id="1">1</div>
              <div className="slot" id="2">2</div>
              <div className="slot" id="3">3</div>
              <div className="slot" id="4">4</div>
              </div>
              <div className="ring">
              <div className="slot" id="0">0</div>
              <div className="slot" id="1">1</div>
              <div className="slot" id="2">2</div>
              <div className="slot" id="3">3</div>
              <div className="slot" id="4">4</div>
              </div>
            </div>
          </div>
          <button className="spin-button" onClick={() => { setManual(true); rotate(); } }>SPIN</button>
        </div>

      );
    }

    export default SlotMachine

Output:

initial API Hit (works as expected, triggers Rotate() exactly once

Got data from pusher 3,1,3 .   <-- new API data recieved 
55  **--- props change ---** 
56 {slots: Array(3), apiHit: false}
55  **--- props change ---**is  
56 {slots: Array(3), apiHit: true}
71 **---ROTATE---**
75 {ringElement: div.ring, prev: null}
83 Prev is null
87 <div class=​"ring" style=​"transform:​ rotateX(2088deg)​;​">​…​</div>​
90 move: 288
92 The diff is 288
96 rotateX(2088deg) for ring0
75 {ringElement: div.ring, prev: null}
83 Prev is null
87 <div class=​"ring" style=​"transform:​ rotateX(1224deg)​;​">​…​</div>​
90 move: 144
92 The diff is 144
96 rotateX(1224deg) for ring1
75 {ringElement: div.ring, prev: null}
83 Prev is null
87 <div class=​"ring" style=​"transform:​ rotateX(2088deg)​;​">​…​</div>​
90 move: 288
92 The diff is 288
96 rotateX(2088deg) for ring2

Subsequent API hits do multiple rotate calls and show props changing before each one.

Got data from pusher 3,0,1 . <-- new API hit 
55  --- props change ---
56 {slots: Array(3), apiHit: true}
71 ---ROTATE---
75 {ringElement: div.ring, prev: 288}
83 Prev is 288
87 <div class=​"ring" style=​"transform:​ rotateX(2088deg)​;​">​…​</div>​
90 move: 288
92 The diff is 0
96 rotateX(1368deg) for ring0
75 {ringElement: div.ring, prev: 144}
83 Prev is 144
87 <div class=​"ring" style=​"transform:​ rotateX(1224deg)​;​">​…​</div>​
90 move: 144
92 The diff is 0
96 rotateX(1584deg) for ring1
75 {ringElement: div.ring, prev: 288}
83 Prev is 288
87 <div class=​"ring" style=​"transform:​ rotateX(2088deg)​;​">​…​</div>​
90 move: 288
92 The diff is 0
96 rotateX(2088deg) for ring2
VM12097 index.js:39 Got data from pusher 3,0,1 . <-- **same** API hit but recorded again????  
55  --- props change --- 
56 {slots: Array(3), apiHit: true}apiHit: trueslots: (3) [3, 0, 1]__proto__: Object
71 ---ROTATE---
75 {ringElement: div.ring, prev: 288}
83 Prev is 288
87 <div class=​"ring" style=​"transform:​ rotateX(2088deg)​;​">​…​</div>​
90 move: 288
92 The diff is 0
96 rotateX(1368deg) for ring0
75 {ringElement: div.ring, prev: 144}
83 Prev is 144
87 <div class=​"ring" style=​"transform:​ rotateX(1224deg)​;​">​…​</div>​
90 move: 72
92 The diff is 72
96 rotateX(1152deg) for ring1
75 {ringElement: div.ring, prev: 288}
83 Prev is 288
87 <div class=​"ring" style=​"transform:​ rotateX(2088deg)​;​">​…​</div>​
90 move: 144
92 The diff is 144
96 rotateX(1584deg) for ring2
VM12097 index.js:39 Got data from pusher 3,0,1
55  --- props change --- 
56 {slots: Array(3), apiHit: true}
71 ---ROTATE---
75 {ringElement: div.ring, prev: 288}
83 Prev is 288
87 <div class=​"ring" style=​"transform:​ rotateX(2088deg)​;​">​…​</div>​
90 move: 288
92 The diff is 0
96 rotateX(1728deg) for ring0
75 {ringElement: div.ring, prev: 72}
83 Prev is 72
87 <div class=​"ring" style=​"transform:​ rotateX(1224deg)​;​">​…​</div>​
90 move: 72
92 The diff is 0
96 rotateX(1152deg) for ring1
75 {ringElement: div.ring, prev: 144}
83 Prev is 144
87 <div class=​"ring" style=​"transform:​ rotateX(2088deg)​;​">​…​</div>​
90 move: 144
92 The diff is 0
96 rotateX(1224deg) for ring2
Kyle Calica-St
  • 2,629
  • 4
  • 26
  • 56
  • You know how to `useEffect`. It's puzzling that you didn't use one for setting a subscription. It's a side effect, isn't it? – marzelin Nov 12 '19 at 20:08
  • @marzelin i'm still learning pusher itself as well as hooks. it is a mount point but i always get confused on using effects that i was on no changes and to just stay there i.e. `[]`? – Kyle Calica-St Nov 12 '19 at 21:25

2 Answers2

3

I think the problem is due to you create Pusher every time your app gets rendered. Move that code outside of your app will solve problem I guess. Updated: and register the event once the app is ready

const pusher = new Pusher('XXXXXXX', {
   cluster: 'XXX',
   encrypted: true
});
const App = () => {
  const [ slots, setSlots ] = useState([]);
  const [ apiHit, setApiHit ] = useState(false);
  useEffect(() => {
    const channel = pusher.subscribe('my-channel');
    const handler = data => {
      console.log(`Got data from pusher ${data}`);
      setSlots(data);
      setApiHit(true);
    };
    channel.bind('my-event', handler);

    return () => {
      channel.unbind(null, handler);
      channel.unsubscribe();
    }
  } ,[]);
  return(
      <div>
        <SlotMachine slots={slots} apiHit={apiHit}/>
      </div>
    );
  };
Dimitri L.
  • 4,499
  • 1
  • 15
  • 19
duc mai
  • 1,412
  • 2
  • 10
  • 17
1

If you want to reduce multiple renders, combine multiple state into one object and update the respective keys

e.g.

// const [manual, setManual] = useState(true);
// const [rings, updateRings] = useState([]);
// const [ slots, setSlots ] = useState([]);
const [state, setState] = useState({ manual: true, rings: [], slots: [] });

and in your effects, code in a way to create and update the state and finally set it once.


 useEffect(()=>{
      console.log(' --- props change --- ');
      console.log(props);
      const newState = { ...state };
      newState.slots = props.slots;
      if(props.slots !== undefined && props.apiHit === true ) {
        newState.manual = false;
        setState(newState);
        rotate();
        setState({ ...state, manual: true });
      }
    }, [props.slots, props.apiHit]);
Zohaib Ijaz
  • 21,926
  • 7
  • 38
  • 60