Designed by Kevin Nielsen
Clone repo
cd rn-dev-proto
yarn install
This project uses Expo. To run the app, you can use the following command:
yarn start
Due to the limited time for the assessment, I decided to focus on the following:
- Use familiar tools and technologies
- Quick research and selection of the Image API, dictionary file, and other technologies
- Deliver MVP using brute force with simple styling
- Optimize spellchecker to work for all edge cases
- Refactor code to be scalable and reusable
- I decided to use Expo to create the app. I have used Expo in the past, and I am very familiar with its development environment. It also has excellent documentation and community support.
- Out of the 3 APIs listed in the prompt, Pixabay had the simplest documentation and was extremely easy to understand. Additionally, it only required me to register an account to gain access to an API key. Within a few minutes, I was able to test a GET request with a search parameter in Postman and get a response back.
- For screen navigation, I decided to use React Navigation. I have used this library for the app I created with Xalt, and it is a very powerful tool. There is a lot of community support, and the documentation is easy to navigate (no pun intended!).
- Home screen with a search bar, search button, search results
- Flexbox with a column direction
- The search bar section will have a text input and search button at the top of the screen below the header
- An icon will be in the text input
- An icon will be used for the search button
- Error handling message below the search bar
- The gallery container (data response) will be below the search bar
- Flatlist will be used to render images
- ScrollView is built-in and is responsive with other utilities
- The Flatlist will be conditionally rendered based on search results
- If there are no results, display an error message
- Flatlist will be used to render images
- Cards will be used to display images in the Flatlist gallery
- An image will be displayed in the card
- The card will be pressable and will navigate to the Image Overlay screen
- The detail section below the image will display image details
- Icons will be used to display image details
- Number of likes
- Number of downloads
- Icons will be used to display image details
- Stack screen for the Image overlay
- Fade transition into the overlay screen
- Use the built-in header with React Navigation
- A back button on the top left of the screen
- Title in the middle of the header
- Close (go back) button at the bottom of the screen
├── rn-dev-proto
│ ├── App.js
| |
│ ├── navigators
│ │ └── MainStack.jsx
| |
│ ├── screens
│ │ ├── Home
│ │ │ ├── index.jsx
| | | ├── SearchInput.jsx
│ │ └── Overlay
│ │ ├── index.jsx
| |
│ ├── components
│ │ ├── Button.jsx
│ │ └── CloseButton.jsx
| | └── etc..
| |
│ └── lib
│ ├── useGetQuery.js
│ ├── apiURL.js
│ ├── spellChecker.js
│ └── theme.js
All files that return JSX are in PascalCase in a .jsx file type, including all folder names that have a root index.js file (which returns JSX). All other files or folders are in camelCase with a .js file type, including files named index.jsx.
App.js- The main entry point for the app
- Contains app wrappers and providers (navigation container, SafeAreaProvider)
- Status bar component (anything that is constant throughout the app)
The design of the folder structure is intended to be scalable, easy to navigate, and follows the principles of the UX/UI. For this project, there are 4 main folders in the root directory: navigators, screens, components, lib.
-
Navigatorsfolder- Contains the navigation stack for the app. For this app, it may not be necessary to have a separate folder, but it allows scalability if other navigators are needed, like a bottom tab, drawer, or other stacks. I strongly believe the screens should be separate from the navigators. When you get 6+ more screens, it can be difficult to navigate through the code if the screens are in the same folder as the navigators.
-
Screensfolder- Contains the components that are specifically used for that screen. For this app, there are only 2 screens,
HomeandOverlay.
- Contains the components that are specifically used for that screen. For this app, there are only 2 screens,
-
Componentsfolder- This is used for components that are screen-agnostic or, in other words, reusable with any screen. Some examples are reusable buttons, icons, or cards that would be in this folder, and dynamically adjusting them using props.
-
Libfolder- This folder is used for any helper functions or utilities used throughout the app. These file names are in camelCase since they do not return any JSX. For bigger apps, this folder could contain more folders depending on the use cases and complexity of the app. In this folder, I created a file called
apiURL.js, which dynamically creates the API URL to Pixabay with the search params. I also created a file calledtheme.jsthat contains any constants used throughout the app. In this case, I added a simple color palette.
- This folder is used for any helper functions or utilities used throughout the app. These file names are in camelCase since they do not return any JSX. For bigger apps, this folder could contain more folders depending on the use cases and complexity of the app. In this folder, I created a file called
Starting with the search input, there is an on-change handler to update and set the state for the search query, and the value is connected to the React Native TextInput (passed down as props into the SearchInput component). Additionally, that handler updates the state to show a reset button that clears the search query and sets a state to show the user is typing (I will expand on that in the submit).
Getting the spell checker to function properly was a bit tricky since the spell checker needed to be triggered before submitting the request.
- Initially, I tried putting it in the on-change handler, but that was causing the spell checker to run after every character was typed. Also, I was running into an issue where the spellChecker function was autocorrecting too quickly (sometimes after typing two letters it would autocorrect before I finished typing the word).
- Applying the useEffect hook allowed me to trigger the spellChecker function after the user stopped typing using a setTimeout function and simply another useState function to determine if the user was typing, and it would be triggered when the searchInput state was updated. From there, the spellChecker function would run and update the searchInput state with the corrected word.
- I felt by adding the setTimeout function, it gave a much better user experience, since they were more likely to finish typing their word and they could see the word autocorrect in real time.
The submit button triggers the handle submit function. Since the submit is making an API request, I used an async-await function.
Created a custom hook called useGetQuery.js that handles the API request and returns the trigger function, data (results), error, and loading state.
- This was a significant refactor for a couple of reasons:
- The
useGetQueryhook is reusable for any screen component. It is more scalable and with a few adjustments, it could work for any API request. - Not quite as important, but it makes the Home screen more readable and easier to navigate through the code.
- The
Inspiration for the refactor
- Although the original design did work, it lacked scalability and was not reusable in other components.
- One of the first couple React Native YouTube tutorials I followed showed how to create a custom hook. Understanding that the prompt asked to NOT use a library or SDK that encapsulates interaction with the chosen API, I felt this was a good opportunity to show my ability to refactor and optimize.
- Experience with Redux Toolkit and RTK Query: I've enjoyed working with this SDK in previous projects. I will say it is rewarding knowing how to create a custom hook from scratch and not needing to install the packages and set up the configuration.
Dictionary File: Found a word list JSON file (2,285 words) on GitHub. Wordlist Link
- This was a simple regex expression that removed any non-letter characters from the string. I used the
replace()method to replace any non-letter characters with an empty string.
1st Attempt
- I wanted to test problem-solving skills without "researching" for help, and I can say on my "first" attempt I was able to get the vowel replacer function to work under certain circumstances and would only change the first vowel if that was mistyped. It was finicky but it was a start, although far from ideal since it did not work for all test cases. After spending a few hours on it (easily the most time spent on one feature), I decided to step up my research game to find the solution in order to deliver the MVP on time.
2nd Attempt
- Using my best logical (Google search) skills, I was able to find a much better solution on LeetCode for a similar problem.
- From there, I was able to modify the code to fit the needs of the prompt to properly auto-correct for mistyped vowels. Additionally, since the solution was so elegantly simplified, I actually adjusted the logic so it was easier for me to read and understand.
- It did force me to get a better understanding of sets and maps and the differences between them versus arrays and objects, so overall it was a great learning experience.
Logic
- The
wordlist.jsonfile is imported using therequire()method. - The word list is then converted into a set using the
new Set()method (a list of unique values). - A Map is initialized using the
new Map()method. - The query (word) gets the non-letter characters removed and is converted to lowercase.
- To add to the Map, I used an iterator over the set to convert words to a masked word (e.g.,
c_t) and then added the masked word as the key and the original word as the value (e.g.,{"c_t" => "cat"}). - From there, there are two conditional statements to see if the initial word is in the Set, and if not, check if the masked word is in the Map. If the masked word is in the Map, then the original word is returned; otherwise, the original word is returned.
- Lastly, if there is no match, the original word is returned.
- Research and select an image search API
- Test API with Postman
- Create Repo
- Create App using Expo
- Create README
- Configure React Navigation
- Search Bar (text input)
- Search Button
- API GET request logic
- Results Area
- Responsive Gallery
- Error Handling
- Responsive Overlay
- Research and select a 3rd-party dictionary file
- Spelling Checker
- Remove non-letter characters
- nyl;on -> nylon
- cak3e -> cake
- Mistyped vowels
- Working with one vowel words
- ce3t -> cat
- ceke -> cake
- Working with 2+ vowel words
- ceku -> cake
- uctur -> actor
- bioity -> beauty
- Remove non-letter characters
- Decisions & Assumptions
- Testing
- Simulators: iOS & Android simulators
- Physical Devices: iOS & Android using the Expo Go