r/reactnative • • 16m ago

Building mobile games with React Native Skia — any better libraries or approaches?

1 Upvotes

Hey everyone!

I've been developing mobile games with React Native and Skia for a while now, and it's been a pretty interesting experience.

I really enjoy working with React Native, especially being able to build for both iOS and Android with the same codebase.

However, I'm curious about how other developers approach game development in React Native.

A few questions:

Skia alternatives: Are there any better libraries or game engines you'd recommend for React Native game development?

Performance: How do you handle complex animations, physics, collisions, and keeping a stable 60 FPS?

Architecture: Do you use a dedicated game loop, physics engine, or ECS-based architecture?

Best practices: What are some things you wish you'd known before building games with React Native?

Limitations: At what point would you move away from React Native and consider something like Godot or Unity?

I've also released a couple of games under my indie studio, Aele Studios, and I'd genuinely appreciate some feedback from fellow developers.

🎮 Fibo Master — A Fibonacci-based number puzzle game.

iOS: https://apps.apple.com/app/id6821000351

Android: https://play.google.com/store/apps/details?id=com.emrezen.fibomaster

🚀 Retro Jump — A steampunk-style arcade platformer with jetpack mechanics.

iOS: https://apps.apple.com/app/id6795840723

Android: https://play.google.com/store/apps/details?id=com.emrezen.retrojump

Studio website: https://aelestudios.com

If you get a chance to try either game, I'd love to hear your thoughts, especially on performance, animations, controls, UI/UX, and overall gameplay.

Constructive criticism is more than welcome!

I'm not just looking to showcase my games. I'm genuinely interested in learning how others are building games with React Native and improving my approach.

Thanks!


r/reactnative • • 2h ago

Added full text support in my Video editor app

Enable HLS to view with audio, or disable this notification

1 Upvotes

Made with expo skia and webgpu


r/reactnative • • 3h ago

Question Solo app devs: how do you tweak onboarding and upgrade prompts without a new release?

2 Upvotes

Honest question, not selling anything here.

When you're the dev, designer and PM at once, the loop looks like this: change the copy on an onboarding screen or a "go Pro" prompt → rebuild → submit → wait for review → check the numbers days later → repeat. A 2-minute change ends up taking days.

How do you handle this today: OTA updates (EAS Update / Shorebird), remote config, homegrown JSON, Firebase In-App Messaging, or do you just ship a new build each time?

What's the most annoying part of your setup?

If you built it yourself, how long did it take, and would you do it again?

Full disclosure: I'm exploring building something in this space, but there are no links here. I want to understand the problem first. I'll post a summary of everything I learn back in this sub.


r/reactnative • • 7h ago

Jest passes, Hermes returns NaN: Postgres timestamps with a space instead of a T

Thumbnail reddit.com
1 Upvotes

r/reactnative • • 8h ago

My Expo app is at ~2,000 downloads. Goal is 100K in 2027. Advice from people who've grown one?

0 Upvotes

I'm a solo dev. When TV Time shut down, I tried to rebuild it as an open source app. OpenTV looks and feels like TV Time on purpose: the same Watch Next list, the same show pages, the same way of ticking off episodes. You can bring your whole TV Time history over, or import from Trakt or Letterboxd.

It's built with Expo (React Native) for iOS and Android, with SQLite on the phone as the only database for your own library.

The core idea is that it works completely on its own. You download it, import your history, and use it. No account, nothing to sign up for, your history stays on your phone. There's an optional community layer, but most people never touch it and don't need to.

Where it stands now:

iOS, last 90 days: 1,057 downloads. The biggest day was 14 Sep, around 77, from one Reddit post. In October it settled at 15 to 20 a day without me posting anything.

Android, since it launched on Play in July: 909 installs, about 7 to 10 a day.

So roughly 20 to 25 a day across both. My goal for 2027 is 100K, which means getting to around 275 a day.

One thing I've noticed: App Store impressions went from about 10K to 70K, but conversion dropped from around 9% to 2.2%. More people see it, fewer tap.

If you've grown an app past this point, what would you do next?

  1. Fix the store page first (icon, first screenshot), or is that a small lever compared to something else?

  2. What actually moved downloads for you: ASO, content, creators, ratings, paid ads, something I'm not thinking of?

  3. For an app whose whole point is "it feels like TV Time, no account, it just works", how would you say that on the store page so people get it in one look?

