Now with the filters rendered here, of course it would be nice to have a save button here at the top that
allows us to save any changes to filters we made.
So in the filters screen, we already have one header button on headerLeft,
I now also want to have one on headerRight, so on the right of my header. So headerRight here is basically
the same as we have it here, so we can just copy this here because we want to have header buttons there
with one button, that will be the save button and the icon we can use there is for example and of course
you can pick a different one but here, I will go with for example save and now when this is pressed however,
that will be a bit tricky. Of course, we can fire a function and
we could console log saving filters here.
Now the actual logic for saving and applying filters is something we'll take care about in the next
module but still, there is some issue with that.
If I save it like this and we go to the filters, of course we can press this,
this works but what would be nice is that we then really gather our current state of filters, so basically
get a snapshot of our current state here and for the moment log this to the console, in the next module
apply this somehow to filter the recipes we're showing on the other screens. Now this might sound
trivial but we'll have a problem here,
our filters, so the information, which filters are set, that's all stored in these state variables here, right
and that's part of our component,
our button however is in the navigation options
and yes, there we get the snap data which for example gives us the navigation prop in the end but what
we don't have here is access to the state of our component and that is what I need here though
and now this here is really an important pattern which might look like a strange hack or workaround
but which is the way of actually communicating between your navigation options and your component
if you depend on changing data in your component inside of your navigation options. So in the navigation
options, you need information about data that changed in your component.
This is also an approach you find in the official docs of the navigation by the way. We can use params,
so that thing we pass between screens, to communicate between our component and the navigation options
and this might sound strange but this is exactly what we'll do now.
Let's say here in our component, we add a new function and I'll name this function here save filters,
you can name it however you want.
It's a function that takes no arguments but that has one job,
it will create an applied filters constant for example which is an object which gathers our filters.
So there we could have glutenFree as a key and we store isGlutenFree as a value,
we have lactoseFree as a key and we store our current isLactoseFree
state value.
We do the same for vegan with isVegan and we do the same for isVegetarian and isVegetarian,
like that and thereafter, I will console log this applied filters thing.
Now the thing is, I would like to give access to this function which is part of my component to my navigation
options so that I can trigger this function from inside the navigation options and for this, we can use
params.
Let's add use effect so that we have a way of executing code whenever our state changes because that's
when I want to forward this updated function which basically captures my current state to my navigation
options.
So here, I can add use effect and use effect takes a function which runs whenever our state changed and
whatever the component re-rendered and in there, we can now use props navigation set params.
We haven't used set params before,
we only worked with get param.
We didn't use set params because before we only set params when we navigated to a new screen, then for
example as in the categories screen, we passed our params like this to the new screen but we can
use set params to update the params values for the currently loaded screen. Now here on this screen,
we got no params, so it's empty,
hence we can set params to a brand new object here and I want to add one entry there but you could
add as many as you want.
I want to add a save key and you can take any name for that key which you want and point at save filters,
so at this function. I don't execute the function, I don't add parentheses,
I just point at it,
I just use this variable which holds a pointer at this function in the end and forward this value, so forward
this pointer or store this pointer to that function in that save key, in that object which I now set as
params in this screen, in the filters screen.
This allows us to go to the navigation options and there we have access to nav data and therefore to
navigation right and that means that here for the save button, when pressing this, we can actually use
navData.navigation.getParam,
that is how we can retrieve a parameter and that's available here in navigation options too, save and I can
retrieve that save parameter which I'm setting here in use effect and I know this is super strange
the first time you see this and it looks like a really dirty hack but it is not.
This is a valid way of communicating between your component and your navigation options which you typically
need when having action items in your navigation options.
This will get us access to our parameters and there we access the save parameter, the save parameter
will be this function and we update the value in that parameter whenever our state changes and we need
to do this because in this function, this is recreated whenever our state changes and it logs in the
latest state values
and that's of course what we want when this function gets executed eventually with the help of params
through that button in the navigation options.
So we use params as a way of communicating between our component and the navigation options,
kind of a workaround but indeed, the one you find in the official docs as well and the way of how you
communicate here. Now before we save this, there are two things we need to do though, use effect right now
runs whenever this component updates. In the end, it should only run when save filters hold a new value.
So I'll add the second argument to use effect which is this array of dependencies and there, save filters,
so this variable here which holds a function is a dependency
and right now, this will always rebuild when you component rebuild,
so it's not really a dependency that helps us but we'll soon fix this
and props also is a dependency. Now actually to avoid unnecessary re-renders whenever anything in the
param component changes,
I'll use a different way of extracting my navigation prop, I'll use destructuring here, like this, navigation
equals props.
This is a syntax which uses object destructuring which means props is an object and this pulls out the
navigation key and stores this in a brand new constant of the same name, so of the same navigation. It stores
the value in the navigation prop in that navigation constant
now, that's what the syntax does and the advantages that we now have a navigation constant here which
we can use in use effect without props because we have this stored in a separate constant now
and now we can add this as a dependency which means when this changes, this wil rebuild
but if anything else in the props changes, this will not unnecessarily rerun the effect.
Now to make sure that save filters only updates when our state changes, we can import the use callback
hook from React, another hook built into React
and this allows us to wrap a function so that this function is actually cached by React and is only
recreated if its dependencies changed.
We wrap this around our save filters functions, just like this, so we pass this function as an argument
to use callback and use callback
also takes a second argument which is an array of dependencies and there, we need to specify all
the dependencies we have here
that could change and that would lead us to recreate that function and that essentially is the case
if one of the four states here changes. So isGlutenFree, isLactoseFree, isVegan and isVegetarian
are now all dependencies of use callback and that means this component function here will be recreated
if any of these states here changes,
if anything else causes this component to re-render, we'll not recreate this function
and that's in turn important because that save filters function is a dependency of use effect.
So if that is recreated, use effect runs again which updates our params and we want to keep this to
a minimum and not constantly update our params but really only do that if we need to do it.
With this in place, we should now be able to press the save button and see the filters we chose,
so let's go to filters here
and now this breaks.
Do you have any idea why it would break?
The problem is, I have a navigation here as a dependency in use effect and I explained why I have
it, right, that I don't want to have props in its entirety and so on. A problem just is, when we call set
params,
we add new params to our navigator in the end and what this also does is it causes navigation to
change.
So in the end, I have an infinite loop here.
If I remove navigation here as a dependency, then this will work. If we now save this,
now if you close both apps here with the help of the task managers and you then restart that on both
operating systems, you'll see that now this indeed works and you can hit save here, now what you'll not
see is the output in the log here.
That however makes a lot of sense because if you have a closer look at these save button in our header,
then what I'm doing here in onPress is I'm in the end executing this function but what does this do?
It retrieves our parameters saved in the or stored in the save key and
what is this?
Well that is as mentioned before, a pointer at the save filters function, so we retrieve that pointer.
If we then don't do anything with that pointer, we're not doing anything.
That is a pointer at a function, so of course we need to execute it like this by adding parentheses.
Alternatively we simply get rid of this anonymous arrow function here and we simply bind onPress to
the result of retrieving our pointer here like that because then this pointer at the function will
be executed for us when we press this,
that's the alternative.
So now if we save this and we therefore let this reload and we go back to the filters,
if I hit save here, we now see an output in the log, let's scroll up a bit here,
everything is set to false.
If I now set lactoseFree and vegetarian to true and I hit save, we see another log and there indeed
vegetarian and lactoseFree is set to true which is exactly what I have here.
So now that is working, let's confirm that it also works on Android by going to filters there as well,
let's set lactoseFree to true and save this and if we do so,
that's our Android output here,
lactoseFree is true, everything else is false,
so now this also works.
So this workaround can be a bit tricky to wrap your head around and avoiding this infinite loop here
also is important,
making sure that you will use callback here to avoid unnecessary rebuilds of this function
here also is something you should do, so that when you update the params and therefore this component
rebuilds, you don't also rebuild that function and therefore also enter an infinite loop.
But with that, we have a way of communicating between component and navigation options which is typically
a pattern you need when you have action items in your action bar and you want to trigger something that
depends on data managed in your component with the help of these buttons.