React custom hooks

by jonahe

HTML

<div id="app"></div>

CSS

body {
  background: #20262E;
  padding: 20px;
  font-family: Helvetica;
}

#app {
  background: #fff;
  border-radius: 4px;
  padding: 20px;
  transition: all 0.2s;
}

li {
  margin: 8px 0;
}

h2 {
  font-weight: bold;
  margin-bottom: 15px;
}

.done {
  color: rgba(0, 0, 0, 0.3);
  text-decoration: line-through;
}

input {
  margin-right: 5px;
}

React

const { useState, useCallback} = React;

function useSequenceStates({possibleStates = [], startStateIndex = 0, shouldLoop = false}) {
  const [currentStateIndex, setCurrentStateIndex] = useState(startStateIndex);
  const goToNextState = useCallback(() => {
    const highestAllowedIndex = possibleStates.length - 1;
    const nextIndex = currentStateIndex + 1;
    if(nextIndex <= highestAllowedIndex) {
      setCurrentStateIndex(nextIndex);
    } else {
      if(shouldLoop) {
        setCurrentStateIndex(0);
      } else {
        console.warn("no next state");
      }
    }
    
  }, [currentStateIndex, possibleStates, shouldLoop]);

  const goToPreviousState = useCallback(() => {
    const lowerstAllowedIndex = 0;
    const previousIndex = currentStateIndex - 1;
    if(previousIndex >= lowerstAllowedIndex) {
      setCurrentStateIndex(previousIndex);
    } else {
      if(shouldLoop) {
        setCurrentStateIndex(possibleStates.length - 1);
      } else {
        console.warn("no previous state");
      }
    }
    
  }, [currentStateIndex, possibleStates, shouldLoop]);


  const currentState = possibleStates[currentStateIndex];

  return [currentState, { goToPreviousState, goToNextState}];
}




function Test() {
 const [allowedStates, setAllowedStates ] = useState(["a", "b", "c", "d", "f"]);
 
 const allowedStates2 = [1,2,3,4];
 const [currentState, { goToPreviousState, goToNextState}] = useSequenceStates({possibleStates: allowedStates, shouldLoop: true });
 const [currentState2, { goToPreviousState: goToPreviousState2 , goToNextState: goToNextState2 }] = useSequenceStates({possibleStates: allowedStates2, shouldLoop: true });
 
 return (
   <div>
     <h2>space separated list of allowed states</h2>
     <input type="text" value={ allowedStates.join(" ")} onChange={ evt => setAllowedStates(evt.target.value.split(" "))} />
     <hr/>
     <h2>1: {currentState}</h2>
     <div>
         <button onClick={goToPreviousState}>
           previous state
        ...