Skip to main content

13 posts tagged with "thoughts"

View All Tags

On Picking The Right Tool

· 5 min read

Explaining to someone without a technical background can be challenging. That challenge is compounded by the creative names developers give to their products. If we are not careful, people may get confused when terms like Go, React, or Flutter slide into our conversations without explanations. The fact that we have so many software products around, sometimes solving the same issue in a slightly different way, has always raised a question in my mind. We can see how Occam's razor can be applied to benefit developers who bear the cost of deciding and picking the right tool for the right job. Why do we continue to have more software doing similar things and add to the complexity?

Of course, I can always defer the argument by citing that things exist for a reason. Bearing in mind the why, the sharing I attended (on Cross Platform Application Development) last night was insightful on the practical considerations when selecting a desirable framework for the job. I experienced a similar conundrum when I was settling on a framework for mobile development. Similar choices between React Native and Flutter. My answer back then: Flutter. I will explain some of my considerations (or the lack thereof) with what was mentioned by the speakers.

First Look

As a student, I like the fact that we do not always have to consider aspects of software durability or robustness when choosing something to learn or use in a side project. When picking a new tool or framework, the first thing that I would do is to take stock of what tools I already know. Though not entirely accurate, that would have given me a rough idea of how well-received and how much support for that technology online. One thing that I learned from the talk is to look at more data points such as Github stars and issue counts. As an example, React-native has 97.5k stars, and Flutter has 129k stars. The popularity of Flutter was one reason why I decided to take it up. Another aspect of popularity is the issue count, which I am still ambivalent about what it portrays. React-native has over 1k issues when Futter has over 5k issues. While I understand that more users might mean more complaints, other factors might also affect this metric. Whether the maintainers of these frameworks actively listen and patch the software to fix pertinent issues can be crucial when the software plays a huge role as a framework that drives the entire code execution.

Familiar VS Trendy

While the above point explains why I would prefer a trendy framework, I also understand the importance of familiarity (language or otherwise). It is especially true when development velocity and product quality are critical. A constant theme mentioned by the speakers was that developers would like to iterate on their products, frequent and fast. Therefore, choosing a familiar tool that most developers are aware of, will make onboarding and upskilling less a hassle. To illustrate, I only got to know Dart as a language used when working with Flutter and my experience with it is only within the period of mobile development that I have done. During the live coding session last night, the presenter was facing a bug due to the displacement of a variable. Looking at the code, I thought that it might be due to the incorrect assignment of the variable, which was not the case! It was the result of not me being unfamiliar with the tool used. Especially in frameworks with a lot of conventions AND configurations, developers might have to slow down significantly. Choosing something that we get exposed to frequently is a fair bet that we can adopt it fast and have it work for us in a reasonable amount of time.

The Right Job

Mobile applications have an unfair advantage of wider outreach and convenience. The ability for mobile frameworks to work cross-platform is, similarly, the biggest selling point that they have against native toolchains. As the sole developer or part of a small team, the right job often includes maximizing output and reducing maintenance costs. To have one framework that builds for both IOS and Android is a bargain that we cannot ignore. To relate to my example, I chose Flutter also because even if I started developing with the sole focus on IOS, it would be a breeze to account for the minor differences so that it works well on Android. An interesting point that I learned from the Shopee example is that sometimes tools can work together to generate greater yield. I have never thought of situations like isolating the support service into web pages(because of the significant number of custom service personnel that use it). If we can put more emphasis on our target audience and to find ways best benefit them, the user experience and the developer experience can both increase dramatically.

It is fascinating that we say to pick the RIGHT tool for the RIGHT job when there is no clear definition for what is RIGHT. Worse still, what is RIGHT is going to change in no time. My conclusion: do research, try things out, adapt and change when necessary.

P.S. This is repost of my writing assignment for CS3216.

The U in UX

· 3 min read

Unlike solving problems in an exam paper, building a product starts with defining a problem worth solving. Problems can come from pain points that we endure going through our quotidian life. Or, they can be astute observations of obstacles that people around us encounter. Some issues might seem mundane, while others have half-decent, duct tape solutions(a better analogy nowadays is a combination of Excel, Google Docs, and Whatsapp). I will be elaborating on three lessons I have learned in the recent UIUX workshop, in terms of product ideation and validation.

