One of my projects for Groove was to upgrade React Native and all dependencies to their latest versions. In our case we were upgrading from React Native 0.67.x to 0.71 and like most projects of this nature that presented quite a few unexpected gotchas along the way. Here I’ll outline some that may be most relevant to other projects, including a few React Native bugs that are still unresolved as of this writing.

Android: Poor performance when scrolling an inverted VirtualizedList (FlatList, SectionList, etc.)

Bug report 1Bug report 2

Scrolling an inverted list on Android can result in slow performance or an unresponsive UI. This is a longstanding bug when building against Android API 33+.

Status: Unresolved as of React Native 0.72.3. A fix was merged but then reverted. A permanent fix appears to still be in development.

Workaround: We had success using patch-package to tweak the styles applied by React Native as described in this comment. You may run into issues with this if you rely on onRefresh or refreshControl.

iOS: Multiline TextInput onKeyPress receives unexpected Backspace events

Bug report

The onKeyPress callback handler will sometimes receive Backspace events when the key was not pressed. What’s interesting here is that this issue manifests itself differently in iOS 15 vs 16:

  • iOS 15: The Backspace event is received after value is reset (such as to an empty string)
  • iOS 16: The Backspace event is received on EVERY change AND when value is reset

Status: Not fixed as of 0.72.3

Workaround: I was unable to find any workarounds for this so you may need to remove usage until a fix is available.

iOS: Multiline TextInput onChangeText will not fire on first character after clearing

Bug report

Say you have a TextInput component that is regularly cleared after some interaction, such as in a chat interface. In this case, the first character typed after clearing will not fire the onChangeText handler. This happens no matter how the value is cleared: whether by changing a reactive value prop OR via the clear() method.

Status: Fix released in 0.72.3 but has not been backported to 0.71.x

Workaround: This issue was introduced in React Native 0.71.8 so sticking to a previous version offers a temporary workaround if an upgrade to 0.72.3 isn’t possible.

iOS: react-native-reanimated 2.x will not build on 0.72+

Bug report

C++ dialect incompatibilities cause a build failure on React Native 0.72+. This was a problem for us because we were stuck on react-native-reanimated 2.x because of a dependency on react-native-vision-camera.

Here’s the build log:

❌  /Users/jeff/Projects/groove/deps/react-native-app/RNReanimated2/ios/Pods/Headers/Public/React-cxxreact/cxxreact/NativeModule.h:28:26: no template named 'optional' in namespace 'std'; did you mean 'folly::Optional'?

using MethodCallResult = std::optional<folly::dynamic>;
                                  ^
❌  /Users/jeff/Projects/groove/deps/react-native-app/RNReanimated2/ios/Pods/Headers/Public/React-cxxreact/cxxreact/ModuleRegistry.h:50:3: no template named 'optional' in namespace 'std'; did you mean 'folly::Optional'?

  std::optional<ModuleConfig> getConfig(const std::string &name);
      ^
❌  /Users/jeff/Projects/groove/deps/react-native-app/RNReanimated2/ios/Pods/Headers/Public/React-jsiexecutor/jsireact/JSINativeModules.h:30:3: no template named 'optional' in namespace 'std'; did you mean 'folly::Optional'?

  std::optional<jsi::Function> m_genNativeModuleJS;
      ^
❌  /Users/jeff/Projects/groove/deps/react-native-app/RNReanimated2/ios/Pods/Headers/Public/React-jsiexecutor/jsireact/JSINativeModules.h:34:3: no template named 'optional' in namespace 'std'; did you mean 'folly::Optional'?

  std::optional<jsi::Object> createModule(
      ^
❌  /Users/jeff/Projects/groove/deps/react-native-app/RNReanimated2/ios/Pods/Headers/Public/React-jsiexecutor/jsireact/JSIExecutor.h:131:3: no template named 'optional' in namespace 'std'; did you mean 'folly::Optional'?

  std::optional<jsi::Function> callFunctionReturnFlushedQueue_;
      ^
❌  /Users/jeff/Projects/groove/deps/react-native-app/RNReanimated2/ios/Pods/Headers/Public/React-jsiexecutor/jsireact/JSIExecutor.h:132:3: no template named 'optional' in namespace 'std'; did you mean 'folly::Optional'?

  std::optional<jsi::Function> invokeCallbackAndReturnFlushedQueue_;
      ^
❌  /Users/jeff/Projects/groove/deps/react-native-app/RNReanimated2/ios/Pods/Headers/Public/React-jsiexecutor/jsireact/JSIExecutor.h:133:3: no template named 'optional' in namespace 'std'; did you mean 'folly::Optional'?

  std::optional<jsi::Function> flushedQueue_;
      ^

Status: Not fixed as of 2.17.0

Workaround: Changing the C++ dialect setting to match the new React Native c++17 default may resolve the issue (mixed results).

One of the great features of Astro are Components: lightweight pieces of reusable site content. Other frameworks call these partials or fragments. One example of a component on this very site is <ContactButton /> which you can see on the sidebar (or menu on mobile).

While building <ContactButton />, I wanted the ability to alter its appearance directly via class props and have them merged with the component’s built-in class. Unfortunately the Astro Component docs don’t make it obvious how to do this but I feel like I landed on the “right way” through a little bit of trial and error.

Consider this basic start to <ContactButton />:

---
import { CONTACT_URL } from "../config";

interface Props {
  text?: string;
}

const { text = "Get in touch" } = Astro.props;
---

<a class="btn btn-primary" href={CONTACT_URL}>{text}</a>

Our only prop is the button text so usage is pretty straightforward:

---
import ContactButton from "../components/ContactButton.astro";
---

<ContactButton text="Click me!" />

Now let’s experiment and see what happens when we provide class to our component:

<ContactButton class="btn-lg" text="Click me!" />

Here’s the result:

<a class="btn btn-primary" href="https://example.com/contact">Click me!</a>

As you can see our class prop was ignored entirely so it looks like we’ll have to manually merge class values within the component rendering script. Our first instinct may be to just use string concatenation to accomplish this:

---
import { CONTACT_URL } from "../config";

interface Props {
  text?: string;
  class?: string;
}

const { text = "Get in touch", class: className } = Astro.props;
---

<a class=`btn btn-primary ${className}` href={CONTACT_URL}>{text}</a>

That works but presents several problems, including the risk of inadvertently having duplicate class names in our final rendering. Thankfully Astro provides a better way to handle this via the class:list directive. Let’s use this to refactor:

---
//...
---

<a class:list={["btn", "btn-primary", className]} href={CONTACT_URL}>{text}</a>

The class:list directive gives us a result with everything we want: automatic de-duplication of class names as well as more flexibility over our input values. We can pass classes as a string, Array, or Set. We can even pass an object of class names as keys and use the object’s values to dynamically include or exclude certain classes.