Why form-wide validation schemas are bad for developers and users - and how we can fix this!
Forms Don't Need to Suck
Transcription
Damn, what an intro. Thanks Yanni. So like Yanni said, my name is Andy Richardson, I'm going to be talking about some forms today. I am a software engineer currently at GraphCDN. We do some pretty cool stuff with edge caching on graphql endpoints, so if you're into that kind of thing, definitely check it out. And yeah, you can find me online, search Andy Richardson, I'll probably pop up. Cool. My boss said that I didn't make a big enough statement about GraphCDN, so there you go Max. If you want to find out more as well, he actually did a talk earlier today, this morning, about GraphCDN and what we do, so definitely give that a peek. But without further ado, forms. So first thing I want you to do is just think about a good form experience. So we've all been on bad form experiences, very confusing, all over the place. But when we think about good form experiences, we probably think about a simple journey. A guided experience so we know where to go throughout the form. And also something that's responsive and fast to use. I think the worst case scenario is having to go back in a form because you've made a mistake early on. So if we put that into an ideal flow, almost like a user journey, we're thinking about populating a field, getting told if you messed up, ideally as soon as possible, and then some kind of progression. And a progression can be moving to the next field, it can be the next page in the form, if it's a multiple-page form, or it could be the final progression, which is a submission. And this is what it looks like currently in react when we make forms. Which is very, very different. Rather than having these progressive fields, which are the primary thing we think about, instead we have forms of the primary thing we think about and we shove everything in a form early on. And it's kind of strange, because that's not what we really do with react. Usually if we have things that happen over a series of pages or so on, we wouldn't put all that logic in one place. But with forms at the moment, we do. So this got me thinking. Is there a way that we can make a form where we don't need to put the schema all in one place? We don't need to put the on submit statement all in one place? What if we do that dynamically as we're moving forward as users going through fields and so on? And yeah, so that's exactly what I wanted to try. And a few... About a year and a half ago, I started a project called Fielder with a goal to try something like this. And I want to run you through the pieces and also show you some examples and see what you folks think. And the end goal isn't really to get people to use Fielder. It's just so that the next project I go on, I don't have to deal with terrible forms because you all have, I don't know, pushed forward a new library that fixes this problem. So yeah. This is a proposed solution. But whatever library applies it, doesn't really matter too much. So the pieces. So if we want to create a great form and we don't want to have to hoist everything to the top of the form, first thing you have is the form. But the big difference here is that this is very, very dumb. So this is just an amalgamation of state. And this will be high up in the component tree. And once we declare it, we forget about it. The next thing is the field. And this is our first class citizen. So here we mount the field. We give it some validation. We give it an initial value. And that is... That's basically how we design our forms with fields as opposed to with just one big form. And then finally we've got progression. And you might think of this as an on submit. And it is definitely an on submit. But it can also be an on progress. So if you think about a wizard, if any of you are familiar with that, a form where you've got multiple pages and you're stepping through, we usually want to guard that just as much as we want to guard that final submit statement. Because there's nothing worse, like we said, than having to go back in a form and fix something somewhere else. So we want to guard that and prevent progression from happening. So yeah. There's an example. But also we can also have some kind of progression which isn't a submit. Cool. All right. I'm gonna show you some examples. So we'll get hands on with code. And let's see what we can make with those three things. So first off, hopefully you can see this. Yeah, it's big enough. So we've got a username field. And we want to populate it with a value. And if the user changes that field or tries to submit and there isn't any value there, we want to show some kind of error. So presentational components out the way. That's a completely different talk. First thing we're gonna do, declare our field. Pretty straightforward. And then we add our validation. And in this case, because we don't care about the particular event that we're validating on, we're just gonna check the value, and if it's not there, we throw an error. And then as you can see further down, we present that error when it occurs. And then finally, we just want to guard that. So we want to make sure that that validation is satisfied before we allow any kind of submission. Cool. So now let's mix it up a bit. Let's take what we have already. And let's add in some async validation. So sometimes this can be quite tricky. I think with Formic, you would have to do an on submit, which checks, does async validation, and then write your own errors or something like that. But in our case, this is the only change. So because everything is located with a field, the field just gets told, hey, there's a submit event, and the validation function gets called. And then we just go, okay, well, if it's a submit validation, let's return a promise, which checks if the username is taken. If it is, then we throw an error. If not, we return, and then the handle submit call will continue and submit. And then, yeah, in this case as well, the only difference now is we just want to show that we're checking that username is taken. So those are two still fairly basic examples, and they don't really demonstrate why having things coupled to fields as opposed to forms are really valuable. I think branching is when that starts to change. If you've ever tried to do a form, which is technically more than one form, and you need to split depending on user values, you've probably gotten really frustrated with most form libraries. And that's because we're expected to hoist all that conditional logic up into the form when we declare the form. But then we also have that conditional logic in our routing as well, and it becomes a bit of a mess and very hard to maintain. So this first example of a form, the user provides a region, and then if they choose the UK, they get asked to put in their favorite tea. If it is a... If they hit the US, then they get asked to put in their favorite coffee. So now let's look at an example of that. First we're going to make that little micro-component that does the region selection. Nothing too exciting. We've got a field that takes region, and we populate the value. And then we're going to call an onComplete. Don't worry about that for now. We will come back to that. That's just some kind of thing to move forward on to the next step of the form. In our UK form, because we only want to validate if we take that branch route, we don't need to worry about hoisting it and doing lots of conditional logic. Instead, we just mount our fields and add our validation. Similarly, with US form, the exact same thing. The cool thing about this is that because we're doing our validation at render time, and our submit at render time when we're going down that route as opposed to prematurely, we don't need to... We don't need to add any conditional logic because if we take the UK form route, we're never going to render that field anyway, so the validation won't apply. It's a lot more straightforward. The final step is now just having that root component that just composites this. So handles rendering those different steps, and then ordering them in the way that's necessary. So you can see here we render our region select on the first step. That will then call on complete, and then we move to the second step, and this is where we start doing our branching. This is the only branching we do. So if you think about that initial example with Formic, you've got your on submit which will have to branch if you're changing different end points to call, and also with your validation, you'll also have to have that conditional logic in your validation, and then when you render, you'll also have to do that branching logic again. Because we're handling submission and validation low in the DOM, in the component tree, we don't really need to branch anywhere other than when we're rendering. So, yeah, and that's kind of the secret, really. I think that this is one of those things that would have made things a lot easier, and I think it's just we've gone as a community down this route of having forms as this big entity, and I think that maybe it could be a lot simpler if we treat them as more dynamic. So you can basically forget about this, and you can forget about this, all this branching on schemas and on submits, and instead you can just branch when you're rendering and just declare validation as you would if you weren't branching. So yeah, that's about it, really. So that's the Summarisation for this. It's not necessarily check out Fielder, although I definitely recommend it if you think this is interesting. But it's more so let's see if we can get forms going more through this direction, because I think it has a lot of potential. Thanks. Thank you, Andy. Now, if you please proceed me to the Q&A lounge. We have some hard-hitting questions. Follow me. Would you like some water? I would love some water. We didn't bring beer. I think that would have been an appropriate time. Was that 6pm that starts? Yeah, I think something like that. Okay, so first question before we get to the Slido questions. What is it with you and forms? Why? I mean, as a developer, I understand it's a meat and potatoes thing, you have to do it, but why are you passionate about this? I don't know. I mean, we have, so Yanni knows this. We had a client who actually was trying to solve this exact problem, and they had forms which were constantly branching, and they had a nightmare trying to hoist those things, hoist all that branching logic. So that's kind of what got me into it. And then after that, I guess it's just, I don't know, when you spend so long ignoring your partner because you're working on forms, it's like, you just got to keep it up now. You know, you got to commit, otherwise she's got questions. So yeah, no, it's, I think just the initial problem that we had with that client really stuck with me, and that's something that I really wanted to explore. Nice. All right, so we have a whole bunch of questions here from the audience, and you're also, you got a new reason for being one of my favorite people. You went massively under time, so we've caught up on being late. Get in. Okay. All right. So what's your favorite tea then? Oh, that's a good question. I like peppermint tea. Oh, nice. My mom actually used to give me tea in a baby bottle, which is probably the most British stereotypical thing ever. That also explains so much. Yeah. But no, peppermint tea is my current favorite. Nice. All right. Well, let's hit these actual technical questions. So have you checked out react hook form, and how would you, you know, like, why go and create field, or what is the deficiency in Nexus instead of the art? No, that's a good question. That came out a bit later than when I started working on this kind of stuff, and it definitely takes some of those approaches to declare a validation and submit logic lower in a form. It still does a lot of hoisting though, so things like validate on change, validate on blur, you can't do that on a field. You have to do that form wide and declare that. So I think it's definitely a step in the right direction, though, for sure. Nice. Well, this is the most popular question. I'm sorry, I have to ask this. It's not a technical one. But have you had any experience with the UK's passenger locator form, and could you please fix it? Damn. Is this the one to get back into? Yeah, when you come to the UK, you have to fill this form, and it is a very frustrating experience, especially on mobile. Yeah, I didn't do it on mobile. I have had experience with it. It wasn't too fun. Unusually, the government form stuff is pretty good, because they actually force you to do one field per page, which turns out to be quite nice. But yeah, no, I personally haven't had too much experience with that, other than just using it once. Nice. So how does this work with typescript? Can this work with typescript? Can you get type safety with this approach? I think, to be honest, this is something that I haven't worked on for the last couple of months with switching roles. But I'm pretty sure we plug together some stuff so that when you declare a field name, then we know what types we're working with. One thing I would mention was that, at least with Fielder, the types that we store are basically whatever gets passed from the input element. So if you're using a number input element, that still returns a string. So we'll store it as a string. If you want to change that into some kind of integer or whatever, you can do that on submit time. Nice. Does this work with react Native? Is there anything DOM-specific here that, you know? Yeah. No, it works with react Native. It wasn't super straightforward, but yeah, no, it does. Nice. What did you need to do to make it work for react Native? What is the kind of platform-specific thing here? Well, Kadi, if she's in the audience, might know about this, because I pestered her a lot. Yeah, it turns out, I mean, you'll know this as well, change handlers in react Native are basically, everyone just has a different name for each element. So it's like on change here, then it's on select somewhere else, and so on. So for react Native support, because there isn't a standard library for form elements, it's mostly about you just need to point the library in the right direction, if that makes sense for your library of choice. But there's pretty detailed docs on that. Nice. All right, so this is a bit of an open-ended, you alluded to this already with a project in the past, but what is the context of the worst form you have experienced, and what was the most frustrating thing? Basically, I guess the question is, what is your origin story? What made you Joker or Batman? Oh, man, well, I was 10 years old, and I was signing up for Facebook. No, I'm kidding. No. To be honest, I think my problem was more the technical side of things, the implementing rather than the using forms. I just angled it as a user story, because I thought it would sound better as a story. But in terms of, to answer your question, I think it's a technical solution, really, rather than a user solution. But there's no big juicy story where you were tearing your hair out and crying in the middle of the night kind of thing? I'm not witty enough to come up with that on the spot, but if I was, I'd 100% make an origin story. I'll come back to you with a post or something like that. Great. All right, let's see if there's a couple more questions. There's a lot of questions that are kind of like, I wouldn't say like, you know, beside the topic, but what did you use for creating your code snippets and animations? Oh, that's a good question. I found that yesterday. That was code... Can someone shout out? Someone's going to know it. Code... What's that? Yeah, sure. Let's go with that. Something like that. Yeah, code hike. I'm not sure. I'll tweet it. There's an excuse to follow me. Andy, are you saying that you made your slides yesterday? I'm saying I finished my slides yesterday. I finished my slides this morning. Most of us don't admit it, but Andy here, he tells the truth and truth only. How does Fielder handle performance and re-rendering? Is there anything particularly you need to do? Are there any gotchas that you have to solve? No, so one thing I worked to make sure was that synchronous validation is synchronous. And that's the case. That's something that I tried to contribute back to Formlink before I started working on this, and I wasn't able to, just because there was a lot of stuff going on at the time. But yeah, so synchronous validation is synchronous. You're not going to get those awkward middle states where valid is true, but then the change event happens, so validation hasn't run yet. That's only for async validation. Anything synchronous is instant. And there is a repo with a performance benchmark, so if you want to check it compared to Formlink, if you're into performance benchmarks, then there's that. I can't say that I've ever really had a performance problem rendering forms, at least one that required heavy benchmarking. But I'm glad that they exist. There's definitely a tick box you can put on your repo readme, the fastest form library. Oh yeah, I like the numbers at least. Exactly. Well, I think that kind of leads us to our next question here, which is, what is the current status of the Fielder library? Do you recommend it's used, you said? You would be okay with another library taking its place? Oh, totally, yeah. Yeah, so what's the status of the project? Would you recommend using it, would you use it yourself? I'd recommend checking out. I've used it myself for a couple of projects. I think, yeah, yeah, I'd definitely say people check it out, but don't feel, it's stable for prod, there's one use case which isn't covered, which is, you know, when you add new fields to forms as you go, like add more, add more, add more, basically the problem with that is if you're declaring fields and using the name as like the unique identifier, then if all your fields have the same name, you just have them overwriting each other. So if you need something like that, maybe think otherwise, but outside of that, I mean, it's pretty stable. I'll be maintaining it for a few more months at least. Nice. All right, and let's finish here with a softball. Somebody's teed it up you real nice here. Where can we find your project, Andy? My projects, like Fielder? Fielder, yeah, yeah. Yeah, if you search Fielder online. Are you telling them to Google it? Google it, basically, yeah, yeah. That's exactly it. Yeah, there's a doc site and stuff like that, but GitHub or if you search in npm for Fielder or whatever, basically any search engine you can find, search Fielder, it'll probably come up, yeah. I highly doubt that, actually. If you type Fielder, can somebody please type Fielder in the feed real quick and see what happens? Because I'm pretty sure that is not going to give you, I think it's going to give you like a baseball player or something. I think it would be first page, because I spent ages, ages doing server-side rendering to get it to work so that Google would actually rank me somewhere other than like page 50, so yeah. But we'll settle that score offstage. I think it's time to move on. Thank you so much, Andy. Yeah, thank you very much. Yeah, great talking to you. Appreciate it, man. Thank you.