<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:webfeeds="http://webfeeds.org/rss/1.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Brains & Beards Insights]]></title><description><![CDATA[Brains & Beards is an unpretentious mobile studio that solves business problems through a mix of design and technology.]]></description><link>https://brainsandbeards.com</link><image><url>https://brainsandbeards.com/favicon.ico</url><title>Brains &amp; Beards Insights</title><link>https://brainsandbeards.com</link></image><generator>GatsbyJS</generator><lastBuildDate>Tue, 09 Sep 2025 04:56:44 GMT</lastBuildDate><atom:link href="https://brainsandbeards.com/blog/feed.xml" rel="self" type="application/rss+xml"/><category><![CDATA[Technology]]></category><category><![CDATA[Programming]]></category><webfeeds:logo>https://brainsandbeards.com/favicon.ico</webfeeds:logo><webfeeds:cover image="https://brainsandbeards.com/static/a0fa7ff82c8900c764ad1d0678a8d1de/b8661/hero-bicycle.png"/><webfeeds:accentColor>FFDE1A</webfeeds:accentColor><item><title><![CDATA[Building a Reusable Animated Button in React Native]]></title><description><![CDATA[Why Build a Custom Button? When building apps in React Native having a consistent and polished button component is crucial for enhancing the…]]></description><link>https://brainsandbeards.com/blog/2025-buttons/</link><guid isPermaLink="false">https://brainsandbeards.com/blog/2025-buttons/</guid><dc:creator><![CDATA[Szymon Koper]]></dc:creator><pubDate>Tue, 09 Sep 2025 16:00:00 GMT</pubDate><enclosure url="https://brainsandbeards.com/static/4ef7bc9b5075ce6b12a4de13ecb5269d/d50c0/header.jpg" length="263691" type="image/jpg"/><media:content>
## Why Build a Custom Button?

When building apps in React Native having a consistent and polished button component is crucial for enhancing the user experience. While there are many UI libraries available, building your own button component gives you complete control over its behavior, animations, and styling.

![A collection of different button states and animations in a mobile app](./1states.png &quot;Different states of our NiceButton component: default, pressed, disabled, and loading&quot;)

In this post, we&apos;ll create a reusable `NiceButton` component that:
- provides smooth press feedback with scale and opacity animations
- supports multiple states (active, disabled, loading)
- displays a loading spinner next to the text
- uses React Native&apos;s built-in components and Reanimated for animations
- is easy to customize and maintain

## Building from Scratch

Let&apos;s start by creating a basic button component using React Native&apos;s `Pressable`. The `Pressable` component is perfect for our needs as it provides more control over touch interactions than the basic `TouchableOpacity`.

![Basic button implementation with default styling](./2default.png &quot;Basic button with default styling and no animations&quot;)

```tsx
import { Pressable, StyleSheet, Text, View } from &apos;react-native&apos;;

interface NiceButtonProps {
  onPress?: () =&gt; void;
  title: string;
  icon?: React.ReactNode;
}

const NiceButton = ({ onPress, title, icon }: NiceButtonProps) =&gt; {
  return (
    &lt;Pressable onPress={onPress}&gt;
      &lt;View style={[styles.button, styles.buttonContent]}&gt;
        {icon &amp;&amp; icon}
        &lt;Text style={styles.text}&gt;{title}&lt;/Text&gt;
      &lt;/View&gt;
    &lt;/Pressable&gt;
  );
};

const styles = StyleSheet.create({
  button: {
    paddingVertical: 14,
    paddingHorizontal: 28,
    borderRadius: 8,
    backgroundColor: &apos;#007AFF&apos;,
  },
  buttonContent: {
    flexDirection: &apos;row&apos;,
    justifyContent: &apos;center&apos;,
    alignItems: &apos;center&apos;,
    gap: 12,
  },
  text: {
    fontSize: 16,
    fontWeight: &apos;600&apos;,
    color: &apos;white&apos;,
  },
});
```

This gives us a basic button with a clean, modern look.

## Adding Press Animation

Now, let&apos;s enhance our button with a satisfying press animation using `react-native-reanimated`. We&apos;ll use a `SharedValue` to track the press state and animate both scale and opacity.

&lt;div class=&quot;gif-container&quot;&gt;

![Button press animation showing scale and opacity changes](3press.gif)

Button being pressed with scale and opacity animations.

&lt;/div&gt;

```tsx
import Animated, {
  useAnimatedStyle,
  useSharedValue,
  withSpring,
} from &apos;react-native-reanimated&apos;;

const NiceButton = ({ onPress, title, icon }: NiceButtonProps) =&gt; {
  const pressAnim = useSharedValue(1);

  const animatedStyle = useAnimatedStyle(() =&gt; ({
    transform: [{ scale: pressAnim.value }],
    opacity: pressAnim.value ** 4,
  }));

  const onPressIn = () =&gt; {
    pressAnim.value = withSpring(0.96);
  };

  const onPressOut = () =&gt; {
    pressAnim.value = withSpring(1);
  };

  return (
    &lt;Pressable onPressIn={onPressIn} onPressOut={onPressOut} onPress={onPress}&gt;
      &lt;Animated.View style={[styles.button, styles.buttonContent, animatedStyle]}&gt;
        {icon &amp;&amp; icon}
        &lt;Text style={styles.text}&gt;{title}&lt;/Text&gt;
      &lt;/Animated.View&gt;
    &lt;/Pressable&gt;
  );
};
```

The `pressAnim` value controls both the scale and opacity of the button. We use `withSpring` for a natural, bouncy feel when pressing and releasing.

## Supporting Different States

A good button component should handle different states gracefully. Let&apos;s add support for disabled and loading states.

&lt;div class=&quot;gif-container&quot;&gt;

![Button in different states: default, disabled, and loading](4transition.gif)

Button showing different states with smooth color transitions.

&lt;/div&gt;

```tsx
type ButtonStatus = &apos;default&apos; | &apos;disabled&apos; | &apos;loading&apos;;

interface NiceButtonProps {
  status: ButtonStatus;
  onPress?: () =&gt; void;
  title: string;
  loadingTitle?: string;
  icon?: React.ReactNode;
  loadingIcon?: React.ReactNode;
  style?: any;
}

const NiceButton = ({
  status,
  onPress,
  title,
  loadingTitle,
  icon,
  loadingIcon,
  style
}: NiceButtonProps) =&gt; {
  const pressAnim = useSharedValue(1);
  const stateAnim = useSharedValue(0);

  const animatedStyle = useAnimatedStyle(() =&gt; ({
    transform: [{ scale: pressAnim.value }],
    opacity: pressAnim.value ** 4,
    backgroundColor: interpolateColor(
      stateAnim.value,
      [0, 1],
      [&apos;#007AFF&apos;, &apos;#E5E5EA&apos;]
    ),
  }));

  const textAnimatedStyle = useAnimatedStyle(() =&gt; ({
    color: interpolateColor(
      stateAnim.value,
      [0, 1],
      [&apos;white&apos;, &apos;#8E8E93&apos;]
    ),
  }));

  useEffect(() =&gt; {
    stateAnim.value = withTiming(status === &apos;default&apos; ? 0 : 1, {
      duration: 300,
    });
  }, [status]);

  const isPressable = status === &apos;default&apos;;

  return (
    &lt;Pressable
      onPressIn={isPressable ? onPressIn : undefined}
      onPressOut={isPressable ? onPressOut : undefined}
      onPress={handlePress}
      disabled={!isPressable}
    &gt;
      &lt;Animated.View style={[styles.button, animatedStyle, style]}&gt;
        &lt;Animated.Text style={[styles.text, textAnimatedStyle]}&gt;
          {status === &apos;loading&apos; &amp;&amp; loadingTitle ? loadingTitle : title}
        &lt;/Animated.Text&gt;
      &lt;/Animated.View&gt;
    &lt;/Pressable&gt;
  );
};
```

We use `interpolateColor` to smoothly transition between active and disabled states.

## Adding Loading Animation

For the loading state, we&apos;ll add a spinning animation. We&apos;ll use `withRepeat` and `withTiming` to create a continuous rotation.

&lt;div class=&quot;gif-container&quot;&gt;

![Loading animation with rotating icon](5loading.gif)

Button in loading state with spinning icon animation.

&lt;/div&gt;

```tsx
const NiceButton = ({
  status,
  onPress,
  title,
  loadingTitle,
  icon,
  loadingIcon,
  style
}: NiceButtonProps) =&gt; {
  const rotation = useSharedValue(0);

  useEffect(() =&gt; {
    if (status === &apos;loading&apos;) {
      rotation.value = withRepeat(
        withSequence(
          withTiming(360, {
            duration: 1000,
            easing: Easing.linear,
          })
        ),
        -1
      );
    } else {
      rotation.value = withTiming(0);
    }
  }, [status]);

  const spinnerStyle = useAnimatedStyle(() =&gt; ({
    transform: [{ rotate: `${rotation.value}deg` }],
  }));

  return (
    &lt;Pressable&gt;
      &lt;Animated.View style={[styles.button, animatedStyle, styles.buttonContent, style]}&gt;
        {status === &apos;loading&apos; &amp;&amp; loadingIcon ? (
          &lt;Animated.View style={spinnerStyle}&gt;
            {loadingIcon}
          &lt;/Animated.View&gt;
        ) : icon ? (
          icon
        ) : null}
        &lt;Animated.Text style={[styles.text, textAnimatedStyle]}&gt;
          {status === &apos;loading&apos; &amp;&amp; loadingTitle ? loadingTitle : title}
        &lt;/Animated.Text&gt;
      &lt;/Animated.View&gt;
    &lt;/Pressable&gt;
  );
};
```

The rotation animation will be applied to any icon we pass as `loadingIcon`. This gives us flexibility to use any icon library or custom component for the loading state.

## Final Touches

Our `NiceButton` component is now complete with all the essential features. You can try it out in this [Expo Snack](https://snack.expo.dev/@sakydpozrux/buttons).

&lt;div class=&quot;gif-container&quot;&gt;

![All states animated](6final.gif)

Complete button implementation with all states and animations.

&lt;/div&gt;

The component is now ready to be used across your app, providing a consistent and polished user experience. For production use, you might want to consider adding haptic feedback using `react-native-haptic-feedback`.

## Usage Example

Here&apos;s how to use the `NiceButton` in your app:

```tsx
export default function HomeScreen() {
  const [status, setStatus] = useState&lt;ButtonStatus&gt;(&apos;default&apos;);

  return (
    &lt;View style={styles.container}&gt;
      &lt;NiceButton
        status={status}
        onPress={() =&gt; console.log(&apos;Button pressed!&apos;)}
        title=&quot;Nice Button&quot;
      /&gt;
      &lt;NiceButton
        status={status}
        onPress={() =&gt; console.log(&apos;Save pressed!&apos;)}
        title=&quot;Save&quot;
        loadingTitle=&quot;Saving...&quot;
        icon={&lt;Feather name=&quot;save&quot; size={20} color=&quot;white&quot; /&gt;}
        loadingIcon={&lt;AntDesign name=&quot;loading1&quot; size={20} color=&quot;white&quot; /&gt;}
      /&gt;
      &lt;View style={styles.fullWidthButtonContainer}&gt;
        &lt;NiceButton
          status={status}
          onPress={() =&gt; console.log(&apos;Full Width Button pressed!&apos;)}
          icon={&lt;Feather name=&quot;arrow-right&quot; size={20} color=&quot;white&quot; /&gt;}
          title=&quot;Wide Button&quot;
          style={styles.fullWidthButton}
        /&gt;
      &lt;/View&gt;
    &lt;/View&gt;
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: &apos;center&apos;,
    alignItems: &apos;center&apos;,
    gap: 20,
    paddingHorizontal: 20,
  },
  fullWidthButtonContainer: {
    width: &apos;100%&apos;,
  },
  fullWidthButton: {
    width: &apos;100%&apos;,
  },
});
```

The component is designed to be easily customizable through props and styles. You can extend it further by adding support for different sizes or custom animations. Remember to keep your button styles consistent across the app for the best user experience.

## Conclusion

Building your own button component gives you full control over its behavior and appearance.

The `NiceButton` component we created provides a solid foundation that you can build upon. It handles different states gracefully and provides satisfying feedback to user interactions. Feel free to customize it further to match your app&apos;s design system.

Happy coding! 🚀
</media:content></item><item><title><![CDATA[Problem with large PRs]]></title><description><![CDATA[I don't think many people enjoy reviewing a large PR. But would it really be better to get five smaller ones instead? Let's find out! Proble…]]></description><link>https://brainsandbeards.com/blog/2025-problem-with-large-prs/</link><guid isPermaLink="false">https://brainsandbeards.com/blog/2025-problem-with-large-prs/</guid><dc:creator><![CDATA[Wojciech Ogrodowczyk]]></dc:creator><pubDate>Fri, 27 Jun 2025 14:20:00 GMT</pubDate><enclosure url="https://brainsandbeards.com/static/16fbabbc01909ade9d6dad7b53e263c5/d50c0/never-dull-studio-rESrSVIwR3M-unsplash.jpg" length="330279" type="image/jpg"/><media:content>
I don&apos;t think many people enjoy reviewing a large PR. But would it really be better to get five smaller ones instead? Let&apos;s find out!

## Problems With Large PRs

So what are the issues with big PRs exactly?

### Blackmail

Sometimes in a PR we can see a new programming pattern, or library. If it&apos;s in a small PR, it&apos;s sometimes hard to see the benefit, because the scale of a problem being solved there is also small. But the real risk comes in a large PR, where it&apos;s easier to evaluate, but much harder to reject the change. After all, the whole feature is already working (hopefully!), do we really want to re-write it now in the old way?

I call it a PR blackmail, because the real choice is: do I accept the change as-is, with a controversial pattern, or do we re-write the whole thing, wasting the previous effort? It&apos;s hard to argue for the re-write.

### Negative Reviews

When you&apos;re going through a small change that&apos;s easy to understand, it&apos;s also easy to praise the parts you like. In a big PR however, it takes a lot of brain CPU just to parse it and keep the whole context in memory, so there&apos;s not much resources left for praising any clever or elegant solutions. Instead, we try to save the energy to scan for potential bugs.

That leads to a review result that&apos;s focused on the negatives and it might be rough feedback for the author. It&apos;s never nice to get an email notification about twelve critical comments that have been left on the feature that you spend three days implementing.

### Comment Ping-pong

Nobody likes the PR comment ping-pong - replying to PR comments suggesting vague changes (&quot;I&apos;d implement it differently&quot;), being asked open questions (&quot;how does that work exactly?&quot;), or opinions that are hard to interpret (&quot;I&apos;m not so sure about that line&quot;), etc.

The longer the PR, the more comments it&apos;ll get. The more comments we have, the longer the feedback loop is to respond to them. The longer the loop the more time passes until the PR gets approved. The more time passes, the bigger the chance of merge conflicts. This does not scale linearly with the size of the PR.

### Hard to Understand What It Does

Sometimes you&apos;ll get a PR with a vague description like &quot;implements feature X&quot;. That often means a big homework to find what is &quot;feature X&quot; actually supposed to be doing, dig through the designs to spot the edge cases, make notes and finally, keep it all in mind when reviewing the PR. Obviously, the PR&apos;s author had to do this work himself, wouldn&apos;t it be nice if they shared what exactly they&apos;re implementing?

## How to Deal With Large PRs

There&apos;s a couple of strategies that help us resolve large PRs with less pain and effort.

### Split Necessary Changes

One thing that you probably don&apos;t want to be doing is adding to the length of the change. However, you also don&apos;t want to merge something as-is just because it&apos;d take too long to make it right.

For some changes you could describe them as a separate ticket to be implemented separately in another PR. This way you&apos;re not lowering the quality threshold to get stuff merged. Just make sure it&apos;s handled straight away and not crawls away to die in the deepest dungeons of your backlog.

### Efficient Communication

You want to avoid the comment ping-pong like the plague. One strategy that works for me in bigger PRs is to review it as I&apos;d normally do, leaving comments on anything I find questionable. But instead of preparing some popcorn and sitting down for a round of ping-pong, I&apos;d reach out to the author directly to schedule a pair-programming session as soon as they&apos;re able to familiarise themselves with my feedback. In that session we&apos;d got through the comments one by one, usually resolving them straight away.

Either by implementing the necessary changes on the spot, clearing up any doubts or misunderstandings, or discussing how a particular situation hould be addressed (if it requires a longer session to change, or test the changes). In this approach we basically treat the initial round of comments as a TODO list, or an agenda of topics to address in the pair-programming session.

Remember that you&apos;re on the same team and it&apos;s in everybody&apos;s interest to get (good!) changes merged as quick as possible. Author of the PR has their eyes probably already on the next ticket, while the reviewers have their own work to worry about. It&apos;s not a competition, especially not a tug of war.

## Are Multiple Smaller PRs Better?

I&apos;d say yes. But they must be a result of splitting the work into smaller tasks, so each of them is still a meaningful, easy to describe change that brings value.

They give faster feedback to the author, they result in higher quality reviews, and allow you a smoother flow of delivery.
</media:content></item><item><title><![CDATA[User-Agent for API calls]]></title><description><![CDATA[User-Agent header is an HTTP header intended to identify the user agent responsible for making a given HTTP request. Whereas the character…]]></description><link>https://brainsandbeards.com/blog/2025-useragent-for-api-calls/</link><guid isPermaLink="false">https://brainsandbeards.com/blog/2025-useragent-for-api-calls/</guid><dc:creator><![CDATA[Wojciech Ogrodowczyk]]></dc:creator><pubDate>Fri, 28 Mar 2025 14:20:00 GMT</pubDate><enclosure url="https://brainsandbeards.com/static/bf18fa97cbdebce1d7a714aceb5f880b/d50c0/pop-zebra-NZ-9RqsE06I.jpg" length="386200" type="image/jpg"/><media:content>
&gt; User-Agent header is an HTTP header intended to identify the user agent responsible for making a given HTTP request. Whereas the character sequence User-Agent comprises the name of the header itself, the header value that a given user agent uses to identify itself is colloquially known as its user agent string.

Basically, that&apos;s a header to use to politely introduce yourself. Not as a particular user, but as the software that user is running.

It&apos;s usually ignored by most developers when they&apos;re starting to build a new app and we send whatever the default is. But later down the line it often comes up that it&apos;s actually useful to have some structured data there. If we set it the right way, it&apos;s a low-effort, GDPR-friendly way of gathering basic user stats. GDPR-friendly, because we&apos;re not hooking up any analytics SDK, there are no unique IDs, just basic data about the user&apos;s app version and device, completely anonymous. How low-effort is it, though?

## How Do I Set It Up?

### Generating User Agent String

First thing, all the data that we&apos;ll be sending in this example is stored on the native side, so either you gather it yourself with a native module or (preferred) use something like [react-native-device-info](https://github.com/react-native-device-info/react-native-device-info). Then you just need to query a few parameters, put them together and here&apos;s your own custom user agent string:

```ts
import {
  getApplicationName,
  getDeviceId,
  getManufacturerSync,
  getModel,
  getReadableVersion,
  getSystemVersion,
} from &apos;react-native-device-info&apos;;

import { isAndroid, capitalize } from &apos;./helpers&apos;;

const app = `(${getApplicationName()}/${getReadableVersion()})`;

const device = isAndroid
  ? ` (${capitalize(getManufacturerSync())} ${getModel()}; Android ${getSystemVersion()})`
  : ` (${getDeviceId()}; iOS ${getSystemVersion()})`;

export const userAgent = app + device;
```

This results in something like `(Sleepy Possum App/1.8.9.12) (iPhone14,6; iOS 18.2)` for iOS or `Toaster Repair/1.2.6.66) (Samsung SM-A156B; Android 14)` for Android.

### Sending Header

Now we just need to hook it up to our API requests. For example, when using `axios` you&apos;d set it up this way:

```ts
import {userAgent} from &apos;./userAgent&apos;

const apiClient = axios.create({
  baseURL: `http://example.com&apos;,
  headers: {
    &apos;User-Agent&apos;: userAgent,
  },
});
```

### Profit

The last thing left is to reap the rewards. Now you can collect this data on each API request to the backend and make notes on things like:

* What&apos;s the oldest app version still in use?
* What&apos;s the breakdown of our iOS and Android user base?
* Are our users using recent OS versions?

I hope that was helpful. Have a great day / night / week! 👋
</media:content></item><item><title><![CDATA[FlatList Optimization: Avoid Re-Rendering When Adding or Removing Items]]></title><description><![CDATA[This post is a follow-up to Don't Re-Render All FlatList Items. In the previous article, we optimized FlatList to prevent unnecessary re…]]></description><link>https://brainsandbeards.com/blog/2025-dont-rerender-new-flatlist-items/</link><guid isPermaLink="false">https://brainsandbeards.com/blog/2025-dont-rerender-new-flatlist-items/</guid><dc:creator><![CDATA[Szymon Koper]]></dc:creator><pubDate>Fri, 14 Mar 2025 13:00:00 GMT</pubDate><enclosure url="https://brainsandbeards.com/static/496ca8d1d87308f581697f0e9aa50e84/d50c0/header.jpg" length="283250" type="image/jpg"/><media:content>
This post is a follow-up to [Don&apos;t Re-Render All FlatList Items](/blog/2022-dont-rerender-all-flatlist-items). In the previous article, we optimized FlatList to prevent unnecessary re-renders when updating list item values. This time, we’ll tackle another common issue: avoiding re-renders when adding or removing items.

Many developers believe they need to wrap every FlatList prop in `useCallback` or `useMemo` to optimize performance. But is that really necessary? Let’s find out.

This issue is especially common in apps with infinite scrolling or pagination, where new items are fetched and added to a list dynamically. If your app uses `onEndReached` to fetch new pages from an API, the same principles apply.

## The Problem: Every Item Re-Renders When Adding a New One

Imagine an app that fetches data from the internet and appends new items to a FlatList. Here’s what we&apos;d expect:

- New items render when added
- Existing items do not re-render (since they haven’t changed)

But in many cases, all items re-render when a new one is added!

### Let&apos;s See It in Action

Here’s an [Expo Snack](https://snack.expo.dev/@sakydpozrux/rerender-only-changed-items) where all items re-render when we add a new one. You&apos;ll edit it yourself later!

&lt;div class=&quot;gif-container&quot;&gt;

![Recording of the example app - adding and deleting items](flatlist-app-demo.gif)

An example app where items are added and removed.

&lt;/div&gt;


How to test:
- Open the console logs in your browser
- Click &quot;Add Item&quot; and check the logs
- Notice that every existing item re-renders, even though they haven’t changed!

&lt;div class=&quot;gif-container&quot;&gt;

![Logs before optimization - too many re-renders](flatlist-logs-before.gif)

Here’s what happens in the console when adding items in the unoptimized version.

&lt;/div&gt;

Example console output:
```
Rendering item: 1
Rendering item: 2
Rendering item: 1  &lt;- Why is this rendering again?
Rendering item: 2
Rendering item: 3  &lt;- Only this one is new
Rendering item: 1
Rendering item: 2
Rendering item: 3
```

### Why FlatList Thinks Everything Changed

When we add a new item to the list, we expect only that new item to render. However, FlatList ends up re-rendering all items. Why does this happen? The issue comes from how React handles state updates.

In our app, the FlatList’s `data` prop is an array of `ids`. We&apos;re working with immutable data, which means we don’t modify an existing array to add a new elements. Instead, we create a new array with the updated data. That means that whenever we add a new item, `ids` gets a brand new array reference. Even though the contents of `ids` are almost the same (except for the new item), FlatList sees a completely new array it should render.

### How This Triggers a Full Re-Render

1. The user clicks &quot;Add Item&quot;, triggering a state update.
2. We create a new `ids` array that includes the new item.
3. The ListScreen component re-renders because it subscribes to Redux state.
4. FlatList receives a new value for the `data` prop (even though most items are unchanged).
5. FlatList re-renders all items, because it thinks the whole list has changed.

This isn’t a bug — FlatList is simply reacting to the updated state as intended. Fortunately, we don’t need to change how FlatList works. Instead, we can stop unnecessary re-renders by making a small tweak in how we define our list items.


## The Fix: Just Add React.memo

Here’s the best part: the fix is incredibly simple. Wrap `ListItem` in `React.memo`. That’s it.

Now, `ListItem` will only re-render when its props change. Since it only takes an `id` and they remain the same, old items won’t re-render when a new one is added.

### Console Output After the Fix
```
Rendering item: 1
Rendering item: 2
Rendering item: 3  &lt;- Only new items render now!
```

&lt;div class=&quot;gif-container&quot;&gt;

![Logs after optimization - only new items render](flatlist-logs-after.gif)

After the fix, only new items render as expected.

&lt;/div&gt;

Boom! No more unnecessary re-renders. Now, only new items render when added while existing ones stay untouched. 🎉

### What Does Actually Happen?

`FlatList` still calls `renderItem` for each item, but `React.memo` prevents actual re-renders unless the item’s props changed.


## Do I Still Need useCallback?

Let’s modify the example by adding a timer at the top of the screen that updates every second, showing the current time (`hh:mm:ss`). This change will cause the entire component to re-render every second, which will also re-render every FlatList item unnecessarily.

### Timer Example

```tsx
const [time, setTime] = React.useState(new Date().toLocaleTimeString());
React.useEffect(() =&gt; {
  const interval = setInterval(() =&gt; {
    setTime(new Date().toLocaleTimeString());
  }, 1000);
  return () =&gt; clearInterval(interval);
}, []);
```

This makes all items re-render every second! 🚨

This happens because the parent component re-renders and creates a new instance of `renderItem`. This means `FlatList` receives a new prop and re-renders all items.

### The Fix: Wrapping Callbacks in `useCallback`

To fix this, we need to wrap `renderItem` and `keyExtractor` in `useCallback`. This ensures those functions stay the same (not just in terms of what they _do_, but also in terms of what they _are_) across renders, so no new unnecessary props.

```tsx
keyExtractor={React.useCallback((id) =&gt; id, [])}
renderItem={React.useCallback(({ item: id }) =&gt; &lt;ListItem id={id} /&gt;, [])}
```

Alternatively, we can define them outside the component function or omit `keyExtractor`, since `id =&gt; id` is the default behavior.


### Better Architecture: Separating Components

A better approach is to extract the clock into its own component. This keeps state updates isolated, preventing unnecessary re-renders.

```tsx
const Clock = () =&gt; {
  const [time, setTime] = React.useState(new Date().toLocaleTimeString());
  React.useEffect(() =&gt; {
    const interval = setInterval(() =&gt; {
      setTime(new Date().toLocaleTimeString());
    }, 1000);
    return () =&gt; clearInterval(interval);
  }, []);
  return &lt;Text style={{ fontSize: 18, fontWeight: &quot;bold&quot; }}&gt;Current time: {time}&lt;/Text&gt;;
};
```

By using `&lt;Clock /&gt;` inside `ListScreen`, only the clock updates, keeping FlatList untouched. This is a better architectural choice because it:
- isolates responsibilities (the clock only manages time)
- prevents unnecessary re-renders in unrelated components
- makes code more maintainable

## Note on FlashList

[FlashList](https://shopify.github.io/flash-list/) takes a different approach to optimizing re-renders and some of these techniques might not be necessary. We won’t cover FlashList in this post, but it’s an alternative worth exploring. Let me know if you’d like a separate post about it!

## Final Thoughts

We saw how FlatList re-renders all items when new ones are added even when it&apos;s not neceąry. By making a simple tweak (`React.memo`), we prevent unnecessary re-renders and improve performance with minimal effort.

If you use FlatList in your app, I encourage you to test whether your list items are re-rendering more than necessary. A small optimization can make a huge difference in performance — especially on lower-end devices. 🚀

And if you missed it, check out part 1: [Don&apos;t Re-Render All FlatList Items](/blog/2022-dont-rerender-all-flatlist-items/).
</media:content></item><item><title><![CDATA[Old dependencies are not vulnerabilities]]></title><description><![CDATA[I usually get triggered with posts like that. There are two types of developers. One type is not afraid to upgrade dependencies. Second…]]></description><link>https://brainsandbeards.com/blog/2025-old-deps-are-not-vulns/</link><guid isPermaLink="false">https://brainsandbeards.com/blog/2025-old-deps-are-not-vulns/</guid><dc:creator><![CDATA[Wojciech Ogrodowczyk]]></dc:creator><pubDate>Thu, 06 Feb 2025 14:20:00 GMT</pubDate><enclosure url="https://brainsandbeards.com/static/9b0c1f416d0b4cc5fe777ea0d35fee74/d50c0/clint-patterson-dYEuFB8KQJk-unsplash.jpg" length="284057" type="image/jpg"/><media:content>
I usually get triggered with [posts like that](https://bsky.app/profile/lukaszreszke.bsky.social/post/3lcfquw7q5s27).

&gt; There are two types of developers. One type is not afraid to upgrade dependencies. Second type is not afraid to live with vulnerabilities.

First reason is probably the fact that (anti)social media is pretty useless to give advice of any kind, due to the very limited context we can operate on. Especially such general advice as &quot;udpate your deps or get hacked&quot;. While I can imagine there&apos;s a category of posts that would be both useful AND true AND could fit within a short post, it would probably be limited to a template of &quot;I did this thing and it worked&quot;. Not &quot;you should do this thing&quot;, not &quot;you should not do this other thing&quot; and neither &quot;this thing works&quot;.

Second reason I found this post disturbing was probably the accumulated PTSD of discussing this exact issue with well-meaning, but overworked security teams. Instead of looking at a project&apos;s domain, they look at a language and try to fit it with some kind of code analysis tool that they could run automatically to get a report or number of some kind. That usually leads to an awkward conversation similar to:

&gt; \- Hey, I ran this tool and it says the app you&apos;re building is not secure. Please, fix it ASAP, so that my tool says it&apos;s secure.
&gt;
&gt; \- Sure, but let&apos;s slow down a bit. Could you explain what&apos;s not secure? What are we vulnerable to actually?
&gt;
&gt; \- I don&apos;t have time for that, but the report is all red and it should be mostly green or yellow.
&gt;
&gt; \- Okay, if you don&apos;t have the time then let&apos;s pick this conversation up when you do and in the meantime you can trust us that we know what we&apos;re doing.

That gets repeated every year.

But let&apos;s get back to the topic. Isn&apos;t it true that outdated dependencies leave us open to attacks?

## Are outdated dependencies dangerous?

Important note: in this post we&apos;re going to operate in the context of what Brains &amp; Beards specialises in - building mobile apps in React Native. This advice does not apply to building backends, nor websites.

In the context of mobile apps in general, usually you&apos;re as vulnerable as the backend that you&apos;re working with. Of course there are practises that are recommended to follow, like for example SSL certificate pinning, or aspects of your app that you should pay particular attention to, like data storage or authentication. But in the end what we&apos;re usually doing is making API calls that any attacker could just as well do manually using `curl`. And if somebody wants to get data off our app, they&apos;ll usually have to physically steal the phone. Not really a scalable attack vector.

I&apos;m not saying that mobile apps can&apos;t be vulnerable. They can and they should be protected appropriately to the domain we&apos;re working in. But the type of vulnerabilities we face are different than code injections on the backend, or XSS on the web. The real vulnerabilities on mobile don&apos;t come from old versions of packages that pop up in red when running `npm audit`.

## What does pop up in red, though?

I&apos;ve just made an experiment and ran the audit on an app I&apos;m working on right now and some of the high / red results (potentially vulnerable outdated dependencies) include:

* A vulnerability in an XML parsing library when my app doesn&apos;t use XML for any network communication
* Regular expression DoS vulnerability in a process spawning library that&apos;s used in my dev tools and linter
* Potential uncontrollable resource consumption in one of the dependencies that my linter uses
* My CLI can be DoSed when flooded with many HTTP headers

Due to the particular nature of JS development (tendency to use many small packages), the chances of finding _some_ high level issue in one of your dependencies&apos; dependencies is pretty high. And due to the nature of working with mobile apps, the chances of this issue being actually exploitable in any way in the build are very very low.

Unfortunately, most often the effort of updating all of them is rather significant.

## But it&apos;s still worth fixing, right?

No.

I wouldn&apos;t go as far as Mitchell Hashimoto (of Vagrant, Terraform, and now Ghostty fame) [in a recent interview](https://www.youtube.com/watch?v=YQnz7L6x068) he gave where (if memory serves me right) he mentioned that he sometimes even avoids updating some dependencies, because he&apos;s worried that this will actually bring vulnerabilities into his codebase. This could happen when a new feature (that he does not use) gets introduced. But maybe that&apos;s a completely valid approach for the kind of problems he solves and the part of the stack that he works with.

However, in the context of building mobile apps in React Native we&apos;re facing the pain of performing the upgrades, the maintenance effort required to keep your codebase on the bleeding edge of your dependencies and the testing required to make sure everything still works. And there&apos;s the opportunity cost of what else could you be doing for your app instead. All of that goes in the red column of costs, while the green column of benefits would usually be empty.

## Conclusion

Should our apps be secure? Definitely.

But let&apos;s talk about actual vulnerabilities of our apps and the attack vectors that our domains face rather than cross something off a list a generic JS code analysis tool gave us.
</media:content></item><item><title><![CDATA[Stale Callback Values in React: a Timer Example]]></title><description><![CDATA[React hooks make managing state simple and predictable. However, when state is used in a closure, such as in a callback function, problems…]]></description><link>https://brainsandbeards.com/blog/2024-stale-callback-values-in-react/</link><guid isPermaLink="false">https://brainsandbeards.com/blog/2024-stale-callback-values-in-react/</guid><dc:creator><![CDATA[Szymon Koper]]></dc:creator><pubDate>Tue, 07 Jan 2025 08:00:00 GMT</pubDate><enclosure url="https://brainsandbeards.com/static/de37263c74b110f31f82de057e1491bf/d50c0/header.jpg" length="635960" type="image/jpg"/><media:content>
React hooks make managing state simple and predictable. However, when state is used in a closure, such as in a callback function, problems can arise if the state value becomes &quot;stale.&quot; This often leads to bugs that are hard to track down, especially in asynchronous contexts like timers.

But the problem of stale callback values is not limited to asynchronous operations. It can also appear in synchronous contexts such as event handlers, `useCallback` dependencies, and inline functions. In this blog post, we’ll use a simple timer example to explore the issue, debug it, and propose solutions. However, the lessons learnt apply to many other scenarios as well.

You can also [explore the example code using Expo Snack](https://snack.expo.dev/@sakydpozrux/stale-callback-value) to follow along and experiment with the solutions.

## Problem: stale callback values

Stale callback values occur when a function in React &quot;closes over&quot; a variable, such as state or prop, but this variable is later updated. The function continues to reference the outdated value captured at the time of the closure&apos;s creation.

While this blog post focuses on a timer implemented with a `useTimer` hook using `setInterval`, the same issue can happen in many other contexts:

- Event handlers: a button click handler that references outdated state
- Memoised callbacks (`useCallback`): callbacks that don&apos;t update because of missing dependencies
- Inline functions: inline functions passed as props that capture stale state or props

&lt;div class=&quot;gif-container&quot;&gt;

![Recording of timer app with stale state bug](useTimer-recorded-bug.gif)

Here’s how the issue manifests in a simple timer app

&lt;/div&gt;

Let’s examine `useTimer` implementation, which tracks elapsed time and provides functions to start, pause, and reset the timer:

```tsx
export const useTimer = () =&gt; {
  const [isRunning, setIsRunning] = useState(false);
  const [elapsedTime, setElapsedTime] = useState(0);

  useEffect(() =&gt; {
    if (!isRunning) return;

    const intervalId = setInterval(() =&gt; {
      setElapsedTime(elapsedTime + 0.1); // This introduces the stale state issue
    }, 100);

    return () =&gt; clearInterval(intervalId);
  }, [isRunning]);

  return { elapsedTime, isRunning, start: () =&gt; setIsRunning(true), pause: () =&gt; setIsRunning(false), reset: () =&gt; setElapsedTime(0) };
};
```

This hook:
- tracks whether the timer is running (`isRunning`) and the elapsed time (`elapsedTime`)
- uses `setInterval` to increment `elapsedTime` while the timer is running
- provides functions to start, pause, and reset the timer

At first glance, this code seems to work as expected. But when you pause and resume the timer, the elapsed time suddenly &quot;jumps.&quot;

The `setInterval` callback captures the initial value of `elapsedTime` when it is created. Even after `elapsedTime` updates, the callback continues to reference the outdated value, causing the timer to behave incorrectly.

You can [try the bugged example on Snack](https://snack.expo.dev/@sakydpozrux/stale-callback-value) and see it in action.


## Debugging the issue
Knowing what exactly happens is half the battle.

### Tracking state with logs

To understand what’s happening, let’s add a `console.log` inside the `setInterval` callback:

```tsx
const intervalId = setInterval(() =&gt; {
  console.log(&quot;Elapsed Time:&quot;, elapsedTime);
  setElapsedTime(elapsedTime + 0.1);
}, 100);
```

Running the timer reveals that `elapsedTime` remains constant inside the `setInterval` callback, even though it’s supposed to update on each tick. This happens because the callback closes over the state value at the time it was created and doesn’t get updated with new values.

### Similar debugging in other contexts

The same approach can be used in other contexts, such as event handlers or inline functions, by logging the state or props captured at the time the function was created. This can help identify when a closure is causing stale values.

## Possible solutions

&lt;div class=&quot;gif-container&quot;&gt;

![Recording of timer app with fixed timer](useTimer-fixed.gif)

Timer is fixed!

&lt;/div&gt;

### Solution 1: use functional state update

The simplest fix is to use the functional form of `setState`:

```tsx
setElapsedTime(prevElapsedTime =&gt; prevElapsedTime + 0.1);
```

This ensures the callback always works with the most recent state value, preventing stale state issues. This approach works well not only for timers but also for event handlers or functions passed to child components.

### Solution 2: Introduce `useRef` for elapsed time

Another approach is to use `useRef` to track elapsed time. Since refs are mutable and don’t cause re-renders, they can hold up-to-date value without being affected by state closure:

```tsx
const elapsedTimeRef = useRef(initialElapsedTime);
elapsedTimeRef.current += 0.1;
setElapsedTime(elapsedTimeRef.current);
```

This avoids the re-renders caused by frequent state updates and ensures the timer remains accurate. `useRef` is also useful for managing mutable data in other scenarios, such as tracking input focus or scroll positions.

### Solution 3: add `elapsedTime` as dependency in `useEffect`

Another approach is to add `elapsedTime` as dependency to the `useEffect` managing the interval:

```tsx
useEffect(() =&gt; {
  if (!isRunning) return;

  const intervalId = setInterval(() =&gt; {
    setElapsedTime(elapsedTime + 0.1);
  }, 100);

  return () =&gt; clearInterval(intervalId);
}, [isRunning, elapsedTime]);
```

This ensures the interval callback always uses the latest `elapsedTime` value but will lead to more frequent interval restarts. The same approach can be used for data-fetching hooks or animations.

### Solution 4: switch to `useReducer` for state management

For more complex state management, `useReducer` centralises all state transitions into a single function:

```tsx
const timerReducer = (state, action) =&gt; {
  switch (action.type) {
    case &quot;TICK&quot;:
      return { ...state, elapsedTime: state.elapsedTime + 0.1 };
    case &quot;START&quot;:
      return { ...state, isRunning: true };
    case &quot;PAUSE&quot;:
      return { ...state, isRunning: false };
    case &quot;RESET&quot;:
      return { elapsedTime: 0, isRunning: false };
    default:
      return state;
  }
};
const [state, dispatch] = useReducer(timerReducer, { elapsedTime: 0, isRunning: false });
```

This approach is robust and extensible, making it ideal for scenarios involving complex state transitions, such as forms or wizards.

### Solution 5: replace polling with request animation frame (bonus)

As a bonus, you can eliminate `setInterval` entirely and use `requestAnimationFrame` for smoother and more precise updates:

```tsx
const animate = (timestamp) =&gt; {
  const delta = (timestamp - lastTimestampRef.current) / 1000;
  setElapsedTime(prev =&gt; prev + delta);
  lastTimestampRef.current = timestamp;
  requestAnimationFrame(animate);
};
```

This solution improves performance and accuracy but is slightly more advanced. It’s particularly useful for animations or tasks requiring precision, such as updating game states or UI layouts.

## Summary

Stale callback values can cause subtle bugs in React, especially in asynchronous scenarios. In this article, we explored:

- what stale callback values are and why they occur
- how to debug the issue in a `useTimer` implementation and other contexts
- five solutions, from simple fixes to more advanced optimizations

By understanding and addressing this problem, you can build more robust and predictable React components.
</media:content></item><item><title><![CDATA[Boosting map scrolling performance]]></title><description><![CDATA[Pioneers in driverless mobility At Brains & Beards, we've been working with Vay from the start, helping them build and improve their React…]]></description><link>https://brainsandbeards.com/blog/2024-boosting-map-vay/</link><guid isPermaLink="false">https://brainsandbeards.com/blog/2024-boosting-map-vay/</guid><dc:creator><![CDATA[Szymon Koper]]></dc:creator><pubDate>Fri, 26 Jul 2024 16:00:00 GMT</pubDate><enclosure url="https://brainsandbeards.com/static/a9761c44b109b8ed73c1700fc270fc58/d50c0/header.jpg" length="1118879" type="image/jpg"/><media:content>
## Pioneers in driverless mobility

At Brains &amp; Beards, we&apos;ve been working with [Vay](https://vay.io/) from the start, helping them build and improve their React Native mobile app.

Vay was the first company in Europe to put driverless cars on public roads, a major milestone in transportation. Now, in Las Vegas, they offer a service where users can either pick up a car from set locations or have it delivered by a remote driver. This setup not only makes things more convenient, but also cuts costs, making advanced mobility more affordable for everyone.

![The main screen of the Vay app](./vay-app-map.png &quot;The main screen of the Vay app showing service areas&quot;)


## The challenge of scaling service areas

As Vay expanded their service to include more areas in Las Vegas, the task of managing and displaying map data became increasingly complex. Initially, the app used GeoJSON data to define the self-parking and remote delivery zones, which were rendered using the `react-native-maps` library. While GeoJSON is effective for representing geographical data, the growing detailed service areas resulted in a significant increase in data load.

The app now had to handle over **70,000 points** on the map, covering approximately **1,500 square kilometers (560 square miles)**.
On some devices, attempting to display the entire GeoJSON dataset caused the UI to **freeze for more than 2 seconds**, severely affecting the user experience. Additionally, the sheer volume of data led to visible artifacts on the map, such as incomplete rendering or delayed updates, which made the situation even worse.

These performance issues, including slow loading times, laggy map interactions, and visual artifacts, were problematic. We needed a solution that could efficiently manage this data and ensure the app ran smoothly.

&lt;div class=&quot;gif-container&quot;&gt;

![Changing zoom level on map with poor performance](vay-geojson-performance-8-220.gif)

Changing the zoom level on the map caused lags and artifacts due to the large amount of data.

&lt;/div&gt;


## Transitioning to map tiles

To address these performance challenges, we transitioned from using raw GeoJSON data to _map tiles_ hosted on the **Mapbox Tiling Service**. Map tiles are square images that are pre-rendered for different zoom levels and cover specific sections of the map.
This approach allows the app to load only the tiles needed for the user&apos;s current view, rather than loading the entire service area at once.

This transition resulted in several key improvements:

- **Elimination of UI freezes**: The app no longer experienced UI freezes when rendering large GeoJSON datasets.
- **Resolution of visual artifacts**: Previously, the large dataset caused triangular artifacts to appear on the map. Switching to map tiles resolved these issues, providing a cleaner and more accurate display.
- **Reduced data load**: By fetching only the relevant map tiles, the app significantly reduced the amount of data it needed to download and process.
- **Simplified frontend code**: The use of map tiles reduced the complexity of frontend code. The service areas, including their styles, are now managed on the **Mapbox Tiling Service**. The `react-native-maps` library that we use supports displaying raster map tiles via `URLTile` component.

&lt;div class=&quot;gif-container&quot;&gt;

![Changing zoom level on map without lags](vay-maptiles-performance-8-220.gif)

The app smoothly showing entire delivery service area after the transition to map tiles

&lt;/div&gt;


## Conclusion

Switching to map tiles has greatly improved Vay&apos;s mobile app performance. This upgrade helps them manage their growing service areas better and sets the stage for future updates.

At Brains &amp; Beards, we&apos;re excited to keep working with Vay to make their services more accessible and user-friendly. We&apos;re looking forward to sharing more updates as we continue to improve the Vay app.
</media:content></item><item><title><![CDATA[Forgotten art of status bar design]]></title><description><![CDATA[The status bar, right after loading / error states, might be the most overlooked mobile app UI element. Despite its apparent simplicity, it…]]></description><link>https://brainsandbeards.com/blog/2024-forgotten-art-of-status-bar-design/</link><guid isPermaLink="false">https://brainsandbeards.com/blog/2024-forgotten-art-of-status-bar-design/</guid><dc:creator><![CDATA[Błażej Lewandowski]]></dc:creator><pubDate>Tue, 26 Mar 2024 19:12:41 GMT</pubDate><enclosure url="https://brainsandbeards.com/static/9fc1cf21561744605e3c0c9279ac27e4/d50c0/header.jpg" length="378494" type="image/jpg"/><media:content>
The status bar, right after loading / error states, might be the most overlooked mobile app UI element. Despite its apparent simplicity, it serves the crucial function of keeping users informed about essential device statuses. Achieving its optimal design needs more planning than the one might think.

## A true cross platform
Once we acknowledge the significance of the status bar, the next step is determining its appearance. From the react native developer perspective, for a particular app screen the decision is not too complicated and it boils down to addressing those 3 general questions:
 - Should we display the status bar icons at all?
 - What color scheme should be used for the bar icons (dark or light)?
 - Do we aim to maintain a consistent UX across both platforms?

While the first two questions may seem straightforward, they come with not that obvious design challenges, which I’ll try to elaborate on in a minute. However, the answer for last question is most likely a resounding “YES”.

In React Native we control the status bar with [StatusBar](https://reactnative.dev/docs/statusbar) component. Whatever our next plans might be, I’d start status bar management by handling it’s appearance with a simple app-specific status bar component:
```tsx
const AppStatusBar: React.FC&lt;AppStatusBarProps&gt; = () =&gt; {
  return (
    &lt;StatusBar
      ...
      translucent
      backgroundColor=&quot;transparent&quot;
    /&gt;
  )
}
```
These two Android specific props are required for rendering our components beneath the status bar while ensuring their visibility. Even if your specific design doesn’t need such features, aligning with this default behaviour on iOS is advisable. Addressing this minor difference upfront can mitigate potential issues in further development, such as positioning elements close to the top edge of device’s screen.

It pays off to have it fixed straight away in a single place in your codebase.

## Splash screen
Another place where I tend to see slips in perfection is the splash screen. Again this is rather a story of making some slight Android adjustments, but this time it is required to fiddle a bit with native files. While this part might depend on your specific splash screen implementation, it’s important to consider the status bar appearance during the app loading phase. It’s not that uncommon to see some grey cut off strips or jumpy transitions into the goal status bar right after the app loads. To me, although it’s a very short time that our user sees it, it might undermine the whole initial impression of app robustness.

Ideally, the status bar’s color should blend with the splash screen content, transitioning seamlessly into  the initial app screen.

&lt;div class=&quot;gif-container&quot;&gt;
  ![splash status bar done right](./allegro-splash.gif)  
  An example of splash screen status bar done right - allegro app.
&lt;/div&gt;

This approach, consistent with our standard practises, has found its way into our [React Native Redbeard app template](https://github.com/brains-and-beards/react-native-template-redbeard/blob/develop/template/android/app/src/main/res/values/styles.xml#L11).

## Placement: root or screen
With our custom `AppStatusBar` in place, another question arises: Where should I put it?  
So I’ve seen two schools for that:
 - explicit placement on each screen
 - root level placement + wherever you need a different look

The former approach seems to me marginally more proper, just because I perceive the status bar as a part of a screen. Explicit per screen placement makes it more predictable and obvious where to eventually make a code change.

The latter approach make much sense, if you don’t need to change the status bar appearance frequently. If your design doesn’t include light / dark screen variations, it’s totally possible, to handle it with a single root level implementation and forget about it.

Regardless of approach taken, beware of a common pitfall. The status bar&apos;s appearance corresponds to whatever `&lt;StatusBar /&gt;` component got rendered last. This caveat may lead to a situation like this:

&lt;div class=&quot;gif-container&quot;&gt;
  ![bottom tab bar navigator issues](./nav-tab-switch.gif)  
&lt;/div&gt;

Both screens A and B feature different looking `&lt;StatusBar /&gt;` components. Because this navigator lazy loads each screen and won’t unmount it on leave, the status bar retains the appearance from the last screen we visit for the first time. In this case it&apos;s screen B with light text in status bar which renders unreadable on screen A.

In such scenarios, it might be better to leverage the imperative API exposed with [StatusBar component static methods](https://reactnative.dev/docs/statusbar#methods).
```tsx
useFocusEffect(
  useCallback(() =&gt; {
    StatusBar.setBarStyle(&apos;dark-content&apos;);
  }, []),
);
```
This little snippet ensures the appropriate bar style whenever the screen gains focus. Just be careful not to mix those two APIs for the same properties to prevent unexpected style overrides.

## Scroll view
It seems intuitive to sync the status bar with the system dark / light mode setting, but in practise this is just a nice to have, but also an easy to avoid feature. Usually problems with status bar begin much earlier, before that “app polish” stage, where we add additional app themes - as soon as we introduce scrollable content.

&lt;div class=&quot;gif-container&quot;&gt;
  ![status bar example trip advisor](./trip-advisor-status-bar.gif)
&lt;/div&gt;

The primary challenge around that is to ensure the proper content / background contrast, having only two bar styles to choose from. As users scroll (often through pictures) it’s easy to leave the top screen part unreadable.

How do we solve that?

### Sticky top bar

&lt;div class=&quot;gif-container&quot;&gt;
  ![status bar example youtube](./youtube-status-bar.gif)
  ![status bar example xbox](./xbox-status-bar.gif)
&lt;/div&gt;

This is the most common solution.  Maintaining a persistent element, such as a search bar or a semi-transparent background, upon scrolling. Applying simple padding, rather than letting arbitrary content fly through the screen edge is a straightforward yet effective strategy.

```tsx
const scrollY = useRef(new Animated.Value(0)).current;
...
&lt;ScrollView
  scrollEventThrottle={50}
  onScroll={Animated.event([
    {
      nativeEvent: {
        contentOffset: {
          y: scrollY,
        },
      },
    },
  ])}
/&gt;
```

This is how the current scroll view position can be tracked. The `scrollY` value can be latter mapped to animate other elements as user scroll.

```tsx
// top screen padding component, fading-in as user scroll

&lt;Animated.View
  style={{opacity: scrollX.interpolate({
    inputRange: [
      0,
      50,
    ],
    outputRange: [0, 1],
    extrapolate: &apos;clamp&apos;,
  })}}
/&gt;
```
### Gradient background

&lt;div class=&quot;gif-container&quot;&gt;
  ![status bar example get your guide](./get-your-guide-status-bar.gif)
  ![status bar example hbo max](./hbo-max-status-bar.gif)
&lt;/div&gt;

A more sophisticated solution would be to use a semitransparent gradient background that gives a little bit of extra contrast for the status bar content. This preserves the nice aesthetics of scrolling all the way to the top screen edge while maximizing the screen space utilization. We can modify our AppStatusBar using [react-native-linear-gradient](https://github.com/react-native-linear-gradient/react-native-linear-gradient) to achieve a similar effect
```tsx{3-6,12}
const AppStatusBar: React.FC&lt;AppStatusBarProps&gt; = () =&gt; {
  return (
    &lt;LinearGradient
      colors={[&apos;#2b2b2b&apos;, &apos;transparent&apos;]}
      style={[{height: 50, opacity: 0.15}, StyleSheet.absoluteFillObject]}
    &gt;
      &lt;StatusBar
        ...
        translucent
        backgroundColor=&quot;transparent&quot;
      /&gt;
    &lt;/LinearGradient&gt;
  )
}
```
I like this way a lot, although I imagine it’s not that universal. The shade would probably not look that good in other colours than black, so in my opinion it better fits the dark themed apps.

### Disappearing status bar

&lt;div class=&quot;gif-container&quot;&gt;
  ![status bar example get your guide](./disappearing-status-bar.gif)
&lt;/div&gt;

This one is not that popular, but seeing it in leading apps makes it worth mentioning. Hiding status bar eliminates the risk of content overlap. It doesn’t have to be hidden all the time. In such a case we can also make it dependent on scroll offset.

```tsx
const handleScroll = (e: NativeSyntheticEvent&lt;NativeScrollEvent&gt;) =&gt; {
  const {
    nativeEvent: {
      contentOffset: {y: scrollY},
    },
  } = e;
  StatusBar.setHidden(scrollY &gt; 50);
};
(…)
&lt;ScrollView
  scrollEventThrottle={50}
  onScroll={handleScroll}
&gt;
(…)
```

While it works, I feel it’s kind of cheating. We’re actually sacrificing some features this way and I think it’s better to reserve this hide / show animations for more immersive context, such as gaming or full-screen media consumption.

### Dynamic background brightness calculations

&lt;div class=&quot;gif-container&quot;&gt;
  ![status bar example klm](./klm-status-bar.gif)
&lt;/div&gt;

This one I find interesting, mainly becaue I haven&apos;t seen this doable. At least on iOS, it looks like we&apos;re able to pick the color for the left and right status bar content sides separately, based on the calculated background brightness.

I save it on the list as an extra. Based on my testing it didn&apos;t work that well. Some surfaces are just half-dark / half-light, and even the most accurate formula for calculations would fail such readability test. Not to mention that two-color status bar looks a bit odd.

Cool effect, but a bit over-engineered. I&apos;d pass on that.

## Animations
The StatusBar component supports some basic predefined animations via boolean `animated` prop. On Android, it ensures smooth color transitions between different `backgroundColor` values, while on iOS it adds an animation for hiding and altering the status bar content color (`barStyle`).

Those optional iOS animations are enabled by default on Android and remain unmodifiable, so I don’t see a reason to not make it more cross platform here as well and set this flag to `true`. With this adjustment, our true cross-platform status bar props count rounds up to three at minimum:

```tsx{5-7}
const AppStatusBar: React.FC&lt;AppStatusBarProps&gt; = () =&gt; {
  return (
    &lt;StatusBar
      ...
      animated
      translucent
      backgroundColor=&quot;transparent&quot;
    /&gt;
  )
}
```
</media:content></item><item><title><![CDATA[mgewcli - maximizing git efficiency with CLI]]></title><description><![CDATA[A tale of efficiency - skip It was a typical work day, we had a little pair programming session, where I was trying to explain to my…]]></description><link>https://brainsandbeards.com/blog/2024-mgewcli-maximizing-git-efficiency-with-cli/</link><guid isPermaLink="false">https://brainsandbeards.com/blog/2024-mgewcli-maximizing-git-efficiency-with-cli/</guid><dc:creator><![CDATA[Błażej Lewandowski]]></dc:creator><pubDate>Tue, 19 Mar 2024 19:12:41 GMT</pubDate><enclosure url="https://brainsandbeards.com/static/2270d90962f67a44e330d7e240e19ad2/d50c0/header.jpg" length="321449" type="image/jpg"/><media:content>
### A tale of efficiency - skip
It was a typical work day, we had a little pair programming session, where I was trying to explain to my colleague some deployment processes. In the realm of remote work such sessions, reliant on screen sharing, are prone to hiccups. Connection might get unstable, so the video might lag a bit. It’s usually better when you take this into account, and try to explain things slowly, **especially when you’re about to teach something**. That time I totally forgot about this rule...  

I launched like a rocket, I took every shortcut that I could, making my teammate totally confused. It was intelligible... What possibly can you understand from this?

```sh
gbD develop
gco your-feature-branch
gcb develop
gpsupf
```

I quickly fixed my little mistake, but this made me realise I can share some git efficiency tips that I think many developers already have at the tips of their fingers. If you’re curious what does that mean ☝️, read on.

### Git GUI vs terminal
Before we begin, let’s put a little disclaimer right here. I use git via CLI. Chances are you might see that as a relict and use a GUI client. That’s ok and it doesn’t mean this article is not for you :) 

While GUI apps are mature now and can offer some benefits over the CLI, I think it’s still beneficial to know how to write, rather than click stuff. Some pros of such approach:
- speed: it’s a muscle memory, keyboard &gt; mouse
- scripting: enjoy the power of automation with simple scripts
- cross-platform consistency: whether you jump on Linux, macOS or connect to remote location via SSH
- hackerman feeling: impress your colleagues, if you’re into it

And the list goes on...

### oh-my-zsh
Another heads-up: I use [oh-my-zsh](https://github.com/ohmyzsh/ohmyzsh). For those who don’t know it - it’s the most popular framework for your zsh config management. It can bring a lot of fun features to your system shell, but let’s focus on the one in particular - git plugin.  

Why this topic? Well, `zsh` has been the default shell on macOS for several versions now (starting from Catalina) and many devs, myself included, adopt `oh-my-zsh` right after seeing its cool customizable themes in action. If you [peek into the default .zshrc config](https://github.com/ohmyzsh/ohmyzsh/blob/e195c7cb438224e8bcea20bdbc2e4b8a6bb3949b/templates/zshrc.zsh-template#L73), you&apos;ll notice the git plugin is enabled by default, so most likely the whole setup is already there.

### oh-my-zsh plugin git
Alright! The plugin is really simple, it just adds a set of handy aliases  and functions for git commands, so you don’t have to type the full long form, e.g. instead of writing `git push` you can just write `gp`, saving six precious keystrokes.  

To give a better idea how does that look in action, here&apos;s a glimpse of some real world everyday commands and their abbreviated counterparts. Full list is available at [plugin&apos;s page](https://github.com/ohmyzsh/ohmyzsh/blob/master/plugins/git/README.md). I recommend going through it at some point if you haven’t yet, I’m sure you’ll discover something cool for yourself in there.

&lt;table&gt;
  &lt;tr&gt;
    &lt;th&gt;abbreviation&lt;/th&gt;
    &lt;th&gt;full command&lt;/th&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;`ga filename.ext`&lt;/td&gt;
    &lt;td&gt;`git add filename.ext`&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;`gaa`&lt;/td&gt;
    &lt;td&gt;`git add --all`&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;`gco-`&lt;/td&gt;
    &lt;td&gt;`git checkout -`&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;`gcb new-branch`&lt;/td&gt;
    &lt;td&gt;`git checkout -b new-branch`&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;`gcp c0mm!t5ha`&lt;/td&gt;
    &lt;td&gt;`git cherry-pick c0mm!t5ha`&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;`gc -m “some commit message”`&lt;/td&gt;
    &lt;td&gt;`git commit -v -m “some commit message”`&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;`gcmsg “different command, same result”`&lt;/td&gt;
    &lt;td&gt;`git commit --message “different command, same result”`&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;`gd`&lt;/td&gt;
    &lt;td&gt;`git diff`&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;`gst`&lt;/td&gt;
    &lt;td&gt;`git status`  &lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;`glola`&lt;/td&gt;
    &lt;td&gt;`git log --graph --pretty=&apos;%Cred%h%Creset -%C(auto)%d%Creset %s %Cgreen(%ar) %C(bold blue)&lt;%an&gt;%Creset&apos; --all`&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;`gm branch`&lt;/td&gt;
    &lt;td&gt;`git merge branch`&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;`gl`&lt;/td&gt;
    &lt;td&gt;`git pull`&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;`gpsup`&lt;/td&gt;
    &lt;td&gt;`git push --set-upstream origin $(git_current_branch)`&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;`grb some-branch`&lt;/td&gt;
    &lt;td&gt;`git rebase some-branch`&lt;/td&gt;
  &lt;/tr&gt;
&lt;/table&gt;
 
 Notice, it’s perfectly fine to add some extra arguments after the aliased command.  
 I’m especially a fan of `gpsup`. Typing this command usually means I just finished my current work and I&apos;m ready to share it in PR up for the review. Very convenient!  

 The learning investment is basically nothing and whenever you find yourself unsure about some specific command there is always a handy way to check what it does:
 
 ```
 type gc
 gc is an alias for git commit -v
 ```

With some aliases you probably gotta ba a little more careful. I can’t say how many times I invoked Ghostscript by mistyping `gs` instead of `gst`… Other than that, I had 0 conflicts with it and such error is hardly catastrophic.

There you have it! GLHF with your new super powers!</media:content></item><item><title><![CDATA[Common Pitfalls in Authentication Token Renewal]]></title><description><![CDATA[In this blog post we'll talk about how to avoid common concurrency pitfalls when renewing authentication tokens in React Native apps. Intro…]]></description><link>https://brainsandbeards.com/blog/2024-token-renewal-mutex/</link><guid isPermaLink="false">https://brainsandbeards.com/blog/2024-token-renewal-mutex/</guid><dc:creator><![CDATA[Szymon Koper]]></dc:creator><pubDate>Tue, 30 Jan 2024 16:00:00 GMT</pubDate><enclosure url="https://brainsandbeards.com/static/24ca8991c665f58e8578180afb74f2c4/d50c0/header.jpg" length="754491" type="image/jpg"/><media:content>
In this blog post we&apos;ll talk about how to avoid common concurrency pitfalls when renewing authentication tokens in React Native apps.


## Intro to authentication tokens

In apps with logins, we rely on **tokens** from the server to validate user identities.
These tokens act as keys, allowing users to stay logged in.
Since tokens have a lifespan, we periodically request new ones by sending a _refresh token_.

The server responds with a fresh _authentication token_, extending the user&apos;s login duration.
However, when the server invalidates a token (indicated by a `401 Unauthorized` message), we gracefully initiate a renewal process.

Join us in navigating the nuances of token renewal in React Native apps. 🗝️


### Naive implementation

Let&apos;s explore a simple method for handling authorized requests and authentication token renewals.

Take a moment to review the code snippet below. Can you spot potential issues?

```tsx
const renewAuthenticationToken = async () =&gt; {
  // It renews the access token, by sending refresh token to the server
  await authService.renewAccessToken()
}

const makeAuthenticatedRequest = async (url: string, options: RequestInit) =&gt; {
  const response = await fetch(url, options)

  if (response.status === 401) {
    await renewAuthenticationToken()
    // Retry the request after successful token renewal using recursion
    return await makeAuthenticatedRequest(url, options)
  }

  return response
}
```

In the snippet, we&apos;ve identified a few general network handling concerns:

 - No possibility to cancel fetch requests
   - Introducing the AbortController would enhance control over network calls by providing the ability to cancel ongoing fetch requests.
 - No request timeout set up
   - Adding a timeout feature ensures that requests do not linger indefinitely, improving the application&apos;s responsiveness.
 - Infinite retry risk
   - To mitigate the risk of endless retries in case of token renewal failures, implementing a retry count limit is crucial. This prevents the system from repeatedly attempting requests beyond a reasonable threshold.
 - Error handling enhancement
   - Improving error handling by providing more detailed information about the cause of errors can aid in better understanding and debugging.

While these aspects contribute to robust network handling, our main focus in this blog post is to elevate the token renewal process. 🚀


### Navigating concurrency pitfails

Let&apos;s revisit our naive implementation and envision the following scenario: an authorized request fails with a `401 Unauthorized` message, triggering the token renewal process.

In the world of apps, it&apos;s common to send multiple requests concurrently.
What happens if a previous token renewal is still in progress, and the app initiates another authorized request?
In our naive implementation, it sparks another renewal process, unintentionally creating a [race condition](https://en.wikipedia.org/wiki/Race_condition).

We&apos;ve unintentionally introduced a race condition where multiple token renewal operations can be in progress simultaneously when a single operation would suffice.

Depending on the backend token renewal implementation, each consecutive renewal might invalidate the previous tokens, leading to a cascade of failed requests.
This not only wastes resources but also negatively impacts performance, drains the battery, and creates a frustrating experience for both users and developers.

Stay tuned as we uncover solutions to navigate these concurrency pitfalls and refine our token renewal process. 🚧


#### JavaScript concurrency

A common misconception is assuming JavaScript is immune to race conditions because of its single-threaded nature.

Yes, JavaScript operates on a single thread, but the underlying environment (JavaScriptCore on iOS, V8 on Android, or Node.js during app development) empowers operations like network requests to run in parallel without blocking the main JS thread.

To renew tokens correctly, we must fortify ourselves against potential race conditions.


#### Don&apos;t send multiple renewal requests

When the token renewal process is underway, we don&apos;t need to initiate another renewal request until we secure a new token.
By abstaining from additional renewal requests, we allow the initial process to conclude seamlessly.

Consider this (overly) simplified implementation:

```tsx{1,3-5,8-10,13,16}
let isDuringRenewal = false

const waitForRenewalToFinish = async (): Promise&lt;boolean&gt; =&gt; {
  // Imagine a function that resolves when `isDuringRenewal === true`
}

const renewAuthenticationToken = async () =&gt; {
  if (isDuringRenewal) {
    await waitForRenewalToFinish()
  }

  try {
    isDuringRenewal = true
    await authService.renewAccessToken()
  } finally {
    isDuringRenewal = false
  }
}
```

This ensures that the token renewal is an exclusive operation.

#### Don&apos;t send requests that will surely fail

However, we&apos;ve addressed one issue, but another persists.
Even with our newfound restraint, subsequent requests might still fail with `401 Unauthorized` before the renewal process completes.

We know that after the first `401 Unauthorized` message, all subsequent authorized requests with the same outdated token are destined to fail.

So, why send them?
Instead, we delay sending authorized requests until we acquire a new token from the renewal operation triggered by one of the previous requests.

```tsx{2-3}
const makeAuthenticatedRequest = async (url: string, options: RequestInit) =&gt; {
  // if token is being renewed now - wait for the new token
  await waitForRenewalToFinish()
  const response = await fetch(url, options)

  if (response.status === 401) {
    await renewAuthenticationToken()
    return await makeAuthenticatedRequest(url, options)
  }

  return response
}
```

### Truly resolving the race condition

Hold on!
The above code is an improvement, but it&apos;s still **not entirely** free from the race condition.

There&apos;s a small window between the check `if (isDuringRenewal)` and setting the flag `isDuringRenewal = true`, where another function could potentially start the renewal process concurrently.

![Van Gogh screaming](./vangoghscream.jpg &quot;RN developer happily resolving race conditions&quot;)

Stay tuned as we introduce a powerful tool, the mighty mutex, to put a complete end to these concurrency challenges. 🛡️


#### Mutex to the rescue

To ensure the integrity of our token renewal process, consider making our checks atomic
Enter the formidable [_mutex_](https://www.npmjs.com/package/async-mutex), an object designed to synchronize parallel operations.

Let&apos;s leverage the `Mutex` from the `async-mutex` package for a robust solution:

```tsx{1,3,6-8,11,14,19-20}
import { Mutex } from &apos;async-mutex&apos;

const tokenRenewalMutex = new Mutex()

const renewAuthenticationToken = async () =&gt; {
  if (tokenRenewalMutex.isLocked()) {
    await tokenRenewalMutex.waitForUnlock()
  }

  try {
    tokenRenewalMutex.acquire()
    await authService.renewAccessToken()
  } finally {
    tokenRenewalMutex.release()
  }
}

const makeAuthenticatedRequest = async (url: string, options: RequestInit) =&gt; {
  await tokenRenewalMutex.waitForUnlock()
  const newOptions = getOptionsWithCurrentToken(options)
  const response = await fetch(url, newOptions)

  if (response.status === 401) {
    await renewAuthenticationToken()
    return await makeAuthenticatedRequest(url, newOptions)
  }

  return response
}
```

With the introduction of the `async-mutex` library, we&apos;ve fortified our solution against concurrent token renewal processes.

The `Mutex` ensures a single, synchronized renewal at any given time. It not only resolves the race condition but also promotes a more robust and efficient token renewal process.


## Conclusion

In this blog, we explored pitfalls in renewing authentication tokens in React Native apps.

The naive approach led to race conditions with multiple token renewals.
To address this, we introduced a `Mutex`, ensuring a single, synchronized renewal process.
The solution prevents race conditions and enhances app efficiency.

Happy coding! 🚀
</media:content></item><item><title><![CDATA[Useful documentation: ADRs]]></title><description><![CDATA[What's an ADR? It's an Architecture Decision Record document. It documents architecture decisions together with its context.

Okay, what's an…]]></description><link>https://brainsandbeards.com/blog/2023-useful-documentation-adrs/</link><guid isPermaLink="false">https://brainsandbeards.com/blog/2023-useful-documentation-adrs/</guid><dc:creator><![CDATA[Wojciech Ogrodowczyk]]></dc:creator><pubDate>Tue, 17 Oct 2023 19:12:41 GMT</pubDate><enclosure url="https://brainsandbeards.com/static/e52f5cc64cb1917f187eb333714bacab/d50c0/header.jpg" length="558432" type="image/jpg"/><media:content>
## What&apos;s an ADR?
It&apos;s an [Architecture Decision Record](https://github.com/joelparkerhenderson/architecture-decision-record) document. It documents architecture decisions _together with its context_.

## Okay, what&apos;s an &quot;architecture decision&quot;?

I could keep playing this game and tell you that&apos;s a decision that influences your app&apos;s architecture, but I&apos;ll stop myself here. Instead, I&apos;ll give you a few guidelines on what I think is worth documenting using ADRs:

* Picking tools, programming languages, frameworks, external libraries, etc. Some decisions are obvious and don&apos;t need a discussion (`zod` for data validation, or `prettier` for code formatting), but some are a tradeoff (state management) and involve a discussion in the team or consideration of alternative options with all their pros and cons. That&apos;s the kind of decision that you&apos;d want to capture (along with its reasoning and other options that you&apos;ve considered).

* Deciding which 3rd party service to use. It&apos;s especially useful to be able to look back at your reasoning when re-evaluating this decision months or years later. For example, you might be just launching beta tests of your app and decide to use Amplitude and the reason you picked it was &quot;good features, reasonable price&quot;. However, as you go public and start gaining more users, costs might blow up and when CFO asks you why are we paying so much you&apos;ll know why (because you forgot that you wanted to change providers before going public). However, the original reason could also have been the existence of a particular feature that is very important for your project and not available in competing solutions. Then it&apos;s even more valuable to be reminded of it when considering migrating elsewhere.

* Contracts between services. Network protocols, data formats, etc. You&apos;d document it if that was _your team&apos;s_ decision.

* Anything that you&apos;ve created a _spike_ or _proof of concept_ for. If this work rests only in Jira, I have a feeling it might get lost. Might be a good idea to co-locate it alongside your code. Whether the result was _not_ to do something, do it, or wait.


## Why would I want to document that?

It will help you make decisions. An ADR needs to contain the _context_ around the decision. So, you&apos;ll need to clear up exactly what problem you&apos;re trying to solve (deciding how to solve) and specify what options you&apos;re considering. Once you write that down, it&apos;s usually much easier to propose a decision. Probably because writing _is_ thinking.

Once somebody worked on the problem, did the research and has a proposal, you can forward the in-progress document to your team as the agenda for a group discussion. This will let everybody come prepared and know what you did (and didn&apos;t) consider.

It stops you from blindly making and changing decisions. Writing down what options are on the table helps consider all the alternatives and writing down your reasoning prevents you from overturning this decision in the future on a whim.

Finally, it saves onboarding time. Often, when a new member joins a team they might wonder why something is built a particular way, or try to port over their favourite solutions from previous projects. Having clear ADRs that explain your patterns and choices helps new hires quickly learn the reasoning behind them and see whether there&apos;s a better solution that they could offer.


## I&apos;m sold, how would I want to document that?

First, setup a place for your ADRs, probably somewhere in the `docs` folder in your repo. You have a `docs` folder, right?

Second, ADRs are time-bound, meaning you&apos;ve made a decision at a particular point in time and it&apos;s important to track that. Of course, you can always check the date in the document, but I find it helpful to number the filenames. For example: `adr-1-i18n-library.md`, `adr-2-state-management-provider.md`, etc.

Third, decide on a format. You can [pick one of the popular ones](https://github.com/joelparkerhenderson/architecture-decision-record/tree/main/locales/en/templates) or write your own. It doesn&apos;t matter much which one you pick, but having _a_ structure will make it easier to write them.

Here&apos;s a couple of things I like to see in an ADR:

### Title 

Obviously.

### Summary

_Date_ when the decision was taken.

Explanation of the _issue_ we want to decide on. Often the problem statement is as important (if not more!) as the solution itself. If you structure this section clearly, it&apos;ll make it not only much easier for you to evaluate possible options, for your teammates to review your proposal, but also for future team members to decide whether the landscape has changed and this problem can now be solved differently.

The _decision_ that was made.

_Status_ of the decision. ADRs are immutable (because they&apos;re just a record of a decision made in the past), but decisions change. This means that we need a way to represent change. So, an ADR document might be &quot;proposed&quot; (or pending) when it&apos;s still being worked on, &quot;accepted&quot; (decided), but it can also be &quot;superseded&quot; by a newer decision that was made later. For example, when you decide to change a 3rd party provider for a service you&apos;d change the status of the ADR that picked the old one from &quot;accepted&quot; to &quot;superseded&quot; and link a new ADR that picks a new provider.

### Details

A list of _assumptions_ (and constraints) that we&apos;re working on. For example, we might be picking a framework to use for a mobile app and we assume that we&apos;re not going to release na iOS version ever (assumption), but we don&apos;t have any team member who knows Kotlin (constraint).

_Options_ (also called &quot;positions&quot;) that you&apos;ve considered _along with their relevant pros and cons_. You don&apos;t need to write _all_ the options and their characteristics (because universe is infinite and that&apos;s a losing game), just stick to the ones that might raise a reasonable question (starting with &quot;Did you consider…&quot;).

Reasoning and _arguments_ behind the decision that was made.

_Implications_ (for you, for other teams, for the future, etc.) of the decision that has been made.

_Notes_ section for house-keeping.




</media:content></item><item><title><![CDATA[Maintainable React Native]]></title><description><![CDATA[We've just launched a course called Maintainable React Native. It differs from other materials that you can find online because it doesn't…]]></description><link>https://brainsandbeards.com/blog/2023-maintainable-react-native/</link><guid isPermaLink="false">https://brainsandbeards.com/blog/2023-maintainable-react-native/</guid><dc:creator><![CDATA[Wojciech Ogrodowczyk]]></dc:creator><pubDate>Tue, 11 Jul 2023 19:12:41 GMT</pubDate><enclosure url="https://brainsandbeards.com/static/318abc69bcdf708d3f05545884322d00/d50c0/header.jpg" length="388301" type="image/jpg"/><media:content>
We&apos;ve just launched a course called [Maintainable React Native](https://brains-beards.ck.page/maintainable-react-native). It differs from other materials that you can find online because it doesn&apos;t just teach you how to make things work, but rather guides you towards building them correctly. The emphasis is on creating software with the right architecture - simple and modular, ensuring that your app remains easy to manage even years later.

## How it works

Every week, we&apos;ll send you a lesson. They are grouped by topic, starting with handling remote data before moving into state management, design systems, etc. We aim to cover every step of the process involved in building a successful mobile app. All of it based on our extensive experience working with React Native since 2016.

We&apos;ve learnt over the years that building things right is important, because it allows you to deal with shifting priorities, new requirements, and surprising features. We all want to stay productive past the greenfield phase instead of getting bogged down with technical debt, long E2E tests on every merge request, or yet another vision-less refactor.

The course is based off software engineering fundamentals, focusing on explaining &quot;why&quot; instead of giving you a recipe that you&apos;re expected to copy-paste. This approach will help you decide what to improve in your current (and future) projects.

## Where do I sign

The course is 100% free (at the moment) and you can enroll by filling in your data just below this article, or go to 
the [Maintainable React Native](https://brains-beards.ck.page/maintainable-react-native) dedicated page.

Hope to see you there!</media:content></item><item><title><![CDATA[The key to fast and secure redux]]></title><description><![CDATA[As my friend recently wrote there are no apps too small for redux. And if you're developing mobile apps like us (that is the context of this…]]></description><link>https://brainsandbeards.com/blog/2023-the-key-to-fast-and-secure-redux/</link><guid isPermaLink="false">https://brainsandbeards.com/blog/2023-the-key-to-fast-and-secure-redux/</guid><dc:creator><![CDATA[Błażej Lewandowski]]></dc:creator><pubDate>Tue, 20 Jun 2023 19:12:41 GMT</pubDate><enclosure url="https://brainsandbeards.com/static/cac0481e781e2df069a51655184a02ad/d50c0/header.jpg" length="2565848" type="image/jpg"/><media:content>
As my friend recently wrote there are [no apps too small for redux](https://brainsandbeards.com/blog/2023-no-small-apps/). And if you&apos;re developing mobile apps like us (that is the context of this article) I wouldn&apos;t believe you if you told me that your app works well without any form of caching. And caching things between app restarts means persistent storage. So what options do we have for redux?

Well it might depend on your use case, but there is a really high chance that you&apos;ll choose redux-persist as it&apos;s listed on top in [redux ecosystem docs](https://redux.js.org/introduction/ecosystem#persistence). Although I believe it is not a perfect plugin (it&apos;s not mainained anymore), many of us see it as a &quot;core-library&quot;. It provides convenient API, it&apos;s written in TypeScript, and - once you manage to correctly set it up - it does the job well.

The most classic approach I saw in &quot;redux-persist apps&quot; is just to save everything at the root level using `AsyncStorage`, maybe some simple blackist or whitelist setting, and that&apos;s it. I get it - it&apos;s simple, it works, and this is what you get by default when you start exploring persistence options.
That&apos;s all great… but! If you prefer building _quality things_ over things that just work (like we obviously do in **Brains &amp; Beards**!) you might be interested in optimising this setup.

What&apos;s cool about redux-persist is that it allows for a nice level of customization. The natural starting point for that would be the *storage engine* it uses. Natural, because you always need to explicitly set it, even in the most minimal configuration.  

The *storage engine* in redux-persist terminology is a module that is able to persist your data and offers 3 simple functions for doing that: `setItem`, `getItem`, `removeItem`. In [docs](https://github.com/rt2zz/redux-persist#storage-engines), we read there are plenty of ready to use engines to choose from, but I can see already several questions being raised:
* Are these recommendations really up to date?  
* Which one should I use?  
* Is it secure enough for my data?  

## MMKV

The simple answers for the first two questions would be `No!` and `MMKV!`. The third one will need a bit more explanation, but we&apos;ll get to it.  

Comparing different options boils down to these 3 main factors:
- Performance
- Stability
- Security

### Performance &amp; stability
Speaking of performance, `mmkv` knocks out the opponents. It&apos;s been one of the main reasons why this storage framework was created. And till this day it remains as the main selling point. Just take a look at this beautiful chart:
![library alternatives](./chart.png &quot;Source: https://github.com/mrousavy/StorageBenchmark&quot;)

Regarding stability, I think it&apos;s ok to trust it. It&apos;s a relatively new invention, but despite the fact it looks like magic I believe it&apos;s not rocket since in the codebasdse. Its feature set is really simple. The library has been already widely adopted and continues to be actively supported. Popularity of this tool is still growing, which hopefully will keep this project alive. You can read more about it [here](https://github.com/mrousavy/react-native-mmkv).

### mmkv + redux-persist

This integration is not as great as it could be, but still I would say it&apos;s pretty straightforward. We can easily create a wrapper that would satisfy the redux-persist API requirements:

```tsx
// src/redux/persistence.ts
import { MMKV } from &apos;react-native-mmkv&apos;
import { Storage } from &apos;redux-persist&apos;

const storage = new MMKV()

const reduxStorage: Storage = {
  setItem: (key, value) =&gt; {
    storage.set(key, value)
    return Promise.resolve(true)
  },
  getItem: key =&gt; {
    const value = storage.getString(key)
    return Promise.resolve(value)
  },
  removeItem: key =&gt; {
    storage.delete(key)
    return Promise.resolve()
  },
}

export { reduxStorage as storage }
```
As I mentioned before, redux-persist will work with any storage as long as it offers compatible API. Unfortunately expected API has to be based on Promises and this is not mmkv&apos;s way of working, so we need to fake it. It&apos;s a bit of a bummer, but from my understanding we&apos;re not losing any performance here and this is only the matter of **when** the code is executed, not **how fast** it will run.

So that&apos;s how you need to wrap the mmkv instance, and this is how it needs to be passed to redux-persist:
```tsx
// src/redux/rootReducer.ts
import { combineReducers } from &apos;redux&apos;
import { persistReducer } from &apos;redux-persist&apos;
import authReducer from &apos;@api/authSlice&apos;
import demoReducer from &apos;@screens/demoSlice&apos;
import { storage } from &apos;./persistence&apos;

const rootReducer = combineReducers({
  auth: authReducer,
  demo: demoReducer,
})

const rootPersistConfig = {
  key: &apos;root&apos;,
  storage,
}

export default persistReducer(rootPersistConfig, rootReducer)
```
Easy! 🚀

&gt;Wait, wait, wait... There seems to be some auth releated stuff and we just stored whatever it is on user&apos;s device in plain text. Are you sure about this change...?

Sadly, such questions are not as frequent as they should be 😄. Sadly - because it is a very accurate question. As soon as we see some sensitive data in our code, extra awarness should be the automatic reaction.

It is definitely not crucial for every app to have some kind of encryption, but it is not uncommon to find a need for it.
The reason for this requirement could be as simple as using API tokens for the FE/BE communication. However, if you&apos;re developing apps related to health or finance, it may also be a strict legal requirement.

### Security

Alright, so to enhance the security of our app data, our strategy is to encrypt it. With mmkv it looks simple, you can just initialise the `MMKV()` JS instance using any developer-defined encryption key and that&apos;s it!? Not really...  
A few problems I see here:  
- Where should I put my encryption key? I don&apos;t know!😱
- Should it stay defined as string variable in JS code? No! This might make our data harder to exploit, but not by much.  
- Should I place it behind some API? Hard pass! We want the solution that works offline.

So while it is possible to encrypt the data with mmkv there are better ways to do it. My recommendation is to go with the officialy recommended native APIs, created specifically for this purpose. Why?  

It&apos;s significally harder to extract the master encryption key. Taking for example Android keystore - it lets you create and use such a key, but the actual key stays unknown even to your app. I also think using platform standards is the simplest way to security.

Luckily both mobile platforms come with such module ([iOS keychain](https://developer.apple.com/documentation/security/keychain_services/) / [Android EncryptedSharedPreferences](https://developer.android.com/reference/androidx/security/crypto/EncryptedSharedPreferences)) and we have a number of libraries that wrap it, so we can easily use it from react-native&apos;s JS thread. (Although the most secure option would be not to rely on community-maintained packages in this part of your app and keep the full control over it.)

### EncryptedStorage + mmkv + redux-persist

[React native docs](https://reactnative.dev/docs/security#android---keystore) point towards a few of those libraries. I picked the [react-native-encrypted-storage](https://github.com/emeraldsanto/react-native-encrypted-storage/). It provides exactly what we need here, integrating with redux-persist seamlessly through already mentioned methods.

Let&apos;s revisit our `persistence.ts` file and add this line to it:

```tsx
// src/redux/persistence.ts
...
export { default as safeStorage } from &apos;react-native-encrypted-storage&apos;
```

So we have two exports now: `storage` and `safeStorage`. We need to move our `authSlice` to the latter.

```tsx
//src/api/authSlice.ts
import { persistReducer } from &apos;redux-persist&apos;
import { safeStorage } from &apos;@redux/persistence&apos;
...
const authPersistConfig = {
  key: authSlice.name,
  storage: safeStorage,
}

export default persistReducer(authPersistConfig, authSlice.reducer)
```

Almost there!

In order to avoid persistence duplications, make sure to blacklist `authReducer` in the root config, otherwise you&apos;ll have it stored twice using different storage engines.
```tsx
// src/redux/rootReducer.ts
import { storage } from &apos;./persistence&apos;
...
const rootPersistConfig = {
  key: &apos;root&apos;,
  storage,
  blacklist: [&apos;auth&apos;],
}

export default persistReducer(rootPersistConfig, rootReducer)
```

Not hard at all and so much better! 🤩

### Debugging

Working with persistent data can be painful without proper tooling, so I&apos;d like to share a few flipper plugins that I found helpful when working on the code above ☝️

- [redux-flipper](https://github.com/jk-gan/redux-flipper) - monitor actions dispatched by redux-persist and how your state exactly reacts to them
![redux-flipper screenshot](./flipper-redux.png)

- [react-native-mmkv-flipper-plugin](https://github.com/muchobien/flipper-plugin-react-native-mmkv) - check and modify your mmkv storage
![mmkv-flipper screenshot](./flipper-mmkv.png)

- [shared preferences viewer](https://fbflipper.com/docs/features/plugins/preferences/) (built in) - see what&apos;s in your encrypted shared preferences
![shared preferences flipper screenshot](./flipper-shared-preferences.png)


## Final words

Stay safe!

…and fast!
</media:content></item><item><title><![CDATA[Working with Remote Data 101]]></title><description><![CDATA[God, grant me the serenity to accept the things I cannot change, the courage to change the things I can, and the wisdom to know the…]]></description><link>https://brainsandbeards.com/blog/2023-remote-data-101/</link><guid isPermaLink="false">https://brainsandbeards.com/blog/2023-remote-data-101/</guid><dc:creator><![CDATA[Wojciech Ogrodowczyk]]></dc:creator><pubDate>Tue, 13 Jun 2023 11:12:41 GMT</pubDate><enclosure url="https://brainsandbeards.com/static/71a929e9df484b9692f36db179ab94d0/d50c0/header.jpg" length="1909730" type="image/jpg"/><media:content>
&gt; God, grant me the serenity to accept the things I cannot change,&lt;br/&gt;
&gt; the courage to change the things I can,&lt;br/&gt;
&gt; and the wisdom to know the difference.

You&apos;ve probably heard [at least one of the versions of this Serenity prayer](https://en.wikipedia.org/wiki/Serenity_Prayer). It&apos;s not only a pretty useful piece of advice for life, but also a helpful tip towards handling remote data successfully when building mobile apps.

One of the key strategies in building a reliable app is to understand what data is flowing through your system. Unless you work in a small team that believes in _full-stack development_, you won&apos;t have much control over what comes to your app from the backend services. This data can come with multiple types of issues with a varying impact:

* It can simply be invalid for your app. Let&apos;s say you have a B2C ecommerce app and you get a product without a name or price. While (for various legacy reasons) this might be valid on the backend it definitely is not a product that you can display like all the others. Either you&apos;ll have to build a special version of all the product details screens and listings to accomodate a product like that, or you just ignore it.
* Data can be malformed. An ID that is a number is passed as a string, or URLs for any detailed pages are not localised. Those are issues that can (and have to!) be corrected _somewhere_ in your app.
* Cosmetic issues: typos in property names, mixing snake case and camel case, misleading naming (field called `distanceInMiles` that returns kilometers instead), etc. Those issues can be fixed on your side, or accepted as quirks and propagated across your codebase as well.

Of course, a lot of those issues are simply differences between how your team sees the domain (and how things _should_ be) and how other teams do. Sometimes there&apos;s a business logic error hidden there that you can simply ask the other team to fix, but a lot of times it&apos;s a matter of preference. Of course, even if you&apos;re right it doesn&apos;t mean that the problem is worth fixing. Good luck [raising priority on your ticket that somebody misspelled _referrer_](https://en.wikipedia.org/wiki/HTTP_referer).

If you value maintainability in the long-term over moving fast and breaking things, you&apos;ll probably want to draw a line where the data enters your app and put a border guard there that does two things:
* Checks for correctness
* Translates from external (backend) to internal (yours) representation (and back to external format when data&apos;s leaving your app)

You _could_ hand-craft a layer of artisanal code somewhere between low-level networking and your business logic, but you don&apos;t have to. Enter [zod](https://zod.dev/).

## Data validation

Let&apos;s take as an example the Marvel API&apos;s endpoint for characters. We&apos;d like to represent a list of them with a possibility of fetching more data for a detailed screen should our user tap any of them. We _could_ do manual checks for the fields that we need (`ID` - to later fetch the details, `name` - to display it in our list, `URL` - to fetch a thumbnail image, and `modified` time - to possibly help us with cache invalidation), but we can also let `zod` do it for us by defining a schema that we&apos;re interested in:

```tsx
import z from &apos;zod&apos;

const HeroUrlBE = z.object({
  type: z.string(),
  url: z.string().url(),
})

const HeroBESchema = z.object({
  id: z.number(),
  modified: z.string().datetime({ offset: true }),
  name: z.string(),
  urls: z.array(HeroUrlBE).nonempty(),
})
```

Then we can simply use this schema to validate data we received using `HeroBESchema.parse(someHeroWannabe)`. 

Note: I often use `BE` sufix to mark types or schemas that represent remote (backend) data representation.

## Data transformation

As I mentioned above, checking correctness is just the first step, because we&apos;d also like to control the shape of data inside our app. 

In that particular case, we get an array of URLs, but we only need one. We can define a schema transformation that will take the remote representation and massage it into shape that we&apos;d like to use. For example:

```tsx
import { D } from &apos;@mobily/ts-belt&apos;

const HeroFESchema = HeroBESchema.transform(hero =&gt; ({
  url: hero.urls[0].url,
  ...D.deleteKeys(hero, [&apos;urls&apos;]),
}))
```

That will strip the fields that you&apos;re not interested in (here `urls`) and set a new one that you want to use (`url`). 

Note: anything that was present in the original data that you&apos;ve received, but hasn&apos;t been mentioned in the validation schema has been automatically removed to avoid any surprises.

## What about TypeScript?

For those of us that use TypeScript a natural question arises - wouldn&apos;t I have to do double work now to define my types? If that were necessary, I wouldn&apos;t be writing this blog post. You can infer types from the schema and easily share them with other parts of your app:

```tsx
import z from &apos;zod&apos;

export type HeroBE = z.infer&lt;typeof HeroBESchema&gt;
export type Hero = z.infer&lt;typeof HeroFESchema&gt;
```

(For those of us who don&apos;t use TypeScript another natural question arises - why am I not using TypeScript?)

## Filtering data

I mentioned earlier in this article that sometimes you&apos;ll need to decide what to do about data that you got, but can&apos;t do anything with (like the ecommerce product with no name). Of course, depending on your domain, you might have a better approach, but a good starting point would be:

- Stop this data from entering your app. Note that we’re talking here about *invalid* objects (data is not there), not *malformed* ones (the data is there, just not in a great shape). If you can’t use it, ignore it.

- File an error. If you get an object that you can’t use, it means there’s an error _somewhere_. Either a technical one (in data validation on one of the sides), or a communication one (there’s a misunderstanding about the contract between the systems). Usually we’d use the same [Sentry reporting system](https://docs.sentry.io/platforms/react-native/) that we use for handling any other exception.

```tsx{5,7-11,29}
import z from &apos;zod&apos;

const mapBEToFE = (hero: HeroBE): Hero | undefined =&gt; {
  try {
    return HeroFESchema.parse(hero)
  } catch (error) {
    if (error instanceof z.ZodError) {
      console.error(&apos;[mapBEToFE] Not valid: &apos;, error.issues)
      // Report error to Sentry
      return undefined
    }

    console.error(&apos;[mapBEToFE] Parsing error:&apos;, error)
    throw error
  }
}

export const fetchHeroes = async () =&gt; {
  // (…)
  try {
    const response = await fetch(requestUrl, {
      headers: {
        Accept: &apos;application/json&apos;,
      },
    })

    const heroes = (await response.json())?.data?.results

    return heroes.map(mapBEToFE).filter(hero =&gt; hero !== undefined)
  } catch (error) {
    console.error(&apos;[fetchHeroes] Networking error:&apos;, error)
    throw error
  }
}
```

Note: we don&apos;t throw away the whole collection when only one of the items is invalid.

## Translating back to BE

If you want to send data back (for example to update a record), you&apos;ll need to adhere to the backend service&apos;s data format. That usually means translating data back to its original form.

Unfortunately, it requires some _monkey work_. While we do know what `Hero` and `HeroBE` are like, `zod` does not give us an easy way to reverse the transformation that we&apos;ve defined before. However, because we can be confident in our data being valid (because we use TypeScript, right?) we don&apos;t really need `zod` here. This map function can be as simple as:

```tsx
import { D } from &apos;@mobily/ts-belt&apos;

const mapFEToBE = (hero: Hero): HeroBE =&gt; ({
  urls: [{ type: &apos;thumbnail&apos;, url: hero.url }],
  ...D.deleteKeys(hero, [&apos;url&apos;]),
})
```

Note: of course here we squashed the URLs array into just one URL and then later made an array back from it, so we&apos;ve lost some data. We probably should not send this particular object as an update (but Marvel API doesn&apos;t allow any POST requests anyway).

## Summary

`zod` offers a nice way to implement the (often tedious) border guards for your remote data with only minimal boilerplate. If you&apos;re doing it by hand (or not doing it 🙈), I&apos;d strongly recommend giving this approach a try. 

However, if you&apos;re already using `zod` and looking for a solution with even less boilerplate, we might soon have an option for you as well. Watch this space!</media:content></item><item><title><![CDATA[No small apps]]></title><description><![CDATA[Disclaimer: This topic is being discussed only in the context that we work in, so from the perspective of mobile apps. For web apps user…]]></description><link>https://brainsandbeards.com/blog/2023-no-small-apps/</link><guid isPermaLink="false">https://brainsandbeards.com/blog/2023-no-small-apps/</guid><dc:creator><![CDATA[Wojciech Ogrodowczyk]]></dc:creator><pubDate>Fri, 05 May 2023 11:12:41 GMT</pubDate><enclosure url="https://brainsandbeards.com/static/77f8837a234eeeb1b3ddffce5a1ff5a1/d50c0/header.jpg" length="117852" type="image/jpg"/><media:content>
**Disclaimer**: This topic is being discussed only in the context that we work in, so from the perspective of mobile apps. For _web_ apps user expectations differ, so they&apos;d require different analysis.

There&apos;s a proliferation of &quot;should you be using Redux in your React app&quot; blog posts that all end up on the same note - it depends. They all write about local component state, Context API, react-query, etc. and conclude that if your app is small enough, Redux is an overkill. Those posts bother me for two reasons:
- They usually make no distinction between React.JS (web) and React NATIVE (mobile) apps. I think the gap between the two is big enough that they warrant separate analysis. 
- On mobile there are no apps small enough that Redux (or MobX, or other equivalent) is an overkill.

But don&apos;t worry, this post is not really about Redux. I want to justify my (probably controversial) opinion that there no small apps and offer a summary of what it means for the way we make technical decisions.

## What do you mean by &quot;no small apps&quot;?

There&apos;s a lot of requirements that pop up for mobile apps irrespective of what you&apos;re actually building. I&apos;d call it an _implied_ scope, because it&apos;s rarely discussed as features that we&apos;re explicitly working on in order to solve the problem that the app is aiming to address. Rather, that&apos;s something that we know (or even worse - don&apos;t) we&apos;ll have to build in somewhere along the way.

I&apos;d group them in two sections - what your users expect from any app and what kind of technical &quot;base features&quot; you&apos;ll have to build under the hood.

## User expectations

The quality threshold for mobile apps is pretty high nowadays and your users will expect your app to meet it. Here&apos;s a few of their expectations that grow the scope of your app.

### Visual themes

Like it or not, some of your users use dark mode and some don’t. But both groups expect your app to fit in. 

Implementing dark mode support basically means an implementation of a global visual theme manager that we can switch based on the time of day or user preferences. On a technical side it not only means you need to wrap everything in a global Context, but you need to start with a design system that accepts different colour (and image asset!) schemes.

### Offline support

This one&apos;s a huge difference in expectations between a web app and a mobile app. When opening a web page, people understand that you might need internet connection, but a mobile app that freezes when trying to refetch some data it already had before is unacceptable.

Depending on the app that you&apos;re building you might want to take the offline support to different levels. For example, in an ecommerce app it&apos;s reasonable to expect that you&apos;d use cached product information, so that the user is not blocked by a loading screen on literally _every_ screen transition. On the other hand, it&apos;s not reasonable to assume that the customer will be able to complete the checkout flow all the way till the end without making any network calls.

However, for a medical app that we worked on once it was important that users input data at a particular time - doesn&apos;t matter whether they&apos;re at home using wifi, deep inside a coal mine, or trekking across Sahara. Coincidentally, another requirement specified that they need to be authenticated every time they open the app. That meant building both an authentication and data entry systems that can be used offline.

### Localisation

To be fair, my experience in building apps is limited to targeting users in the Global North (mostly EU, US, Canada), but I believe (but not _know_) that the following will also hold true if you&apos;re launching apps targeting other regions. 

That being said, you&apos;ll need to at least cover multiple languages. English is usually a good place to start, but if you stick to not using anything else, in US you might alienate your users who&apos;d rather use Spanish, or in Canada - French. In the EU the freedom of moving between countries means that even if you&apos;re building an app for a specific country that only has one official language, you&apos;ll have plenty of foreigners use it. For example, in Poland government services are available not only in Polish, but often also in English and Ukrainian.

And once your&apos;re already introducing a translation system, it&apos;s worth going a tiny step forward and offer full localisation. You can use our [i18n guide](/blog/i18n-in-react-native-apps/) to help you with that.

## Technical requirements

### Authentication

Apart from casual games, there aren’t many apps that make sense without a backend service. And using that usually means accounts, which in turn means the whole authentication flow - sign in and up, forgot password flow, etc. That&apos;s several API calls and at least 3-4 screens plus any explanations that you might offer to the user to guide them through the process.

On top of that, once you already have the user account you should have a profile screen in the app with options to update this data. Finally, if you want to earn money using the app you&apos;d need to add payment management options, transaction history, etc.

Of course, different parts of the app will want to know whether you&apos;re signed in and maybe what kind of user role (premium, admin, etc.) you have, so that information needs to be passed around somehow. That brings us to…

### State management

Here we come back to the starting point and my comment that no app is too small for Redux. Let&apos;s look at the options:

- Local component state. Cool, if you can use it - do it, no question about it. But if you want a component deeper in your hierarchy to know what role (or name) the signed-in user has, you&apos;re out of luck. And sooner or later, you&apos;ll need to know. So local component state can&apos;t be your only option.

- Context API. Great choice for a simple solution, but only if you don&apos;t have more than one of them. Once you have more than one, things start to fall apart for two reasons. One, global variables (state) is only good if it&apos;s limited to one place - if you sprinkle them around it becomes hard to maintain. Two, if you&apos;ve build your state management on using several Contexts and implemented a solution to share information between them, you&apos;ve just implemented Redux from scratch - but probably poorly and with worse performance. Finally, from the analysis above we see we already have two global contexts - a visual theme and user information - without even building any real feature.

- Jotai and other atom based solutions. I haven&apos;t used Jotai in any application, so what do I know 🤷‍♂️ However, the reason why I didn&apos;t is because I&apos;m sceptical about the composability of the solution. Atoms seem like a great idea for _localising_ state, which seems great at first, but sooner or later you&apos;ll come across a business requirement that basically means that something needs to know about everything else. Example: you want to display a product, which has a net price (coming from the backend) + VAT tax, which is based on user&apos;s &quot;location&quot;. This could be a GPS location of the phone (cached in a store somewhere to save battery), the address in the user&apos;s profile (stored in the user store), the location of the bank that issued their payment card (stored in the payment metods store) - or, the most common case, a combination of the above. Then you&apos;re in the territory where you start hoping everything was stored together from the get-go.

- Redux / MobX / Zustand and other equivalents. As Sherlock Holmes used to say: &quot;When you have eliminated all which is impossible, then whatever remains, however improbable, must be the truth&quot;.

### Communication

One more thing that I&apos;d want to highlight in this section as a non-obvious requirement is all the communication-related responsibilities you&apos;ll be expected to handle:

- REST, GraphQL, tRPC, etc. That&apos;s the networking protocol that you chose (or the backend team chose for you) for the basic information exchange.

- Data caching for offline support. We&apos;ve mentioned before that your users expect to be able to use your app also when offline, so caching is a necessity. How hard can it be? Well, a non-obvious requirement here is that you can&apos;t rely on a simple automated system, but you rather need something that allows you to perform manual cache modifications, due to…

- Optimistic updates. Some of the apps can be read-only (example: news reader) and then you&apos;re good, because you never have to update anything. However, most of the apps are not (example: news reader that lets you _favourite_ an article), so in order to let users perform those when offline you&apos;ll need to be able to: manually modify the cache (so that after refresh your users see the new state, for example that an article is marked as favourite), networking queue that will store commands that we want to send to the server once we&apos;re online again, and an error handling system that will appropriately handle errors on those queued requests (for example the article you wanted to _favourite_ has been deleted in the meantime - what do we do?).

- Push notifications. Marketing, amirite?

- Deep linking 🙈

## Wait a minute…

Of course, you don’t need all that for your MVP, but you’ll need it _eventually_ to have a competitive app. When you&apos;re starting out you want to focus on building the parts that bring most value to the user - whether that&apos;s a translation system, or adding a &quot;social login&quot; option. But at some point, once the big things are taken care of, you&apos;ll have to face all of the above.

Also, in some cases, you might (and many companies do) get away with just having a really basic app when:

- Your clients don’t interact with it often. For example, setting up an Xbox requires a mobile app, but some people (like yours truly) don&apos;t really run this app anymore. Coincidentally, the Xbox app is pretty cool and very far from a &quot;basic&quot; version. However, I wouldn&apos;t mind if it were (since I only used it once).

- Your product / service is so much better than your competition that clients would rather put up with a bad app than miss out on the value that it brings. For example: many EV charging apps are _not great_. However, drivers pick where to charge not based on the app they&apos;d rather use, but on a particular charger&apos;s location and price.

## Okay, but how does it look in practise?

I checked what apps I keep on my phone (iOS) that I&apos;d consider _basic_. Either due to the overall feature set, or how _shallow_ those features have been implemented. Here&apos;s a few examples:

- Weather app - that would be a classic example of a basic app done well. A small set of features, no customisation, no user management - that all leads to a tiny app.

- Pocket Tune - just a string tuning app that I use once or twice a year when I remember I still have an ukulele that I wanted to learn how to play 🤷‍♂️ Great, simple app. Hard to build a business around it, though.

- Duo - another basic app. I&apos;m surprised 2FA is not handled by a system app, but this one&apos;s a nice, simple (and also free, do you see a pattern here?) replacement.

- Mullvad VPN - this one&apos;s an example that contradicts everything I wrote above. This one doesn&apos;t have many features (although I have no idea how complicated managing a VPN on a mobile client is), payment is really streamlined (no user accounts) and still it&apos;s a solid business.

- Onewheel - this one&apos;s not a simple app in terms of feature list, but… it does everything in the seemingly simplest way possible. Connecting the board for the first time is a pain, errors don&apos;t tell you anything, no caching, etc. The official app is so bad that the Onewheel community [reverse-engineered the protocols and built an open source version](https://github.com/OnewheelCommunityEdition/OWCE_App).

## So, what are you saying?

I believe virtually any app worth building will eventually outgrow the &quot;small enough&quot; category (given enough development resources). That means for any technical choice you make early you have two choices:

- embrace from the start the size the app will grow into

- plan for switching the temporary solution to a mature one later

And I think that&apos;s good news, because it simplifies the decision making around app architecture.</media:content></item></channel></rss>