Andrei Calazans

Daily

A place where I write public daily thoughts.

29th September, 2022

Hey, I am on vacation but since I on a writing streak (10 days now) I’m going to try to write my dailies until I run out of ideas.

A little bit about wy we are having issues with Relay’s lack of a retry mechanism.

At work we are trying to improve error handling overall. The goal is to stop showing users full screen errors. While initially you would think that the easy solution is to simply stop having errors, that my friend is not possible.

Services has some expected error rate. I have seen services with a 4% error rate for every request - this forces the client to be obliged to deal with them.

Our strategy to improve error handling is to push it down to the leaf components instead of root screen wrappers.

let me explain the app I am working…

Typically when you load a screen you load all of your data upfront in a single place. However, with React and GraphQL (Relay) it is known to be a better pattern to colocate data requests alongside their consuming components. Thus with a typical GraphQL Relay app, data is not requested within the root of the screen, further with data masking - a Relay feature, the root parent component is not even aware of its child’s data requirements.

a bit more about Relay’s GraphQL pattern…

Why colocate the data requests and use data masking? This decouples the components and makes them easier to be refactored, This sets a limited impact boundary for your components.

back to error handling…

With colocated data requests/requirements you can also handle the loading state and error at the component level, making for instance Screen B to render even if one of its child broke.

Our goal is to handle breakage at each component level, gracefully handling the error and giving the user the ability to retry it.

But, with Relay, today you need to refetch the entire query when a fragment (a child component’s data requirements in GraphQL) fails. This is why I am looking into a better way to retry a fragment query.

28th September, 2022

On a side project I have I decided to use Amplify. A few months in this and I regret my decision. Here is a list of why:

0) Quick to get started, slow to do anything complex

As probably most low code or no-code solutions. It’s quick to get you started, but the more you need to customize it the more it slows you down. Below I list a few problems I ran into with Amplify that will backup this argument.

1) Amplify abstracts away many services within Amazon.

Any issues within these services you need domain knowledge of them to figure it out. Not so bad, but has a high a learning cost. Expect to learn CloudFormation, DynamoDB, Lmabda services, IAM policies, and more.

2) Amplify’s GraphQL layer is limiting

Examples of this include:

  • filtering only works at the parent query and not its nested items;
  • its many-to-many relationships are a bit limited and you will find yourself creating many global secondary indexes (GSI) to make any real world query;
  • also creating and updating GSIs are brittle, you can’t update a GSI, you have to delete it first, fully deploy, then recreate it with your changed attributes;

3) Deploying is slow

With Amplify you have a GraphQL schema that dictates the structure of your database. Changing the schema then publishing it requires you to deploy your services through yarn amplify push which takes about 20–30 minutes.

4) Auth layer doesn’t integrate well with the GraphQL layer

If GraphQL is my querying layer and I want to get user information plus data related to that user you would think this should all come from GraphQL right? Well, for Amplify creators it was not ¯_(ツ)_/¯.

Would I recommend Amplify to anyone? Yes, do you have a clearly constrained case where you need a data store for non-nested data and auth integrated? Amplify could be a solution here because of its comes with an Auth UI out of the box plus some other UI components for easy integration.

ps: Last paragraph it is me being nice.

27th September, 2022

Not very inspired today. I am always curious in ways of improving my Vim experience. Today I found out there is a nice way to edit commands you write within Vim.

The way command mode works in Vim is as follow:

1) You execute some command, let’s say you type :echo hello within Vim.

2) If you want to repeat that command you always type @:.

3) Editing commands in normal mode

But if you want to edit any of the previous command you can type : then press <ctrl>-f to enter “command mode” which puts you in normal mode but with all your previous commands in the buffer. You can edit any of the lines and pressing enter on a line executes the command.

Why do I like this so much?

I typically repeat commands with some variations and having the ability to quickly edit a past command like that with Vim’s normal and insert mode makes this much easier.

Stackoverflow about command mode.

Small rant about Relay