The first key lesson that I learned from the workshop (on Beauty is more than skin deep) is to be wary of bad ideas. We want to build something that stands out from existing competitions. We want mass adoption from human beings who have an innate tendency to prefer stability over change. If the above is true, we need to be able to answer the following: Is the problem that we are describing real? Does the proposed solution solve it? It's easy to see the relevance of the first question. During the workshop, we had a group proposing a product idea to help students find mentors easily. Critical response from the audience was how that product would provide utility to people who avail themselves as mentors. Assuming that the product does help their target users(students), it lacked the consideration of another significant group of involved parties(potential mentors). To go deeper into the above example, the proposed solution might not effectively address an inherent issue at hand: perhaps there's a lack of motivated mentors?

My second learning point is on making a product that offers better solutions. I would like to share an anecdote that resonates with the content of the workshop. I was reading blog posts by Kent C. Dodds (a software engineer and OSS educator) and came across his open-source project named "split-guide". It is a tool to help generate code for his workshops. Quoting the problem and the proposed solution highlighted in the repository's Readme for context:

Problem: Managing workshop repositories. A great way to do a workshop repo is to have exercises and exercises-final directories. The problem is keeping these two directories in sync. Solution: This allows you to create a template for both of these in a templates directory. Based on special text in these files, you can specify what parts of the file you want in exercises and exercises-final. This allows you to co-locate what you're going to be showing workshop attendees and what the final answer should be.

