
No Comments Yet
Be the first to share your thoughts and start the conversation.
Be the first to share your thoughts and start the conversation.
How did you manage to remove the blur property and reach here?
Upgrading gives you access to quizzes so you can test your knowledge, track progress, and improve your skills.
By logging in, you'll unlock full access to this and other free tutorials on JSM Pro.
Why? Logging in lets us personalize your learning experience, track your progress, and keep you in the loop with new workshops, coding tips, and platform updates.
You'll also be the first to know about upcoming launches, events, and exclusive discounts.
No spam—just helpful content to level up your skills.
If that sounds fair, go ahead and log in to continue →
Enter your name and email to get instant access
In this lesson, we dive into implementing the search and filter functionality for a React Native application. The focus is on building reusable components that utilize URL state management, allowing users to filter items based on categories and search for specific items seamlessly.
Filter.tsx
and Searchbar.tsx
.Search.tsx
file for user interaction.FlatList
for rendering the filter options, allowing for horizontal scrolling and dynamic styling based on user interaction.00:00:02 Next up, let's implement the filter functionality.
00:00:05 To do that, I'll create a new component right here in the components folder, and I'll call it filter.tsx and use rnfe to quickly spin it up.
00:00:16 And we can do the same thing with the search bar.
00:00:18 So right here in components, I'll create a new one and I'll call it searchbar.tsx.
00:00:26 I'll also run rnfe.
00:00:28 And now we can just import both of these components within our search.tsx where we before had just simple pieces of text.
00:00:38 That is right here within the list header component.
00:00:40 We have the search bar first, which we can import from component search bar.
00:00:46 And then after it, we have the filter.
00:00:49 So I'll also render a self-closing filter component here.
00:00:53 Perfect.
00:00:54 Make sure to import it from the right place, not this one right here.
00:00:58 It's going to be coming from components.
00:01:00 So I always like to utilize this WebStorm autofill that very quickly gets me the needed imports.
00:01:06 I reloaded the screen because there was some weird error happening, but typically once there are no issues, but something shows up as an error,
00:01:13 you just have to reload the screen and then we're back.
00:01:17 So now let's dive right into the filter component and let's get it implemented.
00:01:22 The way in which this filter functionality will work is when you click on a specific category type, its ID will be added to the URL as a search parameter.
00:01:32 And on search screens, as soon as this URL search program values change, we're going to call the same app right query to get all the menu items.
00:01:40 But now with this category selected as a filter, we're not managing category in a local state, but rather we're managing it as a part of a URL state,
00:01:49 similar to what we're doing in Next.js.
00:01:52 So to make that happen, let's head over into our filter and let's destructure the categories that we're going to pass to our filter.
00:02:01 As a prop of a type categories will be an array of different categories coming from types.
00:02:10 Perfect.
00:02:11 Now immediately we can get access to the search params right here, because as I said, we'll be modifying it through a URL.
00:02:18 So I'll say use local search params coming from Expo router.
00:02:22 And I'll create a new use state snippet called active and set active so that we know which one is active at any point in time.
00:02:31 And by default, I'll set it equal to search programs dot category.
00:02:37 So the one that is already selected or I'll make it equal to an empty string like this.
00:02:43 And don't forget to import useState coming from React.
00:02:47 Perfect.
00:02:48 Now, we'll also need to have a handler function when we actually handle a press on a specific category, which will take the ID of a type string of that category.
00:02:59 I'll implement this soon, but let's first craft the different filter data that we're going to pass into the selector.
00:03:06 So filter data will be of a type category, or a $id of a type string and the name of a type string.
00:03:17 And actually it'll be an array of those properties.
00:03:20 So array of different categories.
00:03:22 And it'll be equal to categories.
00:03:26 If categories actually exist, then it's going to be an array where we're going to have an object with id of all and the name of all as well.
00:03:39 But we will also append all of the additional categories that might have been selected.
00:03:45 Else, if we don't yet have any categories, we'll simply make it ID of all and name of all without adding those additional categories that we had right
00:03:55 here at the top.
00:03:57 So how do you think we're going to implement this filter component?
00:04:01 Well, here's the kicker.
00:04:03 It'll actually be a flat list component.
00:04:07 Yep, that's right.
00:04:08 So a flat list doesn't have to be just a vertical list like this one.
00:04:13 It can also be a horizontal list of different things, like here.
00:04:18 So I'll render a flat list so you can see its versatility, and it'll pass in the filter data that we want to render, and we want to define how we want
00:04:27 to render a specific item.
00:04:29 So let me actually put that in a new line so it's easier to see.
00:04:33 We have the data, we have the render item, and we also need a key extractor so that we can differentiate different items.
00:04:42 It'll look like this.
00:04:43 So now, how will each one of these items look like?
00:04:47 Well, first, I will destructure the item out of it, and for each one, we will automatically return a new touchable opacity,
00:04:54 because we want to make it clickable, we want to make it a button.
00:04:58 Each one of these touchable opacities will just render a new piece of text, and that text will simply render the item name.
00:05:09 So now, if we save this, you should be able to see just a list that says All right here at the top.
00:05:15 Not a real list, at least not right now.
00:05:18 But what if we pass more categories to it?
00:05:21 We're calling it right here, and it definitely wants us to pass that array of categories.
00:05:26 which we are fetching from this hook, categories.
00:05:30 So let me just pass categories equal to categories as a prop to the filter.
00:05:35 It says that null is not an assignable type, but in this case, we know that we're going to return some, so I can just add an exclamation right here to
00:05:42 let it know that we know that we're going to pass some props.
00:05:44 Oh, and it looks like I actually forgot to return the categories from the getCategories function.
00:05:50 So we simply have to add return categories.documents.
00:05:56 And now it's going to return them from this function.
00:05:58 And now if we head back over into the filters, we should be getting them as props, and then we should be rendering them right here as part of that flat list.
00:06:07 So now we can see burgers, pizzas, burritos.
00:06:10 They don't look like filters yet.
00:06:12 They look like a list.
00:06:13 But trust me, we can make this list actually behave like a filter.
00:06:18 So let's first style it a bit.
00:06:20 By rendering this flat list as horizontal, I'll also give it a prop of Show Horizontal Scroll Indicator of False because people will know they need to
00:06:30 scroll through it as it's gonna go beyond the screen on the horizontal way.
00:06:34 And I'll also give it a content container class name of gap X2 and the padding bottom of three to create some spacing.
00:06:43 So now it's already starting to look a bit more like a filter list, but let's actually style each one of these buttons within it.
00:06:53 We can do that by giving each touchable opacity a key of item.$id and a class name equal to CN.
00:07:04 to which we can always pass a filter class, but only if the active element is triple equal to the current item, so item.$id,
00:07:15 then we can also give it a BG amber of 500, else we can give it a BG white.
00:07:22 So this way we can differentiate the active or inactive elements.
00:07:27 There we go.
00:07:28 This is better.
00:07:29 Then let's also give this touchable opacity another property.
00:07:33 So I'll do it right below and I'll call it style.
00:07:37 So we can define a new style depending on the platform operating system.
00:07:41 So we'll say platform.os and if it is equal to Android, in that case, I'll also give it elevation of five as well as a shadow color.
00:07:55 of like hash 8 7 8 7 8 7, else I won't do anything on iPhone devices.
00:08:03 So I'll just make it look like this.
00:08:05 Finally, on press of this touchable opacity, we want to call the handle press function.
00:08:12 And to it, I want to pass the ID of the item that we clicked on.
00:08:17 So item dot dollar sign ID.
00:08:21 Finally, we can style the text a bit by giving it a class name equal to cn body-medium, and then if active is triple equal to item.id,
00:08:35 then we'll give it a text white because it's going to be on an orange background, else I'll make it text gray 200. So now if you save it,
00:08:45 It looks like we don't have any currently selected items, but we will have them soon.
00:08:50 So let's actually implement this handle press function so we can actually select some of these filters.
00:08:56 To do that, we're accepting the ID, and I'm going to set active the ID that we clicked on.
00:09:02 As soon as you do this and head back, already you can select an active item, which makes it so much more functional.
00:09:09 But now we have to actually change the router settings in order for our filter to be reflected in our app.
00:09:17 So let me check if ID is equal to all.
00:09:22 like no filter, then we can simply use the router.setPerRams to set it to category of undefined.
00:09:33 It doesn't exist.
00:09:34 Or I think null should be okay as well.
00:09:36 But else, I will set the router.setParams to a category and choose an ID of a category that we're passing into this handle press.
00:09:47 So basically, you can now select any one of these different filters, like wraps, for example, or burgers or pizzas, and immediately see what item you can order.
00:09:58 Or you can just head back over to All, which will cancel the selection and show you all the different items.
00:10:05 This is perfect.
00:10:05 It's working exactly as expected.
00:10:08 So similarly, let's develop the search functionality using the same URL state management.
00:10:15 I'll do that by heading over into our search bar component.
00:10:19 And I'll start with a UI by giving this view a class name of search bar.
00:10:26 And then it'll look something like this.
00:10:29 And within it, we can render not a text, but a text input with a class name equal to flex one and a padding of five.
00:10:39 And text inputs are self-closing components, so we can just remove the closing.
00:10:44 And also give it a couple more props, such as a placeholder of search for pizzas, burgers, and more.
00:10:53 And then we have to handle the value of the search.
00:10:56 So I'll create a new use state snippet and I'll call it query set query at the start equal to the query coming from params.
00:11:07 So like before, I can try to get access to params and make it equal to use local search params.
00:11:14 which will be of a type, an object with an optional query of a type string.
00:11:21 So now as the default state, I can set params.query, which if it exists, it'll auto fill it.
00:11:28 Perfect.
00:11:29 So now we can set the value of this piece of text input to a query.
00:11:37 And we also need to create a handler function.
00:11:40 So I'll call it const handle search, which will be equal to a function that accepts a text of a type string.
00:11:46 And then we can just add it right here.
00:11:50 On change text, we'll call in the handle search function.
00:11:57 And finally, we can also pass a placeholder text color of something like hash A0, A0, A0, and save it.
00:12:08 So now it looks a bit more like an input and you can actually click on it.
00:12:13 And below that text input, I will render a touchable opacity with a class name equal to padding right of five, as well as on press.
00:12:26 For now, we can just call a console log that'll say search pressed.
00:12:33 Later on, we can do something else with it.
00:12:36 And within this touchable opacity, I'll render an image that'll have a source of images dot search.
00:12:44 Make sure to import those images from constants.
00:12:48 And then a class name of size of six with a resize mode of contain.
00:12:57 as well as a tint color equal to something like hash 5D, 5F, 6D.
00:13:05 I found that tint color to work well.
00:13:07 And now if you save it, it should also get this icon on the right side.
00:13:12 And the only thing you need to make it work is actually set the query right here in handle search by saying set query to be equal to this piece of text
00:13:21 that the user is typing in.
00:13:22 And then after that, you can set the router params by saying router.setParams of query to be equal to this new text.
00:13:36 So now, if you take your phone and search for something, like maybe let's go with one of the items here, like a pizza.
00:13:45 Oh, and notice how the keyboard jumps out after every time I type something.
00:13:48 We definitely want to fix that.
00:13:50 So the search is working, but there's one important update that we need to make with every single search that we create.
00:13:57 And that is a concept known as debouncing.
00:14:01 See, if you type really fast, then for every single keystroke that you make, we'll make an API request to our database.
00:14:09 And that is very overwhelming for the server and the database.
00:14:13 It makes our app run slower.
00:14:15 And let's be honest, a user doesn't need output back for every single keystroke.
00:14:19 In most cases, they want to type the full word, right?
00:14:22 So we have to make it so that while they're still typing, we don't make requests, but as soon as they stop typing, we immediately make it.
00:14:31 So we can do that by introducing this concept of a debounce.
00:14:36 There's a simple package that lets us implement it, and it's called use-debounce-hook.
00:14:41 So in our second terminal, just run mpmi use-debounce, and then we can use it as a hook right at the top by creating a new variable const debounced search,
00:14:53 which will be equal to use-debounced-callback.
00:14:57 which you have to import from use the bounds.
00:15:00 And then to it, you can pass a callback function that accepts a text of a type string.
00:15:06 And then instead of us setting the router manually, we'll call it only when we finish typing.
00:15:12 Something like this.
00:15:15 And then as a second parameter, you can provide after how many milliseconds will you actually make the request?
00:15:20 How many milliseconds will you wait for the next keystroke?
00:15:23 And we can say that the wait time is about 500 milliseconds, so half a second.
00:15:27 So now, alongside setting the query to the state, We can also just call the debounced search and pass the text instead of just doing it for every single keystroke.
00:15:38 So now if you type in and you start typing, you can see that you can continue typing and no requests will be made.
00:15:45 But as soon as you stop, one request will be made.
00:15:49 But it still looks like our keyboard gets closed as soon as we stop typing.
00:15:53 So let me test it out one more time.
00:15:55 If I search for something like pepperoni, it actually works out.
00:15:59 But one issue that we have is as soon as you start typing or try to delete something, it actually doesn't fully delete it.
00:16:06 And that's because set params is triggering an update after each keystroke.
00:16:11 But if we switch it over to router.push instead, And then just override the query by providing a forward slash search question mark.
00:16:22 Query is equal to the text we're trying to update it to.
00:16:25 Well, this will not re-trigger for each keystroke.
00:16:29 After you make this change, you can just reload your application, head over to search and try something like pepperoni.
00:16:38 Perfect.
00:16:38 That works.
00:16:39 But still after every keystroke, the keyboard closes.
00:16:43 We don't want that to happen.
00:16:45 And it looks like I also got this warning right here at the bottom saying that text strings must be rendered within a text component.
00:16:53 But let's fix this issue first.
00:16:55 Now, I've done some research.
00:16:57 Or in other words, I've done a chat GPT search and a Google search.
00:17:01 And apparently, the reason why the keyboard is still closing after a short pause is likely not our code, but a common issue in React Native,
00:17:10 where if you put a text input inside of Flatlist's header, you will lose focus and the Flatlist will re-render.
00:17:18 This causes the keyboard to automatically dismiss, even though you're not navigating or tapping anything.
00:17:24 So here's one way in which we can fix it.
00:17:27 Instead of using this debounce search, which I will now completely remove, as we won't be submitting anything on key press.
00:17:36 Rather, within this handle search, I will simply be setting the query, and then if the text is empty, so if there is no text,
00:17:45 in that case, I will also reset it within the URL by saying router.setParams, of a query set to undefined.
00:17:54 So that's one thing that we can make.
00:17:56 But other than that, we'll also handle the submit.
00:17:59 So I'll create a new function const handle submit is equal to a callback function where we check if query.trim is not empty.
00:18:10 So if is empty, so if a query doesn't exist, not query.trim, in that case, we'll set the router dot setPerRams to be equal to the query we typed in.
00:18:25 Oh, and here I actually wanted to check if query exists, then change it.
00:18:29 So that's a mistake on my end.
00:18:30 If query.trim exists, then set it to the query.
00:18:33 And now we're passing the same query value to the text input and onChange handler.
00:18:38 But what is different is that right below our onChange text, I will also add onSubmitEditing, and here I'll call the handle Submit.
00:18:49 This will submit it when you press the Enter key on the keyboard, and we can also set the return key type to be set to Search.
00:18:58 This will show Search in the keyboard UI.
00:19:01 And finally, we want to utilize this button that we have.
00:19:04 So we can say router.setPrograms, and we can set them query equal to the query.
00:19:13 So we actually modify it, but this time on the press of the search bar.
00:19:17 So now if you start typing something, like let's go for pepperoni, notice how at the bottom of my keyboard, right here at the bottom corner,
00:19:26 there's a little magnifying glass.
00:19:29 Can you see it?
00:19:30 Right here.
00:19:31 That's because I chose the return key type of search.
00:19:34 So now the user knows that to be able to submit a search they actually have to click that button and then the search will happen.
00:19:41 Similarly, you can remove everything and it nicely gets removed.
00:19:45 We can also search for a burrito and that works like a charm.
00:19:53 I'll continue searching for better solutions for handling our search bar.
00:19:56 So if I actually do come up with something, I'll put it within the video kit down below and I'll name it something like improved search bar.
00:20:04 So you can just copy it and override what you have right now.
00:20:07 And then I'll also leave some comments so you can figure out exactly what I did to come up with an even better solution.
00:20:12 Okay, but for now we get this warning saying that text strings must be rendered within a text component.
00:20:19 Now this is pointing to this use local search params, so I'm not actually sure where we're rendering regular text.
00:20:26 Oh, there's a letter R right here.
00:20:29 Perfect.
00:20:29 So whenever you have a piece of text, it has to be within a text element.
00:20:33 But in this case, it was just a typo.
00:20:35 So with that in mind, we now not only have amazing filters, which everybody's going to use even more often, but hey, if they want to go for something different,
00:20:45 maybe like a bowl, then they can search for it and find it right here.
00:20:52 Perfect.
00:20:53 With that in mind, search and filtering has now been completed.
00:20:57 So let's go ahead and actually commit it.
00:20:59 I'll say git add dot git commit dash M implement search and filter and git push.
00:21:11 Perfect.
00:21:12 And now that we've implemented this search page, it's a pretty good time to head back over to our Sentry's dashboard to see what we have here.
00:21:20 Looks like Sentry has updated their look, so we can kind of try it out.
00:21:24 Okay, I like that.
00:21:26 It looks like they're not afraid to go with a bit of a wilder design with these cool shadows, and I actually like it.
00:21:33 So you can see that as we were using the app, or some other users maybe, some new errors and events have accumulated.
00:21:40 And actually, it looks like three or four different users encountered this error.
00:21:45 so it would definitely make sense to rank it as a high priority error.
00:21:48 It was first seen two days ago, but it's still present about 12 minutes ago, so it would be a good time to either assign it to somebody or resolve it ourselves.
00:21:58 And let me show you what else we have besides just the issues.
00:22:01 There's also user feedback.
00:22:04 which allows your users to easily create bug reports so they can let you know about these sneaky issues right away.
00:22:11 And every report will automatically include related replays, tags and errors, making fixing those issues that simple.
00:22:18 I thought these are only available on the web.
00:22:20 And as you can see, I'm using Sentry on my course platform.
00:22:24 And right at the bottom, there's this user reporting feature so that users can immediately let us know what went wrong.
00:22:31 But it's super cool to see that it's available for native apps as well.
00:22:34 Other than that, under Explore, there's a new Logs feature, which is right now in beta.
00:22:40 Logs allow you to send, view, and query logs sent from your applications within Sentry.
00:22:46 So in simple words, you can send text-based information from your application to Sentry, and then you can view those alongside relevant errors.
00:22:54 One of the requirements for setting logs up in React Native is to have a Sentry React Native SDK version of 7.0 beta.
00:23:03 And right now, I am at 6.16. So depending on when you're watching this video, logs might already be out of beta, or if you want to immediately dive into
00:23:13 using logs, you can just go ahead and update your Sentry React Native SDK version.
00:23:17 Setting it up is super simple.
00:23:19 It basically gives you access to the logger object, and then you can log different errors or info pieces.
00:23:25 And you can also pass additional attributes directly to these logging functions, such as tracing, debugging, providing info,
00:23:32 warnings, and more.
00:23:34 And the point of it is that you will be able to super easily track it within your Sentry dashboard.
00:23:40 So all of the data will be centralized in one place.
00:23:44 So for example, in our case, if adding an item to cart goes wrong, we can set that as a high severity error.
00:23:51 And then as these logs start piling up, we can immediately know that something is wrong, and we can set sentry up so that it lets us know immediately as
00:23:59 soon as something goes wrong, so we can go ahead and fix it.
00:24:02 Alongside logs, Sentry also allows for replays.
00:24:06 You can basically see a video-like reproduction of your user's sessions, so you can see exactly what happened before, during,
00:24:14 or after an error or a latency issue.
00:24:17 And I like how they say latency issue right here, because oftentimes, these are not caught, so being able to see a replay of how your user is using the
00:24:27 app is super useful.
00:24:28 Now, I could talk about Sentry's features for days.
00:24:31 First of all, because there are so many and they're so detailed.
00:24:34 And second of all, because I just like using them and we're actually using them on our own platform.
00:24:40 But I think we covered a lot of ground on Sentry for a single video.
00:24:44 I'll definitely use it again in the future and I'll show you some other cool features that you can try out.