Search code examples
javascriptreactjsreact-hooksuse-effectuse-state

onClick function requires two clicks to update state


I'm trying to create an app that has favoriting functionality. The functionality actually works but only after the second click.

Edit for additional information: On the first click, the app tries to send information to my database but it's incomplete. It's only sending my initial values. On the second click, it finally appends the additional required values and gets saved to the database.

I'm pretty new to hooks so please feel free to comment on any improvement my code can use. Here is my React component that is handling everything.

import React, { useState, useEffect } from 'react';
import { Button } from 'react-bootstrap';

const ItemInfo = (props) => {

  const [item, setItem] = useState([]);
  const [ favorite, setFavorite ] = useState({favoritable_type: 'Item', favoritor_type: 'User' })

  useEffect(() => {
    fetch(`/items/${props.match.params.id}`)
    .then((response)=>{
      if(response.status === 200){
          return(response.json())
        }
      })
      .then((item) => {
        setItem(item);
      })
  }, []);

  const createFavorite = (data) => {
    fetch(`/favorites`, {
      body: JSON.stringify(data),
      headers: {
        'Content-Type': 'application/json',
        'X-CSRF-TOKEN': props.token
      },
      method: 'POST'
    })
    .then((resp) => {
      if (resp.ok) {
        setFavorite({})
        alert('This items has been favorited!')
      }
    })
    .catch((err) => {
      if (err) {
        console.log(err)
      }
    })
  }

  const handleFavorite = () => {
    const value = { favoritor_id: props.current_user.id, favoritable_id: item.id }
    setFavorite(favorite => ({...favorite, value}));
    createFavorite(favorite)
  }

If I'm being completely honest, I'm not exactly sure what this is doing. I came across an answer that had something similar and it seems to fix the issue in the console but not in the actual POST attempt.

  useEffect(()=> {
    console.log(favorite)
  },[favorite])

All this seems to work fine.

  if (item === null) {
    return <div>Loading...</div>
  } 

  return (
    <React.Fragment>
    <Button onClick={ handleFavorite } >Favorite</Button>
      <h1>{item.title}</h1>
      <p>{item.body}</p>
      <p>{item.user_id}</p>
    </React.Fragment>
  );
};

export default ItemInfo;

I've tried separating some of the functionality into different components, recreating it using a class component and have not been able to figure it out.

Thanks in advance!


Solution

  • Since hook values are constants, they don't change immediately with the setHookName, they change on the next render. You can get somewhat around this by injecting code into the setHookName function like this:

    const handleFavorite = () => {
      setFavorite(oldFavorite => {
        const value = { favoritor_id: props.current_user.id, favoritable_id: item.id };
        const newFavorite = {...oldFavorite, ...value};
        createFavorite(newFavorite);
        return newFavorite;
      });
    }