Using Hooks + Callbacks

I am currently converting this open source template (React + Firebase + Material UI). If you look in many parts of the codebase, you'll noticed after a state variable is changed, there will then be a call back. Here is one example from the signUp method found within SignUpDialog.js file:

signUp = () => {
const {
  firstName,
  lastName,
  username,
  emailAddress,
  emailAddressConfirmation,
  password,
  passwordConfirmation
} = this.state;

const errors = validate({
  firstName: firstName,
  lastName: lastName,
  username: username,
  emailAddress: emailAddress,
  emailAddressConfirmation: emailAddressConfirmation,
  password: password,
  passwordConfirmation: passwordConfirmation
}, {
  firstName: constraints.firstName,
  lastName: constraints.lastName,
  username: constraints.username,
  emailAddress: constraints.emailAddress,
  emailAddressConfirmation: constraints.emailAddressConfirmation,
  password: constraints.password,
  passwordConfirmation: constraints.passwordConfirmation
});

if (errors) {
  this.setState({
    errors: errors
  });
} else {
  this.setState({
    performingAction: true,

    errors: null
  }, () => {        //!HERE IS WHERE I AM CONFUSED
    authentication.signUp({
      firstName: firstName,
      lastName: lastName,
      username: username,
      emailAddress: emailAddress,
      password: password
    }).then((value) => {
      this.props.dialogProps.onClose();
    }).catch((reason) => {
      const code = reason.code;
      const message = reason.message;

      switch (code) {
        case 'auth/email-already-in-use':
        case 'auth/invalid-email':
        case 'auth/operation-not-allowed':
        case 'auth/weak-password':
          this.props.openSnackbar(message);
          return;

        default:
          this.props.openSnackbar(message);
          return;
      }
    }).finally(() => {
      this.setState({
        performingAction: false
      });
    });
  });
}

};

With hooks, I tried something like this within the else statement...

setPerformingAction(true)
setErrors(null), () => {...}

I'll be honest, I am not the greatest at callbacks. From what I think this is doing is then calling the following methods after the state has been set. That said, this is not correct according to eslint and I was hoping to see if anyone could help. Thanks, Brennan.

If I understand your question correctly, you would like to know how to achieve the same behavior that the class based setState callback provides, but using functional components..

Thinking in functional components is a different ballgame than thinking in class based components.. The easiest way to put it is that class based components are more imperative, while hooks/functional components are more declarative.

The useEffect hook requires a dependency array (the part at the end }, [clicks]) is the dependency array) - anytime a variable that is included in the dependency array is changed, the useEffect method is fired.

What this means is you can use useEffect in a similar fashion to a setState callback.. Hooks allow you to focus in on, and have fine-grained control over, very specific parts of your state.

This is a good thread to check out - and more specifically, a good explanation of the difference between class based (setState) and hooks based (useState) paradigms.

The following example demonstrates how to achieve something similar to "callback" behavior, but using hooks/functional components.

const { render } = ReactDOM;
const { Component, useState, useEffect } = React;

/**
 * Class based with setState
 */
class MyClass extends Component {
  state = {
    clicks: 0,
    message: ""
  }

  checkClicks = () => {
    let m = this.state.clicks >= 5  ? "Button has been clicked at least 5 times!" : "";
    this.setState({ message: m });
  }

  handleIncrease = event => {
    this.setState({
      clicks: this.state.clicks + 1
    }, () => this.checkClicks());
  }
  
  handleDecrease = event => {
    this.setState({
      clicks: this.state.clicks - 1
    }, () => this.checkClicks());
  }
  
  render() {
    const { clicks, message } = this.state;
    
    return(
      <div>
        <h3>MyClass</h3>
        <p>Click 'Increase' 5 times</p>
        <button onClick={this.handleIncrease}>Increase</button>
        <button onClick={this.handleDecrease}>Decrease</button>
        <p><b><i>MyClass clicks:</i></b> {clicks}</p>
        <p>{message}</p>
      </div>
    );
  }
}


/**
 * Function based with useState and useEffect
 */
