
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! 😅
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 Githubreadme 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
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 myreviewSeeder
, I calleduserSeeder.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 withid
of 1 and 11.
After an hours-long trip down a rabbit hole, I realized thatuserSeeder.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) }
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) => { ... })}
Amap
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} />)
After that, I was able to confirm that yes, the value of the user’s selection was read by using aconsole.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 thechecked
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 inchecked.
The values in the bracket had to evaluate to true and we were comparingformData.rating
tovalue
(which was the value of the radio button generated from our mapping).
We confirmed that this comparison had to evaluate to true as we wrotechecked = {false}
; the formData.rating value was read on the console, but the button was not checked - which proves that when the comparison isfalse
, 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 thehandleChange
which sets the rating value when the user clicks on a radio button. (Essentially, the function looks atevent.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. }));};
We ran aconsole.log
to compareformData.rating
andvalue.
In the end, we saw the issue was a type mismatch. After researching and seeing a suggestedtoString
method online, we used that with our own code.formData.rating === value.toString()
generatedtrue
and the check was now appearing on the UI. ✅
We could also verify this with theconsole.log
. You can see when the user clicks 2.
Line 88 isformData.rating
and Line 89 isvalue.toString()
. You can see 5 lines appear - which is from our mapping of the 5 ratings, and for each it checks to see if thevalue
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.
3. Uncontrolled Component Warning
Issue: Input fields were locked because they were directly bound touserData
.
Solution: Made a copy ofuserData
to allow edits and saved changes on submit. This prevented the form from locking while enabling updates.
When designing theEdit Account
page, I wanted data fromMy Account
(which was retrieved from aGET
mapping call to also populate onEdit Account
). I used theUserData
context provider in React to carry over those values. While the information did port over correctly toEdit 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 theEdit Account
form’s input fields. Yes, React is a control freak. 😉
Fields were directly bound touserData
(which was set from that aforementioned API call onMy 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 caseuserData
, React cannot fully control the state of that input field.
We resolved it by creating a copy ofuserData
, allowing modifications without altering the original until submission.
const [formData, setFormData] = useState({ ...userData });
formData
is a variable that refers to that copy ofuserData
(with its key-value pairs of data details), so in our form fields, we can use the dot notation ofvalue={formData.email}
.
This also fixed the uncontrolled component warning, ensuring form fields were populated with the initialuserData
values but remained editable. Upon submitting, changes were saved back to the original user data with aPUT
request, ensuring a smooth and functional user experience.
Finally, the user is redirected back toMy Account
after a successfulPUT
call, and that is whereGET
mapping happens to retrieve the user info and set it to theuserData
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 theusername
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 getMy Account
details to populate by username. My API call required theusername
value.
const responseData = await fetchData(`users?username=${username}`);
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 aconsole.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 ausername
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 theusername
value, I even tried to hardcode ausername
value of const username =user1
before the API call, and that worked. Something else was brewing.
As I initially had anaddress
entity linking touser
, I tried to change the dot notation format tousers.address
,users.
,address
, etc- all to no avail either. I then thought perhaps theaddress
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 theuser
information to populate. It did!
When I uncommented outaddress
, then theaddress
would populate. I tested this a few times with mixed results, and noticed I had to wait a bit to uncomment outaddress
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 theusername
, 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]);
Top comments(0)
For further actions, you may consider blocking this person and/orreporting abuse