Relay is missing a retry mechanism for its hooks. I will likely implement one. But it surprises me that it doesn’t have one.

26th September, 2022

I’m becoming more and more convinced to rely less on IDE features and instead leverage lower level commands using the terminal. Why do that? because your IDE will always be constrained by its UI, even with Vim/Neovim. In the terminal you can compose different commands and get different results specific to your desire, the limit boundary is way bigger than an UI.

Here are some flows you do in an IDE that you can do with a terminal:

Search for file

find src -type file -name "Root*"

find in directory src a file named Root plus whatever.

Find matches of a pattern

rg -e "Bugsnag\.notify\("

Get a list of all files that has the matching pattern

rg -e "Bugsnag\.notify\(" -c

Find how many total matches a given pattern had

rg -e "Bugsnag\.notify\(" | wc -l

I’ll be reviewing more and more of these commands as I improve my arsenal.

25th September, 2022

A few months ago I integrated Amplify into a Remix.run app and today I saw an example that took a different approach by using AppSync with ApolloGraphQL.

I felt that approach was a bit overkill. The only trick to get Amplify’s withSSRContext working is to properly map Remix’s request object to the one withSSRContext expects, which I do as follow:

const client = withSSRContext({
  req: { headers: { cookie: args.request.headers.get("cookie") } },
});

The above code would be in a Remix loader function.

24th September, 2022

Today I spent some time trying to figure out why Amplify was stuck on this Resource is not in the state stackUpdateComplete error.

Error:

✖ An error occurred when pushing the resources to the cloud
🛑 An error occurred during the push operation: /
["Index: 0 State: {\"deploy\":\"waitingForDeployment\"} Message: Resource is not in the state stackUpdateComplete"]
⚠️ Review the Amplify CLI troubleshooting guide for potential next steps: https://docs.amplify.aws/cli/project/troubleshooting/

At first, without much AWS experience this kind of error is quite bizarre. Well, after a lots of digging you start to learn the following:

1) Amplify automates creation of resources
2) This automation is done by CloudFormation’s stacks
3) The “resource is not in the state stackUpdateComplete” refers to a stack resource in CloudFormation.

After understanding the following I finally started looking in the right place. CloudFormation’s stack display exactly which stack had an error. In my case, it was some DynamoDB table that was stuck.

A common issue with CloudFormation is configuration drift - this is when your automated configs are different from the actual resources because someone changed something through the UI instead of your config code (in my case these configs are managed by Amplify).

The drift issue is so common they have an entire section/UI dedicated to identifying such issues.

Once I got to the drift problem I was able to rapidly identified one of the resources that drifted. But how do I revert this issue?

You can resolve the drift by either:

1) Manually reverting the change you did via UI
2) Updating the CloudFormation’s stack template (I did this via its change set feature).

I chose the second option since I couldn’t find the UI option that triggered the initial change.

Another tip is to look for the actual failing resource within the stacks, you are likely to find a log there that can help. For my case we were having the GSI error issue described here.

23rd September, 2022

In big tech companies, nothing will get built if it moves no key result (OKR). Today I heard someone say I’m not sure we want to prioritize a given task simply because there is no KR tracking given result.


A better useEffect for when you need to react to a state change.

import { useRef } from 'react';

const useOnUpdated = (cb: () => void, dependency: any) => {
  const last = useRef<any>(undefined);

  if (dependency !== last.current) {
    last.current = dependency;
    cb();
  }
};

But why?

Well, useEffect is often called more times than you expect. This hooks adds simplicity and predictability over behavior. Plus, the single dependency is intentional to make sure we are only doing one thing instead of many things - you would prefer multiple useOnUpdated which will result in easier to maitain code.

22nd September, 2022

There are days we spin our wheels and accomplish almost nothing.

Today was a day I had to remind myself how to find specific files and search for certain patterns. Over time I am more and more convinced that it is better to learn the lower level commands on the CLI than to rely on any IDE feature for common search and edit file or file contents.

How do you find an apk file within your directory?

