0

I'm trying to convert componentDidUpdate to useEffects. The examples I've seen were pretty straight forward but since the componentDidUpdate didn't seem too involved. I need make conditional changes but not sure what I really need to do to accomplish using conditions on the prevProps vs props

Here's the current code that I want to convert so that I can move to functional components away from classes. I added the comment //This is what I need to convert where I'm stuck.

import React, { useState, useEffect } from 'react'
import PropTypes from 'prop-types'
// import './ListingControl.css'
import { cssClasses, isDurationSame } from './util'
import { durationLike, isPlayerState } from './props'
import moment from 'moment'
import {
  SKIP_BACKWARD,
  FAST_BACKWARD,
  FORWARD,
  BACKWARD,
  FAST_FORWARD,
  SKIP_FORWARD,
  PAUSE,
  STOP,
  STEP_FORWARD,
  STEP_BACKWARD,
  DEFAULT_INTERVALS,
  DEFAULT_LENGTHS,
} from './constants'
import listingManager from './player'

const SYMBOLS = {
  [SKIP_BACKWARD]: '\u23ee',
  [FAST_BACKWARD]: '\u23ea',
  [FORWARD]: '\u23f4',
  [BACKWARD]: '\u23f4',
  [FAST_FORWARD]: '\u23e9',
  [SKIP_FORWARD]: '\u23ed',
  [PAUSE]: '\u23f8',
  [STOP]: '\u23f9',
  [STEP_FORWARD]: '\u23e9',
  [STEP_BACKWARD]: '\u23ea',
}

const SkipBackward = () => SYMBOLS[SKIP_BACKWARD]
const FastBackward = () => SYMBOLS[FAST_BACKWARD]
const Forward = () => SYMBOLS[FORWARD]
const Backward = () => (
  <div style={{ transform: 'scale(-1,1)' }}>{SYMBOLS[BACKWARD]}</div>
)
const FastForward = () => SYMBOLS[FAST_FORWARD]
const SkipForward = () => SYMBOLS[SKIP_FORWARD]
const Pause = () => SYMBOLS[PAUSE]
const Stop = () => SYMBOLS[STOP]
const StepForward = () => SYMBOLS[STEP_FORWARD]
const StepBackward = () => SYMBOLS[STEP_BACKWARD]

const NAME_MAPPING = {
  [SKIP_BACKWARD]: 'SkipBackward',
  [FAST_BACKWARD]: 'FastBackward',
  [BACKWARD]: 'Backward',
  [FORWARD]: 'Forward',
  [FAST_FORWARD]: 'FastForward',
  [SKIP_FORWARD]: 'SkipForward',
  [PAUSE]: 'Pause',
  [STOP]: 'Stop',
  [STEP_FORWARD]: 'StepForward',
  [STEP_BACKWARD]: 'StepBackward',
}

const CSS_CLASS_MAPPING = {
  [SKIP_BACKWARD]: 'skip-backward',
  [FAST_BACKWARD]: 'fast-backward',
  [BACKWARD]: 'backward',
  [FORWARD]: 'forward',
  [FAST_FORWARD]: 'fast-forward',
  [SKIP_FORWARD]: 'skip-forward',
  [PAUSE]: 'pause',
  [STOP]: 'stop',
  [STEP_FORWARD]: 'step-forward',
  [STEP_BACKWARD]: 'step-backward',
}

const DEFAULT_TIPS = {
  [SKIP_BACKWARD]: 'skip to left',
  [FAST_BACKWARD]: 'rewind',
  [BACKWARD]: 'play backward',
  [FORWARD]: 'play forward',
  [FAST_FORWARD]: 'fast-forward',
  [SKIP_FORWARD]: 'skip to right',
  [PAUSE]: 'pause',
  [STOP]: 'stop',
  [STEP_FORWARD]: 'forward 1 frame',
  [STEP_BACKWARD]: 'backward 1 frame',
}

function nextDuration(d, durations) {
  for (let i = 0; i < durations.length; i++) {
    if (isDurationSame(d, durations[i])) {
      return durations[(i + 1) % durations.length]
    }
  }

  return durations[0]
}

