using useEffect effectively

I started exploring react's functional component and really like how it has made the code neat and compressed the component to a few lines. We are going to discuss 3 most used scenarios there must be more where you can make use of useEffect hook. In this article, I'll be comparing useEffect with classical component hooks and explaining the whys and hows. So yeah here's the list of use cases.

  1. component with an api call
  2. component which receives props and you want to listen to the change of props
  3. component which receives props and you want to make a change in the props before the first render.

Before jumping into examples and code lets dissect what useEffect is and how it works. It takes two arguments first is a callback and second is an array for listening.

Component with an API call

So in the classical components, we used to make our api calls or any work we want to do before or after rendering of the component we mostly use hooks like componentWillMount or componentDidMount something like this

class UserList extends Component {
  state = {
    users: undefined,
    loading: true,
  }
  componentDidMount() {
    fetch("https://api.github.com/users")
      .then(res => res.json())
      .then(users => {
        console.log(users)
        this.setState({ users, loading: false })
      }
  }

  render() {
    const { loading, users } = this.props;
    if (loading) return <Loader size="small" />
    return (
      <div className="container">
        {
          users.map((user, index) => <UserCard key={index} data={user} />)
        }
      </div>
    )
  }
}

Now this code in functional component looks something like this

function UserList() {
  const [loading, setLoading] = useState(true)
  const [users, setUsers] = useState(undefined)
  useEffect(() => {
    fetch("https://api.github.com/users")
      .then(res => res.json())
      .then(users => {
        setLoading(false)
        setUsers(users)
      }  
  },[])
  if (loading) return <Loader size="small" />
  return (
    <div className="container">
      {
        users.map((user, index) => <UserCard key={index} data={user} />)
      }
    </div>
  )
}

Notice in this component we have an empty array defined as a second argument to useEffect which means it will runs only one time.

If you pass an empty array ([]), the props and state inside the effect will always have their initial values. While passing [] as the second argument is closer to the familiar componentDidMount and componentWillUnmount mental model

Component receives props

So in the classical components, if we want to listen to a props change we mostly listen in componentWillReceiveProps hook and then change the structure of data or if we want to just set it in a state we do that all in there something like this.

class UserList extends Component {
  state = {
    users: undefined,
    loading: true,
  }
  componentWillReceiveProps({ users }) {
    this.setState({ users, loading: false })
  }

  render() {
    const { loading, users } = this.props;
    if (loading) return <Loader size="small" />
    return (
      <div className="container">
        {
          users.map((user, index) => <UserCard key={index} data={user} />)
        }
      </div>
    )
  }
}

Now this code in a functional component will look something like this

function UserList({ apiUsers }) {
  const [loading, setLoading] = useState(true)
  const [users, setUsers] = useState(undefined)
  useEffect(() => {
    setUsers(apiUsers)
    setLoading(false)
  }, [apiUsers])
  if (loading) return <Loader size="small" />
  return (
    <div className="container">
      {
        users.map((user, index) => <UserCard key={index} data={user} />)
      }
    </div>
  )
}

Here we are listening to the change in apiUsers prop so every time there's a change in it useEffect with its callback gets called

Component receives props(first render)

So we sometimes have to deal with a component where we don't want it to listen to any prop but just render the component with whatever props in first go. It looks something like this.

class UserList extends Component {
  state = {
    users: undefined,
    loading: true,
  }
  componentDidMount() {
    const {users} = this.props;
    this.setState({users, loading: false})
  }

  render() {
    const { loading, users } = this.props;
    if (loading) return <Loader size="small" />
    return (
      <div className="container">
        {
          users.map((user, index) => <UserCard key={index} data={user} />)
        }
      </div>
    )
  }
}

Now this code in a functional component will look something like this

function UserList({ apiUsers }) {
  const [loading, setLoading] = useState(true)
  const [users, setUsers] = useState(undefined)
  useEffect(() => {
    setUsers(apiUsers)
    setLoading(false)
  }, [])
  if (loading) return <Loader size="small" />
  return (
    <div className="container">
      {
        users.map((user, index) => <UserCard key={index} data={user} />)
      }
    </div>
  )
}

Notice in this component we have an empty array defined as a second arguement to useEffect which means it will work as componentDidMount and runs only one time after.

Fin

So that was it! I'd love to hear your thoughts on this. Please do comment or email me about your takeout on this and if I miss something do let me know.🤝