COMP10013 Dynamic Web Technologies

Hello, if you have any need, please feel free to consult us, this is my wechat: wx91due

COMP10013 Dynamic Web Technologies

COURSEWORK

React Application and Formal Documentation

Banner ID: B00311948

Project Name: AnimalSnap PWA DocumentaHon

Academic Year: 2023-2024

Module: COMP10013 – Dynamic Web Technologies

Term: 2

Project Idea Type: My own idea for project Approved by Pablo Salva

ApplicaHon Type: Progressive Web App (PWA) with ReactJS

1. Introduction

1.1. Aim and Overview of AnimalSnap

The "AnimalSnap" PWA, developed in this project, is designed to merge the engaging world of wildlife with the cutting-edge technology of Progressive Web Applications (PWA). At its core, AnimalSnap aims to provide users with a platform where they can capture, share, and discover photos and details of various animals encountered in their daily lives or during their travels. Leveraging ReactJS for a seamless, app-like user experience, the application allows users to interact with a suite of features including photo capturing through an integrated camera functionality, geolocation tagging for each animal sighting, and a dynamic, interactive map showcasing the global footprint of all animal encounters logged by the user community.

With an intuitive user interface and user experience, the application not only focuses on the ease of capturing and sharing but also emphasises the discovery and exploration aspect, making it an engaging digital companion for animal enthusiasts and casual explorers alike. Whether you're documenting rare species in remote locations or capturing the urban wildlife in your backyard, AnimalSnap serves as a bridge connecting the natural world with the digital realm, fostering a community of conservation and awareness through shared experiences.

1.2. Structure

This documentation provides a walkthrough for the development process of the “AnimalSnap” progressive web application.

Firstly, this documentation will go over the thought process behind the application state and explain why this implementation was chosen over alterative solutions. Furthermore, the documentation will cover how each of the main functionalities of the application were developed and how they tie-in and communicate with the application state.

Additional information will also be provided regarding the implementation of the user interface and the packages and APIs utilised to produce the desired user experience.

2. State Management

2.1. Redux

Redux is a state management library commonly used for medium-to-large React applications. It is the most popular library of its kind and boasts many features that improve the development experience.

The advantage of Redux is that the application state is centralized, meaning the state is more predictable and easier to test and debug. The application's state can also be decomposed into "slices," which reduces code complexity significantly and allows for a separation of concerns. The Redux tooling also considerably improves the development experience enabling the ability to trace exactly when, why, and how the state changed.

I chose redux for this application as it is moderately complex – consisting of routing and multiple slices of application state such as the posts and location.

Alternatively, I could develop the application with a top-level state. However, this would lead to excessive prop drilling as components deeply nested within the tree require the state as props. Consequently, increasing the overall complexity of the application and crippling the reusability of components.

Another alternative solution would be the Context API provided by the React library. However, implementing the context API in such a way would be developing a “mini redux” in terms of functionality while losing out on the additional features provided by Redux.

Finally, this implementation of Redux is aided by the Redux Toolkit, an opinionated toolset created by redux that provides various abstractions and quality of life features - reducing the boilerplate code traditionally required when implementing Redux logic.

2.2. Creating the Store

The configureStore API is used to create the application store. This store is where the application state is contained. It provides a simple configuration and automatically combines reducers and the redux-thunk middleware.

Figure 1 Configuring the Store

2.3. Location

The locationSlice handles the retrieval of the device's location and provides an API that allows for the updating and retrieval of the location state.

Initial State

First, an initial state is declared. This state contains properties for the coordinates of the device. Additionally, it provides meta information such as the status of the state and any errors that have been produced.

Figure 2 Initial State

Retrieving Location

The getLocation function is a thunk created with the createAsyncThunk API. A thunk is a particular type of Redux function which can contain asynchronous logic; it is essentially middleware that runs each time before the store is updated.

Figure 3 Retrieving Location

The getCurrentPositionAsync is a simple wrapper function I created for the geolocator API. It provides a nice abstraction and cleans up the code inside the thunk.

Figure 4 getCurrentPositionAsync

A thunk dispatches “start/success/failure” actions throughout its life cycle. To illustrate, we can look at the redux development tools and see which actions are dispatched when the application is initialised.

Figure 5 redux development tools

These actions can then be handled with reducers and the “builder callback” notation.

Figure 6 locationSlice

In this scenario, the status is updated to reflect each stage of the process when retrieving the location. The state will be updated with a default latitude and longitude and an error message if unsuccessful. If successful, the reducer will then update the state accordingly with the data returned from the thunk.

Finally, so that components within the application can access this state, selectors have been created, which are helper functions that allow components to read the state. Later chapters will discuss how the state is retrieved and updated by child components.

Figure 7 Helper functions that allow components to read the state.

2.4. Posts

Persisting Data

Before explaining the CRUD implementations of the postSlices, we will first take a detour to discuss how data is persisted on the device.