const ListingControl = (props) => {

    this.state = {}

  const [interval, setInterval] = useState ();
  const [length, setLength] = useState();
  const [playerState, setPlayerState] = useState();

    if (props.listingManager) {
      setInterval(props.listingManager.interval);
      setLength(props.listingManager.length);
      setPlayerState(props.listingManager.playerState);
      props.listingManager.reListControl(ListingControl)
    }

     
  componentDidUpdate(prevProps) {
    if (prevProps.listingManager !== this.props.listingManager) {
      if (prevProps.listingManager) {
        prevProps.listingManager.deListControl(this)
      }
      if (this.props.listingManager) {
        this.props.listingManager.reListControl(this)
      }
    }
  }

  //componentDidUpdate Replacement Area
   useEffect(() => {
    console.log('component updated!')

  }) 


//componentWillUnmountReplacement
  useEffect(() => {
    return () => {
      props.listingManager.deListControl(ListingControl)
    }
  }, []) // notice the empty array

  const onPlayerCommand = c =>{
    if (this.props.onPlayerCommand) {
      this.props.onPlayerCommand(c)
    }
    if (this.props.listingManager) {
      this.props.listingManager.execute(c)
    }
  }

  const onLengthChange = length => {
    if (this.props.onLengthChange) {
      this.props.onLengthChange(length)
    }
    if (this.props.listingManager) {
      this.props.listingManager.onLengthChange(length)
    }
  }

  const onIntervalChange = interval=> {
    if (this.props.onIntervalChange) {
      this.props.onIntervalChange(interval)
    }
    if (this.props.listingManager) {
      this.props.listingManager.onIntervalChange(interval)
    }
  }

  const getControlsState = () => {
    if (this.props.listingManager) {
      return this.state
    } else {
      return props
    }
  }

  render() {
    let { controls, titles } = this.props
    const { interval, length, playerState } = this.getControlsState()

    controls = {
      SkipBackward,
      FastBackward,
      Backward,
      Forward,
      FastForward,
      SkipForward,
      Pause,
      Stop,
      StepBackward,
      StepForward,
      ...controls,
    }

    titles = {
      ...DEFAULT_TIPS,
      ...titles,
    }

    const stopped = playerState === STOP
    const paused = playerState === PAUSE

    const controlDisabled = {
      [SKIP_BACKWARD]: stopped,
      [FAST_BACKWARD]: stopped,
      [BACKWARD]: false,
      [FORWARD]: false,
      [FAST_FORWARD]: stopped,
      [SKIP_FORWARD]: stopped,
      [PAUSE]: false,
      [STOP]: stopped,
      [STEP_FORWARD]: !paused,
      [STEP_BACKWARD]: !paused,
    }

    const controlProps = { controls, playerState, length, interval }

    const getControl = (c) => {
      const C = controls[NAME_MAPPING[c]]

      const p = {
        className: cssClasses(
          'control',
          CSS_CLASS_MAPPING[c],
          controlDisabled[c] ? 'disabled' : ''
        ),
        onClick: () => this.onPlayerCommand(c),
      }

      if (titles[c]) {
        p.title = titles[c]
      }

      return (
        <div {...p}>
          <C {...controlProps} />
        </div>
      )
    }

    return (
      <div className="timeline-player">
        <div className="controls">
          <div
            className={cssClasses('control', 'interval')}
            onClick={() =>
              this.onIntervalChange(nextDuration(interval, DEFAULT_INTERVALS))
            }
          >
            {moment.duration(interval).as('s')}s
          </div>
          <div className="control sep"> / </div>
          <div
            className={cssClasses(
              'control',
              'length',
              stopped ? '' : 'disabled'
            )}
            onClick={() =>
              stopped &&
              this.onLengthChange(nextDuration(length, DEFAULT_LENGTHS))
            }
          >
            {moment.duration(length).as('s')}s
          </div>
          {getControl(SKIP_BACKWARD)}
          {paused ? getControl(STEP_BACKWARD) : getControl(FAST_BACKWARD)}
          {playerState === BACKWARD
            ? getControl(PAUSE)
            : getControl(BACKWARD)}
          {playerState === FORWARD ? getControl(PAUSE) : getControl(FORWARD)}
          {paused ? getControl(STEP_FORWARD) : getControl(FAST_FORWARD)}
          {getControl(SKIP_FORWARD)}
          {getControl(STOP)}
        </div>
      </div>
    )
  }
}

ListingControl.propTypes = {
  controls: PropTypes.object,
  onPlayerCommand: PropTypes.func,
  playerState: isPlayerState(),
  length: durationLike(),
  onLengthChange: PropTypes.func,
  interval: durationLike(),
  onIntervalChange: PropTypes.func,
  titles: PropTypes.object,
  listingManager: PropTypes.instanceOf(listingManager),
}

ListingControl.defaultProps = {
  playerState: STOP,
  length: 'PT10S',
  interval: 100,
}
export default ListingControl

I'm not sure how this will fit in with an example like

useEffect(() => {
    console.log('component updated!')
  }) 

2 Answers2

0

With useEffect you have a second parameter that is an array with dependencies. Any object that get changed on it, useEffect will listen and re-execute the main callback function. So you can try this:

useEffect(() => {
  // handle changes in prevProps.listingManager here
}, [prevProps.listingManager])

Note: Hooks doesn't work in class components, it are made for functional components

UPDATE:

Considering props.listingManager will be updated and props are read-only, you can workaround it with a local version of props.

const [localListingManager, setLocalListingManager] = useState();

and when props.listingManager get updated, you update the local with the new version:

useEffect(() => {
  setLocalListingManager(props.listingManager)
}, [props.listingManager])

With useEffect dependency list (that is [props.listingManager]) you'll have always the new version of props inside it. What means you don't need to have the prevProps anymore due to localListingManager variable

tomrlh
  • 1,006
  • 1
  • 18
  • 39
  • so I' sort of follow what you have for deList but, how would the 'this.props.listingManager.reListControl(this)' work? – JavaScriptGirl Oct 09 '20 at 16:05
  • I'm migrating the code from classes to components. this was my last step to convert but I'm hung up on the conditions in the componentDidUpdate method. – JavaScriptGirl Oct 09 '20 at 16:20
  • Using hooks you can't use ```this``` inside the component, and use ```useState``` hook instead of local state. Can you post your complete class component? – tomrlh Oct 09 '20 at 16:20
  • @tomrlh just updated with this with the entire code. I"m still working on the conversion to components but, that one area is where I'm stuck. – JavaScriptGirl Oct 09 '20 at 16:43
  • Updated the answer, please check if it makes sense to you – tomrlh Oct 11 '20 at 01:39
0

I think migrating to functional components is strange in some situations. I did it before on a huge app. My suggestion is don't try to convert the exact code to hooks, converting the exact code will confuse you. You should understand what you want from the component and rewrite that with hooks, this is easier. Hooks don't behave like a class lifecycle but you could create that behavior with hooks, that is more complicated to say here, I think you should use them as tools, not as a class lifecycle alternative.

Hossein Mohammadi
  • 1,388
  • 11
  • 16