Our approach here of course works. We're storing the inputs in some state, we're storing the validity
in some state and we could do this validity management for all our inputs here and simply have our different
change handler functions here for our different inputs, that would work.
One important note by the way before we move on, since I check the title validity here in the submit handler,
of course we should also add it there as a dependency,
so title is valid must be added there as a dependency in the use callback dependency array,
that's important,
otherwise this function will not be rebuilt when that validity changes
which means we won't be able to submit it thereafter. The problem with our current approach is that
we manage different states to store the input for the different inputs and then we also have different
validity states, at least potentially if we start managing our validity, like this
and for very large forms, of course this means a lot of copy and pasting, a lot of state management.
Now if you have that many states that are kind of connected, you also can do this in a more elegant way,
instead of having your separate states for each input,
you can have one big state which merges all input values and which merges all validities and then use
a concept called a reducer, not a Redux reducer but one supported by React out the box to manage that
state.
You do this with the use reducer hook which again has nothing to do with Redux,
the concept of a reducer is not Redux exclusive, a reducer in the end is just a function that takes input
and spits out some output and therefore React also supports this and you'll see how you can use this
in this lecture now.
So what's the idea of a reducer? Now
first it's important to understand that use reducer is a hook that helps you have state management. Typically
you use it if you have connected states or more complex state and you don't want have a bunch of individual
use state calls and a bunch of individual states which you manage. You then can use use reducer
and here's how it works.
You start by creating a reducer, let's say a form reducer here, you can name it whatever you want
and please note I create it outside of my form component,
you could do it in there but if you don't depend on props, you can do it outside of there so that this
won't rebuild with every re-render cycle and you don't even have to use use callback which of course
also costs some performance if you use it. So use it outside of there, build it outside of there
to avoid unnecessary recreations of that function and now the reducer, just like a Redux reducer
gets the current state and an action but again it's not related to Redux, it's not used by Redux,
reducer functions just always kind of work like this. Now in there,
again just like in a Redux reducer because again, they work like this,
you can check different action types and here I want to check one type, this as well
just use an if statement,
you could use a switch case statement as well
and that I check action.type here is totally up to me,
you can name this whatever you want because you will be the one dispatching the action later and here
I check for the update action which also is an identifier I chose and which just like in Redux,
you could also store this identifier in a standalone constant here, so you could have your reducer update
action here for example, your reducer update identifier like this and use this instead here
if you wanted to and you can name this whatever you want.
So now I'm checking for this action and if this action occurs, I want to store an input value and validate it
but to understand this, we need to understand how we use the form reducer.
So let's leave it like this for the moment and go down to the component itself,
there we start setting up our form state down here,
so here I want to call user reducer and now use reducer take such a reducer function hence I will
pass in my form reducer which I set up here and which is still unfinished.
Use reducer also takes some optional second argument which is the initial state you want to pass
in and that can be anything but here I'll pass in a Javascript object and the initial state I want to
use here is in the end an object which has input values, which then is yet another nested object which I'll
soon populate,
input validities which also is a nested object and maybe an overall form validity,
I'll name it form is valid which initially is false
let's say. So this is now my initial state I pass in here and that's the state which I later want to
change from inside the form reducer. Now on the input values, we can for example add our title and that
is up to you how you name it, which is an empty string initially
let's say, these will be the initial values I want to set on my inputs this will in the end replace
my state management down there, where I manually store my inputs in different states.
Now I will merged this all into one object managed through that reducer.
So I have my title here and actually that's not always empty of course but actually it's only empty
if we have no edited product,
so I will copy this check from there and use this in here.
Now the same of course for the imageUrl, there
I initialize this with editedProduct.imageUrl if it's available and otherwise I
use an empty string,
basically what I did down there as well.
And of course I do the same for the description, here
I want to check if edited product is set and I use this description or set it to an empty string
if it's not available. Now last but not least, we have the price and there I always have an empty string
because we either don't need it in the case of an edited product,
then I don't care about its value or we do need it,
well then I want to initialize it with an empty value
anyways. Now of course each input also has a validity, so we can add a title here and again check whether
editedProduct is set,
if it is set, then I know I'm editing,
so we will have prepopulated the title,
so initially it will be valid,
so in this case my initial value would be true,
otherwise it will be false and now this is also something we can repeat for the imageUrl. If we have
an edited product,
we know it was prepopulated, therefore it will be valid initially,
so we set this to true otherwise I set it to false.
For my description here, it's the same - true or false
and for the price, it's kind of equal,
of course we don't prepopulate the price
if we're editing but if we are editing I don't care about the price anyways because it will not be changeable.
Therefore I know if I am editing, I should treat this as valid because it can't be changed anyways and
therefore I don't care about this input
but I don't want to block form submission because of setting this to false when we can't edit it anyways.
So I will set it to true if this is not planned to be edited,
if we're not editing a product but we're adding one, the price will be editable and therefore we will start
in a false state because the price input will be empty initially, that's what we're setting up here. And
now the overall form validity,
well there again I can check if we have an edited product, which means we are editing, then the form is
true, the form is valid initially,
otherwise it's false initially if we're adding a new product. Now this is my initial state here,
now this reducer function should be able to change that state when actions are dispatched and an action
should be dispatched whatever we type into one of our text inputs,
that will be the trigger. So therefore, we can now get rid of all these state assignments here and hence
we can also get rid of the entire use state import even and make sure we dispatch the reducer
update action for every keystroke we do in an input.
So here in the title change handler, I in the end will remove this code, I'll leave the if check for now but
I'll not have any code in there for the moment, instead down there,
I want to dispatch reducer update and now this is done with the result of the use reducer call because
use reducer of course return something, just like use state did. It also returns an array with exactly
two elements and therefore we can destructure it with this array destructuring syntax which we also
used on use state and there I get my form state as a first value and a dispatch function as a second
value.
This is always what use reducer will return you, a state snapshot which updates whenever you change it
of course,
so whenever you change it, this component will rerender and give you a new state snapshot and a function,
dispatch in the end is a function that allows you to dispatch actions against this reducer.
Of course, you can name these two elements however you want but I think this is a sensible naming,
this is our state snapshot,
this is a function. Now dispatch by the way is a name I already used here from Redux,
so here I will name this dispatch form state to avoid a name clash,
you can name it however you want.
So dispatch form state can now be used here in my title change handler,
I just call it and I need to pass an object there which describes my action and this object will have
a type property,
you don't have to have a type property, you could name this ID or identifier or whatever you want but
I use type here because I actually do check for the type property inside of my reducer function and
this reducer function will be executed for every new action you dispatch. So if I go back down there,
the type will be reducer update or maybe a better name actually would be form update I think, so that's
also what I'll use up here of course and up here,
so form update, maybe form input update, the name of course is up to you but it should be clear about
what it does,
so here, I'll also have form input update,
yes that looks good.
So form input update,
that's what I dispatched down there,
we need to change it here as well
and of course you can also pass any other data you might want to use inside of your reducer
and I do have some other data which I want to use. That would be the value and I'll just name this value
here, this property,
you can name it however you want,
that's the text which was entered,
so I just forward that to the reducer so that we can store it in our state there and the validity of
this input.
So therefore here, I will actually add an is valid prop which initially let's say is false and now I will
remove this else case and check if we're greater than zero here which would mean that it should be
true because then I set is valid equal to true, otherwise it will stay false and
with that, we can also add is valid as a key to the action and forward the value stored inside of our
is valid variable
and this will be false initially but if our input length for the title is greater than zero, then it
will actually be true and now we of course also need to let our reducer know which input triggered this.
So I'll add an input ID field here or just input, whatever you like and use title here
and this key here should be a key which you also have inside of your state, like here in input values
and input validities because this will allow you to write some reducer code which we'll do soon
that is highly reusable and highly flexible because now we can take the next step and make sure that
we transform this to be a function that actually works for all inputs and that is really flexible regarding
how we use it.