capstone debugging: learnings

capstone debugging: learnings

The clock on my monitor silently ticked, while I cried in JSX fragments and Spring beans. It was Week 15 and 16- the final stretch of my coding bootcamp and we were tasked with creating a full-stack app. Time was tight from having to research and execute new concepts, and the bugs- oh someone call pest control! 😅

Cartoon of Emotional Gal

Though it was tough, I did end up finishing and learned a bunch in the process. My project was a volunteer-driven maps application that allows users to crowdsource accessibility information as well as explore and search accessible places near them.

While my Github readme shares more in-depth details of my learnings, I wanted to also share a behind the scenes view of 4 bugs I faced and how I debugged them:

Table of Contents

  1. Duplicate Data Seeding

  2. Rating Button Not Showing as Checked

  3. Uncontrolled Component Warning

  4. Conditional API Calls


1. Duplicate Data Seeding

Issue: When seeding data without using a list, duplicate entries were created.
Solution: Created a variable to store the data seeder return and referenced this variable in other functions. This prevented duplication when checked in Postman.

To ensure the app had places loaded along with user reviews (to simulate a database of Maps places) and feature tags, a data seeding mechanism was used. 🌱. My approach at the beginning was to call the return of the entity seeder class. For example, in my reviewSeeder, I called userSeeder.seedUsers(), thinking that would just get me the return value of the initial list of 10 seeded users. Instead, I ended up with duplicates—Postman showed 20 users instead of 10 😬. When checking that list, I noticed that the usernames repeated twice, i.e. user1 appeared twice with id of 1 and 11.

