Advanced Remix

Focus Management Solution

Kent C. Dodds

Kent C. Dodds

Professional Trainer
Advanced Remix

Check out a free preview of the full Advanced Remix course

The "Focus Management Solution" Lesson is part of the full, Advanced Remix course featured in this preview video. Here's what you'd learn in this lesson:

Kent codes the solution to the Focus Management exercise.


Transcript from the "Focus Management Solution" Lesson

>> All righty, so we had somebody really nice added error messages for us. And so, if you take a look at the code here, we've got our errors being displayed if somebody submits one of these in error. So, if you click on that, then it will say it must be greater than 0, please enter a valid date.

But combining this with, and that all works fine, and in fact even with optimistic UI, if we do give it something greater than 0 and all this, everything is still working and when the revalidation is finished, we're resetting all properly. The one thing that we're missing right now is when we submit, we will display the error, but it would be really nice if we focus the input that has the error so the user can fix that.

And also, we are actually resetting the form so all the other work that they did that was correct is all gone. We don't wanna reset the form if there are errors, and so that is what we're going to be doing. So we're gonna have a couple if statements and this will end up in the else case.

So if there's an error on the amount, so we've got actually let's bring in the error as a dependency array element. We will say error, and actually it's gonna be errors, I should have had Dan do that for me, errors.amount. And actually, errors could be undefined as well, so if there's errors.amount, then we're going to focus on the amount element.

And I believe the easiest way to do that is actually to access it through the form ref. If you want to you can make a ref for every input, but the form ref, actually, we have it typed as a deposit form element, which includes the form elements for all the forms stuff that we've got in here.

So that should make it nice and easy. So we'll say, formRef.current.elements.amount. And if that exists, cuz it's possible it doesn't, we'll say focus. So if there's an error for the amount, then we'll focus on the amount. Otherwise, if there's an error for the deposit date, then we'll do the same oops, for the deposit date.

Sweet. And if there is no error, then we wanna focus, or then we want to get rid of or reset the form. And we also want to focus on the amount, just so that they can enter in these values really quickly. So we come back over here, let's not throttle, so we can load it fast and then throttle.

So then, we say skip the amount, set our date, and set something there. This request is going to happen, revalidation will occur, and then we focus on the element that has the error, so that's nice. We can fill this in, and we get that process again, and when revalidation is complete, then we focus back into the amount.

So the one case where we're thinking, hey, hold on, what if the user has actually already started to fill in the new amount? So like they're moving fast within our reset, then we probably don't wanna reset the work that they're doing. So the one case where we actually wanna do one more check before we will focus on the amount, and before we reset.

So this is gonna actually be another else if, and in this case, we want to check is the currently focused element the same as it was when this was submitted. So is it the created element? I'm actually gonna need to reference cuz I feel like I have a better way of doing this than I'm thinking of right now, and I wanna make sure I do it the same way.

This invoice ID. So sorry, I am cheating. Yeah, intent, that's right. Okay, So the forms actually have a, well, so the submit button on our form has a name and a value, and the name of this submit button is the intent. So we want to say is the currently active element the submit button.

If it is, then we're good to reset because they haven't moved anywhere, they're still sitting on that submit button. If they've moved, and then they probably are off doing something else, we do not want to reset this for them. So we'll say, if the document current element nope, not current script, current element, or activeElement, that's right, is not or is equal to the formRef.current.elements.intent.

So if it's that submit button, then we're good to focus on the amount and reset the form. And that is it, that gives us a nicer user experience for users who want to be quick about everything. Yeah, Mark.
>> When doing optimistic UI, is it the best user experience to wait until the response comes back in order to reset the form?

Won't the user be confused if they can see a new record posted, but the form still has the last entry? Any suggestions how to deal with this scenario, maybe the form field should be disabled?
>> Yeah, that's that's a good question. This is where computer programming turns into an art, I think, where you have to, or not the programming aspect, but the user experience aspect.

Because you have to kind of decide what are the trade-offs that you want. You might have noticed in the remix to do MVC, I did not wait for the request to be finished before resetting the field or the input. I just reset it right away and just assume everything's going to work.

We could do that ourselves as well. You could actually use this as a reference implementation for how to accomplish that. What's actually happening under the hood is this input right here is its own component. And when I create a new one, I actually leave that component there as hidden and make a brand new one.