function MyFunction() {
  const [clicks, setClicks] = useState(0);
  const [message, setMessage] = useState("");
  
  useEffect(() => {
    let m = clicks >= 5 ? "Button has been clicked at least 5 times!" : "";
    setMessage(m);
  }, [clicks]);
  
  const handleIncrease = event => setClicks(clicks + 1);
  const handleDecrease = event => setClicks(clicks - 1);
  
  return(
    <div>
      <h3>MyFunction</h3>
      <p>Click 'Increase' 5 times</p>
      <button onClick={handleIncrease}>Increase</button>
      <button onClick={handleDecrease}>Decrease</button>
      <p><b><i>MyFunction clicks:</i></b> {clicks}</p>
      <p>{message}</p>
    </div> 
  );
}


function App() {
  return(
    <div>
      <MyClass />
      <hr />
      <MyFunction />
    </div>
  );
}


render(<App />, document.body);
p {
  margin: 1px;
}

h3 {
  margin-bottom: 2px;
}

h3 {
  margin-top: 1px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>

This hook gives us the memoized value of an object, but instead of passing an array of dependencies (like with useMemo) we pass a custom compare function that gets both the previous and new value. The compare function can then compare nested properties, call object methods, or whatever else you need to do in order to determine equality.

You can use the setter of useState multiple times in a handler but you should pass a function to the setter instead of just setting it.

This also solves missing dependency if you create your handler with useEffect.

const [state, setState] = useState({ a: 1, b: 2 });
const someHandler = useCallback(
  () => {
    //using callback method also has the linter
    //  stop complaining missing state dependency
    setState(currentState => ({ ...currentState, a: currentState.a + 1 }));
    someAsync.then(result =>
      setState(currentState => ({
        ...currentState,
        b: currentState.a * currentState.b,
      }))
    );
  },
  //if I would do setState({ ...state, a: state.a + 1 });
  //  then the next line would need [state] because the function
  //  has a dependency on the state variable. That would cause
  //  someHandler to be re created every time state changed
  //  and can make useCallback quite pointless
  [] //state dependency not needed
);

Note that this component can actually be unmounted before your async work finishes and would cause a warning if you call state setter when component is unmounted so you'd better wrap the setter.

Last time I told you that checking mounted is pointless because you were checking it in App component and that component never unmounts (unless you can show me some place in your code that does unmount it) but this component looks like something that may unmount at some point.

The user can install and remove a hook procedure by using the menu. When a hook procedure is installed and an event that is monitored by the procedure occurs, the procedure writes information about the event to the client area of the application's main window.

In this case if you want to run some callback function if there are any errors after performing an action, you can use useEffect in this case since react hooks do not accept a 2nd optional callback function as with setState.

you can add errors into the dependency array of useEffect and write your callback function inside useEffect. Which will potentionally mean to run some funtion if there are any errors.

performAnAction = () => {

   if (actionWentWrong) {
     setError();  // maintained in state using useState  ( const [error, setError] = useState(false);
   }

}

useEffect(() => {

   // inside of this function if there are any errors do something
   if(error) {
     // do something which you were previously doing in callback function
   }
}, [error]); // this useEffect is watching if there is any change to this error state

Hooks solve a wide variety of seemingly unconnected problems in React that we’ve encountered over five years of writing and maintaining tens of thousands of components. Whether you’re learning React, use it daily, or even prefer a different library with a similar component model, you might recognize some of these problems.

Building Your Own Hooks Extracting a Custom Hook. When we want to share logic between two JavaScript functions, we extract it to a third Using a Custom Hook. In the beginning, our stated goal was to remove the duplicated logic from the FriendStatus and useYourImagination (). Custom Hooks offer

Only Call Hooks at the Top Level Don’t call Hooks inside loops, conditions, or nested functions. Instead, always use Hooks at the top level of your React function. By following this rule, you ensure that Hooks are called in the same order each time a component renders.

Line 1: We import the useState Hook from React. It lets us keep local state in a function component. Line 4: Inside the Example component, we declare a new state variable by calling the useState Hook. It returns a pair of Line 9: When the user clicks, we call setCount with a new value. React