Infinite scroll with a side of Copilot

Ben Prothe - - 4 mins read

One of my new goals for using AI as a development tool is to start trying to use it for more than just enhanced autocomplete, with some important limitations. First, I want to make sure I actually continue to learn as I code. So I don’t want to use Copilot as a starting point. Second, I don’t want to enter a “vibe coding” loop, blindly accepting changes.

I had a good experience with Copilot recently with a self assigned challenge. The goal was to build an implementation of a generic Infinite Scrolling component or set of hooks/utilities using React and Typescript. Eventually I plan on creating my own components library that I can inject into any of my React projects. This work will probably be a part of that package.

What I Did

1. I Started with Brute Force and Inelegant Solutions

I really wanted to make sure that I started organically so that I had control of naming, techniques used around fetching and detection, file structure, etc. Importantly, I felt like it was important to make sure to have intimate knowledge of how the code worked. I researched this part quite a bit before I put digital pen to paper. I still reflexively separated components and did some simple abstractions, but this wasn’t the focus at this stage.

As for AI use, I probably used the Copilot chat a few times in this process to help find my simple errors. Those were things I would have found eventually but I could use Copilot to speed up the process and reduce frustrations. Otherwise, this was an organic implementation.

2. I Used AI to Help Speed Up Abstraction and Refactoring

My next phase was to use very descriptive prompts to guide Copilot to help me refactor my code and make it more reusable. In my prompts I left, only a little room for the AI to ad-lib and decide on solutions.

3. What AI did not do well?

  • Generic typing. Copilot was more than happy to use “any” and “unknown” types to make TypeScript happy. I had to explicitly tell it to replace “any” with generics several times.

  • Inferring design choices. For instance, Copilot did not infer that the “Loading” component should be used when the fetch was in progress. I found it a bit weird but it was an easy fix.

  • Autopilot. If I was not prescriptive and if I got into more of a “vibe coding” workflow, Copilot would get stuck in loops like this: Generate an error => fix it but create a different error => fix the second error but re-create the first error. I was able to fix this if I scrapped everything from that first prompt and actually prescribe a good approach for how to solve the problem.

  • Example: I wanted to enhance an HOC to accommodate a different use case. My initial prompt was simply, “Make the HOC support a use case where the user could change the API request by sending a different query param, thus requiring the display data to reset”. The solution that the AI came up with was an anti-pattern in React where an array prop was spread onto the dependency array of a useEffect inside the HOC. I just backed up and told the chat to use a solution that included a new boolean prop, “shouldResetData”.

4. What did AI do well?

  • Refactoring… but only when I knew what solution I wanted. The second part of that statement is key. I had to know what abstractions I wanted. So, after I got the “brute force” component working I used prompts like the following: “take these logically grouped hooks and extrapolate them into a custom hook” or “take this functionality and turn it into a generic higher order component that can provide infinite scrolling for any kind of child component”. The outcome was fantastic. It generated what I would have coded, but in a few seconds, not hours. If I was more generic it wouldn’t have been as effective.

  • Catching boilerplate that I’ve missed. If my project didn’t load properly I could describe to the chat what I was seeing and it could pick out what I missed; things like infinite useEffect procs… I might know its a useEffect that’s the issue, but Copilot was really quick at identifying which one was the offender.

Conclusion

I am pretty happy with this outcome. I saved lots of time refactoring but I still felt pretty involved with the coding process. I think it’s pretty important to maintain that grounding connection to what the code is doing. I can still see a world where skills in syntax atrophy if the gen-AI user is not aware of that. For that reason,it’s pretty important to me that I continue to write my first drafts manually. I can use AI to help me remember the syntax, but I want to physically type out my code, test it manually, and use my debugging kit as always.