Having done many coding tutorials, I can see why managing workshop repositories can be a pain. However, it turned out that "split-guide" is no longer maintained. In the latest Pull Request (https://github.com/kentcdodds/split-guide/pull/24), Kent remarked that he thought the solution was neat but complex. The better solution/alternative that Kent settled for? "copy/paste/modify". This is such an apt illustration of how a solution that has more friction can go south.

The last point is on the importance of user validation. A surprising fact that I got out of the UX critique session is how much we can gain from iterations with a simple design and a group of target users poking at it. Feedback from users can guide us in making design choices that meet the eye and lead users down the intended happy path. One of the more striking comments that I remembered from the session was that users might not appreciate the sophisticated algorithms underneath if they are not motivated to use the product.

P.S. This is repost of my writing assignment for CS3216.

Updating State With useState Hook

· 5 min read

Motivation

The useState hook is a convenient method to provide a temporary component state. It is very common to invoke the useState hook for added interactivity of a button or other visual components. There are 3 main concerns when using the useState hook.

  • Is the state necessary?
  • Is the state located at the right level in the component tree?
  • Is the state updated correctly?

The first 2 questions can be answered with some considerations on the choice of state management solutions and whether to use composition vs inheritance. They are somewhat discussed here in Composition vs Inheritance , Lifting State up and Thinking in React. I would like to share a little bit on updating the state properly (which I failed to do so in a new feature that I was implementing).

Encounter

I was adding a search + sort feature to display a list of quizzes in my side project done in Next.js (which is practically React). To keep track of the search input and the sorting option, I used two useState hooks. I have extracted out the related piece of code below, style related classes and components are left out for brevity.

In gist :

  • QuizList component receives a list of quizzes
  • query keeps track of the search input
  • sortBy keeps track of the sorting option
  • filteredQuizzes keeps track of the manipulated copy of quizzes to be displayed
const QuizList = ({ quizzes }: { quizzes: Quiz[] }): JSX.Element => {
const [query, setQuery] = useState('')
const [sortBy, setSortBy] = useState('title')
const [filteredQuizzes, setFilteredQuizzes] = useState<Quiz[]>([])
useEffect(() => {
let result = quizzes
if (sortBy === 'title') {
result.sort((a, b) => a.title.localeCompare(b.title))
} else {
result.sort((a, b) => a.week - b.week)
}
if (query.trim() === '') {
setFilteredQuizzes(result)
} else {
setFilteredQuizzes(
result.filter((quiz) => quiz.title.toLowerCase().includes(query.toLowerCase()))
)
}
}
}, [query, quizzes, sortBy])
return (
<div>
<Search query={query} setQuery={setQuery} />
<RadioGroup onChange={setSortBy} value={sortBy}>
<Stack direction="row">
<span>Sort by:</span>
<Radio value="title">Title</Radio>
<Radio value="week">Week</Radio>
</Stack>
</RadioGroup>
<div>
{filteredQuizzes.map((quiz) => {
return <QuizItemCard key={quiz.id} quiz={quiz} />
})}
</div>
</div>
)

This is how it looks like: before

I must say that the few times I decided to violate the principle of immutability and start my code by declaring variables with let instead of const, they always surprised me with a hidden bug. The above code seemed to work but there was a strange lag when I toggle between the options for sorting by 'Title' vs sorting by 'Week'. In fact, the sorting seemed to be erroneous.

The logic of the code is as follows:

  • check the sorting option, if it is sort by title, sort the list of quizzes in place with a comparison on the titles. Else, sort the list by the week attribute of each quiz
  • then check for search input and keep only the ones that include the search input

I suspected that the inconsistent and delayed sorting behavior was due to the mutation of quiz list in place and the wrong use of setFilteredQuizzes. Coincidentally, the article that I planned to write this week was related to the official React.js FAQ and reading its section on Component State gave me an idea on how to fix the state update.

Fix

As mentioned in the Component State section of the FAQ, setState operations are not immediately invoked and inappropriate usage will result in unintended consequences. Quoting an example code snippet directly from the document:

incrementCount() {
this.setState((state) => {
// Important: read `state` instead of `this.state` when updating.
return {count: state.count + 1}
});
}

handleSomething() {
// Let's say `this.state.count` starts at 0.
this.incrementCount();
this.incrementCount();
this.incrementCount();

// If you read `this.state.count` now, it would still be 0.
// But when React re-renders the component, it will be 3.
}

Because of the above fact, it helps to know that the setState operation can accept either the updated state or a update function that will take in the previous state and return the updated state.

Thus my solution is simple: use the spread operator to make a copy of the list, sort it and return it within an update function. By making updates within the update functions, the latest updated list will be used whenever filteredQuizzes is referenced. Other alternative solutions include updating the filteredQuizzes in event handlers of the sorting radio buttons instead of keeping track of the sorting state.

The fixed version looks like this: after

And the code as follows:

const QuizList = ({ quizzes }: { quizzes: Quiz[] }): JSX.Element => {
const [query, setQuery] = useState('')
const [sortBy, setSortBy] = useState('title')
const [filteredQuizzes, setFilteredQuizzes] = useState<Quiz[]>([])
useEffect(() => {
if (sortBy === 'title') {
setFilteredQuizzes(() => [...quizzes].sort((a, b) => a.title.localeCompare(b.title)))
} else {
setFilteredQuizzes(() => [...quizzes].sort((a, b) => a.week - b.week))
)
}
if (query.trim() === '') {
setFilteredQuizzes((filteredQuizzes) => filteredQuizzes)
} else {
setFilteredQuizzes((filteredQuizzes) =>
filteredQuizzes.filter((quiz) => quiz.title.toLowerCase().includes(query.toLowerCase()))
)
}
}, [query, quizzes, sortBy])
return (
<div>
<Search query={query} setQuery={setQuery} />
<RadioGroup onChange={setSortBy} value={sortBy}>
<Stack direction="row">
<span>Sort by:</span>
<Radio value="title">Title</Radio>
<Radio value="week">Week</Radio>
</Stack>
</RadioGroup>
<div>
{filteredQuizzes.map((quiz) => {
return <QuizItemCard key={quiz.id} quiz={quiz} />
})}
</div>
</div>
)

Conclusion

As someone who is guilty of being lazy, my initial response to the above bug after some experimentation was to remove the sorting option and just sort the quizzes coming in. Due to the fact that I set out to write this article, and that I was inspired by Kent C. Dodds to read the React FAQ, I avoided taking the easy way out and spent some more time thinking about this little problem.

I guess the moral of the story is:

  • Don't always take the easy way out
  • Give problems a second thought
  • Start writing articles