find . -type file -name "*.apk"

How do you find a file that imports a module named “config” which is imported from “mobule-b”?

Turns out “config” is quite a common word and “mobule-b” might be imported 100s of times.

The following can do that for you:
rg -U 'import.*config.*mobule-b'

The trick here is enabling multiline grep with the -U option.

21th September, 2022

Today was a long day. I had a nice opportunity to improve React Native’s horizontal scroll view. I found an edge case where the snap to animation was very slow/sluggish. See PR here.

This was part of yesterday’s work and why I had to run my app by compiling react-native’s source.

I wrote a TIL about git diff’s file filtering feature.

20th September, 2022

Today I had to patch react-native’s source code to fix an Android issue.

Turns out by default react-native only compiles its native code when the new Fabric architecture is enabled else it uses a prebuilt lib.

To turn on compilation of react-native’s source code you need to remove the following if checks in build.gradle and settings.gradle:

M src/packages/app/android/app/build.gradle
@@ -423,7 +423,7 @@ dependencies {
     implementation 'com.google.android.play:core:1.8.0'
 }
 
-if (isNewArchitectureEnabled()) {
+// if (isNewArchitectureEnabled()) {
     // If new architecture is enabled, we let you build RN from source
     // Otherwise we fallback to a prebuilt .aar bundled in the NPM package.
     // This will be applied to all the imported transtitive dependency.
@@ -437,7 +437,7 @@ if (isNewArchitectureEnabled()) {
             //         .because("On New Architecture we're building Hermes from source")
         }
     }
-}
+// }
 
 apply from: file("$nodeModulesPath/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)
M src/packages/app/android/settings.gradle
@@ -7,11 +7,12 @@ apply from: file("$nodeModulesPath/@react-native-community/cli-platform-android/
 include ':app'
 includeBuild("$nodeModulesPath/react-native-gradle-plugin")
 
+include(":ReactAndroid")
+project(":ReactAndroid").projectDir = file("$nodeModulesPath/react-native/ReactAndroid")
+
 if (settings.hasProperty("newArchEnabled") && settings.newArchEnabled == "true") {
-    include(":ReactAndroid")
-    project(":ReactAndroid").projectDir = file("$nodeModulesPath/react-native/ReactAndroid")
-    include(":ReactAndroid:hermes-engine")
-    project(":ReactAndroid:hermes-engine").projectDir = file("$nodeModulesPath/react-native/ReactAndroid/hermes-engine")
+   include(":ReactAndroid:hermes-engine")
+   project(":ReactAndroid:hermes-engine").projectDir = file("$nodeModulesPath/react-native/ReactAndroid/hermes-engine")
 }

Once I compiled its source code I could debug it with Android Studio and find where to fix the issue. I’ll post about the issue on another day.

19th September, 2022

Hello world. It was on my TODO list that I was going to get this done. So, I’m done = ).

But, here are some random thoughts:

1) There are no reliable dev environments in the finance industry

I’ve been working in the Crypto space for a year now and this is a huge pain point for us. Researching about the topic I learned it is a shared pain due to the number of external integrations with legacy bank systems. Plus, it is hard to maintain fakers for every API you have.

2) Handling app errors gracefully

This has been a recorrent topic at work. The app grew and an initial pattern implemented in the early days of the app is becoming bad user experience - full screen errors.

Over the years I noticed a few patterns emerge while building apps. The first is fully neglecting errors and letting apps just crash. The second pattern is typically a solution for the first which is a generic error screen that serves as a catch all, this is the stage where the current app I work with is at. And perhaps the last stage is pushing error handling closer to the leaf UI elements that cause them in the UI tree with addition to proper reaction mechanisms such as retries.

In hingsight it feels like if we instead started backwards - that is handling errors only at the leaf nodes before going higher up the UI tree, this could make it easier over time to deliver a better UX.

But why doesn’t that happen?

I don’t know the answer. But, I would think that the clear lack of paved road in this area makes for less experienced developers to simply neglect error handling as a whole.