Now with this action being dispatched, we can go to the reducer and write some code here in input change
and that's very simple code.
We return a new state where I copy my old state and I want to update the value with action.value
because value is one property I have on this action, the other is isValid and that's the other thing which
I also want to update on every keystroke, isValid for this input, not for the entire form but for this
input managed internally. I'll not update touched here, instead touched should be updated when we actually
lose focus because that means the user is done entering content for the moment,
so now I want to mark this as touched and possibly show validation errors. So here, lost focus handler
is a function which I add and there, I will dispatch
an action. Now for that,
I'll create an action identifier up here,
input blur, like this, input blur like this and now we can dispatch input blur here, don't need to attach
any data and now in our reducer, we can use that with another case, input blur and if that is the
case, I return a new state where I copy the old state because I want to keep value and is valid and not
change that but I want to set touched to true, so as soon as we blur an input, it is treated as touched.
Now we just need to hook up this function, so I will set the onBlur listener here and point at lost focus
handler so that this triggers when this input blurs.
Now that is all nice and this will manage the state inside of this input, of course I still need to forward
the value and the information, whether that input is valid or not, to my parent though, so to the edit product
screen. Hence what we can do is we can add use effect here as an import in the input component and maybe
add it here
and in the use effect function, I want to call a function which I expect to get via props, maybe on input
change, name is up to you and I will just execute this and forward my input state value and my input
state is valid information, so that in the parent component, we can add a function here that listens to
that in the end, that is triggered, which receives these two arguments and then in the parent, so in the screen
component, we can update our state there. Now the important thing is when should this use effect run and
it shouldn't run after every keystroke? Instead this should run whenever our input state value or is
valid changes of course, because that's what we depend on or to be precise, we might even only send this
information if input state touched is true because otherwise the parent component might not care.
So now our dependency here is the entire input state because we use all values but we still will only
fire this function on the parent component if touched is true, so this effect will run more often but
most of the time it won't do anything as long as touched is not true and props of course is also our dependency,
now to avoid an infinite rendering loop here, we can use the object destructuring syntax and pull out
on input change, so that if other props change, we don't refire this effect because on input change is what
we need down there and now we can add this is a dependency and the other things, other changes in the
props will not trigger this.
So now we call this on the parent component whenever this is touched and then we forward the value
and the validity of this input to the parent component.
So let's now make use of that in the edit product screen, there on our inputs like on the title input,
we can add the on input change prop and that's the prop which I target here right, so you need to get
that naming right and this expects to get a function as a value which then in turn will receive two
arguments when called - the value and the validity of that input.
So here I want to point at a function and that can be our let's say input change handler, we can recycle
this, I just rename it to be more fitting.
So the input change handler can be triggered here, like this, there I want to still pass in my identifier
because I want to reuse that for all my inputs and now since I'm in the screen component, we will have
multiple inputs and there, we therefore get our identifier and then we get the value and the is valid
information, right?
So input value and input validity, that's what we get,
right,
because that's what we forward from inside the input component here,
that's what we pass to this on input change
function. So therefore here, we don't need to do validation, we just need to forward that information. We
forward the input value, we forward the input validity and this will only happen when the input is touched
by the way,
so when it loses focus
and of course that's just a restriction I added here, that you need to have lost focus, you could
of course also go to the input component and remove this if check to update the validity and value
in the parent component for every input, for every keystroke. Now one additional thing which I want to
do, I want to wrap this in a use callback call here, so that this function isn't rebuilt unnecessarily
because keep in mind that we passed this function to on input change in the input component and there
on the other hand,
this is a dependency of this effect,
so this effect will rerun whenever this changes.
So it shouldn't change too often to avoid unnecessary effect runs
and here we therefore need to state our dependencies and the only dependency we have is dispatch
form state which will actually never change,
so we're good,
this should never rebuild because the logic in there doesn't change,
we get all values that we need as arguments. With that in my edit product screen,
I still have my forms that of course with all the form values and the form validities but I only manage
this here because I need to submit it later and because I need to derive the overall form validity. I
don't do it because I need to manage every input here,
we do that from inside the input component instead. For submission and so on, we still need the information
about every store, the value
however. Now if I go back to the input, don't forget that you also need to set the initial value prop
and whether this should be initially valid,
so if I go back to the edit product screen, now we should set initial value here to what?
Well, we can check if we have an edited product and if we do, then the initial value here should be
editedProduct.title,
otherwise it's an empty string and this should be initially valid,
so use this prop here
if we have an edited product,
so if this is, with the double bang here, if this is true. If we have no edited product, then this can't
be initially valid
so then I pass false which is good,
which is just what should be the case.
Now we can copy these two lines, also to the imageUrl, there of course we forward the image
URL value as a default value, other than that, it can stay like this. For the price, we'll never
have an initial value and it will never be initially valid. For description on the other hand, we can
again add this and here point at description.
Now with that, there is one thing I need to fix in the input component which I also just thought about, of
course here, the form state is not available anymore,
instead we have our internal state here and I named this input state,
that's what my reducer returns,
so we should use that here of course.
So here we have inputState.value as a value, it's as simple as that
and here we have inputState.isValid as a check and that should be all.
Now if we save this and have a look at this, let's see whether that works. If we go here, we have all the
error messages initially which makes sense because they're all invalid initially,
if I start typing here, I get an error, can't find variable isValid in my input component, that has to be
in that handler that fires for every keystroke, probably here,
yes in the reducer of course I should check
action.isValid,
not just isValid,
I have to get this from the action. So with that fix here,
let's go back and let's try this again. If I start typing, this is cleared, if I
revert this, my error doesn't come back
though, so that that's a little error we'll have to fix. For the price,
0 is then not treated as valid, one also
isn't, so validation has some issues
but entering and so on works but let's see what's wrong with validation now.
Well of course the way validation works here, we're not using it.
Please keep in mind that I always check whether this type of validation is enabled for this input or
this type or this type and we do this with the help of these special props which we need to set. Right
now no validation is enabled, so only the default invalid values are taken into account.
So if I go to the edit product screen, we now enable this special validation by setting the right props,
for example here on the title input, required. On the imageUrl, also required. On the price here, required
and also maybe min equal to zero, so that zero is the minimum number we have to enter or a number greater
than that actually,
so maybe 0.1 is our minimum number. For the description, should also be required and maybe also min length
equal to 5, to have at least 5 characters. There also is one additional thing we need to do here in the
edit product screen, this on input change handler here also needs to be added to every input of course,
not just to the title input. So we need to add this for the imageUrl, otherwise we'll get an error
later, we'll need to add this here to the price input, we'll also need to add this here obviously to
the description input and replace the identifier in the right way.
So let's test this and you'll notice something strange,
if you type here, that works, if you
type in the imageUrl, doesn't work right,
the validation doesn't update and you can't go back either, at least this might be stuck.
So if we reload this with command error or double pressing R on Android and we give this another try here,
let's say on the price you'll notice this works but why doesn't it work and the same would be the
case by the way if you tried it on the imageUrl, here
this works. Why does it work if I previously edited another input because now for example, it doesn't work
on a title anymore? And the problem is related to this being stuck here.
We're rerendering this component way too often and the reason for that is really hard to trace down.
In the end, we have kind of an infinite loop here and
the problem is our input change handler.
We're using callback here to avoid unnecessary recreation but by then binding it like this, we kind
of render this obsolete, it's making no difference now because this will create a new function binding
for every rerender cycle.
So in the end what we have to do is we have to remove bind here, remove it here in all these places because
this will break our app, it will cause
too many rerender cycles because of use callback not having an effect
and instead we might want to forward an ID
prop of title and so on to our input component.
So let's add this on all these inputs, this ID prop is basically what I added in the bind call before,
so we just forward this to the input component so that we can use it in there,
let's do this for all these inputs here and now how can we use that?
Well inside of the input, we now have this ID prop available right,
so there we can simply attach this.
So here we can forward props.id or simply ID so that we don't have to add props here and
instead pull out ID out of props here just as we did it with on input change and add ID
as a dependency of this effect thereafter. And now with that, we still forward the identifier of this input
but in a more elegant way that avoids using bind which in the end recreates this function for every
render cycle and hence leads to an infinite loop
and therefore now if we reload this and we go to the admin section here,
now I can enter something here and also here and now this works because now we avoid this infinite
render cycle and we get the validation as we wanted.
So now this is my approach or this is the approach I wanted to show you here, which you can use to have
a reusable input component which does its own validation, which you can configure from outside and where
you can still manage the overall form.
With that, let's dive into some finetuning,
like for example styling this error message we're displaying, this validation error message and also
controlling when we display it because I don't want to show it all the time.