And so, every single time you submit one, two, three, we've got three inputs, two of which are hidden while those submissions are finished. And so, if one of them ends up being an error, then I actually just bring that field back to be visible, so I can show the error.

And then that's how you can actually just work through them all. So there are actually some pretty interesting, and it's not all that complicated actually. So there are pretty interesting things that you can do to make sure, because the biggest challenge is that if your optimism is misplaced, you got to bring everything back to the state at the time that the user submitted it because otherwise they lose a bunch of work.

And so by going that route of let's just hide this and then we'll bring it back, you can actually preserve all of that state. Ends up being quite simple. So that's the way I implemented it for this. If we were to instantly reset this as soon as the request was made, and then there's an error, we just have to hold that state somewhere so that when the error shows up, we've got to restore all of their previous stuff.

Which is where optimistic UI becomes a little bit tricky, and so there are a couple of tricks that I show in that example that you can use to make that a little easier. But this is a really, really great first experience for an optimistic UI that you could build that is pretty low effort, high reward.

And then you can layer on top of that additional levels of complexity to make that easier, yeah.
>> If you had a really long form where you wanted to save progress as a user is filling it out without submitting it necessarily, how do you do that right next?

>> Yeah, that's a great question. So saving progress in a form, or I actually saw another question somebody asked in the chat now I remember, asking about like a multistep forms, stuff like that. So it depends kind of on how you want to say that you could, of course, save this to local storage if you want to.

Maybe not the most secure thing to do, but if that is not a concern, then that's an option. So in remix, the best way to do this would probably be to use a cookie because then you can store the users, all the users' fields in that. It can be persisted even if they close the tab and everything.

It can be secure, so it's HTTPS only and all that. And then, through some of the other exercises, we'll see doing imperative fetching, so you could actually just on an interval save stuff or maybe on blur save stuff. As far as a wizard, you could also do that in a cookie, or you can, of course, save this in just your local state.

So if you don't care about if the user closes this, I don't care to that or anything, then you could put it in a context provider of your own making above the form. And then as the user blurs the inputs or changes or whatever, you just update that and then just delete it when the user submits the form, potentially.

>> Can you explain the document.activeElement === formRefl.elements.intent a bit again?
>> Yes, so the document, activeElement, is a read only property of the browser that will tell you whatever element currently has focused on the page. The formRef is referring to our form. This is the current value of that ref, which will always be the same, but that's just the way that refs work.

In the elements' property on that form, DOM node is all of the elements of the form that are keyed by their name. And so the name of our submit button is intent. And so we access the submit button by intent. So what this statement is saying is if the document active element is triple, or what this expression is saying I should say, the active element is triple equal to the submit button, that's what we're trying to establish here.

So if the user is still on the submit button, then we can focus the amount and reset. If they're somewhere else, then we don't want to delete their work. And again, this is only one way to approach this problem. In my to-do MVC, I reset everything and just hide stuff and bring it back if we need to, if the user needs to fix an error.

And if I were to go a little further on the optimistic UI experience, with this, I would probably take that same approach with this as well, yeah.
>> Are form libraries like React hooks incompatible then with Remix?
>> If the form library is compatible with the web, then it will be compatible with Remix.

Unfortunately, there are a number of libraries that I will not name. I will name one, Ant Design is a very popular library. I've had trouble with that library for years because I created a testing library which focused very heavily on querying by accessibility and stuff. And Ant Design is just, maybe it's better now, but for a long time I just get a lot of issues of people saying, I can't test this because I can't query the way that you show.

Because they just didn't label their inputs properly, and they didn't render inputs. And so, yeah, that library. And actually I've seen some people have trouble with that particular library with Remix because they don't use the web. And so you're gonna have trouble with anything that doesn't use the web, but I believe that React hook form does just render inputs, or at least you are the one who's rendering, so you can render the inputs.

And even if you're not the one rendering the inputs, if you have some way to be notified of what that state value is, so like on select or on change or something like that, then you can actually work around that by adding an input hidden with the name of whatever and value is {theSelectvalue}, whatever.

So, now you're kinda using their library, but then sticking the value into the form, and so that is literally just a workaround. That's not what you want to do. You could also say type it in, most people do that. I think React has some optimizations if you use the hidden prop though.

So something to look into. But yeah, that would be one way to work around that, but short answer to the question is, yes, I believe that that particular library is compatible.

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now