A couple of weeks ago a client asked us to create an application that displayed all of it's stores. The app had to be built in React Native in order to speed up development time and ensure compatibility among Android and IOS and had to include a full list of our client's stores. This list was fetched from a MongoDB collection, and came as an array of objects containing information for each store (such as location, phone number, email, coordinates). Obviously, a plain list of objects doesn't satisfy a customer, as scrolling through a 189 store list to find a specific one might be extremely painful. So with React Native (our choice to build fast compatible apps) we decided to create a filter. The filter we built included features such as searching, categorizing and ordering according to proximity.
In this article, we will show you how the filter was built by using a mock API to build a filter with search and categorization (in the future we will write another article to show how to handle location based objects, order them and filter them). The tutorial will not cover a step by step of the whole code, but will go through the most important parts when building it. You can find the whole code in this Expo Snack.
You will see this is a front-end built filter, and doesn't use backend filtering. Although backend filtering is a good option (particularly to handle long lists), it works smoothly with the data we have. Bear in mind if you have millions of elements mapping through them will impact negatively on the app's performance.
So, to start we will be using Fruityvice's API that will bring a response with an array of objects containing different information about fruits. An example for the response we get is:
[{
"genus": "Malus",
"name": "Apple",
"id": 6,
"family": "Rosaceae",
"order": "Rosales",
"nutritions": {
"carbohydrates": 11.4,
"protein": 0.3,
"fat": 0.4,
"calories": 52,
"sugar": 10.3
}
}, ...]
Project Structure
Let's get hands on it to the real code. The structure our project will take is:
- A main App.js file where most of the work will happen, here we will set the main states and fetch our data.
- Components folder.
- Assets folder.
- Data folder in order to save the initial state some variables will have.
Fetching the API
The first thing we should do, is fetch the API. We fetch it through a simple fetch function built in a useEffect, meaning each time the component mounts, the API is fetched and the fruits "refresh". The response is saved as a json and we can now work with it.
useEffect(() => {
fetch('https://www.fruityvice.com/api/fruit/all')
.then((response) => response.json())
.then((json) => setFruits(json))
.catch((error) => console.error(error))
.finally(() => setLoading(false));
}, []);
Our App.js Component
We create a <SafeAreaView />
for our App.js (so that the content we build is contained within a visible space). Inside the SafeAreaView we will have three components, the AppBar (that will hold the modal and a logo for our app), the modal itself, and the Wrapper (called <FruitsWrapper />
) where we will render the "card-styled" list of fruits with their information.
On the App.js we'll also do two things that will help us handle the Filtering correctly.
First we'll set a couple of states:
const [fruits, setFruits] = useState([]);
const [filter, setFilter] = useState(initialFilter);
const [intermediateFilter, setIntermediateFilter] = useState(initialFilter)
const [modalVisible, setModalVisible] = useState(false);
- fruits holds the array of objects we fetch from the API
- filter filter is the real filter that will be applied when the user decides to APPLY the filter within the modal
- intermediateFilter is a filter that is setted while the user interacts with the modal, once the apply button is pressed, the intermediateFilter becomes the actual filter
- modalVisible will handle modal visibility
Why the intermediateFilter? the intermediate filter ensures the fields the user clicks while in the modal will be applied correctly. We need to save the user choices as he/she advances through the modal.
Both the intermediateFilter and the filter take up an initialFilter. What is this? The initialFilter
is a js written in our data folder. initialFilter is an object that hold's the initial state of the fields we are going to filter.
export const initialFilter = {
query: '',
genus: '',
carbohydrates: '',
}
The AppBar
The App bar is extremely simple. We have a logo, and a button that when pressed will change the state of the modalVisible
variable to true and show us the modal.
Displaying the info
Before we filter we want to display multiple cards containing information about the fruits so we can then sort them according to the user's choices. For this we have two components <FruitsWrapper />
and <InfoCard/>
<FruitsWrapper />
is the wrapper where we map through the fruits and display them. In this Wrapper we will also have the filtering instance. So as long as there no filters it will display the complete object we receive from the fetch. If there filters, we will push fruits to a new variable that will be empty.<InfoCard/>
is the UI of the card that will hold the object's info. We build only one object, and then map the fetch response and render each fruit (with it's information in the cards).
The <FruitsWrapper />
This component is SUPER important. As the logic applied here makes the magic to display the filtered content.
You can see that at the beginning of the component I declared two boolean variables: filterFruits
and empty
(empty will not be used yet, but will serve us to display that no fruits were fetched). I then set up an empty filterArray where the fruits I filter with my modal will be pushed. After doing this I set filterFruits
equal to allFruits
, the later one being the whole fruit array we brought on the first place. The following logic is key to filtering:
if (filterFruits != undefined && && typeof filterFruits === 'object'){
filterFruits.map((fruit) => {
// I have two things, the filter and the fruits genus (in the array) so if I filter I only want to show the ones that match the genus
if (filter.genus != '' && !fruit.genus.includes(filter.genus)) {
return
}
filterArray.push(fruit)
})
if (filterArray.length > 0) {
filterFruits = filterArray;
}
} else {
filterFruits = false
empty= true
}
The filtering happens if filterFruits
(before known as allFruits) is not undefined (meaning it has some content) and the type of this is an object. What we do is map through each fruit, if it doesn't match the parameters we want it to, we return, else we push it to the filterArray
. If the filter array is bigger than 0 (meaning fruits were pushed) filterArray
(the one where we pushed) becomes filterFruits
.
The Modal
The modal is the most important part of this tutorial. For this we will use React Native's built in modal.
In the application we built for the client we had two modal instances, the filtering modal (that used the native modal) and an alert modal (where we used UI Kitten's modal). We like the native modal because it offers us the possibility of deciding how it transitions and it's easier to set it up fullscreen.
As we mentioned previously we chose to use an intermediate filter within the modal so that the state management can be smooth, and we can access the different states (Remember that the initalFilter
was an object?). Yet, after the user click's the apply button, we want the intermediateFilter
to become the actual filter
A key thing we also have in this modal is the list of all the genus. Instead of mapping all the fruits and displaying the genus, in the App.js we created an array with all the unique genus (so that we don't get them repeated). The following code creates an array of all the fruit.genus unique values:
const genusResult = allFruits.map(item => item.genus)
.filter((value, index, self) => self.indexOf(value) === index)
We loop through this array that we built to create the radio buttons, as you will see in the RadioButton.js
file. This file holds custom built Radio Buttons. The good thing about this is that they are fully customizable, and give us more control over the user selection.
The thing is, the user can only select one genus, and when the user selects the genus selected is saved in the intermediate filter. Once the user decides which genus he/she wants to see, he applies the filter and because of the logic applied in the <FruitsWrapper />
only the fruits that have that genus will be shown.
Closing Remarks
This was a quick tutorial over how to build the filter. We hope it was easy to follow and in the second part we will talk about querying filtering.
Remember the full code is in our Expo Snack
Top comments (0)