Prevent React Component From Calling API Endpoint Twice

I have a POST endpoint that creates an instance on the database once the user creates an account, the rows are filled with information like day of the week, date, user_id, etc.

@api_view(['POST'])
def generate_focus_instance_v2(request):
    try:
        user_id_from_request = request.data['user']
        if UserProgressReport.objects.filter(user=user_id_from_request):
            return Response({"message": "user instance already exists"})

        instance_serializer = UserProgressReportSerializer(data=request.data)
        if instance_serializer.is_valid():
            instance_serializer.save()
            return Response({"message": "user instance created", "data": instance_serializer.data})
        else:
            return Response({"message": "something went wrong", "data": instance_serializer.errors})
    except Exception as error:
        return Response({"message": str(error)})

Here is the React section of my code

  async function handleFocusInstanceCreation() {
    try {
      const response = await fetch(
        `http://127.0.0.1:8000/api/v1/create/focus_instance`,
        {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
          },
          body: JSON.stringify({
            user: currentUser.user_id,
            focus_time: 0.0,
            day_of_the_week: currentDayOfTheWeek,
            date: currentDate,
            week_number: currentWeekNumber,
          }),
        }
      );

      const data = await response.json();
      console.log(data);
    } catch (error) {
      throw new Error(error);
    }
  }


useEffect(() => {
    if (currentUser && currentUser.user_id) {
      handleFocusInstanceCreation();
      console.log("Focus Instance creation called");
    } else {
      console.log("not logged");
    }
  });

The code logic is working as I intended, but because of the double rendering problem of react, my component is being called two times, and that is leading to creating two instances on the database for the same user.

In other words, once the user is redirected to the Home component after creating an account I see two response messages from the API call.

After some research and testing, I know that if I remove the <React.StrictMode> tag my problem goes away, but I don’t know the long-term implications of doing that. (Im kind new to react)

So my question is: should I remove the <React.StrictMode> tag or should I add a feature that limits to one the number of times that my endpoint will be called?

  • The JavaScript you show has nothing React-specific. How are you calling that function?

    – 

  • (As an aside, this is why double rendering is a feature: you have some code in your app that is not safe to call twice, even if it’s being called from a context where it can be called as many times as React wants.)

    – 

  • I included the part where I’m calling the function. This feature is still in the early phase. But before I noticed my console.log being called twice, then after checking the database I see two instances for the same user.

    – 




You’re using useEffect without any dependencies, which means the side-effect code there will be called every time the component re-renders (and indeed, twice (or more – React is at liberty of doing that!) if using strict mode).

You very probably don’t want to use useEffect for data submission anyway; just do it when the user has entered the data and hits a submit button or whatnot.

You could (and maybe should) also make your API and/or data model resilient against this: e.g. add unique-together constraints for user-date pairs, or whatever makes sense; in real life your users will double-click that save button.

To give my share of knowledge, StrictMode will renders components twice (only dev mode but not in production mode) to detect any problems within the script and let you know about them. It is a helpful tool for development, and removing it won’t address the root cause of the double rendering issue.

For example, your app is wrapped by <React.StrictMode>:

  ReactDOM.render(
     <React.StrictMode>
       {app}
     </React.StrictMode>,
    document.getElementById('root')
  );

If so, you can still disable StrictMode by removing the tag from the script:

  ReactDOM.render(
    {app} 
    document.getElementById('root')
  );

Solution

  • Removing the StrictMode will be the last solution without finding the root cause.
  • Limiting the number of times the endpoint should be valuable feature when considered security and scaling like based on use cases.

Adding an endpoint check for validation with meaningful response to ensure no process is done when a duplicate request is received

For example

@api_view(['POST'])
def generate_focus_instance_v2(request):
    try:
        user_id_from_request = request.data.get('user')
        existing_instance = UserProgressReport.objects.filter(user=user_id_from_request).first()
        if existing_instance:
            return Response({"message": "user instance already exists", "data": UserProgressReportSerializer(existing_instance).data})

        instance_serializer = UserProgressReportSerializer(data=request.data)
        if instance_serializer.is_valid():
            instance_serializer.save()
            return Response({"message": "user instance created", "data": instance_serializer.data})
        else:
            return Response({"message": "something went wrong", "data": instance_serializer.errors})
    except Exception as error:
        return Response({"message": str(error)})

Or Database constraint can be the solution to ensure only one user is created for each user.

lets say, you can add a unique constraint on the user field in your UserProgressReport model
Something like this

class UserProgressReport(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    
    class Meta:
        unique_together = ('user',)

Additionally, when using useEffect, to avoid ,ultiple api calls, you should provide an empty dependency array to ensure that the effect only runs once, immediately after the initial render

useEffect(() => {
  if (currentUser && currentUser.user_id) {
    handleFocusInstanceCreation();
    console.log("Focus Instance creation called");
  } else {
    console.log("not logged");
  }
}, []);

If you have specific dependencies that you want to trigger the effect, you should include those dependencies in the array.

For example, if you want the effect to run whenever currentUser changes, you can include it in the dependency array

useEffect(() => {
  if (currentUser && currentUser.user_id) {
    handleFocusInstanceCreation();
    console.log("Focus Instance creation called");
  } else {
    console.log("not logged");
  }
}, [currentUser]);

Final thought – Using an empty dependency array ([]) is suitable if you want the effect to run only once after the initial render.

Hope this helps!

Leave a Comment