
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 explore how to create a search screen using AppWrite, focusing on implementing functionality to filter menus by text and category. The tutorial covers setting up indices, writing functions to fetch menus and categories, and optimizing data fetching using a custom hook. Additionally, we look at rendering the search results in a user-friendly format.
00:00:02 Now that everything is set up, let's develop the search screen and display its results.
00:00:07 For now, it's just completely empty.
00:00:10 First, we have to write a function to get all the menus with a search by text and filter by category ability.
00:00:17 And to do that in AppRide, you'll have to specify what kind of a search you want to do.
00:00:22 Either it's a full text or it's unique.
00:00:25 That's called indexing.
00:00:26 So head over to AppRide console, under the menu, and then head over to indexes.
00:00:32 Here, you can create your first index, you can name it index1 with a key of full text, and we can search by name in an ascending order,
00:00:43 and just click create.
00:00:45 Now, back within the code, specifically within the apprite.ts file, we can now write a function to get all of these menus.
00:00:53 I'll write it right at the bottom of the other functions we have created so far, below the getCurrentUser.
00:00:59 And we can do export const getMenu is equal to an asynchronous function, which will accept two things, a category and the query.
00:01:12 So we can search by both.
00:01:14 And these will be of a type, getMenuPerApps.
00:01:18 Then within it, I'll open up a try and catch block.
00:01:23 In the catch, we will simply throw a new error as a string, if something goes wrong.
00:01:30 and in the try I'll try to first form the query by saying const queries of a type string array because there could be multiple queries at the same time
00:01:41 at the start equal to an empty array and then if the user has passed a category if they want to try to get a menu item by category so if a category exists
00:01:54 then we can say queries.push And we can use AppWriteQueryFunctionality.equal to check whether the categories collection matches the category that we're
00:02:08 trying to filter for.
00:02:10 And in the same way, I'll check if a query exists.
00:02:14 In that case, I will push queries.push query.search name with the following query that we're passing through props.
00:02:25 And once we have those queries, we can actually filter it by saying const menus is equal to await databases.listDocuments.
00:02:35 You know the deal.
00:02:36 We first pass the database ID, then we pass a collection ID.
00:02:40 This time we want it to be app.config.menuCollectionID.
00:02:46 And then we pass the list of queries that we want to filter by.
00:02:49 Finally, we can return menus.documents.
00:02:54 Perfect.
00:02:55 So now we have this getMenu functionality.
00:02:57 And similarly, we can also fetch all kinds of categories from the database.
00:03:02 So in the same file, let's also create the getCategories function by saying export const getCategories which is going to be equal to an asynchronous function
00:03:15 where we can open up a try and catch block.
00:03:17 In the catch, we will simply throw a new error.
00:03:22 And in the try, I'll try to fetch the categories by making it equal to a databases.listDocuments call to which we need to pass a database ID and a collection ID,
00:03:37 which in this case will be the categories collection ID.
00:03:42 See how easy it is once we have properly stored all of these pieces of info and IDs in the right place?
00:03:48 Now it's easier to create functions that give us exactly what we want.
00:03:51 And you could call these app-write functions within a use effect as we want to load the data on the screen and then use the loadings and errors and all
00:04:00 that good stuff.
00:04:01 But that would be repetitive, especially if you have more calls to do on a single screen.
00:04:07 So, to avoid that, we can create a reusable hook that we'll use to call these functions onLoad, onClick, or depending on different params.
00:04:16 So, let's create a new file within the lib folder, and I'll call it useAppWrite.
00:04:24 And since this is a generic function that you can reuse in any of your future apps, and I'll be honest, I used a bit of Chatjpd to generate it,
00:04:33 I'll drop it in the video kit down below so you can actually copy and paste this function right here.
00:04:39 But don't worry, I'll explain how it works.
00:04:41 So everything starts with a couple of interfaces that we define to keep our application type safe.
00:04:48 After that, we make this useAppRideHook accept a function which is going to be a fetcher function that fetches the data,
00:04:56 the params we pass into that additional function, and then skip in case we want to skip the call.
00:05:01 After that, we try to be a bit abstract with how we're defining the data.
00:05:07 We're not saying users or menu items or categories.
00:05:11 We're just calling it data and set data or loading, set loading an error and set error.
00:05:16 And then we have one single function that we've wrapped into a use callback to make it even more performant.
00:05:22 And it's setting the loading, setting the error, and then tries to fetch the data.
00:05:27 based on the function that we passed in and the parameters that we passed in as well.
00:05:31 Finally, it gets that data, we can refetch it with this additional function, and then we return it at the end.
00:05:38 The beauty of this custom hook is that now we can call this data more easily instead of every single time writing all of this code for all of these different calls.
00:05:50 But it'll start making so much more sense once I show you how we can actually call this hook in action because then you'll understand what this fn is and
00:05:58 what the params are.
00:05:59 So let's actually call it within the search page by heading over to tabs, search.
00:06:05 Right at the top, I will say const and destructure the data and make it equal to the call of the useAppWrite custom hook that we created.
00:06:16 To it, we need to pass a couple of props.
00:06:19 We need to pass, first of all, the function that we want to use to call the data.
00:06:25 In this case, let's say that we want to use the getMenu function we created.
00:06:29 So the getMenu is this one.
00:06:32 So in this case, the useUprightHook will actually be doing whatever this function is supposed to be doing.
00:06:39 But hey, we also have to pass some props to this function, like a category or query.
00:06:44 Well, we can do that through this second parameter called params, which is an object.
00:06:49 So right here, we can now say that we can pass a params of a category, for example, an empty string for now, or a query,
00:06:57 also an empty string, or a limit.
00:07:00 We can maybe say, hey, give me six items.
00:07:02 And this is how we fetch not only data, but also the ability to refetch that data if it becomes stale, and you can also get access to the loading of that data.
00:07:12 So let's try to console log it to see what we get back.
00:07:17 I'll save it and reload.
00:07:20 And now, if you head over to the search page, check this out.
00:07:24 We immediately get back all of this gray data, which are our six menu items.
00:07:30 I can see some beef patties right here and cheese.
00:07:32 That sounds good.
00:07:33 But let's say that on this same page, we also want to fetch the categories.
00:07:37 Now, thankfully, we can just call this useAppWriteHook one more time, just below.
00:07:41 By saying const, destructure the data, but this time I will rename it as categories, and I'll make it equal to the useAppWriteHook,
00:07:50 to which I'll pass the function of getCategories.
00:07:54 And this one, we're also importing from the lib app, right?
00:08:00 And we don't need to pass any params because this function isn't accepting any.
00:08:04 But if we do eventually end up having a category or a query, how are we going to fetch it?
00:08:11 Typically in Next.js, we fetch it from the URL params.
00:08:15 But hey, we don't have a URL here or params, right?
00:08:19 Well, thankfully, Expo made it work in a very similar way to how it works in Next.js.
00:08:26 You can say const params is equal to use local search params coming from Expo router, and to it, you can define the type of the params you're trying to get,
00:08:38 such as we can say that maybe we're going to be getting a query of a type string, as well as a category of a type string.
00:08:47 This will be within an object.
00:08:49 So now we know that this query will contain those pieces of data and it'll contain them because we're going to pass them over through params.
00:08:56 I'll show you how we're going to do that.
00:08:58 So from these params, we can actually destructure those two values, which is going to be the category as well as the query.
00:09:07 So now you can say category and pass it right here.
00:09:10 And in the same way, you can pass the query.
00:09:13 In this case, our hook is demanding that we have those fields.
00:09:16 So for the time being, I'll just mark them mandatory as well right here so that we're good.
00:09:20 And I believe we can now fit this into one line nicely.
00:09:25 and maybe even this entire call would fit into one line just so it's a bit easier to understand.
00:09:31 There we go.
00:09:31 So now we're fetching all of these params, and then we have two calls through which we're fetching the data.
00:09:37 The first one fetches the menu items, and the second one fetches the categories.
00:09:43 But in case those params get updated, such as the category that we click on or the query that we're searching for, If that gets changed,
00:09:51 we need to re-update the data.
00:09:54 So we can do that with a simple use effect that'll, in the dependencies, have the category and the query, which means that whatever we put within this
00:10:05 function will get recalled whenever these two change.
00:10:08 So we can simply call the refetch function right here, and to it, we need to pass the category, the query, and the limit,
00:10:17 maybe 6 by default.
00:10:19 And that's it.
00:10:20 This will refetch the data and update it automatically.
00:10:24 So you can see how creating these custom hooks can be super useful in keeping your application clean and easily understandable.
00:10:32 So now that we're fetching the data, and we know we are because I put a console log, we can actually develop the UI of the search screen and render that data.
00:10:41 So I'll start by giving this safe area view a class name equal to BG White and an age of Fool and then right within it I will render our good old flat list.
00:10:56 As the data, I'll simply pass the data and as the render item, well here we decide how we want to show that item.
00:11:05 First, I want to destructure the item and the index of that item within our list, and I will open up a new function right here.
00:11:14 Then within it, I will return a view that'll have a class name equal to flex1, and maxW of 48%. I found that value to work the best.
00:11:27 Don't forget to import the view from React Native.
00:11:31 And within that view, render a piece of text that says menu card.
00:11:37 Of course, make sure to properly close the flat list so you don't get this error.
00:11:42 And now if you head over to the search, you can see six different menu cards because we have a limit of six right here.
00:11:49 Now we can pass some additional properties to the flat list.
00:11:53 So I'll put this data in a new line and the render item in a new line as well.
00:11:58 and indent everything properly, and maybe collapse it, just so we have a clean view.
00:12:03 Additional properties that I'll pass is gonna be a key extractor.
00:12:07 Here, for React Native apps to work properly, you have to give each item a distinct ID, similar to what you have to do in React applications.
00:12:16 So now I'll just rerun it, and we still have six items right here, because it knows their distinct IDs.
00:12:23 I'll also use a prop called NumColumns and set it to 2. It is super cool that you can automatically just pass this magical prop to React Native and it's
00:12:35 going to know to render it in two columns.
00:12:39 But we get a warning saying that changing num columns on the fly is not supported.
00:12:44 You have to change the key prompt on FlatList when changing the number of columns and then force a fresh refresh.
00:12:50 So if we do it now and head over to search, you can see that now they're shown in two different columns.
00:12:58 We can also set the column wrapper class name to a gap of seven to give it some more space.
00:13:05 And we can also give it a content container class name to gap of 7, and a padding X of 5 with a padding bottom of 32 to create some additional spacing.
00:13:19 And there's literally a prop for everything in React Native Components.
00:13:23 Like, it's super cool to be able to this easily change it.
00:13:26 And now we can define the header.
00:13:27 So list header component.
00:13:32 That's a prop as well, so we can define it as a new callback function that will automatically return a view at the top.
00:13:41 And this view will have a class name equal to margin Y of 5 and a gap of 5, 2. And I'll stop saving it every time because as soon as I save it,
00:13:51 I get redirected back to home page.
00:13:53 Next, we have a view within it that'll have a class name equal to flex between flex row and a W of full for full width.
00:14:07 Within it, I'll have another view that'll have a class name of flex start.
00:14:14 And then within it, I'll render a piece of text that says search.
00:14:18 So now I can save it so you get the idea of what it is for.
00:14:21 And I'll give it a class name of small bold, uppercase, and text-primary.
00:14:30 So now we can see search at the top.
00:14:33 And then just below this text, I will render another view That'll have a class name equal to flex start flex row, a gap X of one and a margin top of 0.5.
00:14:50 And within it, I will render another piece of text.
00:14:53 And this one will say, find your favorite food.
00:14:58 And it'll have a class name equal to paragraph semi-bold text, dark 100. So if we save it, now we can see this title.
00:15:10 And we can head below this text and below one, two more views.
00:15:17 And then here you can render the cart button, which if you import it properly from the top, will make it appear right here on the right side.
00:15:25 so now we can see the cart which shares the same global state both on the home page and on the search screen below this view that's wrapping the cart button
00:15:34 we can also render a piece of text that says search input and then below it we can render another piece of text that says filter,
00:15:44 because we'll implement both the search input and the filter very, very soon.
00:15:49 Finally, what happens if we don't have any elements shown right here?
00:15:53 There has to be a way to provide just a loader or an empty state.
00:15:58 Typically, you'd have to do that using the if-else statements.
00:16:01 But believe it or not, with React Native, there's a prop for that too.
00:16:06 We can just call it ListEmptyComponent.
00:16:12 And we'll say, if not loading, Then, we will render a piece of text that says no results.
00:16:21 You could actually test this out if you made the function return an empty array, but for now we're good.
00:16:26 Now, if you compare this to the final design, you'll notice that we have this cascading style, where some elements are on top of the others,
00:16:35 creating this organic look and feel.
00:16:37 so let's actually implement this as well if you head back right at the top of our render item function we can say const is first right call item,
00:16:54 and you can basically check whether it's an even item.
00:16:57 So we'll evenly divide it by two and check if the remainder is zero.
00:17:01 If that is the case, then we can apply some special classes, such as wrapping this view with a CN, always providing it with this class name,
00:17:11 but then only if it's not first right call item, give it a margin top of 10, else give it a margin top of zero.
00:17:21 and properly close it at the end.
00:17:24 If you do this and get back, you'll notice that now they're cascading.
00:17:28 We have one on the left and we have one a bit down.
00:17:31 Here, here, like a waterfall.
00:17:33 So now that we have this layout, we are ready to create the individual menu card element.
00:17:39 So we can actually display it right here.