skip to Main Content

I have a visuals state that contains a list of JSX visuals, and their corresponding references stored in a useRef object. I key into both of these with a ref "counter". Currently in the useEffect, when pressed (a state) is true, I create the visual state and the visual ref along with it. Then the second part of the if statement runs after some time, which sets a timeout for 3 seconds, then deletes the corresponding visuals ref and state. The deletion of the ref works fine, and the state does too, correctly removing the visual by using filter so as to not mutate the state.

The problem is that filtering out the desired visual is setting the last ref in visual_refs to null. I have verified that it is the set_visuals() statement causing the problem, and also verified that the visual I need filtered out is not the one being set to null. If I comment out the deletion of the visual ref above the set_visuals(), the error still occurs

I have had this problem before when deleting a key from a state’s prev_state as I learned that this mutates the state, however filter + reduce should not. What is causing this and why?

enter image description here

let visual_refs = useRef([])
  let curr_animation = useRef([null, true])
  let [visuals, set_visuals] = useState({})
  let glowline = useRef(null)
 

  let counter = useRef(0)


  useEffect(() => {
    if (pressed) {
      audio.current.play()

      set_visuals(prev_state => {
        curr_animation.current[1] = true
        
        return ({
          ...prev_state,
          [counter.current]: (
          <div key={`${counter.current}`} ref={ref => visual_refs.current[counter.current] = ref} 
          className={`visualizer-instance ${color === 'black' ? 'black-visualizer': ''}  ${mp_pressed ? 'mp-visual': ''}`}></div>
          )
      })}) 
      
      attribute_animation(glowline.current, 'opacity', '0', '1', 600, 'cubic-bezier(0,.99,.26,.99)')

    } else if (!pressed && pressed !== null && counter.current in visual_refs.current && curr_animation.current[0]) {
        console.log(visual_refs.current, counter.current)
        curr_animation.current[0].pause()
        attribute_animation(visual_refs.current[counter.current], 'bottom', '0', '300000px', 1000000)
        attribute_animation(glowline.current, 'opacity', '1', '0', 3000, 'cubic-bezier(.19,.98,.24,1.01)')

        let curr_counter = counter.current
        setTimeout(() => {
          delete visual_refs.current[curr_counter]
          set_visuals(prev_state => {
            const new_state = Object.keys(prev_state).filter(key =>  key !== curr_counter.toString()).reduce((acc, key) => {
              acc[key] = prev_state[key]
              return acc
            }, {})
            return new_state
          })
        }, 3000, curr_counter)
          
        counter.current += 1
    }
  }, [pressed])

2

Answers


  1. Chosen as BEST ANSWER

    I changed from filter and reduce to just for looping through it and it fixed the issue. Now Im scared to ever use filter/reduce again.

    setTimeout(() => {
              
              set_visuals(prev_state => {
                let l = {}
                for (let key in prev_state) {
                  if (key !== curr_counter) {
                    l[key] = prev_state[key]
                  }
                }
                return l
              })
              delete visual_refs.current[curr_counter]
            }, 3000, curr_counter)
    

  2. Array.filter() returns a shallow copy of the search parameters, which could be problematic if any of the values returned in the shallow copy are null.

    Otherwise, I know that when working with state in react it is good to have a value (in this case prev_state()) defined to something as a placeholder, because null values usually don’t play nice and update well.

    Have you tried setting prev_state to some placeholder value before attempting to mutate it in set_visuals()?

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