Joel Hooks: This is Jared Palmer. He created Formik for us, which is probably my favorite React library, and definitely the best experience I've ever had doing Internet forms. Thanks, Jared, for making that. He's the lead engineer at the Palmer Group.
We're going to learn how to use React forms with Hooks. We're not jumping into Formik. We're going to start with a blank React project and build it up in the build-your-own Formik style but the punchline is you should probably just use Formik if you're building React forms.
This is more of a way to look at how you would think about these problems and what Formik's doing under the hood and what React Hooks bring to the table. I think you mentioned to me that Formik, when you use it, React Hooks don't come into the picture, but under the hood, that's what you all have transformed it into...
Jared Palmer: Right.
Joel: internally. When you're using Formik, you're just using Formik. You don't really have to think about React hooks, but we thought it would be interesting to show folks what React hooks looks like and do it in the context of forms, which are notoriously difficult to manage state-wise. I'm curious how React hooks play into that.
If you want to just share your screen. If there's questions, if you all have questions and you're watching, there's a Q&A button down there at the bottom, which is a great way. It'll pop up on my screen. I can let Jared know that there's questions, and we'll answer them.
Jared: Should we just get going?
Joel: Yeah. Let's do it.
Jared: What I did here was I went to the React docs. I literally copied in the basic form. Just want to quickly review how forms work in React, for anybody watching, in case this is something new.
In React -- this is in a class component -- you initialize an input state with an empty string. There are two ways to handle value updates. You can omit this value prop and pass in a default value -- that's what's called an uncontrolled input -- or you can basically hook value, your value that you store in state, into the input itself, as well as pass in a change handler.
Most of the time, the universe names these functions like handleChange. What this function does is it takes the value off of the event target, which in this case is the element, and it sets state with that.
If you've ever built a React app, you've done this a lot. Before Formik, this was really, really painful. What ended up happening was I was building this really, really complicated dashboard and administrative application with a bunch of crud stuff. I was building like 30 or 40 forms. This strategy really wasn't going to scale.
There was complex validation. There was business logic. There was dynamic forms. There was just tons of stuff. What React gives you out of the box, React is good at so many things, but it's just not that great at forms compared to maybe some other frameworks out there.
Joel: Why is React bad at forms? Is it a design problem? Is it just the nature of the beast or...
Jared: I don't know if it's bad at forms. I think it's just really good at other things. It's OK at forms. People like Angular forms. Forms lend themselves sometimes to like...I actually don't agree with this, but some people think that forms are like observable in nature. I don't think they are, but it's open for debate.
Basically, because of React's declarative nature and the way that React works in the sense that you describe the what and React takes care of the how, that declarative nature and the bidirectional relationship of data and inputs sometimes gets awkward.
Joel: Is it because Angular binding is part of the core thing?
Jared: Right, exactly.
Joel: If you don't have that, then effectively with the handleChange loop, you're writing your own binding scheme. That gets verbose and a pain in the butt.
Jared: Formik was basically an attempt to solve that. Initially, what's interesting about hooks and what we'll show off today is Formik v1 didn't use context or anything really special. It was actually just about using React state. What was cool about that was it was super-fast.
Now, with hooks, which we're going to build today, we're going to build, basically, Formik v0 through v8 before we went to context, but we're going to build it with hooks. That's fun for me to see this all come full circle here.
Basically, what Formik's saying is, "What if I make a second form?" If I make a second form here, let alone a second input...Let's just start with the second input. Let's say I make a second input. Let's just take our name thing here. Let's make a second input.
Let's call this -- I don't know -- email. Right now, if I touch these, they're both bound to the same handleChange function. The way that normally people do this is they'll use ES6 trick, basically, where you can assign a key dynamically. It'll produce event.target.name, and then we'll pass name attributes.
We'll say this is name and this is email, and that gets placed and passed through on the React synthetic event. Now what we can do is, instead of handleChange value, we can change this up. We'll make this name and email. Now we have two separate fields that are independent of each other.
That's one field, and that's another field, if that makes sense. This trick works, but it gets verbose. Formik was like, "Wait a minute, folks. We can just do this." This logic up here about state, handleChange, and handleSubmit actually has nothing to do with any of this input stuff that happens here.
Initially, Formik was built with a render prop, but for the purpose of this demo, we're going to refactor this right now with hooks and start building out Formik with hooks instead of with the traditional class component.
The thinking here is if I were to make another form, whenever you're looking at React or building React components, you're always thinking about grouping things that are similar and separating things that are dissimilar. The things that are similar between our theoretical next form component and this form component are this change handler, this submit, and this state.
We could grab all of this, and in another demo of mine, if you ever see the indecipherable talk, I will literally copypasta all of this into a new component, but we're going to use hooks. Let's make sure that we're upgraded to the latest version of React here, so just double-check here. We are. There's state of React. There's an 18.1. I guess there was a bug fix today.
Let's just make sure we're in the latest version of React so we can get hooks. Refresh here. Now we're going to make some hooks. We're back.
We're going to basically refactor this form to use our MiniFormik, which is what we're going to build. To do that, we're going to separate this out. We're going to have a function called useFormik. Again, this is not going to be as powerful as the real Formik, so bear with me.
We're going to have a function called useFormik. It's going to take a couple things. It's going to take some props. We'll call them props, but there could just be arguments, options, or whatever you want to call them. We're going to build out and we're going to return an empty object for right now.
We're going to need to refactor our little name form here because we're not going to be able to use this hook inside of the class component because you can't do that at the moment. You can only use hooks inside of functions components.
In the meantime, let's just take these, this handleChange and this handleSubmit, and let's move it up here for a moment. We're going to come back to it. We're going to break our form, and that's OK. We're just going to put these up here. We'll comment them out for a moment.
What we're going to do is we're going to build this useFormik hook. It's going to abstract handleChange. It's going to abstract handleBlur. It's going to abstract handleSubmit. When we refactor this name form into a function component in a moment, we'll be able to call useFormik.
If you've never refactored a class to a hook, this is what that looks like. You can get rid of this render. It's the same thing as just refactoring to a function component. NameForm is what we called this. Didn't accept any props. Let's just get rid of this extra curly here, nameForm.
That's our quick-and-dirty refactor, but you'll notice something. This doesn't exist anymore. This value doesn't exist anymore because there's no state right now. This handleChange function doesn't exist anymore, either.
We're going to get those back, though, from our useFormik function. In the future, we're going to get handleSubmit and handleChange. We haven't built these out yet. We're going to get something else. We need something for values to replace state.values. How is that going to work?
We know we're going to call this useFormik to get there. Now we need to figure out how this is all going to wire up. In the meantime, let's just delete all these this.s. We'll call this state. Now we're back to rendering. This won't work right now because we haven't actually implemented any of these things, but this is what we want to do to get there.
We were abstracting again, handleChange, handleSubmit, handleBlur, all the things you would need to build out a form. We're not going to colocate that with the form itself. We're going to abstract it into our custom hook. That's the name of the game here. Let's get started with that.
Let's call this values and setValues. You could try to get this done using the useState hook, react.useState and starting there. The problem that you'd have with that, unfortunately, is that useState is different from regular React setState in a couple ways that are important here.
The biggest difference is the way it doesn't spread, basically. You don't really want to use it for objects. If you're going to have an object, the rule of thumb and heuristic that I've been following since hook came out, not today, but for the past couple months, is if you're going to store something that's going to have nested values or is an object, you're going to want to use a reducer.
Joel: Can we talk about useState in terms of what's actually going on here? The array destructuring syntax may be different for folks.
Joel: I don't know what's going in and what the actual argument to useState is doing.
Jared: The useState argument is the initial state. That's what you set it as. It gives you back what's basically a tuple. If you've ever heard that programming term before, tuple is an array with two values. It's just that, an array with two elements in the array. We're going to use ES6 destructuring to actually assign those to variable names in one line. We could just do helpers, call this helper.
Joel: It gives us an array as an argument back?
Jared: Right. Helper helpersArray, basically. We could call this this. Then on the next line, we'd be like, "const state is helpersArray, ." I didn't spell it right, but that's what that would be. Instead of doing this and const setState here, it would be helpersArray, 1. That's all that's in this helpers array.
Joel: Those constant names can be anything?
Joel: We're just naming them something sensible.
Jared: It's exactly that. In fact, you probably want to name this not setState. You want to call it like thing and setThing or thing and updateThing when you use it.
Joel: SetState might get weird pretty quick.
Jared: Yeah, I probably would not use setState here. I would call it something else. What's cool is, with ES6 destructuring, you can destructure helpersArray and not even declare helpersArray. We call this thing and setThing. What's important is the way that setThing works, and it works slightly differently than this.setState does.
This.setState, in a class component, will merge state in a shallow manner and also has a callback function. You could do, even though it's commented out, something like this as a callback function here and run something after the state, do something after the state updates.
That doesn't exist in hooks, which we'll get to later. I brought that up because we're going to get to that later and how that has wrinkles throughout all of the way that we're going to approach this. But, I digress.
We don't want to use useState here because our values...Remember how we wired up values just before? We didn't quite fix this. We wanted to do state.name and state.email. We wanted to make sure that these are different parts of our state. We would have to have something like name and email.
The problem is, though, that you don't want to do this with useState. You want to use reducer here. In fact, we're not going to put this name and email inside of useFormik. In reality, we're going to actually put this down where we're actually going to call this function useFormik.
We're going to name it initialValues. We're going to name this useFormik initialValues, and we're going to pass initialValues as name and email. Now, this useFormik hook is going to have initialValues. Instead of doing this props.initialValues, which would work, but because of the behavior of useState, we're going to want to write reducer.
What does the useReducer hook look like? I'm going to use destructuring. We're going to call this state, and we're going to call this dispatch. We'll get back to that. We're going to use react.useReducer. This is a React hook. I'm going to see if I indecipherable .
It takes a reducer, which we'll call reducer. It takes initial state. Our initial state here is going to be called values. We're going to assign this a state of values to props.initialValues, which is what we passed in down here. Let's see what our little error says. "Reducers not defined."
Let's define a reducer, function reducer. This is going to take state and actions and return state by default. This is the baseline reducer. If you haven't checked out the Egghead tutorial on Redux, go check out the Egghead tutorial on Redux.
A reducer is simply a function that takes some sort of state, augments it, and returns a copy of it or itself. It basically is a second argument of this reducer in the Redux universe. It takes some sort of action. An action is some sort of indicator of how it should reduce the state.
UseReducer, same precepts. It's basically a hook that codifies the core concepts of Redux, which is this reducer and actions, into a little, tiny hook you can use. It's inspired also by Reason React, if you ever check that out. Again, we're going to use it here because our state object, our state tree, for Formik is nested, so we don't want to use useState.
What I mean by nested, I mean the actual values part of our state is nested.
Joel: It's like a complex, deep object, right?
Joel: If it was a shallow object, it might make sense, or just a value, like a string, a number, or whatever. Then it makes a lot of sense to do setState.
Joel: Or use the state reducer. I also had a question. This is just a style divergence. You're using react.useReducer instead of importing those more directly?
Joel: I've seen a lot of chatter that this is shaping up to be the preferred way. I was just wondering. Is this how you are using hooks, in general?
Jared: I got on this grind when I was prepping for my React Conf talk. I like to know, when I'm reading a component or reading a file, what's a custom hook or what's a built-in hook. To me, visually, this React. is just way easier.
When you go to refactor things or move things across files, you don't need to mess with your imports because you already imported React. If I take this useFormik thing, I move it to another file, and maybe that file wasn't using useReducer, now I need to go in and fix my import statement. That's annoying to me.
Joel: It makes a nice signpost.
Joel: If nothing else, it's waving a flag.
Jared: Exactly. These things, they're in flux. There's certainly some cool things there, too. When we use Formik, this here is a React thing, and this is a custom hook. In my mind, that's how that all boils down. In addition to values, which is what our initial form tracked, Formik actually does a little bit more than that.
It tracks what fields have been visited, it tracks your error messages, and it also coordinates validation in addition to submission. It does a whole lot more than we had in our initial form. In that, what else is going to go into this state tree?
I call it a state tree there. We're going to keep track of our errors. We'll just put those as empty object for now. We're going to take care of touched. Touched is the nomenclature for a visited field. We're going to keep that as an empty object.
The symmetry of Formik was basically codifying what most people already were doing, which was they were using just little ES6-named key to key off of the name attribute of their input -- oops, sorry -- right here.
We're going to keep that doing that, too. We're going to use the name of input as basically a way for us to access symmetrical aspects of our state. If you think about values, we're going to have values.name. We're going to have maybe errors.name, and maybe we'll have touched.name.
Our error message for the name input is going to be like our state.errors.name, and our value for the name input's going to be state.values.name. Whether or not that input's been visited or not is going to be stored as state.touched.name.
This symmetry actually scales really, really well. It is also pretty intuitive, because it's just like, "Oh, I was storing values. Let's put values into an object. Let's put our errors into an object, and let's put all of the visited fields into an object. Let's make them all symmetrical objects," in the sense that all their keys are named identically.
That's the way that Formik works in your head. What we're going to now do is we're going to now implement some of our reducer, so that we can hook this all up. There are going to be a couple actions here.
These actions are going to be triggered by our dispatcher. This dispatch, this is the second argument of the tuple. Basically, it allows you to dispatch actions to this reducer and fire it. Let's bring back handleChange and uncomment it.
We're going to remove setState here, and handleChange, this event, we're going to assign this to a variable now, handleChange. We're going to just define a function. This function is going to be interesting.
First thing we have to do is because we're in a callback situation here, we want to actually persist this event. This is a pretty deep cut React thing. If you try to access things, React synthetic events inside of callbacks, and you don't persist them, you'll just lose the event.
Any information that's stored on that event object, like the name, for example, or the target's values will just be gone into the ether. You want to persist the event. You'll get a warning if you don't do this, or you'll be very confused for a while.
Joel: Does this effectively convert a React synthetic event into a regular event?
Jared: I'm pretty sure it does mentally, yes. I don't want to say...
Joel: Yeah, yeah, mentally, I think.
Jared: Mentally, yes. We're going to write something called dispatch. Dispatch, again, it's like a decoupled...It's dispatch from, if you ever use Redux, it's going to be like that. This thing, we're going to this setFieldValue.
Our payload of this is going to be exactly this thing we had here, event.target.name and event.target.value. We'll just copypasta right there, and we're just going to do that. What's this going to do?
Well, when handleChange -- and we're going to pass handleChange down -- when handleChange is accessed by and passed into this input, and we start typing, it's going to dispatch setFieldValue, this action.
This is the action right here. There's a payload. You can name this whatever you want. You don't even need to use an object. I like to follow the Reduxy, give a type, give it a payload. There's a whole Redux standard action thing and whatever. It seems like the right thing to do.
Why is that important? Because we're going to write a switch statement up here. We're going to switch on action.type. We will...
Joel: Did they build Redux into React?
Jared: More or less, they took the best aspect of it.
Joel: The reducer, right?
Jared: Yeah, the reducer, right. We're going to add a default case. You always, always, always want to have a default case when you write a switch statement for when you're using it in a reducer. We're going to write a case.
Our case is going to be if...I apologize, I would be using TypeScript, and it would be a lot better autocomplete. This is the case of what to do when we have setFieldValue. When we have setFieldValue, we're always going to return a new version of state.
That's how reducers work. You reduce state, but you also return a new version of it. We're going to spread state from the beginning. What we're going to do is, we're going to set values. We're going to make a new object here.
Then we're going to spread state.values. This is the previous values. Then we're also going to spread our actions payload, action.payload. What's that doing? This action.payload is going to be the exact same thing we were doing with setState before, remember?
We were doing that with handleChange. We were going to call setState, and we were going to set the piece of state with the input's name, and use that as the key of which slice of state we're going to set. We're doing the same thing here. We're just using a reducer and dispatch.
This may look super verbose, and a little bit arguably different and wonky, compared to just plain old setState, and it is. Don't get me wrong, but there are some benefits to this. That is that basically, for this added verbosity, and for using a reducer, this is completely decoupled from our component.
We can test this really easily. You can mock it really easily. There are some gains that the Redux enthusiasts love about using reducers. You get all the benefits of a reducer. In fact, it's like React telling you to use reducers.
You may have seen mashups of Redux and component state before, but this is it for reals. Again, those are the named values for the reducer. For this added boilerplate, you get a lot of things that are wonderful, like easy testing.
Also, all I need to know about Formik's state is in one place. This is the only place that's going to update Formik's state. I can fire these updates from different places, but the actual manipulation of state, I only need to look in one place. That's also pretty nice, too.
Joel: When you have a 2-field form, it looks like a lot, but when you have a 15, like a complex form, it probably overall is a reduction in the amount of...
Jared: That's a great point here. We are in a trivial example, because we're only working on one form and it only has two inputs. If we think about this sign-up, login, request password, reset password, settings, and all these things you're going to build, this stuff adds up quickly and becomes enormously cumbersome if you're not going to abstract it in some fashion.
Certain apps may not have that many forms, but certain apps that do, you definitely are going to want to have something that you can consistently use the same way, and again, share your inputs back and forth, and that behave similarly.
That's really what the point of this is in this exercise. Even though we're only doing this tiny little form here, imagine that this is a part of a much larger system, where you have this over and over and over again.
To keep on going, we've setFieldValue. Let's make sure that works, because we can actually do this before we do anything else. Let's have our reducer here, and let's actually go back down. We passed in handleChange, and handleSubmit doesn't exist yet.
We're actually going to refactor this. Instead of destructuring here, we're going to call this props for a second. We'll just put that destructure statement beneath it. Instead of calling it props, let's call it Formik, because that's really what it is.
It's Formik, the instantiation of a Formik, basically. What we're going to do is we're going to just make a little debugger, so we can see what we've done.
Joel: You've got a typo on line four, too, by the way, when it comes up.
Jared: Got it. Let's just look at our debug. In debug, what do we call our thing here? We called it props. We're going to call this props.Formik, and pass this in. I built a little nice debugger thing, props.Formik. Save there.
In app, we're going to pass in Formik equals Formik. We need to import our debug thing. Again, this is just going to print out some values for us so we can see. There it is.
Joel: It's actions, versus action.
Jared: Actions versus actions. Oh, there it is. That's why we didn't have any. Actions versus action. Where are we here? Oh, actions.type. Action, thank you, awesome. We have some Formik state here. We'll see that this state is not yet being sent down to our input.
Sorry, to our component. The reason for that is, we didn't spread our state. Let's just spread state. There we go, cool. That's our internal Formik state right now. Let's turn off this caps thing. Text transform, do we have it here?
Yeah, let's just move that. It's not actually transformed, so we're not confused here. OK, that's what Formik state looks like internally. We're on our way. We've instantiated Formik state, and this is what we want.
Our values that we had from our old form that we had before, they're now in values in our slice of Formik state. Eventually, we'll put errors and touched there. Let's implement touched, which is again, going to map and track what fields have been visited.
This is very similar to handleChange. In fact, we're going to call it handleBlur. Instead of it being a value -- blur events, you don't care about the value, you just want to care about true and false -- we're just going to set this payload to true.
Instead of calling it setFieldValue, we're going to call it setFieldTouched. Now, we're going to basically copypasta this slice of our reducer, and we're going to call it touched. We need to make sure that we are changing a different part of the React state that matters, the touched part of it, and spreading that across.
Now, we have setFieldTouched. To take advantage of setFieldTouched, we're going to pass it down again through our useFormik. Our useFormik hook is returning all of these goodies that we're going to use throughout all of our inputs.
We're going to get handleBlur, and see, we pass onChange, handleChange. We are going to pass onBlur, handleBlur. We're going to copy that and do that again. Hopefully, React...Yeah, there we go.
We see we have the value. Value's not named, but we actually don't have the value right now. We need to get that out of there. We basically destructured the rest of these arguments, which is the Formik state, which is right here, and are destructuring them, spreading them into this thing called state.
We actually are spreading state here as well. What we should do is, we really want to get the values part of this. We'll have values, touched, and errors. Now, we are in business, because we're going to call this values.name and values.email.
Now, our input values will actually wire up to the values that we're storing in Formik, if that makes sense. Before, they were there, but they weren't being used. They were actually undefined, and so React didn't catch you on that.
If you looked at my console, you would have seen React giving an error, like, "Oh, you're changing an input value from controlled to uncontrolled." Now, we've actually wired things up. Ideally, when I type this now, as you can see, huzzah.
When I type in the name input, the name part of values updates. When I type in the email input, email updates. You saw, as I switched fields, the touched.name became true, which is Formik saying to you that the user's visited the name field.
There's this symmetry there. If I tab here, and I just blur that field, you'll see, these are the values and these are the name. Then eventually, we can implement errors as well. Then you have this really awesome way to think about your form in three places. You have values, errors, and touched.
It becomes pretty powerful to reason about your forms. That's one of the biggest, intuitive, most important aspects of Formik, is that you can think about every form pretty much the same way.
Joel: Can I ask something about form theory?
Joel: Touched, versus being a mouse down, instead it's on blur. This is just a personal wondering, why it's blur, versus when you actually touch it?
Jared: You could do it in two ways. The reason why it's on blur is because it actually has to do with error display. That's a great question. It has to do with error display. We haven't gotten there, but when we do errors, or in generally with Formik, the best way to show errors is a combination of two things.
If the user hasn't visited the field yet, you don't want to show the validation error. You only want to show it to them after they've visited it. After they've blurred it, you may want to change the input around. You may have to do other things.
You really only want to show errors when there's an error and the person has touched the field. If you do that on the first touch, or on mouse down, basically, you can have situations where the error may be shown too soon.
Joel: Like an insta-error, because you haven't typed anything.
Jared: Right. Then the other thing, if it's required, and it's an instantaneous error, then you will see as soon as you touch the form. Then also, the blur event will be fired when somebody submits via enter in certain browsers, not in Safari, but anyway.
Anyways, there is some hoopla there. Blur is basically where you want to stick your visited concept. You could augment it, if you wanted to. Actually, since blur doesn't actually take that much use of this event, this could be any event.
You do on mouse down, if you wanted to, or you want to do something else, you could fire the blur. The important thing is that Formik doesn't actually care, as long as you're consistent, and it's named properly.
Yeah, you might want to have a form that's like that. That's a great point. Formik works in React native. It's conceptually blur, but it could be whatever you want it to be, as long as you fire it off in a way that makes sense to you.
We have handleBlur. That's awesome. We only have a couple more things to do. Let's make our form submittable. Let's do handleSubmit. We'll just do const handleSubmit. This takes an event as well.
What we're going to do with handleSubmit here though is, it's not going to be reusable if we actually do our submission logic inside of useFormik. That doesn't make much sense. We want to be able to reuse this hook all over the place.
We're going to have our hook take a prop, or just an option, called onSubmit that we're going to fire when the user submits. We'll do this. We have to do a couple things first. First, we've got to prevent default.
Then we would do a couple things. This is a bit of cheating, but I'm going to cheat. What we should do in order to make sure that our users would see everything before submission is we should validate our form again.
We haven't implemented validation. We're going to do that later. We should validate our form -- we're going to come back to this -- and we should also touch every single field, and mark every single field as touched.
The reason for that is, you want to make sure that the user sees all of the error messages when they attempt to submit, and they can go back and say, "Oh, I didn't do that. Oh, that should have been emailed. That's and invalid."
Whatever it is, they need to see that when they attempt to submit. The only way, as we just described and went through, if we're only going to show error messages if the person's visited the fields, by that logic, then we should touch all the fields or mark them as visited when the user attempts to submit.
We'll get to in a moment. For now, what we're just going to do is we'll make submit basically a required option here. We could do that in a couple different ways. You might do that, and this is a little different with hooks, because prop types aren't valid.
Let's say that if not props.onSubmit, let's explode the app. Throw new error, "You forgot to pass onSubmit to useFormik." That's a brutal, brutal, brutal thing to do to your users. Maybe you'd want to not do that.
You could decide, again, this is a new thing with hooks. You don't have the print sign-up props types thing, because it's not a component that way. Prop types have been, for all intents and purposes, deprecated.
Let's just assume here that let's put a dummy function in our function with initial values. We'll put onSubmit, and we'll just have that be a no-operate now. We'll do that there. Now, we've passed that. We've validated that.
We'll handleSubmit there, and then basically, this little throwing of an error tells me that since we're not using TypeScript right now, that as the creator of this library that we're defensively programming, so this can be used in any kind of context, that we can guarantee that onSubmit exists.
We could do a check here, like if props.onSubmit, then call it, or just a safe invoke, or something like that. For intents and purposes now, we're just going to call props.onSubmit. What are we going to do with onSubmit?
We're going to pass it our forms values, which is wrapped up in state.values. What that's going to do is -- I don't know why that's doing that -- it's going to wire up...Let's move this up here. It's going to allow our library consumer, our hook consumer, to get values down here in their submit function, values.
Then this handleSubmit that they get back from useFormik, which is passed down to the form, is going to execute this onSubmit function for them on their behalf. This will make sense in a moment. We're going to alert.
I will do json.stringify values. This is all sync. Sometimes, this'll be an async, and that definitely complicates some things. Let's just submit our form here. All right, have that, press submit, and it exploded.
Why did it explode? Let's figure that out. Let's see here. state.values, props.onSubmit, feels good there. We didn't pass handleSubmit down to our function, or through our hook. Ah, TypeScript, I wish you were here. Let's pass this down now. We have our little...
Joel: All that strong typing and hair detection's a crutch at this point for you, I think.
Jared: Oh, I know. I'm not...Why is this not saving? It's not saving, unfortunately.
Joel: Is it untitled? Does the save button work?
Joel: I don't know if that has anything to do with.
Jared: Formik webinar. "You are not authorized." I'm going to copy that and make sure that I'm logged in here. I am logged in. This is the update, when you don't use code sandbox right before you do demos.
I'm not sure why this is not syncing. state.values, props.onSubmit, prevent default. Did I spell that right? I did. Let's do, oh, because I didn't, stringify doesn't...Come on, now. I am being an amateur right now. Hey, there we go.
Joel: You've offloaded some intense visual parsing to TypeScript, because now, the errors, and it's nicer.
Jared: Right, exactly. We have this useFormik hook, which is pretty useful. It could be more useful. It's about to be a lot more useful. The way it's going to be more useful is, let's add validation. This is going to be a little interesting.
This is a nuance between Formik 1 and Formik 2 when Formik 2 drops. I'll get into that in a second. This is pretty useful right now, but the real reason Formik became what it is is because it simplified the way you validate your forms.
It's my favorite aspect of Formik. It's what it's really good for. In fact, if you're not using validation, you should ask yourself if you even need to be using Formik in the first place. You may find situations, like email password and sign-in forms where, "Eh, not worth it," because there isn't a lot of validation.
Joel: That's why I love it, honestly. The validation, I was all-in at this point, because it's great.
Jared: Yeah. I also would like to give a shout-out Jason Quense, because he built Yup, and Formik is best friends with Yup. It's not required -- you can use any validation thing you want -- but Yup is a cool library. Whoops.
Yup's a cool library, and it basically works like prop types. You can write these really, really, really powerful -- or Joy, from the Node universe, with Happy, Joy -- it allows you to use the builder syntax, which is how prop types look, to write these really complicated schemas really, really quickly.
Formik has the special way of interacting with Yup. We love it, because it's great.
Joel: It ends up making a ton of sense, too. When you look at your validation, and it's like, "Oh, look at that," then you look at your form, and it just works together. Completely removes the struggle of doing that, and doing it well, too.
Jared: Sorry, just aside. You use Yup, but have you ever done any of the field level validation? Have you ever messed around with that, using the field component fieldValidate? Sometimes, people say that they like that.
The reason that they argue that they don't love the schema stuff is because it breaks encapsulation. They have a point, but to me, I don't know. The symmetry of having the validation look like this, to me, is the biggest. To have it look like your values slice, that's, for me, people love it, anyways.
Joel: Actually, because Angular forms were good. You mentioned that earlier. People like Angular forms. I haven't used them since Angular 1, but I'm assuming the DNA went across. Formik and Yup reminds of me the good parts of what I liked about that, too.
Yeah, I'm going to overlook the bad parts. It's an evolution, a good upgrade from that process.
Jared: Appreciate that. Now, we're going to get to the super fun, interesting part. What we're going to do is make some concessions. What we're going to build today is synchronous Formik. What I mean by that is, everything here, as it relates to right now, our submission function and validation is going to be synchronous.
If we were to make that constraint, well, then we could do our validation synchronously, execute it, and dispatch it when we set our field value or set field touched. We wouldn't waste any time. It wouldn't be any asynchronous processes, and we wouldn't block user input.
However, real Formik allows you to validate with asynchronous, so using promises methods. Why is that important? You could have something that's super complicated and expensive. You could make an API request. You could check if that username still exists. You could do things like that.
In order to deliver the best user experience, and also to prepare you for React 5 or stuff, Formik will take your validation and actually run it as a second update. It'll update the actual input value first, so when the user types, there's no blocking anything. It's as fast as it possibly can be.
Validation actually occurs asynchronously in a promise. Real Formik does actually some pretty nifty stuff. It actually will cancel the running promise of a newer promise when newer values comes up while it's running.
It's got all these optimizations and stuff that we're not going to get into today, but you should use Formik, because it's very smart about when it actually runs validation as it relates to user input. It's going to get really, really smart in the next versions of React, when the scheduler comes out, and we can coordinate stuff, high priority, low priority.
Anyways, for our purposes of the demo today, we are going to work on a synchronous version of Formik, but we're going to code it up as if it was going to be expandable to not that. If you are looking at this, and you're like, "Well, why didn't you validate inside this handleChange?"
The reason is that's not going to scale if you're going to have this little demo be actually useful. Let's say to take this to CodeSandbox, you expand, and add that out. If you're perf crazy, and you really only have synchronous validation, then yeah, it should go there.
If not, where are we going to put it? We're actually going to put it in a hook called useEffect.
Joel: Perf crazy" as a t-shirt, I think, is pretty cool.
Jared: Merchandise, baby. React useEffect, the most confusing hook, I think. useReducer, it's kind of scary, but once you do it once, you're like, "Oh, yeah, that's just a reducer." useEffect is very different. It is unlike any other React concept. It's brand new.
Initially, I actually had the wrong mental model. Initially, I thought it was like, "Oh, it's componentDidUpdate and componentDidMount combined." That's actually a very false, that's not true, for a whole host of reasons.
React useEffect will just run. It will run. If you pass it a second argument of an array, it will run at different times. If you pass into that array different values, it will run only when those values change. Why does that matter for us?
What we don't to do is, we want to look at this piece of state, and we want to say, "OK, when do we want to validate our forms?" We want to validate our forms when a user makes a change, and when they blur.
That's a debatable thing. Joel, I think you forked Formik because you didn't agree with that. I think you guys, right, way back when?
Joel: Yeah, it was pretty early in Formik's life, even.
Jared: We may have changed it. I don't remember, but I remember you forked it, because you were like, "I don't agree," which is cool. That's why GitHub's awesome.
Joel: Just for the record, we just in the last couple of months went back to using normal, non-forked Formik, because forking stuff is a real pain in the butt.
Jared: Yeah, that's fair. We're going to do with this is we're going to say when our values change, state.values, as this side effecty thing, we're going to run a validation function. Where are we going to get that validation function?
You're going to tell this Formik hook about it. We'll just do a condition, if props.validate. We'll call that thing validate. If that exists, let's validate state.values. We'll call this errors. We'll get to what to do with errors in a second, because it's not actually as obvious as it seems, but we'll do that.
Joel: Wouldn't that be props.validate for the function call?
Jared: Yep. You're saving me from myself again. I got to go, TypeScript, man. Saving me from myself. This is our useEffect. This is going to run whenever values changes. Let's actually log out this console, log to the console errors.
Hopefully, that actually worked. The reason why it it's not actually doing anything...Oh, this is really brutal. The reason why it's not doing anything right now is because we didn't actually hook up our form with any validation.
Let's write a little validation function. We're going to have this be called validate. This is going to take values, because we just gave it values. It's going to return either an empty object or an object that's going to mirror...
Remember, our touched and errors, they all mirror each other, because they have the same keys. Let's just make an empty object here. Let's say let errors. We're going to be mutative here, because it's easier. We're just going to make up some fake validation function for purposes of the demo here.
We'll say if values.name not equal to, I don't know, admin, we're going to make an error. We're going to say that errors.name is, "You are not allowed," or something like that. Then we're going to return errors.
What this is going to do is, it's going to error all the time, unless we type in admin perfectly, it will set errors.name to, "You are not allowed." Now, we need actually dispatch that our state, because it's actually not going to get stored inside of our React's reducer state.
What we'll do is, we'll take, actually abstract this function right here into another little function. We'll call this validate, or validate form. You know what, actually? Let's not do that. Let's just put the dispatcher inside our useEffect. It'll be simpler.
You could abstract, and you might want to do that later, but let's log that out. Now, we see our console's working. Right there, our error object, here, errors, would be, "You are not allowed." We did this, and let's write admin.
You'll see the last error object should be empty. Yes, it's empty. We have errors. We need to actually put into Formik, our actual reducer. To do that, we're going to have something called setErrors. Not setFieldError, but setErrors.
Why would we want to do that? We could do it like a field, but realistically, this validation function is at the top level of our form. You could have this validate function run, you're running it against all values.
Presumably, you could set this error's object as an object. It's not just for one error, per se. We're going to call our action setErrors. Instead of doing a spread, and instead of, again, spreading the previous state's version of errors, we're actually just going to call it action.payload.
We're going to set it to action.payload so that you can overwrite errors if they go away.
Joel: That's errors, right, not error?
Jared: Errors, thank you. Errors that go away, because you could have a situation where the user corrects an error. If you spread something that's undefined on something that's defined, you're actually not going to update it right when it gets merged.
We want to replace the entire error state here. To do that, we'll just dispatch. We'll call this type setErrors, and our payload will be the errors that we just had. This is going to render a lot, because values updates a lot.
It's going to validate every single time that values changes, and it's going to dispatch a new error object every single time. There are some optimizations you could probably do. You need to be a little careful here, because of React useReducer -- this is an important safety tip -- will not update if you return the same, exact shallowly equal version of state.
If your state shallow equals after you you call the reducer, React will opt out of the update. Because in real Formik errors and your values and stuff can be nested and stuff, in real Formik, Formik will deeply compare very quickly and decided whether or not it should actually dispatch this event, for all intents and purposes. For what we're doing right now, this is totally kosher, totally fine. We'll dispatch our errors here.
There it is. Errors you are not allowed. How do we actually display those errors? We see touch name is true right there. We want to display our errors, really complete this whole thing.
Let's go back down to our form. We have errors. We grabbed errors out of Formik there. For each one of our inputs, we're going to do...First, let's check if the error exists. Errors.name.
Then we'll check if the field's been touched before. Touch.name. If so, we'll just render a div conditionally with the error message in there. Errors.name. We'll add this class name of error. Let's see if that worked.
There it is. You are not allowed. That's our little error. Let's see if that's a little better there. Let's see. Little issue with the CSS there, but we'll figure it out. That's our name input there. Make this style red. Give it a little negative margin to fix our little CSS issue.
There we go. Much nicer. Cool. We have our error for our name, which is the only error we have in our form right now. We could actually mirror this error logic, which again is looking at does the error exist, has the field been touched. Then display the error. We'll just copypasta that down to the email.
We'll change this symmetry because we're not going to the name error. We're going to show the email error. Same thing would happen if we had an error on our email. That's all good now.
What we'd also want to do is we want to...Remember how we talked about the handleSubmit? We were going to come back to that and validate. We're going to mark each field as touched beforehand. Now let's make validation real.
What we should do is we should make a couple of different events. We should make an action. We should dispatch a couple things. We're going to call this submitAttempt as the action type. We actually don't need to do any payload there. We know what we have to do with that action.
When the user attempts submit, what we're going to do is we're going to return a slice of state. We're going to maybe be nice to our users. Let's maybe make something called isSubmittingTrue, just so that they have a way to tell if there are consumers that have used Formik know about isSubmitting, whether the form is submitting or not.
What we're going to do is we're going to set touched using this little helper that I'm about to import. This is my only cheat. I apologize. What this function does, setNestedObjectValues, what this is going to do is this is going to recurse our entire tree. I'll show you what it looks like.
It's going to recurse an object. It's going to visit every single key or, if it's an array, visit every item in the array. It's going to set it to a value, as deep as nested as it possibly could ever be.
Why is that useful? That's exactly how we want to set all of our form's touched slice of the tree to true. We want to touch every single field. That's what this does in a nice, recursive way. It's way easier to use this than to implement that from scratch.
What this does is it takes an object and sets every single key of it to a value. We're going to set that to true here because we want to mark all of our fields as true. We're not going to call this setFieldValue. We're going to call this submitAttempt.
While we're here, we can make another one called...We could do submitFailed, but we could just be like submitSuccess. We'll set isSubmitting false. We don't want to do anything else there, but we could do something like submitFailure.
When there's a submitFailure, we could make up some new key that's maybe useful for that error message that come back from validate. We could do a bunch of things here. We'll just set submitting false for now.
You could keep track of if your API had an error and you wanted to keep track of that differently. There are various things you could do. For all this, we'll just keep this here for now and implement the rest of handleSubmit. Let's go back down to handleSubmit.
We'll dispatch our submit attempt. We're going to validate. We're going to call our validation function again. This is where I was thinking about moving out of errors. We actually just care about this little slice here, so handleSubmit.
We want to basically run our validation function if there are not keys of errors. If not object. This is a little keys of errors.length. Basically, this is a quick-and-dirty way of saying if the errors object is empty, then the form is valid. That's what we're checking. We're just checking is the form valid right now.
What we're doing there is we're running the validate function against the values of the form. Then we're going to check the length of the keys of that return errors object. If it doesn't exist or is zero here, because zero will evaluate to true here, then we can go through with our submission. Otherwise, what can we do? We can dispatch those errors again.
These two things, these two, are very, very similar. We could also dispatch our submitSuccess, and we could dispatch submitFail. Maybe you want to put errors into that payload. You can do that in one update, two updates, whatever feels right.
That's our little submission helper, and that's a quick-and-dirty way to do submission. That's for miniFormik. You can add arbitrary actions and slices of state. The reason why they're not showing up in our little debugger that we wrote is because we didn't initialize them in any way. Let's just initial that extra slice of state that we added isSubmitting.
We're going to set that to false by default because you can't render a form in a submitting state. That doesn't make any sense, by default. Now we should see this all toggle, so we should be able to prevent submit if there are errors. It should say isSubmitting false, and if I do that, it stayed false.
I have a little typo here. Where's my typo? It stayed false, which was good. IsSubmitting, where did I do that?
Joel: Those are all right. It's probably down lower. It's probably the new one you just added to the initial state.
Jared: Yeah, that's exactly right. Another TypeScript typo. It's not letting me submit. It just touched all the fields. That's exactly the behavior we want. Let's do that again so you can see. We have submitting is false. It ran on mount.
UseEffect will run on mount, which is even more important why you want to make sure that you're only showing your errors when the users actually touch the field. UseEffect will run on mount, which is a little different than the way componentDidMount, componentDidUpdate works.
ComponentDidMount works, yes, but this is not really the merging of the two, so we definitely want to keep that there. Then, if we go to hit the enter key, it touched all the fields. Name and email are now touched, so now our error message is showing, and we're in business.
Now we have basically synchronous Formik almost in its entirety. There are obviously some caveats. You definitely want to use real Formik. We could make this a little bit even more real in our handleSubmit if we wanted to make our validation a little bit more savvy. I'm sorry, not our validation.
If we wanted to make our submission a little bit more savvy, we could toss this in a try/catch and make this async. To make this sync, we could be like try that, catch a submit error. This is a different error than our validation error. That's important. This would be the error returned, like maybe our API is down or the server told us that name and email were not good.
Now maybe we wanted to dispatch, like submitFailure also could accept this extra payload of a submit error, optionally.
What that could do is allow us to...Let's take submitFailure and let's say that submitError, which is different than our regular error, will be action.payload. Then let's make submitError, we can keep it undefined initially. I don't use null ever because if you use TypeScript it doesn't play nicely with it.
Joel: Null is so weird.
Jared: Null is pretty bad. You can find arguments for and against it. I'm on the against.
Joel: Me, too.
Jared: High five. Once you start programming that way, life will generally improve. If you're very consistent about that, then life improves. I'm not going to even bother initializing that part of state. If it doesn't exist, it doesn't exist.
Now I can write something really simple like grab this submitError, which may or may not exist or could be undefined. SubmitError and submitError, and now I'm done or could be pretty close. I don't need to worry about null. It's way easier that way.
That's one thing there. What do we do with that try/catch real quick, just as a quick summary in case you missed it because it's really subtle. Sorry about these things popping up. I'll have to talk to indecipherable about that.
Basically, what this allows us to do is...Let's try to make our onSubmit now, we'll await this submit. We're going to wait the users on submit, and we'll do that in a try/catch. Remember we made handleSubmit itself async? It's not in the browser, but it's going to work anyways.
We could make this function down here asynchronous. We could do something like let's write a little sleep function. Sleep ms milliseconds is new promise. SetTimeout will resolve the promise and pass in the milliseconds.
This is a one-liner function that will basically -- what did I miss here? I missed an arrow -- allow us to do something like await sleep for 500 milliseconds, and then call our alert. Let's say admin form isValid. Let's submit on enter. That worked. Make this like three seconds or so. We need to type this in as admin now.
Watch isSubmitting, true. You saw there was a delay there, about a second. When I hit that there, it went to false. What that did was basically we allow now our users, our consumers of this useFormik to have asynchronous submissions, which is a nice thing to have. We're also cleaning up the submission state.
If we wanted to, let's say, disable the button or disable the inputs while submission was happening, which is really, really important because you don't want double submits to happen. That could be triggered by somebody pressing enter twice. If you're doing a payment, you definitely want to disable the submit button. That's where isSubmitting helps a lot there, too.
We just built, I'd say, 80 percent of Formik with hooks and with this is awesome new hook, useFormik or useMiniFormik, however, you want to call it. What's different with real Formik is we're just using hooks. We could hook it up to context, and that would allow us to do things like not have to pass around or wire up these.
This is still kind of verbose right here. We still have to wire up handleChange and values and blur. If we wanted to make life easier for our users, we could put what gets returned, this Formik entity, these Formik props, onto context.
There's one wrinkle with that, which is interesting with hooks, which I'd love to talk about for a second. Hooks and context are really interesting. There's a useContext hook, which is cool, but when you use the useContext hook, you still have to render a provider.
What that means is you still need to render some sort of react component that's going to end up rendering whatever your context.provider is. Its children have access to that context. This is why, Joel, you mentioned in the beginning that building this with hooks, even though with Formik, you have to go through this massive transition with reimplemented with hooks.
As an end user, you not going to necessarily care. That's because Formik's API is context-based. While you can totally do this, you can totally wire up your inputs by default, you're not going to necessarily want to because it's just like that.
Because you have to render that provider, which is the Formik component not the Formik hook, that means that basically, Formik can be backwards compatible, which is important. You don't need to pass down this Formik component, basically -- Formik object -- but you could make it nice.
I'm going to show you the last thing here if we have the time now. We can take a Kent Dodds-like approach to all of this. We can make a little helper method for us to make this entire nonsense less repetitive. What we're going to do is we're going to make a little prop getter, which is not my favorite pattern, but it makes sense here.
We'll make a little function here. We're going to make this called getFieldProps. It's going to take a field name. The field name is going to be like name or email. What is it going to return? It's going to return all the props that are necessary for an input. Instead of writing that all out, let's just copy it.
We took value, and we're going to change this from JSX. Instead of values.name, it's going to be state.values. We'll use fieldName to grab the values out. We're just getting that piece of state. In real Formik, we'd use some sort of lodash.get because it'd be deep. You could do nested stuff there, handleChange, and onBlur handleBlur.
These are defined right above our cells. This is handleChange, handleChange, handleBlur. That's pretty sweet. We'll pass that down through what our hook returns, getFieldProps. Now we can, instead of handleChange, handleBlur, values, we can just do getFieldProps.
Anywhere we're doing that, we can do getFieldProps, name. We'll spread the results of that because we're returning an object there. We'll do getFieldProps of email. This is pretty good. We can do one step better. We still have this repetition right here of name and name and email and email. What could we do?
We could curry with getFieldProps and remove the requirement of this name attribute. If we remove this, and instead of handleChange taking an event, handleChange is going to take a field name, like we just did getFieldProps. Then it's going to return a function that returns a function that takes an event.
HandleBlur, same thing. It's going to take a field, and it's going to return a function that takes an event. With getFieldProps, we can take this fieldName, and we can pass it to handleChange. We can pass handleBlur this fieldName, too.
Instead of event.target.name and event.persist, we don't need any of that anymore. We're just going to use the name that is passed in through our little...I'll just do this at once. We don't need to persist the event anymore.
Oh, sorry, that's not true. We actually do need to persist the event, because we're reading off the target there. I lied. We don't need to persist the event in handleBlur. We're not reading off handleBlur's, we're not using the fieldName there.
We are going to read off the event for handleChange. What this does is basically, it allows us to not have this repetition here. We still want to pass type text, but we don't need to pass any other props, basically, to our input or whatever other props are just direct pass-throughs. This still works as expected.
That's some niceties. With a prop getter, you could have that. That's what's really cool about hooks, is they're very flexible. Anything you could do with a renderProp, anything you could do with HOC, you can do with a hook, aside from static analysis.
The only thing you need an HOC for now is relay, I guess, for that kind of optimization. Aside from that, you basically can do whatever you were doing with an HOC or a renderProp with hooks, with one caveat.
If you still have the need for context, you still need to render the provider. If your renderProp, if your library, or your component relies on context, like it's a menu, or it's something like that, where you're imperatively not passing props, and you need context, then there's no direct benefit for hooks of the parent that actually renders the provider.
There are some use cases for hooks. Let's just play pretend for a second with our useFormik. This is in Formik 2. useFormik actually isn't that useful, because you're still going to want to use the Formik itself.
What you would do there if we wanted to build out, I'll just give you an example of how that would work. You'd be like FormikContext is react.createContext. We'd put this as an empty object for now. We could make an actual component called Formik.
It takes the same props as our useFormik. What this is going to do is it's going to get what we just created off of that Formik function. We'll do, just going to pass it straight through. All this Formik component's going to do is it's going to render FormikContext.provider.
Its value of the provider is going to be the Formik props that we need. It's going to render its children and then FormikContext.provider. That is how Formik 2 actually effectively works. That's how the Formik component works.
It basically takes this hook, and because we need to set a context provider, so that useField or use other aspects of Formik work as expected, that's how that all wires up. What else could we do? What do I mean by useField?
Imagine we make a hook now, because we're building off of our misused Formik hook. Let's just keep expanding it. What if we made a thing called useField? Instead of getField, we can just basically take, indecipherable takes a name, a fieldName like we had before.
What this will do is, it will get Formik props, use react.useContext, which is a way to read context through a hook. We'll pass it FormikContext, which we just created. We will return FormikProps.getFieldProps and pass that the fieldName.
We could use this down here. Actually, we can't use this down here yet. The reason for that is we're not setting context. This is what I want to describe. We can't use this function, like const, I'll call this nameField, is useField name.
This won't work if we try to replace this getFieldProps with nameField spread, even though they are the same thing. This won't work, because FormikProps isn't defined. That's because we haven't rendered a provider.
We actually haven't rendered this Formik provider into the DOM yet. This context is not relevant. It's basically reading from this initial object, which doesn't have FormikProps defined yet. That's where this, you can build renderProps with hooks.
You still actually have a use case for renderProps, or you still have a use case for just any React component by default. It's going to just keep erroring then until I get there. That's how you could do it. If we wanted to, again, make this more ergonomic, we could take all of these.
Maybe there's a quick and fast way of doing this, even. Let's see. Let's call this like props equals this. Maybe we can do this in one go. Let's see. Formik, and if we spreadProps here, yeah, this should work. I'm cheating. I don't want to have write out all this JSX right now.
Let's see. We actually can move this all, since this is completely independent of our actual input here. You'll notice that all of these, though, don't exist right now, because they're above the scope of the context provider. They still don't exist.
We need to do some more work here. We need to make useForm, maybe. There's a whole host of stuff that you need to do if you keep this going. I won't bother, but you could see how it's not exactly one to one between hooks and renderProps. There are some cases where you still need renderProps.
Joel: The context Pandora's box.
Jared: Yeah, so, we're going to put that back for our own sake. That is where you would go, if you were to keep going. Our Formik thing is totally useful. It's great for small forms. You can reuse it. If you're just using basic inputs and stuff, it's great.
You have got getFieldProps. You still again, that's pretty close to useField anyways. The thing that gets a little bit frustrating is if you're trying to build out a set of inputs in a design system, and you want those inputs to be used everywhere.
Then that gets a little obnoxious to force your development team to have to pass getFieldProps every single time. That's where context still, and also for backwards compatibility. For the same reasons why you'd use context and not pass props explicitly is why you'd want to use context provider.
That's basically mini Formik. Formik 2 is coming out very, very soon. There's already an alpha, or I think it's actually a gamma. We've gone past beta. We're now at gamma.
Jared: There's a gamma out right now. You can install Formik Next. If you have any questions about Formik Next, submit an issue on GitHub. I hope this was very, very useful to everybody. There's still some more optimizations that we didn't get to, which you should definitely read about.
Read through the hooks documentation. We didn't talk about useCallback. We didn't talk about a couple other things that you should definitely, like Memo, useMemo. There are other hooks, is what my point is, that you need to know about and read.
If this was your first intro to hooks or first deep dive, you're not done yet. Definitely go to the React docs. Read through the hooks stuff that came out today. Read through the FAQ. Do it twice.
Joel: I'm also enjoying usehooks.com, actually. He's gone and written a bunch of really great stuff. Are you still working off the hooks branch? Is that where y'all are at?
Jared: For Formik right now?
Jared: Yeah, we're also off the hooks branch. That's where most of the development is. We'll probably move it to the master soon. We wanted to wait until the release officially came out for hooks, because there are some features that we are wanting, that we're still wanting, we'll have to make some decisions about.
We'll merge that soon and then get Formik 2 out the door. That way, I hope I gave you all a good idea of how to use hooks in the real world. In fact, we'll publish the sandbox, and maybe we'll make this, we can publish this as Formik Lite or something, I don't know.
Joel: I would highly recommend in your production projects to just use Formik. Building your own mini Formik is such a great exercise to just sit down, spend the afternoon, build it out, tweak it, try to optimize that, and all that stuff. It's a fun, great way to play with it, I think.
Jared: Joel, I can even work on putting this...Actually, what would be great is if you see this, and then if you read the source code for V2, it's like, again, you're going to be like, "Oh, that's even smarter. We'll do that." It'll be pretty apples to apples there.
Joel: Jared, thanks so much for taking the time out and showing us this. I really enjoyed it.
Jared: Thank you.
Joel: I appreciate it. Yeah, cool.
Jared: I appreciate that. Thanks for having me, and any questions, hit me up on Twitter or submit an issue to the Formik repo. My DMs are open, so if you ever get confused with hooks or have anything else you want to talk about, again, I'm here for you.
Thanks, again, and thank you, Egghead. This was awesome.