After an hours-long trip down a rabbit hole, I realized that userSeeder.seedUsers() appeared to invoke the seeder function again instead of just returning the initial seeded list.
To fix this, I created a variable to hold the data seeder’s return value and referenced this variable in subsequent functions. This change effectively prevented duplication, confirmed by rechecking in Postman.

 public void run(String... args) throws Exception { List<User> seededUsers= userSeeder.seedUsers(); // Seed users first List<FeatureTag> seededTags = tagSeeder.seedTags(); // Then, seed tags first List<Place> seededPlaces = placeSeeder.seedPlaces(seededTags); // Pass seeded tags to places reviewSeeder.seedReviews(seededPlaces, seededUsers); // Pass seeded places and tags to reviews (because reviews can only exist with a place and places have tags) }
Enter fullscreen mode
Exit fullscreen mode

2. Rating Button Not Showing as Checked

Issue: **The rating button wasn’t properly working or showing as checked upon user selection.
**Solution:
Used the checked attribute to control the selected radio button based on the component’s state, as well as corrected mapping logic.

When adding a review, users are also prompted to rate the accessibility of the place from 1-5. Getting the rating buttons to display as checked was another challenge ⭐.

Kudos to my instructor who worked through with me during office hours.
Firstly, we fixed the mapping logic. The way rating radio buttons are generated is to take the array of ratings [1, 2, 3,4,5] and then map through them.

{[1, 2, 3, 4, 5].map((value) => { ... })}
Enter fullscreen mode
Exit fullscreen mode

A map typically takes 2 parameters: the value (current element of the array being processed and the index of the current element in the array. In this case, (value) => { ... } is an anonymous function that takes value as its parameter. and it is saying for each rating number, we want to have it be a radio button.

{[1, 2, 3, 4, 5].map((value) => { return ( // return a radio button for each number in the array <Form.Check key={value} type="radio" label={value} // set label text for radio button name="rating" value={value} // set value attribute to the current value we are mapping over checked={formData.rating === value.toString()} onChange={handleChange} />
)
Enter fullscreen mode
Exit fullscreen mode

After that, I was able to confirm that yes, the value of the user’s selection was read by using a console.log and confirming the value was read on click. However, the button still appeared as unchecked, and that can be confusing to the end user.

We researched and learned that the checked attribute is what helps React determine which button to select when the form renders. This meant that there was an issue with how we were defining the statement in checked. The values in the bracket had to evaluate to true and we were comparing formData.rating to value (which was the value of the radio button generated from our mapping).

We confirmed that this comparison had to evaluate to true as we wrote checked = {false}; the formData.rating value was read on the console, but the button was not checked – which proves that when the comparison is false, a check will not appear visually in our UI.

Therefore, we dug a bit further into how we were getting those values and comparing them.

formData.rating is set using the handleChange which sets the rating value when the user clicks on a radio button. (Essentially, the function looks at event.target.name aka fields that triggered the change, and gets its value and sets it to form data.

const handleChange = (event) => { const { name, value } = event.target; // destructures the event.target with the keys in brackets // this way, we can use `name` and `value` variables vs `event.target.name, event.target.value setFormData((prevFormData) => ({ ...prevFormData, //takes the form data and makes copy of it [name]: value, //gets value for fields that triggered the change and sets it to form data. }));
};
Enter fullscreen mode
Exit fullscreen mode

We ran a console.log to compare formData.rating and value. In the end, we saw the issue was a type mismatch. After researching and seeing a suggested toString method online, we used that with our own code. formData.rating === value.toString() generated true and the check was now appearing on the UI. ✅

We could also verify this with the console.log. You can see when the user clicks 2.
Line 88 is formData.rating and Line 89 is value.toString(). You can see 5 lines appear – which is from our mapping of the 5 ratings, and for each it checks to see if the value we are mapping over from the array is equal to the user’s selection. When it is mapped over 2, that matches what the user selected, so the check appears visually in the UI.

Rating Console Log

3. Uncontrolled Component Warning

Issue: Input fields were locked because they were directly bound to userData.
Solution: Made a copy of userData to allow edits and saved changes on submit. This prevented the form from locking while enabling updates.

When designing the Edit Account page, I wanted data from My Account (which was retrieved from a GET mapping call to also populate on Edit Account). I used the UserData context provider in React to carry over those values. While the information did port over correctly to Edit Account, the input fields were locked 🔒, preventing edits.

Shoutout to my mentor who helped me to battle this bug. The console showed an error of “uncontrolled component warning.” We learned this error is when the state of a component is not being controlled by React itself, aka React doesn’t have complete control over the Edit Account form’s input fields. Yes, React is a control freak. 😉

Fields were directly bound to userData (which was set from that aforementioned API call on My Account). This resulted in the fields being “locked” and preventing any edits. This also means that when I was trying to edit the input fields, I was essentially trying to edit the original userData. React doesn’t allow direct changes to props because they are supposed to be immutable. So, trying to edit the input fields directly would essentially be trying to modify immutable data, which React won’t allow.

Also, when an input field is directly bound to a piece of data- in this case userData, React cannot fully control the state of that input field.

We resolved it by creating a copy of userData, allowing modifications without altering the original until submission.

 const [formData, setFormData] = useState({ ...userData });
Enter fullscreen mode
Exit fullscreen mode

formData is a variable that refers to that copy of userData (with its key-value pairs of data details), so in our form fields, we can use the dot notation of value={formData.email}.

This also fixed the uncontrolled component warning, ensuring form fields were populated with the initial userData values but remained editable. Upon submitting, changes were saved back to the original user data with a PUT request, ensuring a smooth and functional user experience.

Finally, the user is redirected back to My Account after a successful PUT call, and that is where GET mapping happens to retrieve the user info and set it to the userData context provider- ensuring both the backend and the frontend context providers’ values are updated 💾.

4. Conditional API Calls

Issue: Fetch API calls wouldn’t populate with data on the frontend
Solution: Ensured API call was made only if the username was truthy, triggering the call once the username was available.

A peculiar issue with populating data from fetch API arose 🚧. The first time I noticed this was when trying to get My Account details to populate by username. My API call required the username value.

 const responseData = await fetchData(`users?username=${username}`);
Enter fullscreen mode
Exit fullscreen mode

I started with using local storage to store the username upon a successful sign in, but the API call to get account details would not return anything. I even did a console.log to ensure the username was being correctly read. The instructor gave a hint on how local storage can be slow to load. Thus, I then tested using a username context provider to pass the value – thinking this would resolve it. Still, there was no luck in rendering the API call’s return.

To prove that it was not an issue with the system reading the username value, I even tried to hardcode a username value of const username = user1 before the API call, and that worked. Something else was brewing.

As I initially had an address entity linking to user, I tried to change the dot notation format to users.address, users. , address, etc- all to no avail either. I then thought perhaps the address entity was giving me issues due to how it was set up on Spring Boot with the one-to-one cascade, so I commented it out to see if I could at least get the user information to populate. It did!
When I uncommented out address, then the address would populate. I tested this a few times with mixed results, and noticed I had to wait a bit to uncomment out address for both the user and address to display. This gave me another hint, that perhaps we had to wait for the user information to populate.

A few hours later, what I learned is that given that API calls are asynchronous, there was a possibility that the data might not be available at the time of the call. Also, local storage and context providers are asynchronous too. That means JavaScript won’t wait for the local storage or context operation to finish before continuing to execute the API calls.

That means that when we attempted to fetch “My Account” details based on the username, there was no guarantee that the username would be available immediately. It could take some time for the local storage to be accessed and the username to be retrieved.

By implementing a conditional API call that triggered only if the username was truthy, I ensured the address field was populated correctly. This method checked if the username was available before making the API call, allowing the address data to load appropriately. This method highlighted the importance of conditional logic in ensuring seamless data fetching and rendering in the UI 🎉

useEffect(() => { if (username) { fetchUserData(username); }
}, [username]);
Enter fullscreen mode
Exit fullscreen mode

Discover more from Coursity

Subscribe to get the latest posts to your email.

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *