Jay Meistrich – How to Build the Fastest Apps: Break the Rules | App.js Conf 2026 — Transcript
Full transcript
- 0:15Hello React Native developers.
- 0:18Uh
- 0:19Imagine having to follow William. Uh
- 0:21that was really cool. So here we go. Uh
- 0:24last year's App JS was my first
- 0:26conference talk ever and it was so much
- 0:28fun. It's great to be back here.
- 0:31That talk was about Legend List which
- 0:33was version 1.0.14 at the time.
- 0:37Since then there's been almost 1,400
- 0:38commits and it has over a million
- 0:40downloads per month now which is crazy.
- 0:43Uh
- 0:45>> [applause]
- 0:49>> Since then it now supports both React
- 0:51Native and React DOM. So it's now the
- 0:53fastest list library on both mobile and
- 0:55web.
- 0:56It's been super optimized for chat apps
- 0:58and even has this cool anchoring feature
- 1:00that can scroll user messages to the top
- 1:02built-in.
- 1:04And now it's on version 3 beta 56. It's
- 1:07even faster than V2. It's more stable.
- 1:10Initial scroll is finally perfect. That
- 1:12was the hardest thing.
- 1:14It supports animated item transition,
- 1:16tons of bug fixes. Uh
- 1:18I'm really excited about it.
- 1:20But it's just too many betas.
- 1:22So today I'm changing that.
- 1:24It's 3.0 stable.
- 1:27>> [applause]
- 1:31>> But that's actually not what I'm here to
- 1:33talk about today. I'm here to talk about
- 1:35performance.
- 1:36My plan for this talk was to cover a
- 1:38bunch of different performance topics.
- 1:41But as I benchmarked them and helped a
- 1:42few companies optimize their apps, I
- 1:45realized that one of these is so much
- 1:46more impactful than everything else and
- 1:49most apps are doing it the slow way
- 1:50without even knowing it.
- 1:52So we're not doing that at all. Today
- 1:54we're talking about completely
- 1:55rethinking app architecture.
- 1:57I want to talk about making apps that
- 1:59are really fast, not just fast for React
- 2:02Native app, better than native.
- 2:05I worked with Fernando Rojo on
- 2:07animations and list performance for
- 2:08Vercel's V0 iOS app.
- 2:11It's a beautiful AI chat app that just
- 2:13feels incredible to use.
- 2:15When it was announced, people couldn't
- 2:17believe it was React Native because it
- 2:18felt so good.
- 2:20And here's a React Native Mac app I'm
- 2:22working on.
- 2:23This video is me double-clicking the app
- 2:25and it's fully open and ready instantly.
- 2:28It opens a 10 MB markdown file in 20
- 2:30milliseconds.
- 2:32Let's look at that in
- 2:34in slow motion at 5% speed to see how
- 2:37crazy this is.
- 2:38It's actually already fully open,
- 2:40parsed, and rendering rich content
- 2:42before the window is finished opening.
- 2:46For comparison, here's Zed, which is a
- 2:48text editor built in Rust, known for
- 2:50being very fast.
- 2:52It opens the file quickly, but it takes
- 2:53over 4 seconds to parse and syntax
- 2:56highlight.
- 2:57Most other apps I tried either refused
- 2:59to open it, froze, or crashed. So, 20
- 3:02milliseconds is crazy.
- 3:05Uh here's another app I'm making. It's a
- 3:07React Native Mac MP3 player.
- 3:10It uses less CPU and memory than almost
- 3:12every other music app I tried,
- 3:14even the native Apple Music.
- 3:17These are React Native apps and they're
- 3:19faster than almost every other app on my
- 3:21computer.
- 3:22They're not just fast relative to
- 3:23Electron web view apps, they're faster
- 3:25than most native apps.
- 3:27Because React Native macOS is awesome.
- 3:30But mainly, React Native in 2026 is just
- 3:33so good.
- 3:34>> [snorts]
- 3:35>> The game has totally changed. The new
- 3:38architecture unlocks big possibilities.
- 3:41Hermes keeps getting better. Nitro
- 3:42modules are crazy fast. Lists are no
- 3:45longer a bottleneck. As we just saw,
- 3:47redraw makes incredible graphics easy
- 3:49and fast.
- 3:51Keyboard controller makes keyboard
- 3:52management super smooth. Agent device
- 3:55and Argent have cut my debugging time
- 3:57down to just so much. It's incredible.
- 4:00But still,
- 4:01React Native is seen as a compromise.
- 4:04And that's just not actually true.
- 4:06I've seen and worked on React Native
- 4:07apps that are fully best in class,
- 4:10sometimes better than native apps. I
- 4:11know it's possible.
- 4:14But this year, I've also looked at a lot
- 4:16of code bases for consulting work and
- 4:18helping companies with legacy issues.
- 4:20And I saw the same problems spread all
- 4:22over the place.
- 4:24So, I want us to stop fixing these same
- 4:26symptoms over and over.
- 4:28And let's try to fix that root cause.
- 4:30Because React Native is not slow, the
- 4:32way we use it is.
- 4:35If you've seen one of my talks, you may
- 4:37know my catchphrase, render less less
- 4:39often.
- 4:40But today, I want to make it spicier.
- 4:43Render once.
- 4:45But first,
- 4:46why is rendering too much a problem?
- 4:49Everyone talks about reducing renders,
- 4:51but what's the actual impact here?
- 4:53So, I did a little benchmark in my music
- 4:55app to compare, what if I re-render from
- 4:58the top app all the way down versus in a
- 5:01medium-sized component in the middle
- 5:03down to the bottom?
- 5:04Or I just re-render a tiny text element
- 5:07leaf node at the bottom.
- 5:09When that render passes all the way down
- 5:10from the top,
- 5:12it has to do a lot more work along the
- 5:13way,
- 5:14rather than just updating one text
- 5:16element. But it's got to be a small
- 5:17difference, right? No, it's huge. Even I
- 5:22was surprised by this one.
- 5:24Moving the update target from the
- 5:25top-level app down to the tiniest leaf
- 5:27node made a huge difference. It reduced
- 5:30the CPU usage by 10 times.
- 5:33So, rendering less has a really deep
- 5:35impact.
- 5:36But React state model is designed around
- 5:38re-rendering. Render coordinates
- 5:41everything.
- 5:42Render passes local state down the tree.
- 5:45It updates context consumers. It
- 5:46triggers effects. It updates query
- 5:48properties. It updates external
- 5:50properties. It updates subscriptions.
- 5:53And it also updates UI.
- 5:55That's a lot of responsibility for what
- 5:57we call render.
- 5:59And that's where so many normal react
- 6:00patterns become a big performance
- 6:02problem.
- 6:03So let's say we're making a chat app.
- 6:06We have a reply feature that changes the
- 6:08color of a message. That's cool. We
- 6:11render the message to update the color.
- 6:14And then you want to also show in the
- 6:16composer that you're replying. So now we
- 6:19need to access the same state in sibling
- 6:21components. So what do you do?
- 6:23Say it with me.
- 6:25You lift state up.
- 6:27All right, nobody did it. Uh
- 6:30well, you do what the react docs tell
- 6:31you to do. You lift state up.
- 6:34As as the docs say, it's one of the most
- 6:36common things you'll do writing react
- 6:37code.
- 6:39So you move the state up to the top of
- 6:40the tree and you pass it all the way
- 6:42down.
- 6:43And it renders every component all the
- 6:45way down. It renders the chat screen. It
- 6:48renders the composer with all of its
- 6:50dialogues and buttons and hooks.
- 6:52It renders everything down to the
- 6:54message list. And then it renders every
- 6:56single chat message. All that one
- 6:58message can change a background color.
- 7:01And this is just killing performance.
- 7:03Now react compiler helps a lot and you
- 7:06should absolutely use it.
- 7:08But it can't save you from actual
- 7:09changes.
- 7:11When prop is changed, it has to pass the
- 7:13state all the way down. Compiler can't
- 7:15help this. So to update a style in one
- 7:18message, we have to cascade state's
- 7:20changes through the whole screen.
- 7:22And let's talk about effects.
- 7:24Here's an example from the react docs
- 7:27controlling a modal dialogue.
- 7:29To open a modal on button press, we
- 7:32could just open the modal imperatively.
- 7:34But the react pattern is to set state
- 7:37which triggers a render, then use an
- 7:39effect to open the modal.
- 7:40It It actually change what renders, but
- 7:42it re-renders anyway.
- 7:45Or maybe I want to pause query polling
- 7:46temporarily. I have to set a state so
- 7:49that I can call the use query hook again
- 7:51with different parameters.
- 7:53Setting is paused doesn't actually
- 7:54change what renders, but we have to
- 7:56re-render anyway to update the query.
- 7:59Or I want to mark a chat as read, but
- 8:01only when the window is focused.
- 8:03The use this focused hook re-renders
- 8:05when focus state changes. So, we have to
- 8:07run it in an effect.
- 8:09That doesn't change what renders, but
- 8:11now this screen re-renders every time
- 8:12focus changes.
- 8:15This is all because render is needed for
- 8:17coordinating things that aren't even
- 8:18rendering.
- 8:19We're wasting all of this computing to
- 8:21just run side effects and move state
- 8:23around.
- 8:25Now, let's talk about callbacks.
- 8:27Shout out if you see the problem here.
- 8:30All right, nobody did it.
- 8:33Well, the depths array
- 8:34needs to have a value in it, or this
- 8:36callback will become stale when value
- 8:38changes, and then we'll log the value.
- 8:40The We'll log the old value.
- 8:43So, you add value to the depths array,
- 8:45and it's fixed.
- 8:46But now, on press changes whenever value
- 8:48changes, and that means this big
- 8:50component also re-renders whenever value
- 8:53changes.
- 8:54You can't easily see this by looking at
- 8:56it.
- 8:57It's a hidden performance problem.
- 8:59I've seen tons of mistakes like this
- 9:01causing noticeable freezes when some
- 9:04spaghetti path of a state change
- 9:05accidentally re-renders the whole app.
- 9:08This kind of silly mistake can actually
- 9:10have a really deep impact.
- 9:12And finally, let's talk about state.
- 9:15React state is extremely blunt and
- 9:17over-subscribed.
- 9:19Use state doesn't just create state. It
- 9:22also subscribes the current component to
- 9:24that state.
- 9:25That sounds normal cuz we're all used to
- 9:27it by now, but it's actually a huge
- 9:29limitation.
- 9:30The owner of the state has to re-render
- 9:32when it changes, and then it's
- 9:34responsible for passing it down to
- 9:36whatever uses it.
- 9:37Ownership and subscription are tied
- 9:39together.
- 9:41Context can be even worse.
- 9:43Use context doesn't only subscribe to
- 9:45the field you need, it subscribes to the
- 9:47whole context value.
- 9:49Whenever anything in the context value
- 9:51changes, all subscribers re-render.
- 9:54Say you want to get font scale in your
- 9:56custom text element. This will re-render
- 9:59every text element whenever font scale
- 10:00changes. That's not great, but it
- 10:03doesn't happen too often.
- 10:05But that's a trick. You actually
- 10:07subscribe to the window size, too.
- 10:09So, when a tablet or desktop user
- 10:10resizes the window, every single text
- 10:12element across your whole app re-renders
- 10:15and your app freezes.
- 10:16It doesn't actually need the width here,
- 10:19but use context subscribe to it anyway.
- 10:21So, now you have a huge performance
- 10:23problem for no reason.
- 10:26So, we know that rendering too much, too
- 10:28often, is a major cause of performance
- 10:30problems.
- 10:32>> [snorts]
- 10:32>> But render is the orchestrator of
- 10:33everything, and React encourages
- 10:36re-rendering often and spreading those
- 10:38renders deep and wide.
- 10:40So, that's the model that I want to
- 10:42break. Instead of re-rendering
- 10:44everything everywhere all at once,
- 10:47we render just once.
- 10:49We don't even need to change the
- 10:50framework to do this. We just have to
- 10:52think about state differently and
- 10:54coordinate state separately from render.
- 10:57Currently,
- 10:58render coordinates everything and state
- 11:01owners push the state down.
- 11:03Instead,
- 11:04let's let consumers update themselves as
- 11:06needed.
- 11:08And all we need for that is stable state
- 11:10objects that you can subscribe to.
- 11:13Some people call these signals, some
- 11:15call them observables,
- 11:16Reanimated calls them shared values.
- 11:19The key thing here is that use
- 11:21observable does not subscribe to the
- 11:22state, it just creates it.
- 11:25Any other component can use value it to
- 11:27subscribe to it.
- 11:29This separates the ownership from the
- 11:30subscription. We lift the state
- 11:32ownership up when we need to, but we
- 11:34push state subscription all the way
- 11:36down.
- 11:37So, in this example, the chat screen
- 11:39passes these stable state objects that
- 11:41never change, so it never has to
- 11:43re-render.
- 11:44Then this tiny little reply row
- 11:46subscribes to the state that it needs,
- 11:48and it re-renders itself whenever it
- 11:50needs to.
- 11:51It does almost nothing, so the cost of
- 11:53re-rendering that is tiny compared to
- 11:55re-rendering the whole chat screen.
- 11:58So, whereas normally the whole app would
- 12:00re-render because the state is owned and
- 12:02subscribed at the top,
- 12:04with this setup, only the affected
- 12:05components re-render.
- 12:07Nothing else is visually changing, so
- 12:09they don't need to re-render, so we can
- 12:11just skip all of it.
- 12:13If a callback needs a state value, it
- 12:15can just get the value. It doesn't need
- 12:17to
- 12:18pass through a render. It doesn't need
- 12:20to be constantly re-created. It can just
- 12:22be a stable function that never changes.
- 12:26An observing effect can just re-run
- 12:28itself when the state it cares about
- 12:29changes.
- 12:30It doesn't need a render to update it.
- 12:33It automatically subscribes to any state
- 12:34it accesses.
- 12:36So, we can open a modal without any
- 12:37render coordination and still have the
- 12:39state to track it.
- 12:42Use context is still very useful,
- 12:45but to provide stable state objects.
- 12:47That way the provider never re-renders,
- 12:50and the consumers can select which
- 12:51specific thing to listen to to re-render
- 12:54as little as possible.
- 12:56We could also subscribe to state to
- 12:58derived values. So, rather than
- 13:00subscribing to reply ID, which would
- 13:02re-render every single message,
- 13:04it can
- 13:06um
- 13:06subscribe to a derived value so that
- 13:08only one reply message actually
- 13:10re-renders.
- 13:13So, when I say render once, I don't mean
- 13:14never update. I mean render the app and
- 13:17large coordinating screens once.
- 13:20Let leaf nodes re-render themselves. Let
- 13:22effects re-run themselves. Don't
- 13:24orchestrate through render.
- 13:26This way your apps will just do a lot
- 13:28less work.
- 13:30And this is not just a wild theory I
- 13:31have. I've actually been building all of
- 13:33my apps and libraries like this for 10
- 13:35years.
- 13:36If you use my state library, Legend
- 13:37State, you probably realized a while ago
- 13:40I've just been describing how it works.
- 13:42Uh and this is basically how other
- 13:44frameworks like Solid, Svelte, and
- 13:45Preact work.
- 13:47But it's actually already in React
- 13:48Native. This is how Reanimated works.
- 13:51A shared value is a stable state object.
- 13:54When you get a shared value within an
- 13:56observing hook, it subscribes and
- 13:58updates itself automatically. You can
- 14:00set it anywhere in your code and it'll
- 14:02update the UI without a render.
- 14:04So we're actually already doing this to
- 14:06get the best performance with
- 14:08animations.
- 14:09And if you use Legend List, you also
- 14:12have this pattern in your app already.
- 14:14This is the main reason Legend List is
- 14:15the fastest list library on mobile and
- 14:17web.
- 14:18It just does less work. It uses less
- 14:20CPU.
- 14:22Legend List mounts a pool of absolutely
- 14:24positioned containers up front and then
- 14:26it never re-renders that array ever
- 14:28again.
- 14:29It signals individual containers to
- 14:31re-render themselves when needed.
- 14:34So when you scroll down,
- 14:35it signals one container to re-render at
- 14:38a new position with a new item.
- 14:40This keeps the size of renders as small
- 14:42as possible
- 14:43for best scrolling performance.
- 14:46When an item size changes, which happens
- 14:48quite often, a tiny wrapper component
- 14:50re-renders itself with just a style
- 14:52change and nothing else.
- 14:54When the list size changes,
- 14:56it skips rendering entirely and it
- 14:58updates an animated style.
- 15:00It's not actually animating, but it's
- 15:02more performant than doing a render.
- 15:05These size changes happen very often
- 15:07while you're scrolling as new items come
- 15:09in
- 15:10and you have an estimated size and an
- 15:11actual size. They lay out, you measure.
- 15:14Uh so this happens a lot. It would kill
- 15:16performance if it had to update and
- 15:18render the outer list component with a
- 15:20new size every single time.
- 15:23Legend list is fast because it's
- 15:24extremely careful to do less work.
- 15:27Rendering has a real and significant
- 15:29cost.
- 15:31So, for the fastest possible apps,
- 15:33render less less often.
- 15:35Or push further, render once.
- 15:38Use React to create the structure, then
- 15:40update the smallest leaf nodes directly.
- 15:44But, this isn't just a list hack. This
- 15:45is the model I want you to take back to
- 15:47your apps.
- 15:48So, try this today.
- 15:50Well, maybe not today since we're at a
- 15:52conference. So, try this on Monday.
- 15:55Uh
- 15:55the first step is to find what's
- 15:57actually slow.
- 15:58Look at your slowest screens and
- 16:00interactions. Just throw logs everywhere
- 16:02and just see what's rendering so much.
- 16:05Or use the highlight updates feature in
- 16:07the dev tools to just watch renders as
- 16:08they happen, figure out what's going on.
- 16:11Then just try optimizing that screen to
- 16:13see what you can do.
- 16:15This problem can actually be fully
- 16:16solved with a state library. It doesn't
- 16:18need any framework changes.
- 16:20Obviously, I recommend Legend state.
- 16:23I'm not aware of any other state
- 16:24libraries that do all of this, but
- 16:25there's some other signal style
- 16:27libraries that you can use if you
- 16:28prefer.
- 16:29Or just make your own.
- 16:31The key thing is you want to decouple
- 16:33the state creation and subscription and
- 16:35push the renders down as far as you can
- 16:37to the leaf nodes.
- 16:40I know this is controversial. Everyone
- 16:42has very strong opinions about state.
- 16:44I've gotten a ton of pushback that teams
- 16:47don't want to change their state
- 16:48libraries or they prefer to just use the
- 16:50built-in state and use context because
- 16:52that's what React recommends, right?
- 16:55But, you already use reanimated instead
- 16:56of the built-in animated because it's
- 16:58better.
- 16:59You dropped style sheet for Unistyles or
- 17:01NativeWind or UniWind. If you still have
- 17:04flat lists in your app somehow, talk to
- 17:06me after this.
- 17:07Uh
- 17:08I hope you're still not using
- 17:10TouchableOpacity.
- 17:12So, why are we still clinging on to the
- 17:14built-in state?
- 17:16Some teams just refuse to use a state
- 17:18library or your library developer and
- 17:20you don't want another dependency. I
- 17:22understand that.
- 17:23So, there's also some lower level things
- 17:25you can do to cut out the renders.
- 17:27So, instead of this very familiar
- 17:29pattern, try to use the imperative APIs
- 17:32rather than hooks to avoid the
- 17:33re-renders.
- 17:34So, instead of re-rendering every time
- 17:36window size changes,
- 17:38you can just get the window size when
- 17:39you need it.
- 17:41And then you can subscribe to it. Um Oh,
- 17:45this one.
- 17:46Uh
- 17:47so, you can subscribe to it changing and
- 17:49then do whatever you want with it. If
- 17:50you want to pass that through state,
- 17:51cool. If you want to send it to post hog
- 17:53or whatever, it's fine.
- 17:55Um
- 17:57And if you're a library author, please
- 17:59add an imperative API as well as a hook
- 18:01like dimensions has.
- 18:03So, then you can get the value when you
- 18:05need it.
- 18:06You can use an event listener to hook
- 18:07into it if you want to. You don't need
- 18:09to do a re-render for it.
- 18:12Another pattern I've used is to make
- 18:13hooks take a callback function and
- 18:15return a ref.
- 18:17So, then the users can return the use
- 18:19the ref when they need to get the value.
- 18:20They can pass the ref around. It's a
- 18:22stable object. Or they can just listen
- 18:25to changes with a callback and do
- 18:26whatever they want with it.
- 18:29You can change your callbacks to
- 18:30something like use latest callback or
- 18:32use event callback which updates them
- 18:34without dependency arrays and that makes
- 18:36the callback stable
- 18:38so they don't cause render cascades down
- 18:39the tree.
- 18:41If you really want to stick with context
- 18:43for some reason but does not have the
- 18:45terrible performance problems that come
- 18:47with it,
- 18:48you could use something like use context
- 18:49selector.
- 18:50It still re-renders on every change but
- 18:52at least it lets you select only the
- 18:54small piece you care about.
- 18:56But these are mostly small fixes to the
- 18:58symptoms.
- 18:59The deepest impact we can get is from
- 19:01rethinking state at a higher level.
- 19:04High-level orchestrating components
- 19:06should never re-render. They should own
- 19:08the state but not subscribe to it.
- 19:10Re-renders should be pushed down to the
- 19:12leaf nodes.
- 19:13I know this is a departure from normal
- 19:15React patterns,
- 19:17but in my experience, it really does
- 19:19make apps a lot faster, and I think it's
- 19:21worth a try.
- 19:23I really believe state architecture is
- 19:25by far the biggest hidden bottleneck in
- 19:27most apps,
- 19:29so you should really care about it and
- 19:30optimize the bananas out of it.
- 19:33And I care so much about this, I built a
- 19:35whole state and sync library because it
- 19:37was the only way to get the best
- 19:39performance.
- 19:41This architecture, in my opinion, is how
- 19:43you get from fast for React Native to
- 19:45really fast.
- 19:47Render once.
- 19:49Thank you.
About this transcript
This page contains the full transcript of Jay Meistrich – How to Build the Fastest Apps: Break the Rules | App.js Conf 2026 by Software Mansion, generated from the public captions YouTube serves with the video. The transcript has 3,329 words across 588 segments, with the original timestamps preserved so you can click any line to jump to that moment in the embedded player.
What you can do with it
Use the transcript to take notes, quote the speaker, build a study guide, generate a summary with ChatGPT or Claude via the YouTube Summary tool, or export it as a timed subtitle file with YouTube to SRT. You can also re-open it in the transcriber to translate the transcript into 100+ languages.
Free YouTube transcript tool
YouTube2Text is a free YouTube transcript generator — no signup, no daily limit. Paste any YouTube link and get the full transcript instantly, with timestamps, click-to-jump, translation to 100+ languages, AI prompts for ChatGPT, Claude, and Gemini, and exports to TXT, SRT, VTT, or Markdown.