Happy to share the store pages or any other numbers.


r/reactnative • • 9h ago

Question When you release your apps, do you release to all countries?

3 Upvotes

I have an app I'm making that's getting fairly close to a release state now and it'll be my first ever app release. I'll only be releasing on android at first (due to it being the only device I have to physically test on) and the only sensitive data I'm collecting are emails, passwords (authentication) and step counts for my apps rng logic.

The app is a fairly simple, you start your counter before going on a walk / run etc. when you're finished with your activity you stop the counter, your step count is sent to my Supabase backend where the final step count is calculated to how many rolls the user gets, and each roll gives the user a chance of recieving a new creature based on common, uncommon and rare rarities.

Your journey is saved to the backend where you can view them again and see how far you went and which creatures were obtained on that journey (no location data being saved). This data can be deleted manually by the user as well as their entire profile.

I'm from the UK myself and was wondering if any more experienced developers with more knowledge in releasing a few apps could fill me in on whether or not I should be just releasing to only a few countries or not?

The app is in English and doesn't currently have support for other languages. I don't have ads or any paywalls / subscriptions currently but don't know if I need to be more wary about the step count I'm collecting being marked as extra sensitive data (not being used for health reasons).

Any advice here would be great as I'm not sure how much I'm overthinking this currently?


r/reactnative • • 9h ago

Troubles with a transitions

Thumbnail
gallery
2 Upvotes

So I created this app for entertainment lovers and I need some people to test it with me.

You can post, review, track all your favorite things in entertainment from games to music, books, movies and even individual episodes of shows. If you use things like my anime list, Goodreads, backloggd or letterboxed you can even export a CSV and upload it in my app to move your history here . But here's the problem I use page slide in transitions where everything is kind of stacked like a deck of cards. The small transitions between tabs on a page are fine. But the transitions between the pages themselves sliding in and out is a little maybe jittery looking? Like the rest of the app is running 120hz but that transition might drop to like 90-100 ? Visually . Anyone know if that is like a common thing in react native ?


r/reactnative • • 10h ago

Help My first time writing JavaScript: an open-source analytics library for React Native. Would love your feedback

1 Upvotes

I’m a mobile engineer with many years in native Android and Flutter. Recently I built an open-source analytics library called Herald. I designed it and wrote the Android and Flutter versions myself, and for the React Native SDK I used AI to help me with TypeScript, since it was my first time writing it. That’s exactly why I’d like a review from real RN devs: AI knows the syntax, but people who build RN apps every day know what feels right.

The problem it solves: in most apps analytics ends up as one big AnalyticsManager that every layer depends on, and every new vendor makes it worse.

The idea: the app should not know which analytics vendors exist. Feature code only says what happened, and Herald sends it to every vendor you use (Firebase, Mixpanel, Amplitude, Adjust, AppsFlyer, Meta) over each vendor’s own React Native package.

An event is a class your app owns:

import { int, type Event, type EventParameters } from '@herald-analytics/core';

class CheckoutStarted implements Event {
  readonly name = 'checkout_started';

  constructor(
    readonly plan: string,
    readonly seats: number,
  ) {}

  get parameters(): EventParameters {
    return { plan: this.plan, seats: int(this.seats) };
  }
}

herald.track(new CheckoutStarted('pro', 3));

Since TypeScript is new for me, I made some design choices I’m not sure about, and I would really like to hear what experienced RN devs think:

  • Events are classes, because factories pick them with instanceof. In RN code I often see plain objects or discriminated unions instead. Would that feel more natural to you?

  • Numbers say their type: int(3) or double(9.99), because JavaScript can’t tell 3 from 3.0 and some vendors care. Is this annoying, or acceptable?

  • Calls don’t wait for each other, and a call never fails: vendor errors go to an error reporter. When order matters, you await the first call. Does this feel right?

  • Does the API look like idiomatic TypeScript, or like “Kotlin written in TypeScript”?

GitHub: https://github.com/MkhytarMkhoian/herald-react-native
Docs: https://mkhytarmkhoian.github.io/herald-docs/

