So I got this IMAGE PICKER placeholder here,
and my idea here simply is to display an image picker
that can be used by the user to attach an image to the form
and to upload an image when the form is submitted.
Now for that, I'll actually build a separate component
since this image picker is a bit more complex,
and hence, I'll go to this components folder,
and then there in the meals folder,
I'll add my image-picker.js file.
We could also add it in any other sub folder here,
but since I'm only using it in this
meal-related form, I'll add it here.
Attached you also find an image-picker.module.css file
with some styles we'll use.
And then here we can and should export a component function
that can be called ImagePicker like this.
And the job of this component function
is to output some markup
and then also handle the picking process.
Now for the markup, I'll start with a div here
with a class name of classes.picker.
And for classes to be available,
we should of course import classes
from ./image-picker.module.css.
And then in that div, I wanna start by adding a label,
a label with a configurable text, which is expected
to be received via props.
So I'll add the label prop here
and then use that here for outputting that label text.
Now I will add the htmlFor prop here
to connect this label to some input,
and I'll connect it to an input with a name
of image or with an ID of image to be precise.
And therefore we of course also need such an input.
But I will nest that input into a div here,
a div to which I assign a className of controls.
And then in there I wanna have an input element
where the type should be set to file,
because this should be an input
that allows us to select a file.
Now the ID here should be image
so that we connect this label to this input.
And I'll add the accept prop
to control which files are accepted.
And here I only wanna accept files that are
of type image/png or image/jpeg,
so no other files can be uploaded
with help of that picker.
I'll also give this input a name of image,
which will later be important for extracting
that uploaded image.
Alternatively, we can also make this image picker
a bit more configurable by accepting that name
as a prop and then setting this like this.
We could now also use this name as a value for the ID
to make this even more configurable.
And I will do that here.
Also using it here for htmlFor.
Now with that, we got a very basic unfinished version,
but we can now use this image picker in our form.
So back in page.js, in this form here
where I have this image picker,
we can output the ImagePicker
using our own custom ImagePicker component.
Of course, for that, you must import it
from that components folder.
And with that added, you will see that image picker here.
Now this would work.
You could now click it and then select images.
But I actually wanna make this image picker
a bit more fancy.
I wanna make sure that it also shows a preview
of the picked image.
And I wanna get rid of this ugly button here,
and instead display my own button
over which I have full control.
And therefore, back here in the image-picker.js file,
we can start by adding a class here to this input,
and that should be the input class.
And this class will essentially make sure
that the input itself is hidden.
So now we can't see it anymore down there
because I instead now wanna render my own button.
So therefore here, maybe after the input,
we can output a button,
which should receive a class of button
and which should be of type button, which is important
so that it won't submit the surrounding form.
If you would not set the type,
it would by default be type submit,
and it would submit the surrounding form,
which I don't want to happen here.
So the type should be button.
And then we can set a text
of pick an image here on that button.
Now with that, we got this button here, which is great,
but of course the button isn't doing anything.
And what I want the button to do is click this input,
which is not visible anymore for me.
It's still part of the DOM.
It's just not visible
because of the CSS styles I applied
and now I wanna use my own button
to click this input under the hood so that I still
leverage this input, but I don't show it
because it's not looking that nice.
Now to make that work, we of course need
to handle clicks on this button
and then forward them, so to say, to this input.
Now we can handle clicks to this button by adding a
function here to our image picker, like handlePickClick
or anything like that.
And now we can assign this function as a value
for the onClick prop here.
Now if you do that, you'll be greeted by an error
because as I briefly mentioned earlier,
event handlers like this,
so whenever you are assigning a function
or any value to the onClick prop
or any other event handling prop cannot be used
in server components, which kind of makes sense
because those interactions happen on the client
in the browser.
The user clicks on the button in the browser.
Therefore, we must mark this ImagePicker
as a client component
by adding this use client directive
at the top of this file.
With that done, the error goes away,
but of course, we're not doing anything
when the user clicks the button.
What I wanna do is, as mentioned, forward that click.
I wanna trigger a click on this input,
and we can do that with help of refs,
a feature built into React.
We can create a ref with the useRef hook,
which is imported from React,
so which has nothing to do with NextJS,
but which instead works the way you know it from React,
which gives you a ref, like a imageInput ref
that you can then connect to an HTML element.
And I'll actually just name it imageInput.
But now we can use that ref
and set it as a value for the ref prop
on this imageInput field here like this.
And that now allows us to use this ref
to trigger the click method.
However, of course, we have to access .current first
because that then gives us access
to the actual connected element and object.
But that then is the code we can use
to trigger a click on this input,
which is connected to this ref.
With that, if I now click my own button,
this image picker opens up again.
But now as a final step, I wanna make sure that as soon
as I pick an image, I also show a preview of that here.