react-router icon indicating copy to clipboard operation
react-router copied to clipboard

[Docs]: Tutorial: missing Cancel functionality in Tutorial. Interesting navigation case study worth including

Open ThomasGreenspan opened this issue 1 year ago • 1 comments

Describe what's incorrect/missing in the documentation

In the Tutorial, one of the features implemented is a cancel button (here). Specifically the button is set up to navigate back one step in the history (navigate(-1)). Unfortunately, this feels only half implemented: it is correct when editing a pre existing contact, but when used for a new contact, it leaves an empty contact behind. The correct behavior would be to remove the new empty contact and then navigate.

I think this is worth including because it deals with a slightly more complex case of wanting to perform some state change and then navigate (as opposed to just one or the other). I would do the change myself if I knew the correct way to do this but I have run into interesting issues that I'm not sure how to address cleanly. Beyond cleaning up the app, it seems like a compelling case for adding to the tutorial.

My attempts. I see two possible approaches (without changing the structure):

  1. in the cancel buttons onClick function, remove the contact if it's new, then navigate.
  2. integrate into the ContactEditor action. Maybe save the previous url and the conditionally remove the contact and move to the saved url if the button pressed was cancel and it was new.

My attempt at 1. was the following:

  • In contacts.js, add contact.isEmpty = true in createContact and contact.isEmpty = false in the updateContact function.

  • In edit.jsx change the cancel button to the following (with useFetcher import and call not shown):

    <button
        type="button"
        onClick={() => {
          if (contact.isEmpty) {
            fetcher.submit({}, {
              method: "post",  
              action: `/tutorial/contacts/${contact.id || ""}/destroy`
            });
            return; // return. Otherwise navigation cancels revalidation and messes up nav bar.
          }
          navigate(-1); 
        }}
      >
        Cancel
    </button>
    

    Ideally we would not need to return after the call to fetcher.submit. However if we do not have it, the side nav does not update - i.e. it still shows the "No Name" contact. The entry is gone from the fakeDB so clicking on it pops up an error but the nav itself won't update until the next revalidation. I believe this is because the navigation cancels the revalidation? The nav is fixed at the next revalidation (I created a button that calls revalidator.revalidate() from useRevalidator to test this but also any other navigation on the page does it). With the return statement, the redirect in the destroy action is used.

    As an aside, it seems to me that the fact that without the return it goes to the expected place (i.e. wherever the user was before hitting the New button) also implies that navigation is somehow overriding the redirect (as opposed to being applied on top of the redirect in which case the desired place would be at navigate(-2)). I was surprised by this. Another indication that something in the tutorial about interactions between navigate, redirect, and revalidation would be useful.

I have not attempted approach number 2 but it feels a bit clunky conceptually.

Bonus: There is similar behavior when someone has hit the New button and then navigates away from it without saving or canceling (ideally we would get similar behavior to the Cancel behavior I described above). I think this could be another useful thing to add because it deals with navigating away from a route. I don't know what type of functionality/hooks there are for leaving a route but it seems like it could be useful (maybe just native React hooks?).

~~

It's worth noting that if we were thinking purely from the perspective of building a good App, we would probably want to not create the New contact until it was saved (post edit). This would automatically take of both cases I described above. The only thing that's not immediately obvious to me is how to cleanly keep the nav with a "fake" "No Name" contact while editing (and even that may be obvious to someone more experienced, possibly with more traditional states). I think the above scenarios with the current setup are interesting case studies though.

ThomasGreenspan avatar Aug 23 '23 03:08 ThomasGreenspan

I was also wondering about the Cancel button while going through the tutorial. The solution I came up with is a little different.

Instead of using useFetcher you can also wrap the button in a form:

<Form action={`/contacts/${contact.id}/delete`} method="POST">
  <button type="submit">Cancel</button>
</Form>

This matches the tutorial's Edit Contact button, so I guess it's a more 'remix-y' solution to the problem.

As an alternative to adding the 'isEmpty' property to the contact you can add a query string parameter when redirecting to the edit page:

return redirect(`/contacts/${contact.id}/edit?mode=create`);

The edit page can then pick this up (via useSearchParams) and change the behaviour of the cancel button accordingly

nilsm avatar May 04 '24 16:05 nilsm