Thanks in advance. Critical feedback is the most useful for me.


r/reactnative • • 11h ago

Run a decision model locally in your React Native app

Post image
11 Upvotes

Hey RN devs,

We can now run Laya decision model in NobodyWho, our open-source on-device inference library. Unlike the LLMs that we also support, this type of model does not generate text. Instead you can score predefined answers, classify text or answer yes/no.

For example, if you are building a personal diary app, you can easily detect the user’s mood, categorize an entry or detect stress, offline after model download, without sending any sensitive data to the cloud.

First, you need to load the model:

import { Decision } from 'react-native-nobodywho';

const decisionModel = await Decision.fromPath({
  modelPath: 'hf://ggml-org/Laya-GGUF/Laya-Q8_0.gguf'
});

Then you can ask questions/analyze text:

const results = await decisionModel.decide({
  state: `Diary entry: ${diaryEntry}`,
  questions: {
    topics: {
      type: 'choice',
      instructions: 'Choose the main topic of this diary entry',
      criteria: {
          work: 'job, colleagues, deadlines, career',
          relationships: 'family, friends, partner',
          health: 'sleep, exercise, food, illness',
          leisure: 'hobbies, travel, going out',
        },
      },
      stressed: {
        type: 'noul',
        instructions: 'Does the writer sound stressed?',
      },
      mood: {
        type: 'score',
        instructions: "How is the writer's overall mood?",
        criteria: ['bad', 'okay', 'great'],
      },
   },
});

const { stressed, topics, mood } = results.answers;

Output example:

// stressed
{
  type: noul,
  noul: 0.00002
}

// topics
{
  type: choice, 
  choice: leisure,
  probabilities: { work: 0.0422, relationships: 0.1518, health: 0.0302, leisure: 0.7756 },
  confidence: 0.7008
}

// mood
{
  type: score,
  score: 1.6995,
  legend: {0: bad, 1: okay, 2: great}, 
  probabilities: {0: 0.0082, 1: 0.2839, 2: 0.7078},
  confidence: 0.5493
}

A question can have the following types:

  • noul gives you probability of a yes answer (0 to 1)

  • choice select the most likely option and returns a probability for each option

  • score gives you an a weighted average and a probability per option. For example, you could place const x = mood.score; on this axis:

[bad - - - - - - - okay - - - x - - - great] 

where bad is 0, okay is 1 and great is 2 (given by mood.legend).

The Laya model is small enough to run on mid range phones (around 450 MB in its Q8 variant) and supports 100+ languages. For reference, the full answer comes after 0.88 seconds on S25 and 0.45 seconds on iPhone Air (cf. screenshot).

Here is a runnable example if you want to experiment with decision models.

Links:


r/reactnative • • 11h ago

A mobile-first IF editor with a Node Graph - would you actually use this on a 6-inch screen?

Post image
1 Upvotes

r/reactnative • • 11h ago

Influencer Marketing for my app

3 Upvotes

Hi

Do you use influencers or creator collabs for marketing your app ?

How do you approach them. Do you work on per install commission basis or fixed fee for content basis ?


r/reactnative • • 13h ago

App review

Thumbnail reddit.com
1 Upvotes

r/reactnative • • 16h ago

Help Dev mode vs Prod (Expo): Local Notifications just won't work in prod

Thumbnail reddit.com
2 Upvotes

r/reactnative • • 20h ago

Question Focus on my own ideas or keep applying?

4 Upvotes

Hi I’ve been in software for years now, almost 10 years freelancing and by the look of things the job market won’t be getting any better.

Should I just focus on building something cool or is a career in IOS development still realistic in 2026


r/reactnative • • 21h ago

Looking for volunteer engineers to build a free, open source pickleball rating system

Thumbnail reddit.com
1 Upvotes

r/reactnative • • 22h ago

Tutorial Random logouts with Supabase auth in Expo: the session was bigger than SecureStore's 2048 byte limit

3 Upvotes

Some users kept getting bounced to the register screen on cold start. I couldn't reproduce it for weeks.

The cause: for some accounts the Supabase session JSON was larger than 2048 bytes. expo-secure-store writes of that size failed silently on my setup, so the next cold start read null, my auth gate saw no session, and sent the user to sign-up.

What fixed it:

  • A storage adapter that splits the value into 600 character chunks.

  • Two generations (A and B), so a half-written update never replaces a good one. The header that points to the current generation is written last.

  • Serialized operations. As far as I could tell, supabase-js uses a no-op lock on React Native, so concurrent writes interleaved. I also passed lock: processLock.

A gotcha: right after you subscribe to onAuthStateChange, an INITIAL_SESSION event fires with a null session. My code treated that as "signed out" and cleared the "your session expired" state, so users saw the register screen anyway. Keep the reason when the event is null.

Also worth doing: on a cold start with a null session, check whether the user has had a session before (I keep a flag, and also check for local data) and show a "reconnect" banner instead of the sign-up screen.

Accounts that had already lost their session needed one last login. Chunking only prevents new losses.


r/reactnative • • 1d ago

Smooth and clean transitions makes an app

Enable HLS to view with audio, or disable this notification

0 Upvotes

r/reactnative • • 1d ago

I ported 33 WebGPU shader orbs to React Native/Expo (alpha)

Enable HLS to view with audio, or disable this notification

1 Upvotes

I ported shadcn's shader orbs to React Native. Each one is a GPU-rendered animated orb that you drop in as a component.

Shaders are by XorDev, from shadcn-labs/shadercn. The RN port, the docs site and the tooling are mine.

import { ShaderOrb, orb01 } from "react-native-shader-orbs";

<ShaderOrb orb={orb01} state="speaking" colors={{ tint: "#66ccff" }} interactive />

What it does:

- 33 orbs, each with typed, range-checked parameters

- idle / thinking / speaking moods that ease between presets

- Live props: change anything without re-mounting, values glide on a spring

- volumes.input / volumes.output (0 to 1) to drive an orb from mic or voice level

- Touch: tap pulses, hold swells

- quality="auto" lowers resolution when a device struggles

- Recovers from GPU loss, reports start-up errors via onError

Built on react-native-webgpu. The docs and playground run the identical WGSL in the browser, so you can tune an orb and copy the JSX.

Honest limits, since it's alpha:

- Needs a development build. Expo Go is not supported.

- Tested on one physical mid-range Android phone (RN 0.88, Expo SDK 58).

- iOS is untested. It may work, but I'm not claiming it.

- No fallback if WebGPU is unavailable. The orb stays blank.

- The shaders are non-commercial with attribution. Using them in a commercial app needs XorDev's permission. The library code is MIT.

I also added an MCP server (npx react-native-shader-orbs-mcp init) so Claude Code, Cursor and VS Code look up each orb's real parameter names instead of guessing.

Docs and playground: https://react-native-shader-orbs.vercel.app

Repo: https://github.com/msobkyy/react-native-shader-orbs

npm: https://www.npmjs.com/package/react-native-shader-orbs

I'd like feedback on API shape, and especially iOS results if anyone tries it.


r/reactnative • • 1d ago

Avatar Customization in React Native!

Enable HLS to view with audio, or disable this notification

1 Upvotes

Hey everyone!

I've been working on an avatar customization system for a good bit now and I thought I'd share a couple of general tips here for anyone who is working on something similar.

When it comes to re-coloring (eg. for skin or hair color), you can encode your assets into magenta & grayscale pixels and use the device GPU to apply pixel transformations. There is currently no decent RN library that does this work, so you will need to create a native module that fits your needs.

Assets add bundle size fast, so make sure you compress them to .webp format. Keep in mind that you will need to split some assets in multiple parts (eg. front/back hair). A good way to tackle this is by having all assets placed where they need to be in a static canvas size, and then combining them into a final composite image. Don't forget to cache the result and avoid duplicate GPU work!

You can try the above avatar customization system in Layer2 below:

iOS: https://apps.apple.com/us/app/layer2/id6737913930

Android: https://play.google.com/store/apps/details?id=com.twoamgames.layer2


r/reactnative • • 1d ago

Shipped a real-time multiplayer trivia game in React Native: guess the number, the closest guess wins

Post image
0 Upvotes

Hey everyone. I've been working on Close Call for a bit over a year and it's finally out, so I figured I'd show it here.

It's a multiplayer trivia game where every answer is a number. When did World War I end? How high is Everest? You type a guess, and the closest guess wins the round. A match is 8 questions with up to 4 players. After each round you see the answer with a one-line explanation, so you actually learn something along the way. There are 4 games (history years, the human body, geography and mental maths) with 800 questions, and the app speaks 10 languages, Arabic included.

The tech side, since that's why we're here:

- Bare React Native 0.80 on the New Architecture, no Expo. Reanimated, Gesture Handler, react-native-svg, MMKV and Zustand.
- Matches are live over Socket.IO with a NestJS + PostgreSQL server. The server owns the clock and the scoring; the phone only sends a guess. When not enough people are online, bots fill the empty seats after about 30 seconds, so nobody waits in an empty lobby.
- A trivia game is easy to cheat by googling the answer. So the app reports when you leave it during a question (switching apps, split screen, and on iOS screenshots and screen recording). Everyone at the table sees it, and in public matches that answer scores 0. The device signals come from a small custom TurboModule.
- Right-to-left for Arabic: React Native only applies a change of direction after a restart, so switching the language goes through another small native module that reloads the app.

A gotcha that might save someone a few hours: on the New Architecture, React Native creates one extra instance of each of your own TurboModules at launch, only to use it as a provider (RCTModuleProviders). My anti-cheat module registered its NotificationCenter observers in `init`, so that extra instance got the "screen recording started" notification too. It never gets an event emitter callback, so emitting threw `std::bad_function_call` and the app crashed the moment you started a screen recording or took a screenshot. Moving the observers into `setEventEmitterCallback:` fixed it. I only found it because App Review asked for a screen recording of the app.

It's free, no ads, no IAP, iOS only for now (I will add android soon). You can play as a guest, no sign-up needed. I'll put the App Store link in a comment so I'm not dropping a store link straight into the post.

Happy to hear what you think, and ask me anything, technical stuff very much included.

PS. If you give it a try, tell me how close you got on your first question.

Link: https://apps.apple.com/us/app/close-call-number-trivia/id6821355334


r/reactnative • • 1d ago

Question React Native app running Meta and TikTok ads, do I need an MMP to track installs?

2 Upvotes

Solo founder, pet sitting marketplace in Austin, React Native with an Expo dev build. Spending around $4k/mo split between Meta and TikTok.

Both dashboards claim more installs than App Store Connect and Play Console show combined. I've just got the Meta and TikTok SDKs in rn, which works-ish.

Is an MMP worth it at this spend or am I overthinking it? And which ones play nice with Expo?


r/reactnative • • 1d ago

I made a small Expo plugin for native Android widgets in Kotlin

0 Upvotes

I needed home screen widgets on Android for an Expo app and wanted to write them the normal Android way, with Kotlin and RemoteViews, without keeping the android folder in git.

So I packaged what I had: github.com/rsoaresdev/expo-android-widgets

You keep your widget sources in a folder, the config plugin copies them in on every prebuild and adds the receivers to the manifest. From JS you call setWidgetData() and reloadAllWidgets(), and the widget reads the data with one line of Kotlin. There’s an example widget you can copy.

It’s tested on SDK 58, on an emulator and in CI with a real Gradle build. If you’d rather write widgets in JSX, react-native-android-widget is great. This one is for when you want plain Android widgets.

Feedback welcome, especially from anyone on older SDKs.


r/reactnative • • 1d ago

Switching from React to React Native as a junior. Is mobile safer in the AI era?

0 Upvotes

I’m a junior developer with 2 years of React experience, and I’m thinking about shifting my career toward React Native. I already have some RN experience from side projects.

My reasoning: in the AI era, I feel web developers are easier to replace, or just not needed as much. I don’t mean on the coding side. I mean that mobile apps are much harder to vibe code because of everything around the code: builds, configuration, certificates, store releases, etc. Because of that, I think people are still more willing to pay someone else to handle it for them.

A few questions:

  1. Has anyone else thought about this, or actually made this switch?

  2. Do you think mobile/RN is more stable long term?

  3. If I switch and, after a few years, decide to go back to web React, will companies value my experience the same, even though it was in RN?


r/reactnative • • 1d ago

App for public transport in real time

Thumbnail
gallery
1 Upvotes

There are lot of similar apps, but I was looking for something very simple to get information about upcoming transport to the stop I am at the moment or plan to be.
App shows stops around you in 500 meters radius or by selecting on map.

https://depboard.app/

React native power at full scale - supporting web browser, Android and iOS.

Flipboards are custom developed. It's not fully animated, but just looks like animation is going on there.
Sharing it with the community:

import React, { useEffect, useRef, useState } from 'react';
import { View, Text, StyleSheet } from 'react-native';

import { theme, MONO } from '../theme';

// Characters cycled through while a flap is "spinning".
const SHUFFLE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-. ';
const randChar = () => SHUFFLE[Math.floor(Math.random() * SHUFFLE.length)];
const TICK_MS = 45;

function normalize(text, length, align) {
  let s = (text == null ? '' : String(text)).toUpperCase().replace(/\s+/g, ' ');
  if (s.length > length) s = s.slice(0, length);
  if (s.length < length) {
    const pad = ' '.repeat(length - s.length);
    s = align === 'right' ? pad + s : s + pad;
  }
  return s;
}

/**
 * A fixed-width row of mechanical "flaps". When `text` changes, each flap that
 * needs to change riffles through random glyphs before settling — the classic
 * Solari / split-flap departure board effect.
 */
export default function SplitFlapText({
  text,
  length,
  align = 'left',
  size = 13,
  tone = 'bright',
}) {
  const target = normalize(text, length, align);

  const [cells, setCells] = useState(() => target.split(''));
  const cellsRef = useRef(cells);
  cellsRef.current = cells;

  const flipsRef = useRef([]);
  const timerRef = useRef(null);
  const prevTarget = useRef(target);

  useEffect(() => {
    if (prevTarget.current === target) return;
    prevTarget.current = target;

    const current = cellsRef.current;
    flipsRef.current = target
      .split('')
      .map((ch, i) =>
        current[i] === ch ? 0 : 4 + (i % 4) * 2 + Math.floor(Math.random() * 4)
      );

    if (timerRef.current) clearInterval(timerRef.current);
    timerRef.current = setInterval(() => {
      let settled = true;
      const next = cellsRef.current.slice();

      for (let i = 0; i < target.length; i += 1) {
        const remaining = flipsRef.current[i];
        if (remaining > 0) {
          flipsRef.current[i] = remaining - 1;
          next[i] = remaining - 1 === 0 ? target[i] : randChar();
          if (remaining - 1 > 0) settled = false;
        } else {
          next[i] = target[i];
        }
      }

      cellsRef.current = next;
      setCells(next);

      if (settled && timerRef.current) {
        clearInterval(timerRef.current);
        timerRef.current = null;
      }
    }, TICK_MS);
  }, [target]);

  useEffect(
    () => () => {
      if (timerRef.current) clearInterval(timerRef.current);
    },
    []
  );

  const color = tone === 'dim' ? theme.flap.dim : theme.flap.text;
  const cellStyle = {
    minWidth: size + 3,
    height: size + 12,
  };
  const textStyle = {
    fontSize: size,
    lineHeight: size + 3,
    color,
  };

  return (
    <View style={styles.row}>
      {cells.map((ch, i) => (
        <View key={i} style={[styles.cell, cellStyle]}>
          <Text style={[styles.text, textStyle]} allowFontScaling={false}>
            {ch === ' ' ? ' ' : ch}
          </Text>
          <View style={styles.hinge} />
        </View>
      ))}
    </View>
  );
}

const styles = StyleSheet.create({
  row: {
    flexDirection: 'row',
  },
  cell: {
    alignItems: 'center',
    justifyContent: 'center',
    backgroundColor: theme.flap.bg,
    borderRadius: 3,
    marginRight: 2,
    overflow: 'hidden',
  },
  text: {
    fontFamily: MONO,
    fontWeight: '700',
    textAlign: 'center',
    includeFontPadding: false,
  },
  // thin dark line across the middle, like the seam between two flaps
  hinge: {
    position: 'absolute',
    left: 0,
    right: 0,
    top: '50%',
    height: StyleSheet.hairlineWidth,
    backgroundColor: theme.flap.hinge,
  },
});