As this is a progressive web application, I had two options available to me that I could use to persist the data locally. The first option was localStorage, but it had a maximum limit of 5MB and would quickly become a bottleneck. As a result, the only realistic solution was the IndexedDB API, a database implemented within the browser.

After some research into this API, I learned it could be tricky to work with – simple queries can be complex, and extensive boilerplate code was required for basic procedures. As a result, I discovered Dixie, a popular library for the IndexedDB that provides an elegant and simple API.

With the Dexie library, setting up a database can be accomplished in a few code lines.

Figure 8 Importing Dexie

Initial State

The initial state of the postsSlice follows the same pattern as the locationSlice. Of course, in this case, it contains the posts property, which is an empty array that will hold post objects.

Figure 9 Initial State of postsSlice

Creating Posts

Figure 10 addPost function

The addPost is a thunk that accepts a post data as its argument, creates a new post, and persists it in the database. A unique id is generated with the nanoid function, which comes as part of the Redux ToolKit.

The actions dispatched by this thunk are handled once again using the “builder callback” notation.

What is notable is looking at the “fulfilled” case, we see the posts array is updated with the push method. A general rule is state should never be directly mutated. However, in this case the state isn’t being mutated. The Redux toolkit uses the immer library, which allows me to write code in a mutable fashion that is updated immutably behind the scenes—reducing code complexity.

Figure 11 Reducing code complexity

Retrieving Posts

The getPosts thunk is relatively simple. It retrieves the posts from the database and returns the data. The actions dispatched by this thunk are handled once again using the “builder callback” notation.

Figure 12 getPost functionality

Figure 13 Status and Errors

Updating Posts

Updating data is traditionally a more complex procedure. The updatePost thunk accepts a post object as the argument and extracts the id, name, type and description.

The id is first used to check if the post exists. If the post does not exist, an error is returned that will be handled by “builder callback” notation.

As I only want to allow the user to be able to update the name, type, and description properties. I create a new object, spread the properties of the existing post in the object, and then overwrite the name, type, and descriptions properties with the values passed down as the initial argument.

Figure 14 Update Post Functionality

The builder works almost identically to the previous implementation. The only difference in this scenario is if the post is successfully updated. I find the index of the old post that matches the id of the updated position and replace it with the updated post.

Figure 15 Different cases incuding update functionality

Deleting Posts

Deleting post is also very simple, it uses the id and attempts to delete the post with the following id and if successful it will return the id that will be handled by the builder.

Figure 16 Deleting Posts

In this case the builder will filter the state and remove the post which matches the id of the post that was deleted.

Figure 17 Different cases of Deleting Posts functionality

Selectors

Similar to the locationSlice, there are selectors exported that handle the accessing of the state. Taking a further look, you will see that accessing the posts array is written as `state.posts.posts`.

This is due to the name of the object also being named posts, and it contains the posts object. I could update this in the future by renaming the posts array to “items”. However, as it stands, there is nothing wrong with this approach, and this would only be a semantic improvement.

Figure 18 Selectors for state accessing

2.5. Map

The mapSlice was not originally planned as part of the implementation. I expected that the state of the map would be contained inside a parent component as local state. However, I discovered that the map component's parent was unmounted when switching routes, and the state was lost. This resulted in the map component repositioning each time the user navigates pages, leading to an unpleasant user experience.

As a result, the mapSlice was implemented. It has a simple reducer function that updates the state of the map and a selector so the map component can retrieve the state when it remounts, meaning it will “remember” its original position.

Figure 19 mapSlice creatingSlice

3. Setup

The initial setup of the application begins with adding the store to the application with the Redux Provider. I then added the React Router library to handle routing and navigation. The ColorModeScript component is from the Chakra UI library, and it prevents the flash of white when retrieving the theme data.

Finally, the service worker is registered, which handles the progressive web application features such as caching.

Figure 20 ServiceWorker Registration

On application start up, the useEffect hook will run and dispatch the getLocation and getPost actions from the Redux store.

Figure 21 useEffect with locationStatus dependency

I wanted to create a splash screen that indicated that the content was being loaded to the user (see Appendix A). To achieve I checked the locationStatus and postStatus of the store. While the store is loading, I display the SplashScreen component. Once the loading is completed, the main content is displayed.

Modern devices were extremely quick at loading, which resulted in the splash screen would appear for less than a second, which was a jarring experience. I used the setTimeout function to create an artificial delay to help alleviate this issue.

The Outlet is the dynamic component that is displayed from React Router.

Figure 22 The Outlet Component

The isNestedRoute variable is retrieved from a custom hook I created that uses the React Router API to determine if the browser views a nested route, i.e., “post/new”. This hook allowed me to display components dynamically depending on the route being viewed. Such as showing a back button for each nested route.

Figure 23 isNestedRoute variable is retrieved from a custom hook



发表评论

电子邮件地址不会被公开。 必填项已用*标注