简体   繁体   中英

How to resolve a useReducer's dispatch function inside a promise in ReactJS

I am building a simple drum machine app with react and I'm using the Context Api with hooks and useReducer. The app's initial state is an object with a power key set to false.

Basically what i want to achieve is; when the power is 'on' (true), it will dispatch an action to display a blinking "-LOADING PRESETS-" for a short period of time like 2 seconds then fire another dispatch action to display the default settings like "Drums" that doesn't blink

When the power is off it will reset everything to the initial state. I used the useEffect with 2 functions that returns a promise to simulate an asynchronous steps.

I got almost everything right and going; the drum pads, audio and lights are all working except for the loading feature. The problem is it skips displaying the blinking "LOADING PRESETS" and automatically resolve to display the default settings "Drums" that blinks (it shouldn't). I am attaching a class to make the text blink and removing the class after the 2 seconds delay expires. I'm not sure how and where to clean up / clear setTimeout so i commented it out for now. Need help!

Here's the App.js code:

const initialState = {
  power: false,
  display: '...',
  sound_mode: '...',
  volume_lvl: 0.4
};

const stateReducer = (state = initialState, action) => {
  switch (action.type) {
    case 'TOGGLE_POWER':
      return { ...state, power: !state.power };
    case 'ADJUST_VOLUME':
      return { ...state, volume_lvl: action.volume };
    case 'SWITCH_TO_SYNTH_MODE':
      return {
        ...state,
        display: action.display,
        sound_mode: action.sound_mode
      };
    case 'SWITCH_TO_DRUM_MODE':
      return {
        ...state,
        display: action.display,
        sound_mode: action.sound_mode
      };
    case 'CHANGE_DISPLAY':
      return { ...state, display: action.display };
    case 'LOAD_SETTINGS':
      return {
        ...state,
        display: 'Kick',
        sound_mode: 'Drums',
        volume_lvl: 0.4
      };
    case 'RESET_DEFAULTS':
      return {
        ...state,
        power: false,
        display: '...',
        sound_mode: '...',
        volume_lvl: 0.4
      };
    default:
      return state;
  }
};

function App() {
  const [appState, dispatch] = useReducer(stateReducer, initialState);

  return (
    <AppContext.Provider value={{ appState, dispatch }}>
      <div className='DrumApp'>
        <Header />
        <DrumInterface />
        <Footer />
      </div>
    </AppContext.Provider>
  );
}

The Header section where the power button is:

function Header() {
  const { appState, dispatch } = useContext(AppContext);
  const { power } = appState;
  const powerAudioRef = useRef();
  const displayRef = useRef();

  useEffect(() => {
    function loadPrompt(power) {
      return new Promise((resolve, reject) => {
        if (power === true) {
          displayRef.current = setTimeout(() => {
            resolve([
              dispatch({
                type: 'CHANGE_DISPLAY',
                display: '--loading preset--'
              }),
              document.getElementById('preset-status').classList.add('loading')
            ]);
          }, 2000);
        }
      });
    }

    function loadSettings() {
      return new Promise((resolve, reject) => {
        resolve([
          //document.getElementById('preset-status').classList.remove('loading'),
          dispatch({ type: 'LOAD_SETTINGS' }),
          clearTimeout(displayRef.current)
        ]);
      });
    }

    async function loadPresetSettings() {
      await loadPrompt(power);
      await loadSettings();
    }

    loadPresetSettings();

    // return () => {
    //   clearTimeout(displayRef.current);
    // };
  }, [power, dispatch]);

  useEffect(() => {
    if (power === false) {
      dispatch({ type: 'RESET_DEFAULTS' });
    }
  }, [power, dispatch]);

  const handlePower = () => {
    dispatch({ type: 'TOGGLE_POWER' });
    handlePowerSound();
  };

  const handlePowerSound = () => {
    document.getElementById('power-btn-audio').play();
    document.getElementById('power-btn-audio').currentTime = 0;
  };

  return (
    <header id='App-header'>
      <div id='logo'>
        <h1>
          <span className='orange'>Drum</span>Machine
        </h1>{' '}
        <p className='small'>
          Powered by:{' '}
          <FaReact
            className={power === true ? 'react-icon spin' : 'react-icon'}
          />{' '}
          React
        </p>
      </div>
      <div id='power'>
        <button
          id='power-btn'
          className={power === true ? 'on' : null}
          onClick={handlePower}
        >
          <FaPowerOff />
        </button>
        <audio
          ref={powerAudioRef}
          id='power-btn-audio'
          src='https://res.cloudinary.com/dzsmdyknz/video/upload/v1532750168/sample-swap/sfx-and-unusual-sounds/bleeps-blips-blonks-blarts-and-zaps/boip.mp3'
          className='btn-audio-efx'
        >
          Your browser does not support the audio element.
        </audio>
      </div>
    </header>
  );
}

UPDATE:

Finally, I am able to solve the problem of simulating asynchronous processes, here's my code:

useEffect(() => {

    /* Display the loading preset and add a classname to make it blink */
    function promptLoadingStart() {
      return new Promise((resolve, reject) => {
        let wait = setTimeout(() => {
          dispatch({ type: 'CHANGE_DISPLAY', display: '--loading preset--' });
          document.getElementById('preset-status').classList.add('loading');
          clearTimeout(wait);
          resolve('prompt loading started');
        }, 2000);
      });
    }

    /* Remove the classname for blinking text */
    function prompLoadingEnd() {
      return new Promise((resolve, reject) => {
        let wait = setTimeout(() => {
          document.getElementById('preset-status').classList.remove('loading');
          clearTimeout(wait);
          resolve('prompt loading ended');
        }, 1000);
      });
    }

    /* Display the initial settings */
    function loadSettings() {
      return new Promise((resolve, reject) => {
        let wait = setTimeout(() => {
          dispatch({ type: 'LOAD_SETTINGS' });
          handlePowerSound();
          clearTimeout(wait);
          resolve('load settings done');
        }, 200);
      });
    }

    async function loadPreset() {
      const step_one = await promptLoadingStart();
      console.log(step_one);
      const step_two = await prompLoadingEnd();
      console.log(step_two);
      const step_three = await loadSettings();
      console.log(step_three);
    }
    if (power === true) {
      loadPreset();
    }
  }, [power, dispatch]);

Guys if you have a much better solution I want to know how. Thanks!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM