<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Sachin Rupani</title>
    <description>The latest articles on Forem by Sachin Rupani (@sachinrupani).</description>
    <link>https://forem.com/sachinrupani</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2654630%2F592f0f6b-1fcb-445d-af23-89079d04710c.png</url>
      <title>Forem: Sachin Rupani</title>
      <link>https://forem.com/sachinrupani</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/sachinrupani"/>
    <language>en</language>
    <item>
      <title>Designing a Scalable React Native + Expo Router Folder Structure</title>
      <dc:creator>Sachin Rupani</dc:creator>
      <pubDate>Sun, 08 Feb 2026 11:03:21 +0000</pubDate>
      <link>https://forem.com/sachinrupani/designing-a-scalable-react-native-expo-router-folder-structure-3dnj</link>
      <guid>https://forem.com/sachinrupani/designing-a-scalable-react-native-expo-router-folder-structure-3dnj</guid>
      <description>&lt;p&gt;Over the years, one lesson has repeated itself across teams and products:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Your folder structure is not about aesthetics — it’s about decision-making at scale.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Recently, I’ve been working on a React Native app using Expo + Expo Router, and I want to share a structure that has worked exceptionally well for large, long-lived apps.&lt;/p&gt;

&lt;p&gt;This post is meant to share insights with the dev community, especially folks building apps that go beyond MVPs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Note&lt;/em&gt;&lt;/strong&gt;: All the below folders will reside in &lt;strong&gt;src/&lt;/strong&gt; folder at the highest level.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;src/&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;└── 📁src
    └── 📁app
    └── 📁components
    └── 📁config
    └── 📁hooks
    └── 📁lib
    └── 📁providers
    └── 📁screens  
    └── 📁utils
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🧭 &lt;strong&gt;app&lt;/strong&gt;/ — Routing as a First-Class Citizen&lt;/p&gt;

&lt;p&gt;Expo Router shines when routes reflect user flow, not technical shortcuts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;└── 📁app
    └── 📁(authenticated)
    └── 📁(home-tabs)
    └── 📁(unauthenticated)
    ├── _layout.tsx
    └── index.tsx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why this works so well:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;(unauthenticated)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Login, OTP, onboarding&lt;/li&gt;
&lt;li&gt;No tabs, no distractions&lt;/li&gt;
&lt;li&gt;Clear boundary for auth guards&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;(authenticated)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Entry point after login&lt;/li&gt;
&lt;li&gt;Handles app-level layouts, redirects, and global state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;(home-tabs)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only the screens that truly belong to bottom tabs&lt;/li&gt;
&lt;li&gt;Everything else (modals, flows, detail screens) lives outside tabs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This makes the flow crystal clear:&lt;br&gt;
Unauthenticated → Authenticated → Tab-based home → Non-tab flows&lt;br&gt;
No guessing. No accidental tab nesting. No router spaghetti.&lt;/p&gt;

&lt;p&gt;🧱 &lt;strong&gt;components&lt;/strong&gt;/ — Design System, Not Random Reuse&lt;/p&gt;

&lt;p&gt;The structure follows Atomic Design, but applied pragmatically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;components/
 ├── atoms
 ├── molecules
 ├── organisms
 └── templates
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key principles:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Atoms&lt;/strong&gt; → Pure, reusable, testable UI primitives&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Molecules&lt;/strong&gt; → Small compositions with intent&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Organisms&lt;/strong&gt; → Feature-aware UI blocks&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Templates&lt;/strong&gt; → Layout patterns, not screens&lt;/p&gt;

&lt;p&gt;This ensures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;UI consistency across the app&lt;/li&gt;
&lt;li&gt;Easy refactors when design systems evolve&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Components stay reusable without becoming generic junk drawers.&lt;/p&gt;

&lt;p&gt;🧠 &lt;strong&gt;lib&lt;/strong&gt;/ — The App’s Brain (Not a Dumping Ground)&lt;/p&gt;

&lt;p&gt;Here, lib/ is intentionally structured:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lib/
 ├── auth
 ├── backend
 ├── implementation
 ├── interface
 └── vector-icon
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;backend&lt;/strong&gt;/&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API clients (Axios / fetch wrappers)&lt;/li&gt;
&lt;li&gt;TanStack Query client setup&lt;/li&gt;
&lt;li&gt;Server-state hooks&lt;/li&gt;
&lt;li&gt;Backend data models&lt;/li&gt;
&lt;li&gt;interface / implementation&lt;/li&gt;
&lt;li&gt;Clear contracts&lt;/li&gt;
&lt;li&gt;Platform-agnostic abstractions&lt;/li&gt;
&lt;li&gt;Easy to mock, test, or replace later&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Eg.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;└── 📁backend
    └── 📁_models
    └── 📁server-state
        └── 📁queries
            ├── useGetThoughtOfDayApi.ts
        ├── query-client.ts
    └── 📁supabase
        ├── supabase-client.ts
        ├── supabase-safe-call.ts
    └── 📁supabase-db
        └── fetch-though-of-day.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;auth&lt;/strong&gt;/&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Auth state, providers, and boundaries live together&lt;/li&gt;
&lt;li&gt;No auth logic leaking into UI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This separation pays off when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;APIs change&lt;/li&gt;
&lt;li&gt;You swap backend providers&lt;/li&gt;
&lt;li&gt;You test without the network&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;📱 &lt;strong&gt;screens&lt;/strong&gt;/ — Screens Are Not Routes&lt;br&gt;
A subtle but important distinction.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;screens/
 ├── authenticated
 └── unauthenticated
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Screens contain UI + screen-level state&lt;/li&gt;
&lt;li&gt;Routes (app/) only decide when a screen is shown&lt;/li&gt;
&lt;li&gt;This keeps navigation thin and screens testable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Result:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Screens are portable. Routes are declarative.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;🧰 &lt;strong&gt;utils&lt;/strong&gt;/, &lt;strong&gt;hooks&lt;/strong&gt;/, &lt;strong&gt;providers&lt;/strong&gt;/ — Supporting the Scale&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;utils&lt;/strong&gt;/&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pure logic, zero React dependency&lt;/li&gt;
&lt;li&gt;Easy to test, easy to trust&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;hooks&lt;/strong&gt;/&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;App-specific behavior&lt;/li&gt;
&lt;li&gt;Not generic utilities disguised as hooks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;providers&lt;/strong&gt;/&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Theme, query client, safe area, global app context&lt;/li&gt;
&lt;li&gt;Single source of truth for app-wide concerns&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🏗️ &lt;strong&gt;Why This Structure Scales&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scales across multiple teams&lt;/li&gt;
&lt;li&gt;Encourages clear ownership&lt;/li&gt;
&lt;li&gt;Reduces cognitive load for new engineers&lt;/li&gt;
&lt;li&gt;Supports feature-based growth without rewrites&lt;/li&gt;
&lt;li&gt;Works equally well for React Native + Web (Expo)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most importantly, it reflects how users move through the app, not how the framework works internally.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;💡 Final Thought&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Frameworks evolve.&lt;/li&gt;
&lt;li&gt;Product requirements change.&lt;/li&gt;
&lt;li&gt;Teams grow.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A good folder structure doesn’t fight that — it absorbs it. Hope this helps someone designing their next large-scale React Native app. Would love to hear how others are structuring their Expo Router projects 👋&lt;/p&gt;

&lt;p&gt;☑️ &lt;strong&gt;Pro tip&lt;/strong&gt;:&lt;br&gt;
Keep screen components lean. Let screens focus on composition and navigation, while individual section components own their state, custom hooks and API or TanStack Query logic close to where it’s used.&lt;/p&gt;

&lt;p&gt;Hope this helps anyone designing a production-grade Expo Router app.&lt;br&gt;
Would love to hear how others are structuring their apps 👇&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>exporouter</category>
      <category>typescript</category>
      <category>mobileapp</category>
    </item>
    <item>
      <title>Simple remove transition animation in react native</title>
      <dc:creator>Sachin Rupani</dc:creator>
      <pubDate>Sat, 04 Jan 2025 12:37:24 +0000</pubDate>
      <link>https://forem.com/sachinrupani/simple-remove-transition-animation-in-react-native-29l3</link>
      <guid>https://forem.com/sachinrupani/simple-remove-transition-animation-in-react-native-29l3</guid>
      <description>&lt;h2&gt;
  
  
  Hello Devs
&lt;/h2&gt;

&lt;p&gt;Today I would like to share a bit of item remove animation code (&lt;strong&gt;translate / scale animation&lt;/strong&gt;) in &lt;strong&gt;react native&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here's a glimpse of what I've prepared.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhx6q54k4wj3ela8sn0n6.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhx6q54k4wj3ela8sn0n6.gif" alt=" " width="720" height="1606"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Suppose, you have a list of items in your project (very common example, which most of us deal with in our day to day projects).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export interface ItemEntity {
  id: number;
  taskTitle: string;
}

export const sampleItems: Array&amp;lt;ItemEntity&amp;gt; = [
  {
    id: 1,
    taskTitle: "Breakfast",
  },
  {
    id: 2,
    taskTitle: "Lunch",
  },
  {
    id: 3,
    taskTitle: "Sports",
  },
  {
    id: 4,
    taskTitle: "Dinner",
  },
];
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And these items populated into a flat list for displaying inside a react native application&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// state data items to be populated in flat list
 const [dataItems, setDataItems] = useState&amp;lt;Array&amp;lt;ItemEntity&amp;gt;&amp;gt;(sampleItems);

// Handler function to remove the item (after animation ends)
  const _handleAfterRemovedAction = (givenEntity: ItemEntity) =&amp;gt; {
    setDataItems(
      dataItems.filter(
        (itemIterated: ItemEntity) =&amp;gt; itemIterated.id !== givenEntity.id,
      ),
    );
  };

// item in a list
const _renderItem = ({item, index}: {item: ItemEntity; index: number}) =&amp;gt; {
    return (
      &amp;lt;ListViewItem
        itemEntity={item}
        onTaskCompletedWithAnimation={_handleAfterRemovedAction}
      /&amp;gt;
    );
  };

// Flat List component with some styles
&amp;lt;FlatList
      style={stylesToUse.listViewStyle}
      contentContainerStyle={stylesToUse.listViewContentContainerStyle}
      ListHeaderComponent={_renderHeader()}
      data={dataItems}
      keyExtractor={(item: ItemEntity, index: number) =&amp;gt;
        `${item.id}_${item.taskTitle}_${index}`
      }
      renderItem={_renderItem}
      ItemSeparatorComponent={_renderSeparator}
      showsVerticalScrollIndicator={false}
    /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, the &lt;strong&gt;animation&lt;/strong&gt; magic happens in our item component on the click of remove button.&lt;/p&gt;

&lt;p&gt;&lt;u&gt;Code Snippet&lt;/u&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import {Animated} from 'react-native';

 const _removeItemWithAnimation = () =&amp;gt; {
    Animated.parallel([
      Animated.timing(translateAnim, {
        // Negative Value: Translate to left, Positive Value: Translate to right
        toValue: -1000,
        duration: 800,
        useNativeDriver: true,
      }),
    ]).start(() =&amp;gt; { /** on end of animation, this block executes, handle your removal logic */});
  };
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;u&gt;Full code&lt;/u&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ListViewItem.tsx&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import {Animated, Text, TouchableOpacity, View} from 'react-native';
import {ItemEntity} from '../../data/ItemEntity';
import {listViewItemStyles} from './styles/ListViewItem.styles';

export type PropsListViewItem = {
  itemEntity: ItemEntity;
  onTaskCompletedWithAnimation?: (givenItem: ItemEntity) =&amp;gt; void;
};

export const ListViewItem = ({
  itemEntity,
  onTaskCompletedWithAnimation,
}: PropsListViewItem) =&amp;gt; {
  const stylesToUse = listViewItemStyles;

  const translateAnim = new Animated.Value(0); // Initial position

  // trigger remove animation
  const _removeItemWithAnimation = () =&amp;gt; {
    Animated.parallel([
      Animated.timing(translateAnim, {
        // Negative Value: Translate to left, Positive Value: Translate to right
        toValue: -1000,
        duration: 800,
        useNativeDriver: true,
      }),
    ]).start(() =&amp;gt; onTaskCompletedWithAnimation?.(itemEntity));
  };

  const _renderTitle = () =&amp;gt; {
    return (
      &amp;lt;Text style={stylesToUse.titleTextStyle}&amp;gt;{itemEntity.taskTitle}&amp;lt;/Text&amp;gt;
    );
  };

  const _renderRemoveAction = () =&amp;gt; {
    return (
      &amp;lt;TouchableOpacity
        style={stylesToUse.deleteButtonContainer}
        onPress={() =&amp;gt; {
          _removeItemWithAnimation();
        }}&amp;gt;
        &amp;lt;Text style={stylesToUse.deleteButtonTextStyle}&amp;gt;Remove&amp;lt;/Text&amp;gt;
      &amp;lt;/TouchableOpacity&amp;gt;
    );
  };

  return (
    &amp;lt;Animated.View
      style={[
        stylesToUse.container,
        {
          transform: [{translateX: translateAnim}]
        },
      ]}&amp;gt;
      &amp;lt;View style={stylesToUse.row}&amp;gt;
        {/* Task Title */}
        {_renderTitle()}

        {/* Remove button */}
        {_renderRemoveAction()}
      &amp;lt;/View&amp;gt;
    &amp;lt;/Animated.View&amp;gt;
  );
};

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;ListViewItem.styles.ts&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import {StyleSheet} from 'react-native';

export const listViewItemStyles = StyleSheet.create({
  container: {
    backgroundColor: '#e1e1e1',
    borderRadius: 8,
    elevation: 1,
  },

  row: {
    padding: 20,
    flexDirection: 'row',
    alignItems: 'center',
    gap: 8,
  },

  titleTextStyle: {
    flex: 1,
    fontSize: 16,
    fontWeight: '500',
    color: '#212121',
  },

  deleteButtonContainer: {
    paddingHorizontal: 12,
    paddingVertical: 8,
    backgroundColor: '#EF5350',
    borderRadius: 24,
  },

  deleteButtonTextStyle: {
    fontSize: 12,
    color: '#ffffff',
  },
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The translation removal animation can be relevant in following use cases.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Marking a task as done/incomplete.&lt;/li&gt;
&lt;li&gt;Remove an item from the list (we just did)&lt;/li&gt;
&lt;li&gt;Archiving an item
etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  BONUS
&lt;/h2&gt;

&lt;p&gt;We can also combine the translate animation with scale animation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const scaleAnim = new Animated.Value(1); // Initial scale value

  const _removeItemWithAnimation = () =&amp;gt; {
    Animated.parallel([
      Animated.timing(translateAnim, {
        // Negative Value: Translate to left, Positive Value: Translate to right
        toValue: -1000,
        duration: 800,
        useNativeDriver: true,
      }),
       Animated.timing(scaleAnim, {
        // toValue 0 indicates the item will shrink to size 0
        toValue: 0,
        duration: 800,
        useNativeDriver: true,
      }),
    ]).start(() =&amp;gt; onTaskCompletedWithAnimation?.(itemEntity));
  };
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And our animated view (in ListViewItem.tsx)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   &amp;lt;Animated.View
      style={[
        stylesToUse.container,
        {
          transform: [{translateX: translateAnim}, {scale: scaleAnim}], 
        },
      ]}&amp;gt; ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Play around with &lt;code&gt;duration&lt;/code&gt; and &lt;code&gt;toValue&lt;/code&gt; to experience yourself.&lt;/p&gt;

&lt;p&gt;Or you can just use scale animation and omit translate animation based on your requirement.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;Quick Bytes:&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Translation&lt;/strong&gt; - Translation refers to moving of an item from one direction to the other or we can say position of an item changes entirely.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scale&lt;/strong&gt; - Item's height/width updates. Either it's enlarged or it shrinks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Happy coding.&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>translateanimation</category>
      <category>typescript</category>
    </item>
  </channel>
</rss>
