<?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>DEV Community: Vikrant Negi</title>
    <description>The latest articles on DEV Community by Vikrant Negi (@vikrantnegi).</description>
    <link>https://dev.to/vikrantnegi</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F59047%2F5f75c582-d590-4f39-adb5-22f143c232f3.jpg</url>
      <title>DEV Community: Vikrant Negi</title>
      <link>https://dev.to/vikrantnegi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vikrantnegi"/>
    <language>en</language>
    <item>
      <title>6 Mistakes I Made Running My First Controlled Experiment as a Developer</title>
      <dc:creator>Vikrant Negi</dc:creator>
      <pubDate>Mon, 26 Oct 2026 14:00:00 +0000</pubDate>
      <link>https://dev.to/vikrantnegi/6-mistakes-i-made-running-my-first-controlled-experiment-as-a-developer-3ajc</link>
      <guid>https://dev.to/vikrantnegi/6-mistakes-i-made-running-my-first-controlled-experiment-as-a-developer-3ajc</guid>
      <description>&lt;p&gt;I've been a developer for about ten years. I know how to find out whether code works: write a test, run it, read the output.&lt;/p&gt;

&lt;p&gt;Finding out whether code &lt;em&gt;helps people&lt;/em&gt; is a different job. I learned that by getting it wrong, repeatedly, over five weeks.&lt;/p&gt;

&lt;p&gt;The project was an AI tutor for SQL with two versions, called arms. Arm A makes you try before it helps. Arm B gives the answer whenever you ask. Six people practised with one version or the other, then took a test a week later with no tutor. (Part 1 of this series has the full story and result.)&lt;/p&gt;

&lt;p&gt;I kept a log of every assumption that broke. It ended up with 24 entries. Most of them are the same few mistakes in different clothes. Here are the six worth knowing about before you run an experiment of your own, whether it's a learning study or an A/B test on a feature.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The test measured a different skill than I thought
&lt;/h2&gt;

&lt;p&gt;Before the study, I screened seven people with a written SQL test. No database, no Run button. Just write the query.&lt;/p&gt;

&lt;p&gt;They solved &lt;strong&gt;4 of 35&lt;/strong&gt; questions they attempted.&lt;/p&gt;

&lt;p&gt;One colleague scored 0 out of 10. All four of his answers were conceptually right. He lost every mark to one habit: putting the sort direction in quotes. One run against a real database would have shown him the error.&lt;/p&gt;

&lt;p&gt;Five days later, same people, I added a Run button. They solved &lt;strong&gt;11 of 16&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Same people, one change. The first test wasn't measuring SQL skill. It was measuring "can you write perfect SQL in one shot with no feedback", which nobody does at work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The rule:&lt;/strong&gt; a test only predicts performance under the conditions it recreates. Before trusting a number, ask what conditions produced it. Then ask whether those are the conditions you care about.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. I counted at the wrong grain
&lt;/h2&gt;

&lt;p&gt;Four of my 24 logged mistakes were this one. I had real numbers, but counted at the wrong level.&lt;/p&gt;

&lt;p&gt;The effect I cared about lives at one learner on one question. I called that a &lt;strong&gt;person-question&lt;/strong&gt;. Three learners × 12 questions = 36 person-questions per arm.&lt;/p&gt;

&lt;p&gt;But I kept slipping into "per person" or "per question" without noticing. Per person, I had 3 data points per arm, which tells you almost nothing. Per question, I mixed strong and weak learners together and hid who was struggling.&lt;/p&gt;

&lt;p&gt;The worst version came early. I planned the study around "3 learners × 16 questions gives 48 moments where the tutor has to decide what help to give". But a question you solve on the first try needs no help. Neither does one you never open. So the real event isn't "a question asked". It's "a question where the first working attempt is wrong".&lt;/p&gt;

&lt;p&gt;When I measured that rate, it was about 1 in 7 person-questions. The same 48 person-questions would give me about &lt;strong&gt;7&lt;/strong&gt; help decisions, not 48. I had to cut the study from two topics down to one to have anything to measure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The rule:&lt;/strong&gt; for every number, say its unit out loud. "25 of 36" means nothing. "25 of 36 person-questions (3 learners × 12 questions)" can be checked.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. "No help" is still a design
&lt;/h2&gt;

&lt;p&gt;Arm B was my control: the version I compared the new idea against. It gave the answer on demand, and otherwise just showed the database's error message. I treated that as neutral. It wasn't.&lt;/p&gt;

&lt;p&gt;One learner made &lt;strong&gt;80 attempts&lt;/strong&gt; in 47 minutes. None of them ran. His only real problem was writing keywords without a space: &lt;code&gt;GROUPBY&lt;/code&gt; instead of &lt;code&gt;GROUP BY&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;SQLite's error message names the word &lt;em&gt;after&lt;/em&gt; the broken one. So it pointed him at the wrong place. He rewrote his column names. He rewrote the &lt;code&gt;HAVING&lt;/code&gt; line four different ways. In 63 tries on one question, he never touched &lt;code&gt;GROUPBY&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here's the trap. Suppose the bare error message is bad in a specific way, and the new version happens to fix that. Then part of the improvement you measure is just that fix, not the idea you were testing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The rule:&lt;/strong&gt; a control group is a design choice, not the absence of one. "No help" still has a user experience. Look at it as carefully as the new version.&lt;/p&gt;

&lt;p&gt;This applies directly to A/B tests. If your control is the old checkout with a known confusing button, and your variant fixes the button, you might credit your new feature for the button fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Question order was a variable, and I didn't count it
&lt;/h2&gt;

&lt;p&gt;The 12 test questions ran in skill order. Simple skills first, the hardest skill last.&lt;/p&gt;

&lt;p&gt;Three of six learners stopped early, after question 8 or 9. Some ran out of time. Some gave up. On narrow screens, the question list cut off after question 9, so some may not have seen the rest.&lt;/p&gt;

&lt;p&gt;Because of the order, every early stop landed on the hardest skill. So my biggest gap between the arms, &lt;strong&gt;6 points&lt;/strong&gt; on the hardest skill, was really just &lt;strong&gt;6 blank answers&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That's the worst kind of hidden bias. It doesn't add random noise. It produces a clean-looking result in exactly the place you were going to read most carefully.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The rule:&lt;/strong&gt; anything that changes along with the outcome is a variable, even page order. Randomise order per user, and log it.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Being in the group is not the same as using the feature
&lt;/h2&gt;

&lt;p&gt;Arm A's whole difference is what happens when you press Help. If you never press it, both arms are the same page.&lt;/p&gt;

&lt;p&gt;Three of my learners never pressed Help during practice. One of them was in Arm A. He never experienced the try-first tutor at all, and still scored 12 of 12 on the test.&lt;/p&gt;

&lt;p&gt;My headline result counted everyone in the arm they were assigned to: 36 vs 25, an 11-question gap. Counting only the four people who used Help as designed, the gap was 24 vs 21. &lt;strong&gt;Three questions.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Both numbers are honest. They answer different questions. "What happens if you give people this tool?" versus "What happens when people actually use it?" You need both, and you need to say which is which.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The rule:&lt;/strong&gt; log what each user actually received, not just which group they were in. Report both side by side.&lt;/p&gt;

&lt;p&gt;In product terms: if half your variant group never saw the new feature because it's below the fold, your A/B test is mostly measuring the old page twice.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. I didn't say the study was unsupervised
&lt;/h2&gt;

&lt;p&gt;This is the one a judge caught at Demo Day.&lt;/p&gt;

&lt;p&gt;He asked whether the practice and the exam were done live. They weren't. Learners got a link and did it in their own time. That wasn't on my slides, and he'd assumed it was live.&lt;/p&gt;

&lt;p&gt;Unsupervised means you can't see what else people used. Google. ChatGPT. A friend.&lt;/p&gt;

&lt;p&gt;And it's not equal across arms. Arm B learners never needed to leave the page; the answer was one click away. Arm A learners were held at a gate until they made an attempt. That's exactly when you'd open ChatGPT in another tab.&lt;/p&gt;

&lt;p&gt;If they did, they skipped the struggle that Arm A exists to create. That weakens Arm A's tutor only, and pulls the result toward "no effect". The unsupervised exam does something similar: outside help lifts both arms and shrinks the gap.&lt;/p&gt;

&lt;p&gt;So my setup most likely made the result look weaker, not stronger. That's a good thing to be able to say. I just didn't say it, so the audience filled the gap with their own default.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The rule:&lt;/strong&gt; if a condition would change how someone reads your result, it goes on the slide. Live or remote. Supervised or not. Who dropped out. People assume the standard version of whatever you leave out.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one habit that saved me
&lt;/h2&gt;

&lt;p&gt;Before I saw a single answer from the test, I wrote down how I'd read the result. That included ten checks that could break it. Things like "neither arm above 80%", so there's room to see a difference, and "Arm B tries less than Arm A before asking for help", so the arms actually behave differently.&lt;/p&gt;

&lt;p&gt;Two of those checks failed. Arm A hit 100%. Arm B tried before asking for help almost as often as Arm A.&lt;/p&gt;

&lt;p&gt;Because I'd written the checks first, I couldn't explain them away. I had to change my claim. My hypothesis went from "trying first improves recall" to "trying first changes what you do after a wrong answer". That change is the most useful thing the project produced.&lt;/p&gt;

&lt;p&gt;If you take one thing from this post: &lt;strong&gt;write down what would prove you wrong before you look at the data.&lt;/strong&gt; It's the same discipline as writing a failing test before the fix. Ten years of TDD habits turned out to transfer after all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Check the conditions.&lt;/strong&gt; A test measures the skill its conditions recreate, not the one you named it after.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check the grain.&lt;/strong&gt; Give every number its unit, and make sure it's the unit where the effect lives.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Design your control.&lt;/strong&gt; "No change" has its own UX, and that UX can create your result.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Randomise anything ordered.&lt;/strong&gt; Position is a variable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Log who actually used the feature.&lt;/strong&gt; Being in a group and using the feature are different numbers. Report both.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State your setup.&lt;/strong&gt; Your audience will assume the default for anything you don't say.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All the logs, including the 24 broken assumptions, are in the repo:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/vikrantnegi" rel="noopener noreferrer"&gt;
        vikrantnegi
      &lt;/a&gt; / &lt;a href="https://github.com/vikrantnegi/withholding-pilot" rel="noopener noreferrer"&gt;
        withholding-pilot
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Learning OS — 100x Cohort 7 final capstone&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Vikrant Negi. Solo. Demo Day track. Submission 7 Oct 2026.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;An SQL tutor that makes you try before it helps, and a 6-person pilot testing whether that
changes what you can do a week later, with the tutor gone.&lt;/p&gt;
&lt;p&gt;For where things stand &lt;strong&gt;today&lt;/strong&gt;, read &lt;code&gt;STATUS.md&lt;/code&gt;. That is the only file that claims to be current.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Try it&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Live app:&lt;/strong&gt; &lt;a href="https://tranquil-starlight-f0129e.netlify.app/" rel="nofollow noopener noreferrer"&gt;https://tranquil-starlight-f0129e.netlify.app/&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Withholding tutor: &lt;a href="https://tranquil-starlight-f0129e.netlify.app/index.html?arm=A" rel="nofollow noopener noreferrer"&gt;https://tranquil-starlight-f0129e.netlify.app/index.html?arm=A&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Answer-giving baseline: &lt;a href="https://tranquil-starlight-f0129e.netlify.app/index.html?arm=B" rel="nofollow noopener noreferrer"&gt;https://tranquil-starlight-f0129e.netlify.app/index.html?arm=B&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What each one does:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;?arm=A&lt;/code&gt; opens the withholding tutor. Press Help before trying, and it refuses. Try once, and
Help gives a hint. Two more tries, and it gives the answer.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;?arm=B&lt;/code&gt; opens the baseline. Help gives the full answer at once.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Same page, same 16 practice questions, same grader. One config flag is the only difference.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;The result&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Arm A scored 36 of 36 person-questions (3 learners x 12 held-out questions). Arm&lt;/strong&gt;…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/vikrantnegi/withholding-pilot" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Which of these have you hit in your own A/B tests? I'd bet #5 is the most common one in product teams. Please feel free to leave any feedback.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>learning</category>
      <category>testing</category>
      <category>career</category>
    </item>
    <item>
      <title>Don't Let the LLM Decide: How I Built a Predictable AI Tutor</title>
      <dc:creator>Vikrant Negi</dc:creator>
      <pubDate>Mon, 19 Oct 2026 14:00:00 +0000</pubDate>
      <link>https://dev.to/vikrantnegi/dont-let-the-llm-decide-how-i-built-a-predictable-ai-tutor-1g3</link>
      <guid>https://dev.to/vikrantnegi/dont-let-the-llm-decide-how-i-built-a-predictable-ai-tutor-1g3</guid>
      <description>&lt;p&gt;The easy way to build an AI tutor is to give the LLM everything. Pass it the question, the learner's attempts and the answer. Tell it: "Help the learner, but don't give the answer away too early."&lt;/p&gt;

&lt;p&gt;That works in a demo. It fails in an experiment. And it fails quietly in production too.&lt;/p&gt;

&lt;p&gt;In the first post of this series I explained the experiment. I built an SQL tutor in two versions, called arms. Arm A makes you try before it helps, then gives a hint before the answer. Arm B gives the answer whenever you ask, like most AI tools. Six people used one or the other, then took a test a week later with no tutor.&lt;/p&gt;

&lt;p&gt;This post is about how I built it. The core rule fits on one line:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The LLM never decides whether to help, or how much. Plain code decides. The LLM only writes the hint's wording, after that decision is made.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why the LLM can't be the one deciding
&lt;/h2&gt;

&lt;p&gt;Picture the prompt version. "If the learner seems stuck, give a hint. If they've really tried, give the answer."&lt;/p&gt;

&lt;p&gt;Now try to answer one question: &lt;em&gt;what did Arm A receive?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You can't. "Seems stuck" is a judgment the model makes fresh every time. Two learners in the same situation can get different help. The same learner can get different help on a retry. The thing you're trying to test becomes a moving target.&lt;/p&gt;

&lt;p&gt;There's a second problem. With six people, there were only about 23 moments in the whole study where the tutor had to decide what help to give. You can't judge a model's decision-making from 23 examples. But you &lt;em&gt;can&lt;/em&gt; fully test a plain function, and I did, with 123 unit tests.&lt;/p&gt;

&lt;p&gt;So I split the system into three kinds of parts, a framing from my 100x Engineers AI course:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;part&lt;/th&gt;
&lt;th&gt;does what&lt;/th&gt;
&lt;th&gt;why it sits there&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;human&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;writes and runs SQL&lt;/td&gt;
&lt;td&gt;the skill being measured is theirs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;deterministic&lt;/strong&gt; (plain code)&lt;/td&gt;
&lt;td&gt;the try-first rule, the hint/answer rules, the answer checker, the leak guard&lt;/td&gt;
&lt;td&gt;the one thing being tested must be fixed and checkable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;probabilistic&lt;/strong&gt; (the LLM)&lt;/td&gt;
&lt;td&gt;writes the hint text, nothing else&lt;/td&gt;
&lt;td&gt;prose about &lt;em&gt;this specific mistake&lt;/em&gt; is the one thing code can't write&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The model is one small component. It's not the product.&lt;/p&gt;

&lt;h2&gt;
  
  
  The policy: a pure function
&lt;/h2&gt;

&lt;p&gt;Here's the heart of &lt;code&gt;policy.js&lt;/code&gt;, trimmed. No DOM, no network, no LLM. It takes the log for one question and returns an action.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ACTION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;NONE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;         &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;none&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;// already solved&lt;/span&gt;
  &lt;span class="na"&gt;REFUSE_GATE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;refuse_gate&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// Arm A, no real attempt yet&lt;/span&gt;
  &lt;span class="na"&gt;REFUSE_RETRY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;refuse_retry&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Arm A, hint given, keep trying&lt;/span&gt;
  &lt;span class="na"&gt;HINT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;         &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hint&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;// the LLM writes the text&lt;/span&gt;
  &lt;span class="na"&gt;REVEAL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;       &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;reveal&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="c1"&gt;// the reference query&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;DEFAULT_N&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// attempts with the hint before the answer&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;decide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;N&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;N&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;DEFAULT_N&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;counted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;countableAttempts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attempts&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;helpServed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;helpServed&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attempts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outcome&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;correct&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ACTION&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NONE&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="c1"&gt;// Arm B: the entire difference between the two groups is this line.&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arm&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;B&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ACTION&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;REVEAL&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="c1"&gt;// Arm A: the gate. No help of any kind until one attempt counts.&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;counted&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ACTION&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;REFUSE_GATE&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;helpServed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;ACTION&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;REVEAL&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ACTION&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;REVEAL&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;helpServed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;ACTION&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HINT&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;hint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ACTION&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HINT&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sinceHint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;counted&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;hint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;afterCountedAttempts&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;sinceHint&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;N&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ACTION&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;REVEAL&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ACTION&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;REFUSE_RETRY&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The only thing of interest here is the Arm B line. That one &lt;code&gt;if&lt;/code&gt; is the whole difference between the two versions. Anyone can read it and know exactly what each group got.&lt;/p&gt;

&lt;p&gt;Every decision is logged with &lt;code&gt;counted&lt;/code&gt; and &lt;code&gt;sinceHint&lt;/code&gt;. So for any Help press, I can look at the log and prove &lt;em&gt;why&lt;/em&gt; the tutor responded the way it did.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;N&lt;/code&gt; is fixed at 2 for everyone. If it changed per person, I'd be testing two things at once, and three people per group can't separate them.&lt;/p&gt;
&lt;h2&gt;
  
  
  What counts as an attempt?
&lt;/h2&gt;

&lt;p&gt;Arm A's rule is "try first". I call it the gate: no help until you've made an attempt. So the code needs to decide what a real attempt is. This turned out to be the hardest part of the policy.&lt;/p&gt;

&lt;p&gt;My first version said: an attempt counts if its result set is different from the last one. Simple, and wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The bug:&lt;/strong&gt; a query that doesn't run has no result set. So for a learner who couldn't get anything to execute, no attempt ever counted. The gate never opened. In my earlier test rounds, 101 of 122 attempts had a syntax error and didn't run. This wasn't an edge case. It was the main case.&lt;/p&gt;

&lt;p&gt;And a query that fails to parse can still carry a full, wrong mental model:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That doesn't run. It's also a perfect example of the exact mistake I was teaching about (filtering groups with &lt;code&gt;WHERE&lt;/code&gt; instead of &lt;code&gt;HAVING&lt;/code&gt;). It should count.&lt;/p&gt;

&lt;p&gt;But &lt;code&gt;asdf&lt;/code&gt; doesn't parse either. If junk counts, a learner can type three bits of junk and get a hint while thinking about nothing. Research on tutoring systems calls this "gaming the system", and learners find that shortcut fast.&lt;/p&gt;

&lt;p&gt;So I added a &lt;strong&gt;substance bar&lt;/strong&gt;. An attempt must contain one SQL keyword and one name from this question's schema:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isSubstantive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;identifiers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;low&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sql&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;low&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;SQL_KEYWORDS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;low&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;identifiers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// fail open, see below&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;identifiers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
      &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;low&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RegExp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;b&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;b`&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;low&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// so "id" doesn't match "video"&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Matching is by substring on purpose. Real learners wrote &lt;code&gt;GroupBy&lt;/code&gt; and &lt;code&gt;havingcount&amp;gt;=3&lt;/code&gt;. That's mangled spelling, but a real mental model. A strict token match would throw those away.&lt;/p&gt;

&lt;p&gt;I checked it against all 128 attempts from the screening round. 121 passed. The 7 rejected were 6 empty submissions and one bare &lt;code&gt;SELECT&lt;/code&gt;. No real attempt was lost.&lt;/p&gt;

&lt;p&gt;Notice the &lt;code&gt;fail open&lt;/code&gt; line. If the schema list fails to load, the bar only checks for a keyword. A misconfigured page must not silently refuse every learner. That would turn Arm A into "no help at all" without anyone noticing.&lt;/p&gt;
&lt;h2&gt;
  
  
  The leak guard
&lt;/h2&gt;

&lt;p&gt;Here's the uncomfortable part. To write a useful hint about a wrong query, the LLM has to see the correct one. I call that the reference query. So it's holding the answer while it writes. Sometimes it just prints it.&lt;/p&gt;

&lt;p&gt;If that reaches the learner, Arm A quietly becomes Arm B on that question. The experiment stops testing anything, and nothing in the logs looks wrong.&lt;/p&gt;

&lt;p&gt;So every hint passes through &lt;code&gt;hint-guard.js&lt;/code&gt; before the learner sees it. It's deterministic. It makes no model calls. It looks at text and says yes or no.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;inspect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;referenceQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;learnerQuery&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// 1. A runnable query: SELECT plus a real table after FROM/JOIN.&lt;/span&gt;
  &lt;span class="c1"&gt;// 2. A clause that distinguishes the reference query,&lt;/span&gt;
  &lt;span class="c1"&gt;//    unless the learner already wrote it themselves.&lt;/span&gt;
  &lt;span class="c1"&gt;// 3. Too much of the reference reproduced word for word.&lt;/span&gt;
  &lt;span class="c1"&gt;// 4. A walkthrough: four or more SQL keywords named.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;If a hint is rejected, the system asks the model once more. If that fails too, it serves a hand-written fallback for that question. Every attempt and every rejection is logged.&lt;/p&gt;

&lt;p&gt;Results: on 101 real hints, &lt;strong&gt;zero false alarms&lt;/strong&gt;. Against 24 leaks I wrote specifically to beat it, it caught 22.&lt;/p&gt;
&lt;h2&gt;
  
  
  What can go wrong
&lt;/h2&gt;

&lt;p&gt;Each rule in the guard exists because an earlier, simpler rule broke.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"SELECT and FROM means a leak."&lt;/strong&gt; My first check rejected any hint containing both words. But they're normal English. "You started with SELECT, but where does the data come FROM?" is a great hint, and it got blocked. The fix: a &lt;em&gt;runnable&lt;/em&gt; query must name a real table. The reference query tells you which tables exist, so look for those.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The semicolon rule.&lt;/strong&gt; I also rejected "select ... ;" on the idea that a semicolon ends a statement. English uses semicolons too. "Nothing is processed; you need to say where the data comes from" got blocked. I deleted that rule instead of patching it, because the table rule already covered real queries.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quoting the learner back to themselves.&lt;/strong&gt; The prompt asks the model to quote what the learner wrote. So a hint would say "your &lt;code&gt;MAX(price)&lt;/code&gt; is fine". &lt;code&gt;MAX(price)&lt;/code&gt; is part of the answer, so the guard blocked it. 7 of 8 rejections in one test run were this. But the learner already wrote it. It's in their editor. Now a clause only counts as a leak if the learner &lt;em&gt;doesn't&lt;/em&gt; already have it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The leak with no SQL in it.&lt;/strong&gt; "The FROM keyword picks the products table, then GROUP BY groups the rows by category..." No clause appears verbatim, but you could type the answer straight from it. The signal is shape, not content. A real hint talks about one or two things. A dictation names everything.&lt;/p&gt;

&lt;p&gt;I didn't pick the threshold by feel. I measured it. Across the 101 real hints: 66 named no SQL keyword, 29 named one, 5 named two, 1 named three, and none named four. The two prose leaks named six and seven. A limit of four separates them with room on both sides.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The silent fallback.&lt;/strong&gt; The LLM sits behind a Supabase edge function, so the API key never reaches the browser. Supabase pauses free projects after about a week without traffic. When paused, nothing errors. Arm A just serves the hand-written backup hints. The app looks fine, but it's quietly running a different tutor. I found this once, fixed it, and added a script to check it before every session.&lt;/p&gt;
&lt;h2&gt;
  
  
  What this means for your code
&lt;/h2&gt;

&lt;p&gt;If you're putting an LLM into a product, ask one question about each decision: &lt;strong&gt;does this need judgment, or does it need to be the same every time?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If the behaviour has to be explainable, testable or consistent, it belongs in code.&lt;/li&gt;
&lt;li&gt;If it needs language about &lt;em&gt;this specific case&lt;/em&gt;, that's what the model is for.&lt;/li&gt;
&lt;li&gt;Put a deterministic check between the model and the user for anything that would be expensive if the model got it wrong.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In my tutor, that split is why I could say exactly what each group got. It's also why the experiment could fail honestly. When two of my checks failed, I knew the failure was about learners, not about a model behaving differently on a Tuesday.&lt;/p&gt;

&lt;p&gt;Next in the series: the mistakes I made running my first controlled experiment as a developer, including the one a Demo Day judge caught.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/vikrantnegi" rel="noopener noreferrer"&gt;
        vikrantnegi
      &lt;/a&gt; / &lt;a href="https://github.com/vikrantnegi/withholding-pilot" rel="noopener noreferrer"&gt;
        withholding-pilot
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Learning OS — 100x Cohort 7 final capstone&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Vikrant Negi. Solo. Demo Day track. Submission 7 Oct 2026.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;An SQL tutor that makes you try before it helps, and a 6-person pilot testing whether that
changes what you can do a week later, with the tutor gone.&lt;/p&gt;
&lt;p&gt;For where things stand &lt;strong&gt;today&lt;/strong&gt;, read &lt;code&gt;STATUS.md&lt;/code&gt;. That is the only file that claims to be current.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Try it&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Live app:&lt;/strong&gt; &lt;a href="https://tranquil-starlight-f0129e.netlify.app/" rel="nofollow noopener noreferrer"&gt;https://tranquil-starlight-f0129e.netlify.app/&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Withholding tutor: &lt;a href="https://tranquil-starlight-f0129e.netlify.app/index.html?arm=A" rel="nofollow noopener noreferrer"&gt;https://tranquil-starlight-f0129e.netlify.app/index.html?arm=A&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Answer-giving baseline: &lt;a href="https://tranquil-starlight-f0129e.netlify.app/index.html?arm=B" rel="nofollow noopener noreferrer"&gt;https://tranquil-starlight-f0129e.netlify.app/index.html?arm=B&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What each one does:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;?arm=A&lt;/code&gt; opens the withholding tutor. Press Help before trying, and it refuses. Try once, and
Help gives a hint. Two more tries, and it gives the answer.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;?arm=B&lt;/code&gt; opens the baseline. Help gives the full answer at once.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Same page, same 16 practice questions, same grader. One config flag is the only difference.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;The result&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Arm A scored 36 of 36 person-questions (3 learners x 12 held-out questions). Arm&lt;/strong&gt;…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/vikrantnegi/withholding-pilot" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Try both arms live: &lt;a href="https://tranquil-starlight-f0129e.netlify.app/index.html?arm=A" rel="noopener noreferrer"&gt;Arm A&lt;/a&gt; · &lt;a href="https://tranquil-starlight-f0129e.netlify.app/index.html?arm=B" rel="noopener noreferrer"&gt;Arm B&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>javascript</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Should an AI Tutor Just Give You the Answer? I Tested It</title>
      <dc:creator>Vikrant Negi</dc:creator>
      <pubDate>Mon, 12 Oct 2026 14:00:00 +0000</pubDate>
      <link>https://dev.to/vikrantnegi/should-an-ai-tutor-just-give-you-the-answer-i-tested-it-2ldh</link>
      <guid>https://dev.to/vikrantnegi/should-an-ai-tutor-just-give-you-the-answer-i-tested-it-2ldh</guid>
      <description>&lt;p&gt;I built an AI tutor for SQL that won't hand you the answer straight away. Then I tested it against a normal version that does.&lt;/p&gt;

&lt;p&gt;Six people practised with one version or the other. A week later, they took a 12-question SQL test with no tutor at all.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;"try first" group&lt;/strong&gt; got all of their answers right: 36 of 36 (3 people × 12 questions).&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;"give me the answer" group&lt;/strong&gt; got 25 of 36.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That looks like a clear win. But when I presented it at my course's Demo Day, I told the room I don't credit the win to my tutor.&lt;/p&gt;

&lt;p&gt;This post explains why. It also covers the one question from a judge that I wish I had answered on my slides.&lt;/p&gt;

&lt;h2&gt;
  
  
  The question
&lt;/h2&gt;

&lt;p&gt;This was my final project for the 100x Engineers AI course. I wanted to answer one design question every AI tutor faces:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When a learner is stuck and presses Help, what should the tutor give them?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Most tools give the answer. ChatGPT does. Copilot does. It feels helpful, and the learner moves on.&lt;/p&gt;

&lt;p&gt;But a large study (Bastani and colleagues, PNAS 2025) should worry anyone building these tools. Students who practised maths with an AI that gave answers did &lt;em&gt;worse&lt;/em&gt; on a later exam than students who had no AI at all. Once the AI was gone, they were worse off.&lt;/p&gt;

&lt;p&gt;So I asked a narrower question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If the tutor makes you try first, and gives a hint before the answer, can you do more a week later, once the tutor is gone?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What I built
&lt;/h2&gt;

&lt;p&gt;An SQL tutor on one web page. You read a question in plain English, write a SQL query, and press Run. The page tells you if you're right. When you're stuck, you press &lt;strong&gt;Help&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;What Help does is the whole experiment. I built two versions of the page. In experiment-speak, each version is called an &lt;strong&gt;arm&lt;/strong&gt;, so you'll see "Arm A" and "Arm B" below.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;when you press Help&lt;/th&gt;
&lt;th&gt;Arm A: try first&lt;/th&gt;
&lt;th&gt;Arm B: give the answer&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;before you've tried anything&lt;/td&gt;
&lt;td&gt;refuses: "give it one run first"&lt;/td&gt;
&lt;td&gt;shows the answer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;after one attempt&lt;/td&gt;
&lt;td&gt;a hint, written by an LLM&lt;/td&gt;
&lt;td&gt;shows the answer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;after two more attempts&lt;/td&gt;
&lt;td&gt;shows the answer&lt;/td&gt;
&lt;td&gt;shows the answer&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Arm B is the &lt;strong&gt;baseline&lt;/strong&gt;: it behaves like most AI tools do today, so it's what I compare Arm A against.&lt;/p&gt;

&lt;p&gt;Everything else was identical: the same page, the same 16 practice questions, the same checking. One setting was the only difference.&lt;/p&gt;

&lt;p&gt;A judge at Demo Day called the experiment "arm wrestling". The name stuck, so it's the name of this series.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the test worked
&lt;/h2&gt;

&lt;p&gt;Six people took part, mostly developer colleagues. Three used Arm A and three used Arm B. I split them so both groups had similar SQL skill, based on two earlier rounds of testing.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Practice.&lt;/strong&gt; 16 SQL questions on &lt;code&gt;GROUP BY&lt;/code&gt; and &lt;code&gt;HAVING&lt;/code&gt;, with the tutor.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Wait 5 to 7 days.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Final test.&lt;/strong&gt; 12 new questions on the same skills. No Help button for anyone.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Step 3 is the point. I didn't care how well people did &lt;em&gt;with&lt;/em&gt; the tutor. I cared what they could still do once it was gone.&lt;/p&gt;

&lt;p&gt;Before I saw any results, I wrote down how I would read them. That included ten checks that, if they failed, would mean the result couldn't be trusted. More on why that mattered below.&lt;/p&gt;

&lt;h2&gt;
  
  
  The result
&lt;/h2&gt;

&lt;p&gt;Each group had 3 people answering 12 questions, so 36 answers per group.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;out of 36 answers&lt;/th&gt;
&lt;th&gt;Arm A: try first&lt;/th&gt;
&lt;th&gt;Arm B: give the answer&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;correct, after any number of tries&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;36&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;25&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;correct on the &lt;strong&gt;first&lt;/strong&gt; try&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;17&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;17&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;questions they at least opened&lt;/td&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;td&gt;29&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;wrong first tries they went on to fix&lt;/td&gt;
&lt;td&gt;19 of 19&lt;/td&gt;
&lt;td&gt;8 of 12&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Look at the second row. On the first try, the two groups were &lt;strong&gt;identical&lt;/strong&gt;: 17 each.&lt;/p&gt;

&lt;p&gt;So the tutor didn't make anyone better at writing a correct query from memory. The whole gap came from what people did &lt;em&gt;after&lt;/em&gt; a wrong answer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Arm A opened every question. Arm B left 7 blank.&lt;/li&gt;
&lt;li&gt;Arm A fixed all 19 of its wrong first tries. Arm B fixed 8 of 12.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's not what I predicted. I expected people to &lt;em&gt;remember&lt;/em&gt; more. What I saw was people who &lt;em&gt;kept going&lt;/em&gt; and fixed their mistakes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I don't claim the win
&lt;/h2&gt;

&lt;p&gt;This is the part I'm proudest of, even though it looks like a loss.&lt;/p&gt;

&lt;p&gt;Two of my ten checks failed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The groups didn't behave as differently as planned.&lt;/strong&gt; Arm A's whole idea is "try before you get help". That only matters if Arm B people &lt;em&gt;don't&lt;/em&gt; try first. But they did. On average, Arm B people made almost one attempt (0.83) before pressing Help anyway. So Arm A didn't create a habit that Arm B lacked.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The test was too easy for Arm A.&lt;/strong&gt; Arm A scored 100%. When a group hits the top score, you can't tell how far ahead it really is. Maybe it was just ahead by a little.&lt;/p&gt;

&lt;p&gt;Then two things skewed the result that I hadn't planned for.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Question order.&lt;/strong&gt; The 12 questions ran from easiest skill to hardest. Three people stopped early, so they all missed the hardest questions. My biggest gap between the groups, 6 answers on the hardest skill, was really just 6 blank answers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not everyone used Help.&lt;/strong&gt; One Arm A person never pressed Help during practice. So he never experienced the "try first" tutor at all, and he still scored 12 of 12. If I count only the four people who actually used Help, the gap shrinks from 11 answers to 3.&lt;/p&gt;

&lt;p&gt;Put together: the result leans toward "try first". But I can't say "try first" &lt;em&gt;caused&lt;/em&gt; it.&lt;/p&gt;

&lt;p&gt;So the claim I can defend is narrower:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Making learners try first seems to change what they do after a wrong answer. It did not change what they could get right on the first try.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The judge's question
&lt;/h2&gt;

&lt;p&gt;At Demo Day, a judge asked: "Were the practice and the test done live, with you watching?"&lt;/p&gt;

&lt;p&gt;They weren't. I sent each person a link, and they did it in their own time.&lt;/p&gt;

&lt;p&gt;He said that wasn't on the slides, and he had assumed it was live.&lt;/p&gt;

&lt;p&gt;He was right to ask. It changes how you read the result.&lt;/p&gt;

&lt;p&gt;When someone works unsupervised, you can't see what else they used. A ChatGPT tab is one Cmd+Tab away.&lt;/p&gt;

&lt;p&gt;And it doesn't affect both groups equally. Think about who has a reason to leave the page:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Arm B&lt;/strong&gt; people have no reason. The answer is one click away.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Arm A&lt;/strong&gt; people are blocked until they make an attempt. That's exactly when you'd paste the question into ChatGPT.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If Arm A people did that, they skipped the struggle the tutor was built to create. That makes Arm A's tutor less effective than it should be, and shrinks the gap between the groups.&lt;/p&gt;

&lt;p&gt;The final test was unsupervised too. If people looked things up there, both groups score higher, and the gap shrinks again.&lt;/p&gt;

&lt;p&gt;So working unsupervised probably made my result look &lt;em&gt;weaker&lt;/em&gt; than it really was, not stronger. That's a good story. I just didn't tell it, because it wasn't on the slide.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The lesson:&lt;/strong&gt; if a detail about how you ran the test would change how someone reads the result, put it on the slide. If you leave it out, people assume the standard version, and that may not be what you did.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd do differently
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Shuffle the question order for each person.&lt;/strong&gt; Then stopping early doesn't always hit the hardest questions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use harder test questions,&lt;/strong&gt; so neither group can score 100%.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make "correct on the first try" the main score.&lt;/strong&gt; It's the number my new prediction says &lt;em&gt;won't&lt;/em&gt; change. That makes it the number that could prove me wrong.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Track who actually used the feature.&lt;/strong&gt; Someone who never presses Help never experienced the tutor.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run the test live,&lt;/strong&gt; or at least log when people switch tabs or paste.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Key takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A big gap can come from somewhere you didn't look.&lt;/strong&gt; 36 vs 25 looked like better memory. The first-try row showed it wasn't.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write your checks before you see the results.&lt;/strong&gt; That's the only reason I could say "two checks failed" instead of explaining them away afterwards.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Changing your prediction isn't failing.&lt;/strong&gt; Mine moved from "people remember more" to "people recover better". That change is the most useful thing the project produced.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Say how you ran the test.&lt;/strong&gt; Live or unsupervised, question order, who actually used the feature. Readers fill in any gap with their own assumption.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Next in this series: how I built the tutor so that &lt;strong&gt;the LLM never decides whether to help&lt;/strong&gt;. Plain code makes that decision, and the LLM only writes the hint. That one rule is what made the experiment possible to check.&lt;/p&gt;

&lt;p&gt;The app is live, and you can try both versions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Arm A, try first: &lt;a href="https://tranquil-starlight-f0129e.netlify.app/index.html?arm=A" rel="noopener noreferrer"&gt;https://tranquil-starlight-f0129e.netlify.app/index.html?arm=A&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Arm B, give the answer: &lt;a href="https://tranquil-starlight-f0129e.netlify.app/index.html?arm=B" rel="noopener noreferrer"&gt;https://tranquil-starlight-f0129e.netlify.app/index.html?arm=B&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/vikrantnegi" rel="noopener noreferrer"&gt;
        vikrantnegi
      &lt;/a&gt; / &lt;a href="https://github.com/vikrantnegi/withholding-pilot" rel="noopener noreferrer"&gt;
        withholding-pilot
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Learning OS — 100x Cohort 7 final capstone&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Vikrant Negi. Solo. Demo Day track. Submission 7 Oct 2026.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;An SQL tutor that makes you try before it helps, and a 6-person pilot testing whether that
changes what you can do a week later, with the tutor gone.&lt;/p&gt;
&lt;p&gt;For where things stand &lt;strong&gt;today&lt;/strong&gt;, read &lt;code&gt;STATUS.md&lt;/code&gt;. That is the only file that claims to be current.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Try it&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Live app:&lt;/strong&gt; &lt;a href="https://tranquil-starlight-f0129e.netlify.app/" rel="nofollow noopener noreferrer"&gt;https://tranquil-starlight-f0129e.netlify.app/&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Withholding tutor: &lt;a href="https://tranquil-starlight-f0129e.netlify.app/index.html?arm=A" rel="nofollow noopener noreferrer"&gt;https://tranquil-starlight-f0129e.netlify.app/index.html?arm=A&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Answer-giving baseline: &lt;a href="https://tranquil-starlight-f0129e.netlify.app/index.html?arm=B" rel="nofollow noopener noreferrer"&gt;https://tranquil-starlight-f0129e.netlify.app/index.html?arm=B&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What each one does:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;?arm=A&lt;/code&gt; opens the withholding tutor. Press Help before trying, and it refuses. Try once, and
Help gives a hint. Two more tries, and it gives the answer.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;?arm=B&lt;/code&gt; opens the baseline. Help gives the full answer at once.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Same page, same 16 practice questions, same grader. One config flag is the only difference.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;The result&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Arm A scored 36 of 36 person-questions (3 learners x 12 held-out questions). Arm&lt;/strong&gt;…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/vikrantnegi/withholding-pilot" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;If you've built anything that decides how much an AI should help, I'd love to hear how you measured it. Please feel free to leave any feedback.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>learning</category>
      <category>llm</category>
      <category>career</category>
    </item>
    <item>
      <title>Why LLMs Can't Count Letters (And How They Fake It)</title>
      <dc:creator>Vikrant Negi</dc:creator>
      <pubDate>Tue, 23 Jun 2026 17:13:16 +0000</pubDate>
      <link>https://dev.to/vikrantnegi/why-llms-cant-count-letters-and-how-they-fake-it-3j9k</link>
      <guid>https://dev.to/vikrantnegi/why-llms-cant-count-letters-and-how-they-fake-it-3j9k</guid>
      <description>&lt;p&gt;You've probably noticed this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ask a language model to count the letters in "strawberry" and it'll confidently give you the wrong answer.&lt;/li&gt;
&lt;li&gt;Ask it to rhyme "entrepreneur" and it fumbles.&lt;/li&gt;
&lt;li&gt;Ask it to fix a typo in "teh quick brown fox" and it handles it without blinking.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Same model. Wildly different results. Why?&lt;/p&gt;

&lt;p&gt;The answer isn't "the model is bad at spelling." It's in how these models actually read text, and it's not what most people assume. Once you see the mechanism, you can predict which of these tasks a model will nail and which it'll botch, before you even run it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The model never sees your text
&lt;/h2&gt;

&lt;p&gt;When you type a message into an LLM, the model doesn't receive a string. It receives a sequence of integers.&lt;/p&gt;

&lt;p&gt;Before your input ever touches the neural network, it passes through a &lt;strong&gt;tokenizer&lt;/strong&gt;: a separate component that converts raw text into token IDs. The string &lt;code&gt;"strawberry"&lt;/code&gt; might become &lt;code&gt;[496, 19772]&lt;/code&gt;. That's what the model works with.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F62w5iwmkifoe7hrqbecn.png" 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F62w5iwmkifoe7hrqbecn.png" alt="The model never sees your text: a tokenizer converts the string to integers before the network runs" width="800" height="296"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The tokenizer is trained separately, before the LLM itself, and then frozen. The model never updates it. It just inherits whatever mapping the tokenizer learned. So the very first thing that happens to your prompt is a lossy translation the model has no say in.&lt;/p&gt;

&lt;h2&gt;
  
  
  How that mapping gets decided: BPE
&lt;/h2&gt;

&lt;p&gt;The algorithm most modern LLMs use is called &lt;strong&gt;Byte Pair Encoding (BPE)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here's the mechanism in one sentence: BPE starts with individual characters as the vocabulary, then repeatedly merges the most frequently co-occurring pair of tokens in the training corpus, until it hits a target vocabulary size.&lt;/p&gt;

&lt;p&gt;So it starts with the characters: &lt;code&gt;s, t, r, a, w, b, e, r, r, y&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Then it notices which pairs show up together constantly and merges them: &lt;code&gt;s·t·r·a·w&lt;/code&gt; collapses into &lt;code&gt;straw&lt;/code&gt;, &lt;code&gt;b·e·r·r·y&lt;/code&gt; collapses into &lt;code&gt;berry&lt;/code&gt;. And so on, until the vocabulary stabilizes at a target size (GPT-4 uses ~100,000 tokens).&lt;/p&gt;

&lt;p&gt;The result: &lt;code&gt;"strawberry"&lt;/code&gt; might tokenize as &lt;code&gt;["straw", "berry"]&lt;/code&gt;, not because those are linguistically meaningful units, but because those byte sequences were frequent enough in training data to survive the merges.&lt;/p&gt;

&lt;p&gt;This is the key intuition: &lt;strong&gt;the splits track frequency, not grammar.&lt;/strong&gt; Common words like &lt;code&gt;"the"&lt;/code&gt; become a single token. Rare or technical words get shredded into pieces. A word like &lt;code&gt;"thransformer"&lt;/code&gt; (a typo) might become 4 or 5 tokens the model has never seen grouped that way.&lt;/p&gt;

&lt;h2&gt;
  
  
  The information that gets thrown away
&lt;/h2&gt;

&lt;p&gt;Here's the thing that matters: once a sequence of characters becomes a token ID, &lt;strong&gt;the characters are gone&lt;/strong&gt;.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffb0tg4mk3qmncg4s1yni.png" 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffb0tg4mk3qmncg4s1yni.png" alt="Once it's a token ID the characters are gone: " width="800" height="261"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The model sees &lt;code&gt;19772&lt;/code&gt; (or whatever integer maps to &lt;code&gt;"berry"&lt;/code&gt;). It does not see &lt;code&gt;b&lt;/code&gt;, &lt;code&gt;e&lt;/code&gt;, &lt;code&gt;r&lt;/code&gt;, &lt;code&gt;r&lt;/code&gt;, &lt;code&gt;y&lt;/code&gt;. There's no "look inside the token" operation. No character-level access. The sub-token structure is destroyed at tokenization and never passed forward.&lt;/p&gt;

&lt;p&gt;This is why counting letters is hard. When you ask "how many R's in 'strawberry'?", the model has to reconstruct character-level information from statistical patterns in training data, even though &lt;code&gt;strawberry&lt;/code&gt; is just two opaque integers to it. It might have seen this exact question answered correctly often enough to get it right. It might not. There's no reliable mechanism, just pattern matching over a destroyed signal.&lt;/p&gt;

&lt;p&gt;Same reason rhyming is unreliable. Rhymes are about sound patterns at the character level. The model has no direct access to those. It's working from memory of what it's seen rhymed before, not from actual phoneme analysis.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Quick gut-check:&lt;/strong&gt; before reading on, predict it yourself. Spelling a word backwards, easy or hard for an LLM? (Hard. Same reason as counting letters: it requires character-level access the token ID doesn't carry.)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  So how does it handle typos?
&lt;/h2&gt;

&lt;p&gt;This is the interesting part, and the apparent contradiction.&lt;/p&gt;

&lt;p&gt;If the model has no character-level access, how does &lt;code&gt;"teh quick brown fox"&lt;/code&gt; get correctly interpreted as &lt;code&gt;"the quick brown fox"&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;The answer is attention.&lt;/p&gt;

&lt;p&gt;The model attends to every token in relation to every other token in the sequence. &lt;code&gt;"teh"&lt;/code&gt;, even though it has a different integer ID than &lt;code&gt;"the"&lt;/code&gt;, appears surrounded by the same context tokens: &lt;code&gt;"quick"&lt;/code&gt;, &lt;code&gt;"brown"&lt;/code&gt;, &lt;code&gt;"fox"&lt;/code&gt;. The model has seen &lt;code&gt;"the"&lt;/code&gt; in exactly that context billions of times during training. The attention mechanism builds a contextual representation of &lt;code&gt;"teh"&lt;/code&gt; that ends up in the same neighborhood as &lt;code&gt;"the"&lt;/code&gt; in the model's internal space.&lt;/p&gt;

&lt;p&gt;It's not correcting the typo. It's representing the token based on what surrounds it, and that representation ends up semantically equivalent.&lt;/p&gt;

&lt;p&gt;This works because &lt;code&gt;"teh"&lt;/code&gt; is a common typo. It appears frequently enough in training data, in the same contexts as &lt;code&gt;"the"&lt;/code&gt;, that the model has learned to bridge them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this breaks down
&lt;/h2&gt;

&lt;p&gt;Apply the same logic and you can predict where it'll fail. Three places it gets shaky:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A misspelled rare technical word&lt;/strong&gt;, like &lt;code&gt;"thransformer"&lt;/code&gt; instead of &lt;code&gt;"transformer"&lt;/code&gt;, is much harder to recover. The correct word appears less frequently in training data to begin with, and the broken form has even less signal. Attention can try, but the weights aren't there to make a confident bridge.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A misspelling in an ambiguous context&lt;/strong&gt;, where multiple words could fit, is also risky. The context constraint that makes &lt;code&gt;"teh"&lt;/code&gt; obvious doesn't apply when the surrounding tokens don't narrow things down.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anything that needs precise character-level reasoning&lt;/strong&gt;, like counting letters, detecting palindromes, generating anagram lists, or strict rhyming, is fundamentally unreliable. Not because the model is dumb, but because the information was structurally discarded before the model ever ran.&lt;/li&gt;
&lt;/ul&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg9zup535z0n2808q4sb5.png" 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg9zup535z0n2808q4sb5.png" alt="The same mechanism predicts where recovery fails: common forms bridge, rare ones don't" width="799" height="278"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The mental model worth keeping
&lt;/h2&gt;

&lt;p&gt;BPE and attention are complementary by design:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;BPE&lt;/strong&gt; trades character structure for compression efficiency. Fewer tokens means shorter sequences, which means cheaper and faster inference.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Attention&lt;/strong&gt; partially compensates by using context to recover meaning that the token IDs alone don't carry.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The system works well for what language is mostly made of: common words, common patterns, common errors. It degrades gracefully, not catastrophically, on the edges.&lt;/p&gt;

&lt;p&gt;But it's worth knowing where those edges are. Next time a model struggles with something that seems trivially easy (count the letters, spell this word backwards, does this rhyme with that), it's not a reasoning failure. It's a tokenization constraint that no amount of compute fixes.&lt;/p&gt;

&lt;p&gt;The information was thrown away before the model ever woke up.&lt;/p&gt;




&lt;h3&gt;
  
  
  Go deeper
&lt;/h3&gt;

&lt;p&gt;Tokenization is step one of the transformer pipeline. If you want the whole stack (embeddings, positional encoding, attention internals (Q/K/V), the feed-forward network, the residual stream, and the next-token loop), 0xkato wrote an excellent from-the-ground-up walkthrough: &lt;a href="https://0xkato.xyz/how-llms-actually-work/" rel="noopener noreferrer"&gt;&lt;strong&gt;How LLMs Actually Work&lt;/strong&gt;&lt;/a&gt;. It picks up right where this post leaves off.&lt;/p&gt;

&lt;p&gt;Find me on &lt;a href="https://x.com/vikrant_negi" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>llm</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Six Lines, Zero API Calls: Running LLMs On-Device in React Native</title>
      <dc:creator>Vikrant Negi</dc:creator>
      <pubDate>Mon, 22 Jun 2026 04:49:24 +0000</pubDate>
      <link>https://dev.to/vikrantnegi/six-lines-zero-api-calls-running-llms-on-device-in-react-native-3ahl</link>
      <guid>https://dev.to/vikrantnegi/six-lines-zero-api-calls-running-llms-on-device-in-react-native-3ahl</guid>
      <description>&lt;p&gt;Every AI feature I've worked on has done the same quiet thing: collect the user's text, send it to someone else's server, pay per token, and pray the network holds. That's fine until it isn't:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Your user is on a flight, with no network and a dead feature.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It's a journaling app, where "we send your private thoughts to a third party" is a hard no.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Finance notices the OpenAI bill climbing in a straight line with usage.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There's another option most React Native devs still treat as exotic: run the model on the device. No API call, no network, no per-token cost. The first time I wired this into an offline text-enhancement tool with Expo, the surprise wasn't that it worked. It's that the actual model code was about six lines. The hard parts were everywhere except the model.&lt;/p&gt;

&lt;p&gt;This is a walkthrough of react-native-executorch (by Software Mansion, the Reanimated and Gesture Handler folks), built on Meta's ExecuTorch runtime. We'll build a working local chat screen, but more importantly I'll show you the traps. The ones that cost me an afternoon each. The ones an AI-generated tutorial will confidently get wrong because the API changed underneath it.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/software-mansion" rel="noopener noreferrer"&gt;
        software-mansion
      &lt;/a&gt; / &lt;a href="https://github.com/software-mansion/react-native-executorch" rel="noopener noreferrer"&gt;
        react-native-executorch
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Declarative way to run AI models in React Native on device, powered by ExecuTorch.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div&gt;
  &lt;a rel="noopener noreferrer" href="https://github.com/software-mansion/react-native-executorch/blob/main/docs/static/img/logo-hero.svg"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fsoftware-mansion%2Freact-native-executorch%2Fraw%2Fmain%2Fdocs%2Fstatic%2Fimg%2Flogo-hero.svg" alt="RNE Logo" width="25%"&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;div&gt;
  &lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;React Native ExecuTorch
  &lt;/h1&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
  &lt;a href="https://swm-delivery.com/www/delivery/ck-slug.php?zoneid=zone-gh-react-native-executorch-1&amp;amp;n=1" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/b11f833d3fe69e1c913c0c492a40066b4b9a76b3963e85542dad5291a8bf4bd9/68747470733a2f2f73776d2d64656c69766572792e636f6d2f7777772f696d616765732f7a6f6e652d67682d72656163742d6e61746976652d6578656375746f7263682d313f6e3d31"&gt;&lt;/a&gt;
  &lt;a href="https://swm-delivery.com/www/delivery/ck-slug.php?zoneid=zone-gh-react-native-executorch-2&amp;amp;n=1" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/d441cafe1c6ab8bedc87399a4954779be942eff21f83810be52065651387678c/68747470733a2f2f73776d2d64656c69766572792e636f6d2f7777772f696d616765732f7a6f6e652d67682d72656163742d6e61746976652d6578656375746f7263682d323f6e3d31"&gt;&lt;/a&gt;
  &lt;a href="https://swm-delivery.com/www/delivery/ck-slug.php?zoneid=zone-gh-react-native-executorch-3&amp;amp;n=1" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/016f6595dfd55a0a21ce5ed40caaa40ecabf3c6c87c911c3563375e8a9782ce9/68747470733a2f2f73776d2d64656c69766572792e636f6d2f7777772f696d616765732f7a6f6e652d67682d72656163742d6e61746976652d6578656375746f7263682d333f6e3d31"&gt;&lt;/a&gt;
  &lt;a href="https://github.com/software-mansion/react-native-executorch/graphs/contributors" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/4efe273b5f42656fb7aea5280b7b03c48f2aa7726ce862e8a2f9e75fa83e5ae3/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f636f6e7472696275746f72732f736f6674776172652d6d616e73696f6e2f72656163742d6e61746976652d6578656375746f7263683f7374796c653d666f722d7468652d626164676526636f6c6f723d303030303842" alt="GitHub - Contributors"&gt;&lt;/a&gt;
  &lt;a href="https://github.com/software-mansion/react-native-executorch/stargazers" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/7505fab2e945a6132bb269eb140aabe32a29ff8d014e5c0fd6b8346a6ef5e87d/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f73746172732f736f6674776172652d6d616e73696f6e2f72656163742d6e61746976652d6578656375746f7263683f7374796c653d666f722d7468652d626164676526636f6c6f723d303030303842" alt="GitHub - Stars"&gt;&lt;/a&gt;
  &lt;a href="https://discord.gg/ZGqqY55qkP" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/e51047a22e3dac98689c685272191724fab79d9f4998d0b26eba5891c5914ff3/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f446973636f72642d4a6f696e25323055732d3030303038423f6c6f676f3d646973636f7264266c6f676f436f6c6f723d7768697465267374796c653d666f722d7468652d6261646765" alt="Join our Discord community"&gt;&lt;/a&gt;
  &lt;a href="https://docs.swmansion.com/react-native-executorch/" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/514f990b395918ac034db1a87a1fd6cb036ad08bb728d2ab4d39e099a33ff322/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f446f63756d656e746174696f6e2d3030303038423f6c6f676f3d676f6f676c65646f6373266c6f676f436f6c6f723d7768697465267374796c653d666f722d7468652d6261646765" alt="Documentation"&gt;&lt;/a&gt;
  &lt;a href="https://swmansion.com/contact" rel="nofollow noopener noreferrer"&gt;
    &lt;img src="https://camo.githubusercontent.com/3540cdadfdbfa8813bbead5b113ddf4a4415cecd7d29d75fa4ffa36e53484d4e/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4869726525323055732d3030303038423f6c6f676f3d7265616374266c6f676f436f6c6f723d776869746526636f6c6f723d6461726b677265656e267374796c653d666f722d7468652d6261646765" alt="Hire Us"&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;p&gt;
  &lt;a href="https://github.com/software-mansion/react-native-executorch/blob/main/README.md" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/2efd8fcd3ca563644070f28ba0203388afb9a28adbf20bcb098cf996fa7513c6/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f454e2d3030303038423f6c6f676f3d266c6f676f436f6c6f723d776869746526636f6c6f723d303030303842267374796c653d666f722d7468652d6261646765" alt="README"&gt;&lt;/a&gt;
  &lt;a href="https://github.com/software-mansion/react-native-executorch/blob/main/readmes/README_es.md" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/6a0e34c718bed2cc7f3d02be3b22e096a340a36c5138d08f7bed140edf583bc0/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f45532d3030303038423f6c6f676f3d266c6f676f436f6c6f723d776869746526636f6c6f723d303030303842267374796c653d666f722d7468652d6261646765" alt="README ES"&gt;&lt;/a&gt;
  &lt;a href="https://github.com/software-mansion/react-native-executorch/blob/main/readmes/README_fr.md" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/47d87a459c057539cd67ebdd44c0d30d1bfaea0c7e5449c50a97785a26f3dbc0/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f46522d3030303038423f6c6f676f3d266c6f676f436f6c6f723d776869746526636f6c6f723d303030303842267374796c653d666f722d7468652d6261646765" alt="README FR"&gt;&lt;/a&gt;
  &lt;a href="https://github.com/software-mansion/react-native-executorch/blob/main/readmes/README_cn.md" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/1bf5a490bd9770557c07173e89c3da1e002a505ca9285c0b16e46bb65455e8f1/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f434e2d3030303038423f6c6f676f3d266c6f676f436f6c6f723d776869746526636f6c6f723d303030303842267374796c653d666f722d7468652d6261646765" alt="README CN"&gt;&lt;/a&gt;
  &lt;a href="https://github.com/software-mansion/react-native-executorch/blob/main/readmes/README_pt.md" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/ec5c3bd410eff0518fbebdaf4dae5b5145311ec61b3486cfe13f3305116b999a/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f50542d3030303038423f6c6f676f3d266c6f676f436f6c6f723d776869746526636f6c6f723d303030303842267374796c653d666f722d7468652d6261646765" alt="README PT"&gt;&lt;/a&gt;
  &lt;a href="https://github.com/software-mansion/react-native-executorch/blob/main/readmes/README_in.md" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/3259d0d1cb6844ca44d98c8f61c24fc781fddc0ad98b522ed3d2c090a51e5d04/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f494e2d3030303038423f6c6f676f3d266c6f676f436f6c6f723d776869746526636f6c6f723d303030303842267374796c653d666f722d7468652d6261646765" alt="README IN"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;React Native ExecuTorch&lt;/strong&gt; provides a declarative way to run AI models on-device using React Native, powered by &lt;strong&gt;ExecuTorch&lt;/strong&gt; 🚀. It offers out-of-the-box support for a wide range of LLMs, computer vision models, and more. Visit our &lt;a href="https://huggingface.co/software-mansion" rel="nofollow noopener noreferrer"&gt;HuggingFace&lt;/a&gt; page to explore these models.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://executorch.ai" rel="nofollow noopener noreferrer"&gt;&lt;strong&gt;ExecuTorch&lt;/strong&gt;&lt;/a&gt;, developed by Meta, is a novel framework allowing AI model execution on devices like mobile phones or microcontrollers.&lt;/p&gt;

&lt;p&gt;React Native ExecuTorch bridges the gap between React Native and native platform capabilities, enabling developers to efficiently run local AI models on mobile devices. This can be achieved without the need for extensive expertise in native programming or machine learning.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.npmjs.com/package/react-native-executorch" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/1ddd5685fbf1df14105b0c36acb16cb135706580fa380eec58ec9eba6855b12c/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f762f72656163742d6e61746976652d6578656375746f7263683f636f6c6f723d303030303842" alt="npm version"&gt;&lt;/a&gt;
&lt;a href="https://www.npmjs.com/package/react-native-executorch" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/8f2c87e453d85efb9a0c7d3c6c73d35cd4c985cbcd53376863e6b72de22c3f0b/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f762f72656163742d6e61746976652d6578656375746f7263682f6578656375746f7263682d6e696768746c793f6c6162656c3d6e696768746c7926636f6c6f723d303030303842" alt="npm nightly"&gt;&lt;/a&gt;
&lt;a href="https://github.com/software-mansion/react-native-executorch/actions/workflows/ci.yml" rel="noopener noreferrer"&gt;&lt;img src="https://github.com/software-mansion/react-native-executorch/actions/workflows/ci.yml/badge.svg" alt="CI"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;strong&gt;Table of Contents&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/software-mansion/react-native-executorch#supported-versions" rel="noopener noreferrer"&gt;Supported Versions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/software-mansion/react-native-executorch#real-world-example" rel="noopener noreferrer"&gt;Real-world Example&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/software-mansion/react-native-executorch#quickstart---running-lfm25" rel="noopener noreferrer"&gt;Quickstart - Running LFM2.5&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/software-mansion/react-native-executorch#one-installation" rel="noopener noreferrer"&gt;1️⃣ Installation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/software-mansion/react-native-executorch#two-setup--initialization" rel="noopener noreferrer"&gt;2️⃣ Setup &amp;amp; Initialization&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/software-mansion/react-native-executorch#three-run-the-model" rel="noopener noreferrer"&gt;3️⃣ Run the Model!&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/software-mansion/react-native-executorch#demo-apps" rel="noopener noreferrer"&gt;Demo Apps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/software-mansion/react-native-executorch#ready-made-models" rel="noopener noreferrer"&gt;Ready-made Models&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/software-mansion/react-native-executorch#documentation" rel="noopener noreferrer"&gt;Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/software-mansion/react-native-executorch#license" rel="noopener noreferrer"&gt;License&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/software-mansion/react-native-executorch#whats-next" rel="noopener noreferrer"&gt;What's Next?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/software-mansion/react-native-executorch#react-native-executorch-is-created-by-software-mansion" rel="noopener noreferrer"&gt;React Native ExecuTorch is created by Software Mansion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Supported Versions&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;The minimal supported version are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;iOS…&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/software-mansion/react-native-executorch" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  The mental model
&lt;/h2&gt;

&lt;p&gt;Before any code, one idea that saves a lot of confusion: the LLM is one part of your app, not the app itself. The library gives you the model and nothing else. Everything around it is still your job.&lt;/p&gt;

&lt;p&gt;It helps to picture three things working together:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Your normal app code&lt;/strong&gt; is predictable. Same input, same output, every time: the buttons, the list, the navigation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The model&lt;/strong&gt; is not. Give it the same prompt twice and you'll get slightly different answers, because it generates text by predicting likely next words, not by looking facts up. That's also why it sometimes states wrong things with total confidence. It isn't a defect you can patch, it's how the thing works, so plan for it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The person reading the output&lt;/strong&gt; is the final check. They decide what to trust and what to ignore.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;react-native-executorch owns only the middle piece. It hands you a stream of words and a few status flags. It does not manage your chat UI, decide when to run the model, or judge whether the answer is any good. Those are yours to build.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;New Architecture only.&lt;/strong&gt; The library does not support the old RN architecture. If your app is still on it, that's your first migration.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Expo SDK 54+&lt;/strong&gt; if you're on Expo (which I'd recommend). Older SDKs break on the file-system APIs the library now depends on.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;A custom dev build, not Expo Go.&lt;/strong&gt; This relies on native modules. Expo Go will not load it. This trips up &lt;em&gt;everyone&lt;/em&gt; the first time.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;A real iOS device for release builds.&lt;/strong&gt; Because ExecuTorch runs natively, you can't produce an iOS &lt;em&gt;release&lt;/em&gt; build targeting the simulator. Debug on the sim is fine; release testing needs hardware.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last pair isn't optional advice, it's the difference between "why won't this run" and a working build. Write them on a sticky note.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;Installation is two steps: install the core package, then add a resource fetcher adapter.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
npm &lt;span class="nb"&gt;install &lt;/span&gt;react-native-executorch

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

&lt;/div&gt;



&lt;p&gt;Then a &lt;strong&gt;resource fetcher adapter&lt;/strong&gt;. These are platform-specific, so install the one that matches your project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
&lt;span class="c"&gt;# Expo projects&lt;/span&gt;

npm &lt;span class="nb"&gt;install &lt;/span&gt;react-native-executorch-expo-resource-fetcher expo-file-system expo-asset

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

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
&lt;span class="c"&gt;# Bare React Native&lt;/span&gt;

npm &lt;span class="nb"&gt;install &lt;/span&gt;react-native-executorch-bare-resource-fetcher @dr.pogodin/react-native-fs @kesha-antonov/react-native-background-downloader

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Before you call any other API&lt;/strong&gt;, you must initialize ExecuTorch with that adapter, once, at your app's entry point:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// App.tsx (or index.js), top level, runs once&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;initExecutorch&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-native-executorch&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ExpoResourceFetcher&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-native-executorch-expo-resource-fetcher&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;initExecutorch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;resourceFetcher&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ExpoResourceFetcher&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Skip this and the first model you load throws &lt;code&gt;ResourceFetcherAdapterNotInitialized&lt;/code&gt;. It's the most common setup mistake, and an easy one to miss because &lt;code&gt;initExecutorch&lt;/code&gt; lives at your entry point, far from where you actually call &lt;code&gt;useLLM&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;One more, if you plan to bundle a model with the app via &lt;code&gt;require()&lt;/code&gt; instead of downloading it. Add the binary extensions to Metro:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// metro.config.js&lt;/span&gt;

&lt;span class="nx"&gt;defaultConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resolver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;assetExts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pte&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// exported model&lt;/span&gt;

&lt;span class="nx"&gt;defaultConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resolver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;assetExts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// tokenizer&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Loading a model
&lt;/h2&gt;

&lt;p&gt;Here's the whole "load an LLM" surface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;models&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useLLM&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-native-executorch&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Chat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;llm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useLLM&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lfm2_5_1_2b_instruct&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;models.llm.*&lt;/code&gt; is a factory of pre-exported, ready-to-run models. One factory call gives the runtime everything it needs, already bundled:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the model itself, in ExecuTorch's &lt;code&gt;.pte&lt;/code&gt; format, already converted&lt;/li&gt;
&lt;li&gt;the matching tokenizer&lt;/li&gt;
&lt;li&gt;the tokenizer config&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://huggingface.co/software-mansion" rel="noopener noreferrer"&gt;Software Mansion hosts the full lineup on HuggingFace&lt;/a&gt;, so you point at a model and the library handles fetching and wiring up the rest. No manual file juggling.&lt;/p&gt;

&lt;p&gt;I'm using LFM2.5 1.2B here because it's the library's own default and small enough to behave on mid-range hardware. You've got real choices though. The bundled lineup includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Text models:&lt;/strong&gt; Qwen 3 (0.6B / 1.7B / 4B), Llama 3.2 (1B / 3B), Phi 4 Mini, SmolLM 2, Hammer 2.1&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Vision-capable:&lt;/strong&gt; Gemma 4 and LFM2.5-VL&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why I'd start small:&lt;/strong&gt; a 4B model is noticeably smarter and noticeably more likely to crash with an out-of-memory error on a budget Android. Pick the smallest model that clears your quality bar, then size up only if you must.&lt;/p&gt;

&lt;p&gt;The hook gives you state to drive your UI:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;llm.downloadProgress&lt;/code&gt;: 0 to 1 while the model downloads on first launch&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;llm.isReady&lt;/code&gt;: flips true when it's loaded and usable&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;llm.error&lt;/code&gt;: populated if anything blows up&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;llm.isGenerating&lt;/code&gt;: true while tokens are streaming&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;llm.response&lt;/code&gt;: the generated text, updated &lt;em&gt;token by token&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wiring the chat
&lt;/h2&gt;

&lt;p&gt;There are two ways to use this hook, and the docs name them well: &lt;strong&gt;functional&lt;/strong&gt; vs &lt;strong&gt;managed&lt;/strong&gt;. The distinction matters, so don't skim it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Functional: you own the state
&lt;/h3&gt;

&lt;p&gt;You pass the full message array every time, you keep the history, you get a token stream back. Nothing is remembered for you.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;models&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useLLM&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Message&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-native-executorch&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;View&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Button&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-native&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Chat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;llm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useLLM&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lfm2_5_1_2b_instruct&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleGenerate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;system&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;You are a concise, helpful assistant.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;

      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Explain a closure in one sentence.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="c1"&gt;// resolves to the full string; llm.response updates live as it streams&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;final&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;done:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;final&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isReady&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Loading model… &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;downloadProgress&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;%&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;View&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Generate"&lt;/span&gt; &lt;span class="na"&gt;onPress&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleGenerate&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;

      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;View&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the shape of &lt;code&gt;generate&lt;/code&gt;: it both returns a promise &lt;em&gt;and&lt;/em&gt; streams into &lt;code&gt;llm.response&lt;/code&gt;. So you render &lt;code&gt;llm.response&lt;/code&gt; for the live typewriter effect, and &lt;code&gt;await&lt;/code&gt; the return value when you need the finished string for, say, saving to a DB. Same call, two consumption patterns.&lt;/p&gt;

&lt;h3&gt;
  
  
  Managed: the library owns the state
&lt;/h3&gt;

&lt;p&gt;If you're building an actual back-and-forth chat, you don't want to hand-roll the history array. &lt;code&gt;sendMessage&lt;/code&gt; plus &lt;code&gt;messageHistory&lt;/code&gt; plus &lt;code&gt;configure&lt;/code&gt; does it for you:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;models&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useLLM&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;DEFAULT_SYSTEM_PROMPT&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-native-executorch&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ManagedChat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;llm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useLLM&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lfm2_5_1_2b_instruct&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;configure&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;chatConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;systemPrompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;DEFAULT_SYSTEM_PROMPT&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; Keep answers short.`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;

      &lt;span class="na"&gt;generationConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;temperature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

        &lt;span class="na"&gt;topP&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;send&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Who are you?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;View&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messageHistory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;: &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;

      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Send"&lt;/span&gt; &lt;span class="na"&gt;onPress&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;disabled&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isReady&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;View&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;configure&lt;/code&gt; only affects the managed path. &lt;code&gt;chatConfig&lt;/code&gt; and &lt;code&gt;toolsConfig&lt;/code&gt; do nothing to &lt;code&gt;generate()&lt;/code&gt;. That's a subtle footgun: set a system prompt in &lt;code&gt;configure&lt;/code&gt;, then call &lt;code&gt;generate&lt;/code&gt; and wonder why it's ignored. Mode and config have to match.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My take:&lt;/strong&gt; use managed for chat, functional for one-shot transforms (summarize this, rewrite that, extract JSON from this). The text-enhancement tool I mentioned was pure functional. There's no conversation, just &lt;em&gt;input string to improved string&lt;/em&gt;, and managed state would've been overhead I'd have to fight.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reality check
&lt;/h2&gt;

&lt;p&gt;This is the section a docs-paraphrase can't write, so here's the honest list of what actually bit me.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Dismounting mid-generation crashes the app.&lt;/strong&gt; Hard crash, not a warning. If the user navigates away while tokens are still streaming, you go down. The fix is to interrupt and wait:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// before unmount / on a stop button&lt;/span&gt;

&lt;span class="nx"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;interrupt&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// then wait until llm.isGenerating === false before tearing down&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wire a stop button to &lt;code&gt;interrupt()&lt;/code&gt; and gate &lt;code&gt;isGenerating&lt;/code&gt; into your navigation guards. I learned this the way everyone does, with a back-button press during a long answer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. First launch downloads a model. A big one.&lt;/strong&gt; These files run from roughly 700MB to over a gigabyte. The hosted models stream down on first use and cache in your app's documents directory, but if you don't render &lt;code&gt;downloadProgress&lt;/code&gt;, the user stares at a dead screen and force-quits. Build the loading UX &lt;em&gt;first&lt;/em&gt;, not last. And consider letting users pick a model, or bundle a small one for offline-from-install.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. RAM, not CPU, is your ceiling.&lt;/strong&gt; Crashes on cheaper devices are almost always out-of-memory, not slowness. Use quantized models. If you're testing on an Android emulator and it dies, bump the emulator's RAM before you blame your code. I wasted real time debugging "my" bug that was just a starved emulator.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Expo Go will never work.&lt;/strong&gt; Said it above, saying it again, because you &lt;em&gt;will&lt;/em&gt; forget once and spend ten minutes confused. Native modules mean a custom dev build.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. One model runner at a time.&lt;/strong&gt; The architecture is built around a single active model instance. Don't try to stand up two &lt;code&gt;useLLM&lt;/code&gt; components side by side and expect both to run.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Token batching exists for a reason.&lt;/strong&gt; A fast model can push 60+ tokens/sec, and if every token triggers a React re-render, your UI jank-fest begins. The library batches token emissions (default around 10 tokens or 80ms, whichever first). If generation feels choppy or your list stutters, tune &lt;code&gt;outputTokenBatchSize&lt;/code&gt; and &lt;code&gt;batchTimeInterval&lt;/code&gt; in &lt;code&gt;generationConfig&lt;/code&gt; rather than reaching for a &lt;code&gt;FlatList&lt;/code&gt; rewrite.&lt;/p&gt;

&lt;h2&gt;
  
  
  Taking it further
&lt;/h2&gt;

&lt;p&gt;Once the basic loop works, the library has more than chat:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tool calling.&lt;/strong&gt; Define functions the model can invoke (check weather, toggle a setting, hit a local API). You give it &lt;code&gt;tools&lt;/code&gt; plus an &lt;code&gt;executeToolCallback&lt;/code&gt;, and in managed mode it parses and runs the calls for you. Use a model whose chat template actually supports it. Hammer 2.1 is purpose-built for function calling.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Structured output.&lt;/strong&gt; Need clean JSON instead of prose? There's a helper that turns a schema (plain JSON Schema or Zod) into formatting instructions, plus a validator to fix and check the result. This is how you'd build an offline "extract fields from this text" feature.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Vision and audio.&lt;/strong&gt; Gemma 4 and LFM2.5-VL take a &lt;code&gt;capabilities&lt;/code&gt; array and accept an &lt;code&gt;imagePath&lt;/code&gt; or audio buffer on &lt;code&gt;sendMessage&lt;/code&gt;. On-device OCR into an LLM is a genuinely good offline-translation pattern.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;RAG.&lt;/strong&gt; There's a companion &lt;code&gt;@react-native-rag/executorch&lt;/code&gt; package that plugs this LLM (and on-device embeddings) into a vector store for fully local retrieval-augmented generation. If your "model is one component" instinct is itching, that's the package that proves the point.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;p&gt;Get the basic loop running, then start making it real. A sensible order:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Validate on real hardware first.&lt;/strong&gt; Clone the repo, run &lt;code&gt;examples/llm&lt;/code&gt; on an actual phone (not the simulator), and watch the first-launch download happen. The number that matters is cold-start time on a mid-range device, and it should drive your model choice more than any benchmark table.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Study a real, shipped app.&lt;/strong&gt; The minimal example gets you running; a production app shows you the parts the docs skip. &lt;a href="https://github.com/software-mansion-labs/private-mind" rel="noopener noreferrer"&gt;Private Mind&lt;/a&gt; is Software Mansion's open-source, fully offline AI chatbot built on this library (it's live on the App Store and Play Store). Clone it and poke around: model downloading and management, on-device benchmarking, chat history, and custom assistant presets are all in there to learn from.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Check the benchmark pages before you commit to a model.&lt;/strong&gt; The docs publish real inference-time, memory, and model-size numbers per model and device. Pick from those, not from vibes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Build the loading screen before the chat screen.&lt;/strong&gt; On-device AI lives or dies on that first impression, and it's the one thing the model code can't do for you.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Lazy-load with &lt;code&gt;preventLoad&lt;/code&gt;.&lt;/strong&gt; Every hook takes a &lt;code&gt;preventLoad&lt;/code&gt; flag so a model doesn't download or eat RAM until the user actually opens that feature. On a multi-feature app, this is the difference between a 1GB cold install and a fast one.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once it runs, the fun part is what you build on top. A few possibilities the library makes surprisingly easy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Hybrid privacy: redact on-device, then go to the cloud.&lt;/strong&gt; &lt;code&gt;usePrivacyFilter&lt;/code&gt; is a local model that finds personal info (names, emails, phone numbers, addresses, even API keys) in text without it ever leaving the phone. Scrub that text locally, then send the safe version to a bigger cloud model. You get GPT-class quality without handing over the sensitive bits.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;A fully offline voice assistant.&lt;/strong&gt; Chain speech-to-text (Whisper is built in), then the LLM, then text-to-speech. Speak, get a spoken answer, no network anywhere in the loop. The library even supports streaming LLM output straight into speech as it generates.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Point-the-camera answers.&lt;/strong&gt; Run on-device OCR on a photo of a sign, menu, or document, feed the extracted text to the LLM, and you've got an offline translator or document Q&amp;amp;A tool. Like a private, no-signal version of Google Lens.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Answers from the user's own notes.&lt;/strong&gt; The companion &lt;code&gt;@react-native-rag/executorch&lt;/code&gt; package wires the model and on-device embeddings into a local vector store, so the model can answer questions grounded in the user's documents, fully offline.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of these is the same core idea pointed at a different problem: the model is one piece, and the product is the experience you wrap around it.&lt;/p&gt;




&lt;ul&gt;
&lt;li&gt;Docs: &lt;a href="https://docs.swmansion.com/react-native-executorch" rel="noopener noreferrer"&gt;docs.swmansion.com/react-native-executorch&lt;/a&gt; · &lt;/li&gt;
&lt;li&gt;Repo: &lt;a href="https://github.com/software-mansion/react-native-executorch" rel="noopener noreferrer"&gt;software-mansion/react-native-executorch&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>reactnative</category>
      <category>ai</category>
      <category>expo</category>
      <category>javascript</category>
    </item>
    <item>
      <title>How Graphify Stopped My Team from Burning Thousands of Tokens Per Query</title>
      <dc:creator>Vikrant Negi</dc:creator>
      <pubDate>Sun, 21 Jun 2026 11:26:47 +0000</pubDate>
      <link>https://dev.to/vikrantnegi/how-graphify-stopped-my-team-from-burning-through-cursors-context-window-2d32</link>
      <guid>https://dev.to/vikrantnegi/how-graphify-stopped-my-team-from-burning-through-cursors-context-window-2d32</guid>
      <description>&lt;p&gt;Every time I asked Cursor about our auth flow, it would open 8-12 files, read through each one, and burn through tokens before even starting to answer. For a React Native codebase maintained by 6 developers, this adds up fast.&lt;/p&gt;

&lt;p&gt;I lead a front-end team building React Native apps. Cursor is our primary IDE. And while Cursor is great at understanding code, the way it retrieves context — by grepping and reading raw files sequentially — doesn't scale well on larger projects.&lt;/p&gt;

&lt;p&gt;Then I found &lt;a href="https://github.com/safishamsi/graphify" rel="noopener noreferrer"&gt;Graphify&lt;/a&gt;, and it fundamentally changed how our AI assistant interacts with our codebase.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Graphify actually does
&lt;/h2&gt;

&lt;p&gt;Before I get to the setup, it's worth understanding the mechanism — because it's not just another Cursor plugin.&lt;/p&gt;

&lt;p&gt;Graphify pre-processes your entire project into a &lt;strong&gt;knowledge graph&lt;/strong&gt;. Think of it as a map of your codebase where every function, class, module, and concept becomes a node, and every relationship between them becomes an edge. When Cursor needs to answer a question, instead of reading 47 files to understand a flow, it runs &lt;code&gt;graphify query "auth flow"&lt;/code&gt; — a scoped subgraph lookup that returns only the relevant nodes and connections.&lt;/p&gt;

&lt;p&gt;The important part: &lt;strong&gt;code extraction happens entirely locally&lt;/strong&gt; via tree-sitter AST parsing. No API calls, no code leaving your machine. You only hit an LLM when processing docs, PDFs, or images. For a pure code project (which most React Native apps are), the extraction cost is basically zero.&lt;/p&gt;

&lt;p&gt;The output is three files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;graph.json&lt;/code&gt; — the full graph (the core artifact your assistant queries)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;graph.html&lt;/code&gt; — an interactive visualization you can explore in a browser&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GRAPH_REPORT.md&lt;/code&gt; — an architecture summary with god nodes, surprising connections, and suggested questions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That report alone is worth the setup. It finds connections between modules you didn't realize existed and surfaces the most-connected concepts in your project — the things everything flows through.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up Graphify for Cursor
&lt;/h2&gt;

&lt;p&gt;I'm on an M1 MacBook Pro. Here's the exact setup that worked for me.&lt;/p&gt;

&lt;h3&gt;
  
  
  Install
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;uv  &lt;span class="c"&gt;# if you don't have it already&lt;/span&gt;
uv tool &lt;span class="nb"&gt;install &lt;/span&gt;graphifyy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Important: use &lt;code&gt;uv tool install&lt;/code&gt;, not &lt;code&gt;pip install&lt;/code&gt;.&lt;/strong&gt; On M1 Macs, pip can break Python path resolution because Graphify resolves the Python binary at runtime from a cached path. &lt;code&gt;uv&lt;/code&gt; isolates the environment and avoids this entirely. I learned this the hard way.&lt;/p&gt;

&lt;h3&gt;
  
  
  Build the graph
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;your-project
graphify extract &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First run takes a few minutes depending on codebase size. Since our projects are all code (no PDFs/docs), the entire extraction runs locally on tree-sitter — no API key needed for this step.&lt;/p&gt;

&lt;h3&gt;
  
  
  Install the Cursor integration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;graphify cursor &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This writes a &lt;code&gt;.cursor/rules/graphify.mdc&lt;/code&gt; file with &lt;code&gt;alwaysApply: true&lt;/code&gt;. If you've worked with Cursor's rule system, you know what this means — it gets injected into every conversation automatically, telling Cursor to prefer &lt;code&gt;graphify query&lt;/code&gt; over reading raw source files.&lt;/p&gt;

&lt;p&gt;Quick note for anyone working with &lt;code&gt;.mdc&lt;/code&gt; files: a rule with &lt;code&gt;alwaysApply: false&lt;/code&gt; and no &lt;code&gt;globs&lt;/code&gt; and no &lt;code&gt;description&lt;/code&gt; will never auto-apply — it becomes manual &lt;code&gt;@&lt;/code&gt;-mention only. Graphify sets &lt;code&gt;alwaysApply: true&lt;/code&gt;, which is the correct move for a global project context rule.&lt;/p&gt;

&lt;h3&gt;
  
  
  Set up the git hook
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;graphify hook &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This installs a &lt;code&gt;post-commit&lt;/code&gt; hook. Every time anyone commits, it auto-rebuilds the graph using tree-sitter — fully local, takes seconds. It also sets up a git merge driver so if two devs commit graph updates in parallel, &lt;code&gt;graph.json&lt;/code&gt; gets union-merged automatically instead of leaving conflict markers.&lt;/p&gt;

&lt;p&gt;After this, you basically forget Graphify is running. The graph stays current with every commit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rolling this out to a team
&lt;/h2&gt;

&lt;p&gt;This is where I had the most questions before adopting it, so let me cover the practical stuff.&lt;/p&gt;

&lt;h3&gt;
  
  
  What to commit
&lt;/h3&gt;

&lt;p&gt;Add these to your repo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;graphify-out/
├── graph.json          # the core artifact
├── graph.html          # interactive visualization
├── GRAPH_REPORT.md     # architecture summary
├── manifest.json       # tracks extracted files (uses relative paths — portable)
└── cache/              # optional — commit for faster rebuilds

.cursor/rules/graphify.mdc    # the Cursor integration rule
.graphifyignore               # if you create one
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add this to &lt;code&gt;.gitignore&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;graphify-out/cost.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;cost.json&lt;/code&gt; tracks Graphify's own LLM API spend during extraction — not tokens saved in Cursor. It's local accounting, not a shared artifact.&lt;/p&gt;

&lt;h3&gt;
  
  
  Impact on teammates who don't use Graphify
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Zero.&lt;/strong&gt; This matters — Graphify is purely additive.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;graphify-out/&lt;/code&gt; folder is just inert data files. No build process depends on them, no CI breaks without them. The &lt;code&gt;.mdc&lt;/code&gt; file only affects Cursor users who have that rule active. If a dev without Graphify installed triggers the &lt;code&gt;post-commit&lt;/code&gt; hook, it fails silently (binary not found) and the commit goes through normally.&lt;/p&gt;

&lt;p&gt;You can adopt this incrementally. Install it yourself, commit &lt;code&gt;graphify-out/&lt;/code&gt;, and other devs benefit from the graph whenever they install Graphify later. No coordination needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Recommended team workflow
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Initial setup on &lt;code&gt;main&lt;/code&gt;&lt;/strong&gt; — do the first &lt;code&gt;graphify extract .&lt;/code&gt; on main and commit &lt;code&gt;graphify-out/&lt;/code&gt;. This becomes the shared baseline everyone pulls.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feature branches update automatically&lt;/strong&gt; — the git hook rebuilds incrementally after every commit on any branch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;On merge to main&lt;/strong&gt; — the graph from the feature branch comes with it. The merge driver handles concurrent updates.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One edge case: if someone runs a long-lived feature branch without the hook installed, their &lt;code&gt;graph.json&lt;/code&gt; will drift. A manual &lt;code&gt;graphify extract . --update&lt;/code&gt; before raising the MR resyncs it. I've considered adding this to our MR checklist, but honestly the hook handles 99% of cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  What can go wrong
&lt;/h2&gt;

&lt;p&gt;A few things I ran into or anticipated:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The M1 pip issue.&lt;/strong&gt; Already mentioned above — use &lt;code&gt;uv tool install&lt;/code&gt;, not pip. Wasted about 20 minutes debugging path resolution before figuring this out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can you actually measure token savings?&lt;/strong&gt; Honestly, not directly. Cursor doesn't expose per-conversation token usage. What you &lt;em&gt;can&lt;/em&gt; observe: response latency drops (graph queries are faster than multi-file reads), context window pressure reduces (fewer "context limit reached" moments), and Graphify logs every query at &lt;code&gt;~/.cache/graphify-queries.log&lt;/code&gt; with nodes returned and duration — so you get a sense of how targeted the retrieval is.&lt;/p&gt;

&lt;p&gt;The rough mental model: without Graphify, Cursor answering "how does auth connect to the API layer?" reads 8-12 files. A typical React Native file is ~200-400 tokens. That's ~3000 tokens just for context loading, before the answer. With Graphify, it reads a scoped subgraph — a fraction of that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;cost.json&lt;/code&gt; is not "tokens saved."&lt;/strong&gt; I initially confused this. &lt;code&gt;cost.json&lt;/code&gt; tracks what Graphify itself spent on LLM calls during extraction. For a code-only project, this is near zero. It has nothing to do with what Cursor saves in conversation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Graph drift on branch switches.&lt;/strong&gt; If you install the &lt;code&gt;post-commit&lt;/code&gt; hook but not the &lt;code&gt;post-checkout&lt;/code&gt; hook, switching branches won't rebuild the graph. The latest version of Graphify supports &lt;code&gt;post-checkout&lt;/code&gt; hooks too — check if &lt;code&gt;graphify hook install&lt;/code&gt; sets both up for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd do differently
&lt;/h2&gt;

&lt;p&gt;If I were setting this up again:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Run the initial extraction on a clean main branch first.&lt;/strong&gt; I initially ran it on a feature branch and had to redo it when I realized the team needed the baseline on main.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check &lt;code&gt;GRAPH_REPORT.md&lt;/code&gt; before sharing with the team.&lt;/strong&gt; It surfaces god nodes and surprising connections — some of which might reveal architectural issues you'd rather address before making the graph a shared artifact.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skip committing &lt;code&gt;cache/&lt;/code&gt; initially.&lt;/strong&gt; It speeds up rebuilds but adds repo size. Start without it and add later if rebuild times become annoying.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Who should use this
&lt;/h2&gt;

&lt;p&gt;Graphify makes the most difference when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your codebase has &lt;strong&gt;50+ files&lt;/strong&gt; and multiple modules that interact&lt;/li&gt;
&lt;li&gt;You use Cursor (or Claude Code, Codex, Gemini CLI — it supports 10+ assistants)&lt;/li&gt;
&lt;li&gt;You're on a team and tired of your AI assistant not "knowing" the codebase between sessions&lt;/li&gt;
&lt;li&gt;You do code reviews and need to quickly understand how a change connects to the rest of the system&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For small projects with 5-6 files, the graph adds structural clarity but the token savings are minimal — the files already fit in a context window.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;uv
uv tool &lt;span class="nb"&gt;install &lt;/span&gt;graphifyy
&lt;span class="nb"&gt;cd &lt;/span&gt;your-project
graphify extract &lt;span class="nb"&gt;.&lt;/span&gt;
graphify cursor &lt;span class="nb"&gt;install&lt;/span&gt;  &lt;span class="c"&gt;# or the equivalent for your IDE&lt;/span&gt;
graphify hook &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's 5 commands to set up. The graph builds, your assistant starts using it, and the hook keeps it current. No config files to write, no services to run.&lt;/p&gt;

&lt;p&gt;Check out the &lt;a href="https://github.com/safishamsi/graphify" rel="noopener noreferrer"&gt;Graphify repo&lt;/a&gt; for the full docs. If you're on Cursor specifically, the &lt;code&gt;.mdc&lt;/code&gt; integration is the smoothest path — it just works.&lt;/p&gt;

&lt;p&gt;If you've been feeling like Cursor "forgets" your codebase every conversation, or burns through context reading files it shouldn't need — Graphify is worth the 10 minutes to set up. It was for me.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I'm a React Native tech lead exploring AI-assisted development workflows. Find me on &lt;a href="https://twitter.com/vikrant_negi" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; and &lt;a href="https://github.com/vikrantnegi" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>cursor</category>
      <category>productivity</category>
      <category>webdev</category>
    </item>
    <item>
      <title>This is how earned money by blogging on Dev</title>
      <dc:creator>Vikrant Negi</dc:creator>
      <pubDate>Tue, 30 Jun 2020 11:40:52 +0000</pubDate>
      <link>https://dev.to/vikrantnegi/this-is-how-earned-money-by-blogging-on-dev-5bep</link>
      <guid>https://dev.to/vikrantnegi/this-is-how-earned-money-by-blogging-on-dev-5bep</guid>
      <description>&lt;p&gt;Making money by writing blogs in not new, many people do it for the living. I've also written few paid articles but most of the times, but mostly I write articles for sharing my knowledge, learning new things and help others to learn.&lt;/p&gt;

&lt;h2&gt;
  
  
  Back Story
&lt;/h2&gt;

&lt;p&gt;Recently, I wrote an article on DEV titled &lt;strong&gt;Become a VS Code Ninja with these Extensions and Tools [2020]&lt;/strong&gt;. The article was very well received in the DEV community and was an overnight success. The article got hundreds of reactions and views within few hours.&lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/vikrantnegi" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nqA2bTi6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://media.dev.to/cdn-cgi/image/width%3D150%2Cheight%3D150%2Cfit%3Dcover%2Cgravity%3Dauto%2Cformat%3Dauto/https%253A%252F%252Fdev-to-uploads.s3.amazonaws.com%252Fuploads%252Fuser%252Fprofile_image%252F59047%252F5f75c582-d590-4f39-adb5-22f143c232f3.jpg" alt="vikrantnegi"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/vikrantnegi/become-a-vs-code-ninja-with-these-extensions-and-tools-2020-1119" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Become a VS Code Ninja with these Extensions and Tools [2020]&lt;/h2&gt;
      &lt;h3&gt;Vikrant Negi ・ Jun 26 '20&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#javascript&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#beginners&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#codenewbie&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#vscode&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;p&gt;Here is a screenshot of how many views I've received so far on my post (Jun 26 - Jun 30).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fioen21uf8k7dhsltf4d6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fioen21uf8k7dhsltf4d6.png" alt="Article Stats" width="800" height="100"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the next day, I received a mail from uphold about the funds being transferred into my account.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fm6si68os9umj820asc11.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fm6si68os9umj820asc11.png" alt="Payment Received Screen Shot" width="800" height="317"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At first, I was surprised since I never thought that I would actually be getting paid for writing on DEV. &lt;/p&gt;

&lt;p&gt;Although, the money received was not that much but, the feeling of getting paid and appreciated for your hard work is beyond imagination.&lt;/p&gt;

&lt;h2&gt;
  
  
  How did I get paid?
&lt;/h2&gt;

&lt;p&gt;Did you know the Dev recently got Web Monetized?&lt;/p&gt;

&lt;p&gt;Web Monetization is a standard that provides content creators with an alternative solution to get paid for the content they share on the web. When a user visits a monetized web page, their browser (if it supports Web Monetization) may send micropayments to the site.&lt;/p&gt;

&lt;p&gt;Read more about Web Monetization on DEV in following post:&lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/devteam" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__org__pic"&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SsFI-Xwo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://media.dev.to/cdn-cgi/image/width%3D150%2Cheight%3D150%2Cfit%3Dcover%2Cgravity%3Dauto%2Cformat%3Dauto/https%253A%252F%252Fdev-to-uploads.s3.amazonaws.com%252Fuploads%252Forganization%252Fprofile_image%252F1%252Fd908a186-5651-4a5a-9f76-15200bc6801f.jpg" alt="The DEV Team" width="150" height="150"&gt;
      &lt;div class="ltag__link__user__pic"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GMRzL-jB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://media.dev.to/cdn-cgi/image/width%3D150%2Cheight%3D150%2Cfit%3Dcover%2Cgravity%3Dauto%2Cformat%3Dauto/https%253A%252F%252Fdev-to-uploads.s3.amazonaws.com%252Fuploads%252Fuser%252Fprofile_image%252F1075%252F1c1975ce-97e8-401f-b99f-1ea88f9cae3e.jpeg" alt="" width="150" height="150"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/devteam/dev-is-now-web-monetized-21db" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;DEV is now Web Monetized&lt;/h2&gt;
      &lt;h3&gt;Peter Kim Frank for The DEV Team ・ Jun 10 '20&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#webmonetization&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#gftwhackathon&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#coil&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#meta&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;p&gt;You can now web-monetize your DEV posts. You will receive micropayments for time spent reading your posts directly. So the more people read your posts the more money you will make.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do I enable it for my DEV account?
&lt;/h2&gt;

&lt;p&gt;In order to start generating micro-payments with Web Monetization on your DEV content, just following these steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set up a wallet (here are a &lt;a href="https://webmonetization.org/docs/ilp-wallets"&gt;few options&lt;/a&gt;). Dev recommends UpHold.&lt;/li&gt;
&lt;li&gt;Once your wallet is set up, you'll receive a Payment Pointer address. If signed up for uphold you can find your Payment Pointer address &lt;a href="https://webmonetization.org/docs/uphold#find-your-payment-pointer"&gt;here&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Go to your DEV &lt;a href="https://dev.to/settings/misc"&gt;account settings&lt;/a&gt; and add your Payment Pointer to the Web Monetization section&lt;/li&gt;
&lt;li&gt;That's it. You'll now receive a micro-payment every time a monetized browsers visit your your profile page and posts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Read more on how to web-monetize your DEV posts go to the following DEV post:&lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/devteam" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__org__pic"&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SsFI-Xwo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://media.dev.to/cdn-cgi/image/width%3D150%2Cheight%3D150%2Cfit%3Dcover%2Cgravity%3Dauto%2Cformat%3Dauto/https%253A%252F%252Fdev-to-uploads.s3.amazonaws.com%252Fuploads%252Forganization%252Fprofile_image%252F1%252Fd908a186-5651-4a5a-9f76-15200bc6801f.jpg" alt="The DEV Team" width="150" height="150"&gt;
      &lt;div class="ltag__link__user__pic"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LBrAYN44--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://media.dev.to/cdn-cgi/image/width%3D150%2Cheight%3D150%2Cfit%3Dcover%2Cgravity%3Dauto%2Cformat%3Dauto/https%253A%252F%252Fdev-to-uploads.s3.amazonaws.com%252Fuploads%252Fuser%252Fprofile_image%252F1%252Ff451a206-11c8-4e3d-8936-143d0a7e65bb.png" alt="" width="150" height="150"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/devteam/you-can-now-web-monetize-your-dev-posts-but-don-t-get-your-hopes-up-too-quickly-goc" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;You can now web-monetize your DEV posts! (But don't get your hopes up too quickly)&lt;/h2&gt;
      &lt;h3&gt;Ben Halpern for The DEV Team ・ Jun 10 '20&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#webmonetization&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#gftwhackathon&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#coil&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#meta&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  Future is bright on DEV
&lt;/h2&gt;

&lt;p&gt;Earnings at this point is going to be quite small, but there’s a bright future ahead as more people and platforms begin to adopt this standard. As large percentage of DEV authors will have enabled their own payment pointer, monetized browsers will be able to directly reward their work.&lt;/p&gt;

&lt;p&gt;Happy coding! 🙏 and Happy monetizing 🤑&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus
&lt;/h2&gt;

&lt;p&gt;Yay!! I received another payment mail from the uphold. 🤑&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Follow me on Twitter for more hidden gems &lt;a href="https://twitter.com/vikrant_negi"&gt;@vikrant_negi&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;small&gt;Cover image credit - Photo by &lt;a href="https://unsplash.com/@sharonmccutcheon?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Sharon McCutcheon&lt;/a&gt; on Unsplash&lt;/small&gt;&lt;/p&gt;

</description>
      <category>webmonetization</category>
      <category>tutorial</category>
      <category>beginners</category>
      <category>codenewbie</category>
    </item>
    <item>
      <title>Become a VS Code Ninja with these Extensions and Tools [2020]</title>
      <dc:creator>Vikrant Negi</dc:creator>
      <pubDate>Fri, 26 Jun 2020 19:17:40 +0000</pubDate>
      <link>https://dev.to/vikrantnegi/become-a-vs-code-ninja-with-these-extensions-and-tools-2020-1119</link>
      <guid>https://dev.to/vikrantnegi/become-a-vs-code-ninja-with-these-extensions-and-tools-2020-1119</guid>
      <description>&lt;p&gt;VS Code is undoubtedly the best editor out there especially when it comes to writing javascript. It is fast, feature rich and has huge pool of extensions available which greatly enhances developer experience.&lt;/p&gt;

&lt;p&gt;I've been using VS Code for the past 4 years and over these years I came across many these extensions that I would like to share with you. I'm so addicted to these extensions that my developer life would be incomplete without them.&lt;/p&gt;

&lt;p&gt;Here is a list of my VS Code extensions:&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://marketplace.visualstudio.com/items?itemName=atomiks.moonlight"&gt;Moonlight Theme&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;I've a habit of changing themes quite often and this is the current theme that I'm using these days. Colors are not very light and are easy on eyes both during day and night.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gq6e5DsM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/atomiks/moonlight-vscode-theme/master/preview.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gq6e5DsM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/atomiks/moonlight-vscode-theme/master/preview.png" alt="Moonlight Theme" width="800" height="733"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://marketplace.visualstudio.com/items?itemName=formulahendry.auto-rename-tag"&gt;Auto Rename Tag&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Automatically rename paired HTML/XML tag, same as Visual Studio IDE does.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--eWea0-We--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://raw.githubusercontent.com/formulahendry/vscode-auto-rename-tag/master/images/usage.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--eWea0-We--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://raw.githubusercontent.com/formulahendry/vscode-auto-rename-tag/master/images/usage.gif" alt="Auto Rename Tag" width="800" height="521"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://marketplace.visualstudio.com/items?itemName=christian-kohler.path-intellisense"&gt;Path Intellisense&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Visual Studio Code plugin that autocompletes filenames. Very helpful in React projects when importing components from different folders.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/iaHeUiDeTUZuo.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/iaHeUiDeTUZuo.gif" alt="Path Intellisense" width="480" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://marketplace.visualstudio.com/items?itemName=mrmlnc.vscode-scss"&gt;SCSS IntelliSense&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Advanced autocompletion and refactoring support for SCSS. Must have for front-end developers that use Sass.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fszt3wxhfngzeiqcneiwg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fszt3wxhfngzeiqcneiwg.png" alt="SCSS IntelliSense" width="800" height="390"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://marketplace.visualstudio.com/items?itemName=JakeWilson.vscode-placeholder-images"&gt;Placeholder Images&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Generate and insert placeholder images into your HTML. Easy to find placeholders images directly in the VS Code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cNh8O_k2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://raw.githubusercontent.com/Jakobud/vscode-placeholder-images/master/images/example.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cNh8O_k2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://raw.githubusercontent.com/Jakobud/vscode-placeholder-images/master/images/example.gif" alt="Placeholder Images" width="692" height="269"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer"&gt;Live Server&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Launch a development local Server with live reload feature for static &amp;amp; dynamic pages.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--q_KwLl8P--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://raw.githubusercontent.com/ritwickdey/vscode-live-server/master/images/Screenshot/vscode-live-server-animated-demo.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--q_KwLl8P--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://raw.githubusercontent.com/ritwickdey/vscode-live-server/master/images/Screenshot/vscode-live-server-animated-demo.gif" alt="Live Server" width="800" height="428"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode"&gt;Prettier - Code formatter&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Code formatter using prettier.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F7ya6yprr6rmlvualclgs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F7ya6yprr6rmlvualclgs.png" alt="Prettier - Code formatter" width="800" height="389"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://marketplace.visualstudio.com/items?itemName=xabikos.ReactSnippets"&gt;Reactjs code snippets&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Code snippets for Reactjs development in ES6 syntax.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--sqhZ9fAy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://raw.githubusercontent.com/xabikos/vscode-react/master/images/component.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sqhZ9fAy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://raw.githubusercontent.com/xabikos/vscode-react/master/images/component.gif" alt="Reactjs code snippets" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://marketplace.visualstudio.com/items?itemName=Shan.code-settings-sync"&gt;Settings Sync&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Must have extension in my opinion, it synchronize all your VS Code Settings, Snippets, Themes, File Icons, Launch, Keybindings, Workspaces and Extensions Across Multiple Machines Using GitHub Gist.&lt;/p&gt;

&lt;p&gt;Very helpful in case you change your development machine and want to import all your previous VS Code data.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fhws0lt2izval51kh1kn2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fhws0lt2izval51kh1kn2.png" alt="Settings Sync" width="800" height="390"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://marketplace.visualstudio.com/items?itemName=aaron-bond.better-comments"&gt;Better Comments&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Improve your code commenting by annotating with alert, informational, TODOs, and more!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zm9m8obi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/aaron-bond/better-comments/master/images/better-comments.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zm9m8obi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/aaron-bond/better-comments/master/images/better-comments.PNG" alt="Better Comments" width="459" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://marketplace.visualstudio.com/items?itemName=CoenraadS.bracket-pair-colorizer"&gt;Bracket Pair Colorizer&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;A customizable extension for colorizing matching brackets.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--risYyGHZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/CoenraadS/BracketPair/master/images/example.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--risYyGHZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/CoenraadS/BracketPair/master/images/example.png" alt="Bracket Pair Colorizer" width="625" height="119"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker"&gt;Code Spell Checker&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;If you are like me who can write a single word without mistake this extension is a must have. Checks your code for spell errors and provide suggestions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gqd8Ec5p--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://raw.githubusercontent.com/streetsidesoftware/vscode-spell-checker/master/packages/client/images/suggestions.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gqd8Ec5p--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://raw.githubusercontent.com/streetsidesoftware/vscode-spell-checker/master/packages/client/images/suggestions.gif" alt="Code Spell Checker" width="736" height="547"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint"&gt;ESLint&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Integrates ESLint JavaScript into VS Code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fedxtlpjek3j7o9yalchy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fedxtlpjek3j7o9yalchy.png" alt="ESLint" width="800" height="389"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://marketplace.visualstudio.com/items?itemName=wix.vscode-import-cost"&gt;Import Cost&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Display import/require package size in the editor.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YRxJvnXi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://file-wkbcnlcvbn.now.sh/import-cost.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YRxJvnXi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://file-wkbcnlcvbn.now.sh/import-cost.gif" alt="Import Cost" width="800" height="139"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://marketplace.visualstudio.com/items?itemName=WallabyJs.quokka-vscode"&gt;Quokka.js&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Live Scratchpad for JavaScript. Write Javascript and see your outputs in the editor itself. Also has a Interactive Examples which are useful for both experienced developers (as references) and for new developers (to learn).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--P50dZQdF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://quokkajs.com/assets/img/main-video.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--P50dZQdF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://quokkajs.com/assets/img/main-video.gif" alt="Quokka.js" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://marketplace.visualstudio.com/items?itemName=TabNine.tabnine-vscode"&gt;TabNine&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Another must have extension. TabNine is a all-language autocompleter — which uses machine learning to help you write code faster.&lt;/p&gt;

&lt;p&gt;This is a Dark Magic extension, after installing and using it for sometime it gives very accurate suggestions which leave you awestruck.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fm1syjq6v8wdxid9kwe9v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fm1syjq6v8wdxid9kwe9v.png" alt="TabNine" width="800" height="391"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://marketplace.visualstudio.com/items?itemName=jpoissonnier.vscode-styled-components"&gt;vscode-styled-components&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Syntax highlighting and IntelliSense for styled-components.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zlN5hSIW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/styled-components/vscode-styled-components/master/demo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zlN5hSIW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/styled-components/vscode-styled-components/master/demo.png" alt="vscode-styled-components" width="600" height="268"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://marketplace.visualstudio.com/items?itemName=VisualStudioExptTeam.vscodeintellicode"&gt;Visual Studio IntelliCode&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;The Visual Studio IntelliCode extension provides AI-assisted development features for Python, TypeScript/JavaScript and Java developers in Visual Studio Code, with insights based on understanding your code context combined with machine learning.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HJcmXlgG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://docs.microsoft.com/en-us/visualstudio/intellicode/media/python-intellicode.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HJcmXlgG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://docs.microsoft.com/en-us/visualstudio/intellicode/media/python-intellicode.gif" alt="Visual Studio IntelliCode" width="800" height="355"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Bonus
&lt;/h1&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://github.com/sozercan/OpenInCode"&gt;OpenInCode&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;📂 Finder toolbar app to open current folder in Visual Studio Code&lt;/p&gt;

&lt;h1&gt;
  
  
  Closing
&lt;/h1&gt;

&lt;p&gt;I'll keep updating this list with new extensions that I find. Feel free to suggest the extensions that I might have missing. I would love to know what new extensions developers are using.&lt;/p&gt;

&lt;p&gt;I've also created a git repo with the VS Code extensions and settings that I frequently use. Find the repo &lt;a href="https://github.com/vikrantnegi/vscode-personal-preference-setting"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;small&gt;Cover image credit - Photo by &lt;a href="https://unsplash.com/@flowforfrank?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Ferenc Almasi&lt;/a&gt; on Unsplash&lt;/small&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>beginners</category>
      <category>codenewbie</category>
      <category>vscode</category>
    </item>
    <item>
      <title>Build Facebook-Like Placeholder Loaders in React Native</title>
      <dc:creator>Vikrant Negi</dc:creator>
      <pubDate>Thu, 20 Feb 2020 07:07:22 +0000</pubDate>
      <link>https://dev.to/vikrantnegi/build-facebook-like-placeholder-loaders-in-react-native-20d4</link>
      <guid>https://dev.to/vikrantnegi/build-facebook-like-placeholder-loaders-in-react-native-20d4</guid>
      <description>&lt;p&gt;A good user experience is what separates a awesome app from a regular one. Users expect their apps to be blazing fast since no one enjoys waiting. When building mobile apps, we often use loaders, progress bars, loading indicators to tell users when something is happening or loading.&lt;/p&gt;

&lt;p&gt;The latest trend is to show placeholder/skeleton screens as loaders. This pattern of using placeholder improves the user's perceived experience when waiting for something to load. You might have seen these kinds of loaders in apps like Facebook and Netflix.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YarQEIfc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/6ugpxa5n6qri5hbpm1a0.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YarQEIfc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/6ugpxa5n6qri5hbpm1a0.gif" alt="Netflix Placeholder Loader"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Loaders in React Native
&lt;/h2&gt;

&lt;p&gt;In most cases, using the good Ol' &lt;a href="https://facebook.github.io/react-native/docs/activityindicator"&gt;&lt;code&gt;ActivityIndicator&lt;/code&gt;&lt;/a&gt; along with React Native modal would suffice. However, as a user and as a developer, I find them to be too mundane and old fashioned.&lt;/p&gt;

&lt;p&gt;To make these loaders interesting, I created a React Native package that uses Airbnb's Lottie and &lt;a href="https://facebook.github.io/react-native/docs/modal#docsNav"&gt;Modals&lt;/a&gt; to create beautiful animated loaders. If you want to read more more on how to use it read &lt;a href="https://medium.com/better-programming/how-to-create-a-beautifully-animated-loader-in-react-native-21da37a8f6b0"&gt;this&lt;/a&gt; article.&lt;/p&gt;

&lt;p&gt;In this article we will be learn how to implement placeholder screens using &lt;a href="https://github.com/mfrachet/rn-placeholder"&gt;rn-placeholder&lt;/a&gt; library. We will also use Google Books API to fetch some books data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;To follow this tutorial, please make sure you have the following installed on your local development environment and have access to the services mentioned below:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://nodejs.org/en/"&gt;Node.js&lt;/a&gt; (&amp;gt;=8.x.x) with npm/yarn installed.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.expo.io/versions/latest/workflow/expo-cli/?"&gt;expo-cli&lt;/a&gt; (&amp;gt;=3.0.4)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Make sure to update &lt;code&gt;expo-cli&lt;/code&gt; if you haven't updated in a while since &lt;code&gt;expo&lt;/code&gt; releases are quickly out of date.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;Let's set up a new Expo project using expo-cli.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;expo init placeholderLoading
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Choose blank template and hit &lt;code&gt;Enter&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Then it will ask to name the project, type &lt;code&gt;Placeholder Loading&lt;/code&gt; and hit &lt;code&gt;Enter&lt;/code&gt; again.&lt;/li&gt;
&lt;li&gt;Lastly, it will ask you to press &lt;code&gt;Y&lt;/code&gt; to install dependencies with &lt;code&gt;yarn&lt;/code&gt; or &lt;code&gt;n&lt;/code&gt; to install dependencies with &lt;code&gt;npm&lt;/code&gt;. Press &lt;code&gt;y&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This will create a new React Native app using expo-cli.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running App and Installing Dependencies
&lt;/h2&gt;

&lt;p&gt;Go into the project directory that we just created.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;placeholderLoading
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;To run the project type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;yarn start
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Press &lt;code&gt;i&lt;/code&gt; to run the iOS Simulator. This will automatically run the iOS Simulator even if it's not opened.&lt;/p&gt;

&lt;p&gt;Press &lt;code&gt;a&lt;/code&gt; to run the Android Emulator. Note that the emulator must be installed and started already before typing a otherwise it will throw an error in the terminal.&lt;/p&gt;

&lt;p&gt;You can also run the app in your device by installing &lt;code&gt;expo&lt;/code&gt; app from the app store and then scanning the QR code.&lt;/p&gt;

&lt;p&gt;Now, install &lt;code&gt;rn-placeholder&lt;/code&gt; and &lt;code&gt;react-native-responsive-dimensions&lt;/code&gt; packages&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;yarn add rn-placeholder react-native-responsive-dimensions
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;rn-placeholder&lt;/code&gt; will help us display some placeholder stuff before rendering our text or media content.&lt;br&gt;
And, &lt;code&gt;react-native-responsive-dimensions&lt;/code&gt; will provide us with responsive height and width that automatically adjust themselves based on the screen size of the device.&lt;/p&gt;
&lt;h2&gt;
  
  
  Creating Components
&lt;/h2&gt;

&lt;p&gt;Now we'll create two components &lt;code&gt;BookCardComponent&lt;/code&gt; and &lt;code&gt;BookCardPlaceholderComponent&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here is the code for &lt;code&gt;BookCardComponent&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;BookCardComponent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;thumbnail&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;authors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onPress&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;View&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shadow&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TouchableOpacity&lt;/span&gt;
        &lt;span class="nx"&gt;onPress&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onPress&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;
          &lt;span class="na"&gt;flexDirection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;row&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;
        &lt;span class="p"&gt;}}&lt;/span&gt;
      &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;View&lt;/span&gt;
          &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;
            &lt;span class="na"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;responsiveHeight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;responsiveWidth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="na"&gt;marginRight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
          &lt;span class="p"&gt;}}&lt;/span&gt;
        &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Image&lt;/span&gt;
            &lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;thumbnail&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;
            &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;
              &lt;span class="na"&gt;borderRadius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;resizeMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;contain&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;100%&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;100%&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;}}&lt;/span&gt;
            &lt;span class="nx"&gt;imageStyle&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;borderRadius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;
          &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/View&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;View&lt;/span&gt;
          &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;
            &lt;span class="na"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
          &lt;span class="p"&gt;}}&lt;/span&gt;
        &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Text&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;marginBottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;fontSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Text&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Text&lt;/span&gt;
            &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;marginBottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;
            &lt;span class="nx"&gt;numberOfLines&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="nx"&gt;ellipsizeMode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tail&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
          &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nx"&gt;by&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;authors&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Text&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/View&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/TouchableOpacity&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/View&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Code for &lt;code&gt;BookCardPlaceholderComponent&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;BookCardPlaceholderComponent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Placeholder&lt;/span&gt;
    &lt;span class="nx"&gt;Animation&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;ShineOverlay&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;
      &lt;span class="na"&gt;marginVertical&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;marginHorizontal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;borderRadius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
    &lt;span class="p"&gt;}}&lt;/span&gt;
    &lt;span class="nx"&gt;Left&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;PlaceholderMedia&lt;/span&gt;
        &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{[&lt;/span&gt;
          &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;responsiveWidth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;responsiveHeight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;]}&lt;/span&gt;
      &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="p"&gt;)}&lt;/span&gt;
  &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;PlaceholderLine&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;marginTop&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;responsiveHeight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;PlaceholderLine&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;marginTop&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;responsiveHeight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;PlaceholderLine&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Placeholder&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;BookCardPlaceholderComponent&lt;/code&gt; will be rendered initially until we received data from the Google Books API and after receive data we'll use the &lt;code&gt;BookCardComponent&lt;/code&gt; for rendering the books data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Update App.js
&lt;/h2&gt;

&lt;p&gt;We'll be using React &lt;a href="https://reactjs.org/docs/hooks-intro.html"&gt;Hooks&lt;/a&gt; in this tutorial but you can easily convert it for classes.&lt;/p&gt;

&lt;p&gt;Add the following code to your &lt;code&gt;App.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;books&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setBooks&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;([...&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;({})]);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;isDataFetched&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setDataFetched&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://www.googleapis.com/books/v1/volumes/?maxResults=30&amp;amp;q=danbrown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;responseJson&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;responseJson&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;booksList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;book&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;volumeInfo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;authors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;imageLinks&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bookId&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;book&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;bookId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;thumbnail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;imageLinks&lt;/span&gt;
              &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;imageLinks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;thumbnail&lt;/span&gt;
              &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://i.ibb.co/YLC0nQQ/not-found.png&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;authors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;authors&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;authors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/,/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
          &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="nx"&gt;setBooks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;booksList&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;setDataFetched&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;renderBookComponent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;thumbnail&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;authors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;bookId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;BookCardComponent&lt;/span&gt;
        &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;bookId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;authors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;authors&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;thumbnail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;thumbnail&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;renderX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;FlatList&lt;/span&gt;
      &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;books&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;renderItem&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;renderBookComponent&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;keyExtractor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bookId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;renderPlaceholders&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nx"&gt;books&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;BookCardPlaceholder&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SafeAreaView&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isDataFetched&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;renderX&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;renderPlaceholders&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/SafeAreaView&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;First, we setup some initial state using &lt;code&gt;useState&lt;/code&gt; hook.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;books&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setBooks&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;([...&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;({})]);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;isDataFetched&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setDataFetched&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;In &lt;code&gt;books&lt;/code&gt; state we'll update all the books that we'll receive from the API and for the initial state we created an array of empty 10 objects.&lt;/p&gt;

&lt;p&gt;We also initialized &lt;code&gt;isDataFetched&lt;/code&gt; state and set its initial value &lt;code&gt;false&lt;/code&gt;. It will be used to track the status of the data received from the API. When we received the data and done with the data manipulation, we'll set its value to &lt;code&gt;true&lt;/code&gt; by using &lt;code&gt;setDataFetched&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In the next step, we'll use &lt;code&gt;useEffect&lt;/code&gt; hook to call the Google Books API and update our respective states with the data received.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://www.googleapis.com/books/v1/volumes/?maxResults=30&amp;amp;q=danbrown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;responseJson&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;responseJson&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;booksList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;book&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;volumeInfo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;authors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;imageLinks&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bookId&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;book&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;bookId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;thumbnail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;imageLinks&lt;/span&gt;
            &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;imageLinks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;thumbnail&lt;/span&gt;
            &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://i.ibb.co/YLC0nQQ/not-found.png&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;authors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;authors&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;authors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/,/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;

      &lt;span class="nx"&gt;setBooks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;booksList&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;setDataFetched&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now, in the return we'll make use of the &lt;code&gt;isDataFetched&lt;/code&gt; state to conditionally render the components.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;isDataFetched&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;renderX&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;renderPlaceholders&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;In &lt;code&gt;renderX()&lt;/code&gt; we'll return a &lt;code&gt;FlatList&lt;/code&gt; which will render the list of Books using &lt;code&gt;renderBookComponent&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;renderPlaceholders()&lt;/code&gt; we'll render &lt;code&gt;BookCardPlaceholder&lt;/code&gt; using the map function. This is what the users will see during the data fetching.&lt;/p&gt;

&lt;p&gt;So during the data fetching &lt;code&gt;BookCardPlaceholder&lt;/code&gt; will be rendered and when the data is fetched successfully &lt;code&gt;BookCardComponent&lt;/code&gt; will be rendered by the &lt;code&gt;FlatList&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here is how the final app looks like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--eFFyL2UZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/xsfi84sddsahuaim0jcr.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--eFFyL2UZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/xsfi84sddsahuaim0jcr.gif" alt="Finished app demo gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;We have successfully created a React Native app that uses &lt;code&gt;rn-placeholder&lt;/code&gt; to show placeholder as loaders during data fetching just like facebook, Netflix and other popular apps.&lt;/p&gt;

&lt;p&gt;This is just a simple implementation of placeholders screens as loaders. You can customize these placeholder components according to your need or app theme.&lt;/p&gt;

&lt;p&gt;Find the source code in the Github repo &lt;a href="https://github.com/vikrantnegi/react-native-placeholder-loading"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus
&lt;/h2&gt;

&lt;p&gt;I've also created a Open source Book Searching React Native app in which I used these placeholder components as loaders. Feel free to have a look at its source code &lt;a href="https://github.com/vikrantnegi/RN-Book-Search"&gt;here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>reactnativeloaders</category>
      <category>facebookplaceholderloaders</category>
      <category>netflixloaders</category>
    </item>
    <item>
      <title>Creating a Multi-Language App in React Native</title>
      <dc:creator>Vikrant Negi</dc:creator>
      <pubDate>Mon, 08 Jul 2019 17:12:46 +0000</pubDate>
      <link>https://dev.to/vikrantnegi/creating-a-multi-language-app-in-react-native-1joj</link>
      <guid>https://dev.to/vikrantnegi/creating-a-multi-language-app-in-react-native-1joj</guid>
      <description>&lt;p&gt;Language has always been an essential part of any business. As the business grows it becomes important for the business to expand in different countries and regions. To achieve success in these local regions it is important to provide localization.&lt;/p&gt;

&lt;p&gt;When it comes to the mobile app, the conditions are no different. As the app users expand to different countries, it becomes important to provide the users with the ability to use the app in their native languages.&lt;/p&gt;

&lt;p&gt;In this article, we are going to build a React Native app that supports multi-language using &lt;a href="https://github.com/react-native-community/react-native-localize"&gt;react-native-localize&lt;/a&gt; package.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;This tutorial requires basic knowledge of React Native. To set up your development machine, follow the official guide &lt;a href="https://facebook.github.io/react-native/docs/0.59/getting-started"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To make sure we are on the same page, following are the versions used in this tutorial -&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Node v10.15.0&lt;/li&gt;
&lt;li&gt;npm 6.4.1&lt;/li&gt;
&lt;li&gt;yarn 1.16.0&lt;/li&gt;
&lt;li&gt;react-native 0.59.9&lt;/li&gt;
&lt;li&gt;react-native-localize 1.1.3&lt;/li&gt;
&lt;li&gt;i18n-js 3.3.0&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;We'll be building a React Native app which will support &lt;strong&gt;English&lt;/strong&gt;, &lt;strong&gt;French&lt;/strong&gt; and &lt;strong&gt;Arabic&lt;/strong&gt; language.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you want to have a look at the source code right away, here is the Github &lt;a href="https://github.com/vikrantnegi/react-native-multi-language-app"&gt;link&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To create a new project using &lt;code&gt;react-native-cli&lt;/code&gt;, type the following in the terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;react-native init multiLanguage
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;





&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;multiLanguage
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  Add required libraries
&lt;/h4&gt;

&lt;p&gt;Install &lt;code&gt;react-native-localize&lt;/code&gt; by typing the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;yarn add react-native-localize
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;and then link it using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;react-native &lt;span class="nb"&gt;link &lt;/span&gt;react-native-localize
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;If you face any error during installation, check the installation instructions &lt;a href="https://github.com/react-native-community/react-native-localize#setup"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The &lt;code&gt;react-native-localize&lt;/code&gt; lib gives you access to a lot of localization related device constants, but does not come with an i18n lib.&lt;/p&gt;

&lt;p&gt;We'll use &lt;a href="https://github.com/fnando/i18n-js"&gt;&lt;code&gt;I18n.js&lt;/code&gt;&lt;/a&gt; to provide a I18n translations on the JavaScript.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;yarn add i18n-js
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Since &lt;code&gt;i18n-js&lt;/code&gt; does not seem to provides any cache / memoization we'll use &lt;code&gt;lodash.memoize&lt;/code&gt; for the same.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;yarn add lodash.memoize
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Add Translations
&lt;/h2&gt;

&lt;p&gt;Create a &lt;code&gt;translations&lt;/code&gt; directory inside &lt;code&gt;src&lt;/code&gt; and then create, three JSON files as below for each language.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;en.json&lt;/code&gt; for English&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;fr.json&lt;/code&gt; for French&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ar.json&lt;/code&gt; for Arabic&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Inside these files will be JSON object with keys and values.&lt;/p&gt;

&lt;p&gt;Key will the be same for each language and will be used in the app to display text.&lt;/p&gt;

&lt;p&gt;The value will be the actual translation that we want to show to the user and will be different for each language.&lt;/p&gt;

&lt;p&gt;For English:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hello&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello World!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;For French:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hello&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Salut le Monde!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;For Arabic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hello&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;أهلاً بالعالم&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Similarly, you can add more key-value pairs for every text that will be used in the app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add Main Code
&lt;/h2&gt;

&lt;p&gt;Open &lt;code&gt;App.js&lt;/code&gt; file and the following imports:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;RNLocalize&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-native-localize&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;i18n&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;i18n-js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;memoize&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lodash.memoize&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Use for caching/memoize for better performance&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;I18nManager&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;SafeAreaView&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;ScrollView&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;StyleSheet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;View&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-native&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;After that, we'll add some helper functions and constants that we'll use later.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;translationGetters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// lazy requires (metro bundler does not support symlinks)&lt;/span&gt;
  &lt;span class="na"&gt;ar&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./src/translations/ar.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;en&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./src/translations/en.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;fr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./src/translations/fr.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;translate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;memoize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;i18n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;setI18nConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// fallback if no available language fits&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fallback&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;languageTag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;isRTL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;languageTag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isRTL&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nx"&gt;RNLocalize&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findBestAvailableLanguage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;translationGetters&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="nx"&gt;fallback&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// clear translation cache&lt;/span&gt;
  &lt;span class="nx"&gt;translate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clear&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="c1"&gt;// update layout direction&lt;/span&gt;
  &lt;span class="nx"&gt;I18nManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forceRTL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isRTL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// set i18n-js config&lt;/span&gt;
  &lt;span class="nx"&gt;i18n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;translations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;languageTag&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;translationGetters&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;languageTag&lt;/span&gt;&lt;span class="p"&gt;]()&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="nx"&gt;i18n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;locale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;languageTag&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now, we'll create our &lt;code&gt;App&lt;/code&gt; class component.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Component&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;setI18nConfig&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// set initial config&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;componentDidMount&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;RNLocalize&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;change&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handleLocalizationChange&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;componentWillUnmount&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;RNLocalize&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;removeEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;change&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handleLocalizationChange&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;handleLocalizationChange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;setI18nConfig&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forceUpdate&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SafeAreaView&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;safeArea&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Text&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;translate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hello&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Text&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/SafeAreaView&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;styles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;StyleSheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;safeArea&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;backgroundColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;white&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;alignItems&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;center&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;justifyContent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;center&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;fontSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;In our constructor method, we called &lt;code&gt;setI18nConfig()&lt;/code&gt; which will set the initial configuration.&lt;/p&gt;

&lt;p&gt;Then in &lt;code&gt;componentDidMount()&lt;/code&gt; we'll add an event listener which will listen for any changes and call &lt;code&gt;handleLocalizationChange()&lt;/code&gt; if any changes occur.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;handleLocalizationChange()&lt;/code&gt; method does two things one is that it fires &lt;code&gt;setI18nConfig()&lt;/code&gt; and &lt;code&gt;forceUpdate()&lt;/code&gt;. This is necessary for Android devices as the component needs to be re-render for the changes to be visible.&lt;/p&gt;

&lt;p&gt;We will remove the listener in &lt;code&gt;componentWillUnmount()&lt;/code&gt; lifecycle method.&lt;/p&gt;

&lt;p&gt;Finally, in &lt;code&gt;render()&lt;/code&gt; we'll print out &lt;code&gt;hello&lt;/code&gt; by using &lt;code&gt;translate()&lt;/code&gt; and passing our &lt;code&gt;key&lt;/code&gt; as a parameter into it. It will then automatically figure out the language and the text that needs to be shown for that language.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running the App
&lt;/h2&gt;

&lt;p&gt;Now its time to see our if the translations are working.&lt;/p&gt;

&lt;p&gt;Run your app in simulator or emulator by typing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;react-native run-ios
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;





&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;react-native run-android
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;It should be something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gMyDmYLF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/cdi7x7lta93kbzjo0t80.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gMyDmYLF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/cdi7x7lta93kbzjo0t80.png" alt="Hello World English"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, change your device language setting to French and open your app again.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Bhd_OcQS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/smekwd55qpvnlgvewzzo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Bhd_OcQS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/smekwd55qpvnlgvewzzo.png" alt="Hello World French"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Similarly, you can change your language setting to Arabic and see hello in Arabic.&lt;/p&gt;

&lt;p&gt;So far so great.&lt;/p&gt;

&lt;p&gt;But what happens if I choose some random language whose translation is not added in the app? What language will it fall back to?&lt;/p&gt;

&lt;p&gt;Well as it turns out the goal of &lt;code&gt;findBestAvailableLanguage&lt;/code&gt; is to return the best available translation. So It will look at your language preference setting to figure out the fallback language.&lt;/p&gt;

&lt;p&gt;If you go to the &lt;strong&gt;Language &amp;amp; Region&lt;/strong&gt; setting in your iOS simulator you can see the preference order of the languages.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_kMTzGUN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/jwd7qsj50g2rcff925r0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_kMTzGUN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/jwd7qsj50g2rcff925r0.png" alt="Language Preference Setting"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If the language chosen is not in the preferred language &lt;code&gt;findBestAvailableLanguage&lt;/code&gt; returns &lt;code&gt;undefined&lt;/code&gt; (so the retained value will be your &lt;a href="https://github.com/vikrantnegi/react-native-multi-language-app/blob/master/App.js#L25"&gt;fallback&lt;/a&gt;) because none of the user's preferred languages are available in your translations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bonus
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;react-native-localize&lt;/code&gt; has API that can provides access to lot localization related device constants. Be sure to check out the full APIs available in the &lt;a href="https://github.com/react-native-community/react-native-localize#api"&gt;docs&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Adding multi-language support is that easy. You can now use react-native-localize to easily provide multiple language support in your app which can help in increasing user's app usage.&lt;/p&gt;

&lt;p&gt;Find the source code in the Github repo &lt;a href="https://github.com/vikrantnegi/react-native-multi-language-app"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Originally published on &lt;a href="https://medium.com/better-programming/creating-a-multi-language-app-in-react-native-9828b138c274"&gt;Medium&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you like this article, go ahead and show some love and share.&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>reactnativelocalize</category>
      <category>i18n</category>
      <category>multilangugae</category>
    </item>
    <item>
      <title>React Native, Local, Scheduled, Push Notification With Firebase</title>
      <dc:creator>Vikrant Negi</dc:creator>
      <pubDate>Thu, 04 Jul 2019 13:30:15 +0000</pubDate>
      <link>https://dev.to/vikrantnegi/react-native-local-scheduled-push-notification-with-firebase-37gf</link>
      <guid>https://dev.to/vikrantnegi/react-native-local-scheduled-push-notification-with-firebase-37gf</guid>
      <description>&lt;p&gt;With ever going increase in app usage, the app developers try to find new ways to keep their user engaged with their apps. Push notifications are one way to keep your users engaged and informed.&lt;/p&gt;

&lt;p&gt;Push notifications are the communications channel used by apps to drive user re-engagement and retention. Push notification technology has come a long way from being a single messages delivery system to a rich and interactive medium.&lt;/p&gt;

&lt;p&gt;There are two types of notifications, remote and local notifications.&lt;/p&gt;

&lt;h3&gt;
  
  
  Remote Notifications
&lt;/h3&gt;

&lt;p&gt;With remote notifications, you use one of your company’s servers to push data to user devices via the Apple Push Notification service or FCM.&lt;/p&gt;

&lt;h3&gt;
  
  
  Local Notifications
&lt;/h3&gt;

&lt;p&gt;Both Android and IOS support the ability to trigger notifications from your application locally. These can either be displayed immediately, or scheduled to be displayed at a later date.&lt;/p&gt;

&lt;p&gt;In this article we will be using local notifications feature to send daily a reminder to the user at a particular time every day.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;This tutorial requires basic knowledge of React Native. To set up your development machine, follow the official guide &lt;a href="https://facebook.github.io/react-native/docs/0.59/getting-started" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To implement push notifications in React Native application, we are going to use the &lt;a href="https://github.com/invertase/react-native-firebase" rel="noopener noreferrer"&gt;react-native-firebase&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;React Native Firebase has Notifications module that supports for both remote (FCM) and local notifications.&lt;/p&gt;

&lt;p&gt;To make sure we are on the same page, the following are the versions used in this tutorial:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Node v10.15.0&lt;/li&gt;
&lt;li&gt;npm 6.4.1&lt;/li&gt;
&lt;li&gt;yarn 1.16.0&lt;/li&gt;
&lt;li&gt;react-native 0.59.9&lt;/li&gt;
&lt;li&gt;react-native-firebase 5.2.3&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;To create a new project using &lt;code&gt;react-native-cli&lt;/code&gt;, type the following in terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;react-native init localReminders
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;localReminders
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Install React Native Firebase
&lt;/h4&gt;

&lt;p&gt;Install &lt;code&gt;react-native-firebase&lt;/code&gt; by following installation instructions &lt;a href="https://rnfirebase.io/docs/v5.x.x/installation/initial-setup" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Make sure you have completed both iOS and Android setup.&lt;/p&gt;

&lt;h4&gt;
  
  
  Install Modules
&lt;/h4&gt;

&lt;p&gt;React Native Firebase only provides your application with access to Core features.&lt;/p&gt;

&lt;p&gt;To user notification in our react native app, we'll need to also install the &lt;strong&gt;Cloud Messaging&lt;/strong&gt; and &lt;strong&gt;Notifications&lt;/strong&gt; modules.&lt;/p&gt;

&lt;p&gt;Install &lt;strong&gt;Cloud Messaging&lt;/strong&gt; by following the instructions &lt;a href="https://rnfirebase.io/docs/v5.x.x/messaging/ios" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Install &lt;strong&gt;Notifications&lt;/strong&gt; by following the instructions &lt;a href="https://rnfirebase.io/docs/v5.x.x/notifications/ios" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Again, make sure you followed the steps for both iOS and Android.&lt;/p&gt;

&lt;h2&gt;
  
  
  Initial code setup
&lt;/h2&gt;

&lt;p&gt;In your &lt;code&gt;App.js&lt;/code&gt; file add these imports:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Component&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Alert&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-native&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;firebase&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-native-firebase&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Dashboard&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./src/Dashboard&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create an empty &lt;code&gt;Dashboard.js&lt;/code&gt; file in your &lt;code&gt;src&lt;/code&gt; folder. We'll fill this with code in later part of the tutorial.&lt;/p&gt;

&lt;p&gt;Now create a &lt;code&gt;class&lt;/code&gt; component called &lt;code&gt;App&lt;/code&gt; as follow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;App&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Component&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;componentDidMount&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Create notification channel required for Android devices&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createNotificationChannel&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Ask notification permission and add notification listener&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;checkPermission&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;createNotificationChannel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;

  &lt;span class="nx"&gt;checkPermission&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;

  &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Dashboard&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We've created a few helper methods in this class component that we called on &lt;code&gt;componentDidMount()&lt;/code&gt; lifecycle method. In render, we'll return the &lt;code&gt;Dashboard&lt;/code&gt; component.&lt;/p&gt;

&lt;p&gt;Let's review and add code to these methods and see what they do and why do we need them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create Android Notification Channel
&lt;/h2&gt;

&lt;p&gt;As of Android 8.0 (API Level 26), notifications must specify a Notification Channel or they will not appear.&lt;/p&gt;

&lt;p&gt;To allow React Native Firebase to work seamlessly across all versions of Android, you will need to create a channel before you can display a notification. This block of code can be re-run multiple times, so it is safe to do it each time your application starts.&lt;/p&gt;

&lt;p&gt;To create this notification channel we created a custom helper method &lt;code&gt;createNotificationChannel()&lt;/code&gt;. Update it with the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;createNotificationChannel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Build a android notification channel&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;channel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;firebase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Android&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Channel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;reminder&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// channelId&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Reminders Channel&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// channel name&lt;/span&gt;
    &lt;span class="nx"&gt;firebase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Android&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Importance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;High&lt;/span&gt; &lt;span class="c1"&gt;// channel importance&lt;/span&gt;
  &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;setDescription&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Used for getting reminder notification&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// channel description&lt;/span&gt;

  &lt;span class="c1"&gt;// Create the android notification channel&lt;/span&gt;
  &lt;span class="nx"&gt;firebase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;notifications&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;android&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Android channel accept three parameters &lt;code&gt;channelId&lt;/code&gt;, &lt;code&gt;name&lt;/code&gt; and &lt;code&gt;importance&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For full reference documentation, please see &lt;a href="https://rnfirebase.io/docs/v5.x.x/notifications/reference/AndroidChannel" rel="noopener noreferrer"&gt;&lt;strong&gt;AndroidChannel&lt;/strong&gt;&lt;/a&gt; and &lt;a href="https://rnfirebase.io/docs/v5.x.x/notifications/reference/AndroidNotifications" rel="noopener noreferrer"&gt;&lt;strong&gt;AndroidNotifications&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Notification Permission and Listener
&lt;/h2&gt;

&lt;p&gt;Before we can send any notification we need to make sure we have the notification permission and then, when we have that permission we can add a notification listener. This notification listener will listen for any notification and return us with the &lt;code&gt;notification&lt;/code&gt; when received.&lt;/p&gt;

&lt;p&gt;Update your &lt;code&gt;checkPermission()&lt;/code&gt; method with the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;checkPermission&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;enabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;firebase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;messaging&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;hasPermission&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// We've the permission&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;notificationListener&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;firebase&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;notifications&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onNotification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;notification&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Display your notification&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;firebase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;notifications&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;displayNotification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;notification&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// user doesn't have permission&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;firebase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;messaging&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;requestPermission&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;Alert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Unable to access the Notification permission. Please enable the Notification Permission from the settings&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the notification listener receives a notification, we call &lt;code&gt;displayNotification()&lt;/code&gt; method with the &lt;code&gt;notification&lt;/code&gt; received which will display the notification.&lt;/p&gt;

&lt;p&gt;We can now receive and show the notification. Now its time to schedule a local notification which will fire at some point in the future.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dashboard Screen
&lt;/h2&gt;

&lt;p&gt;We are going to create a &lt;strong&gt;Dashboard&lt;/strong&gt; screen which will have a switch to enable and disable notifications and a time picker to set time to receive notifications. It will look something like below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fwnv5ophowh6o4yxuh6l1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fwnv5ophowh6o4yxuh6l1.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To make this UI Screen we'll add few third party helper libraries.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://react-native-training.github.io/react-native-elements/" rel="noopener noreferrer"&gt;react-native-elements&lt;/a&gt; - UI component library&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://momentjs.com/" rel="noopener noreferrer"&gt;moment&lt;/a&gt; - Handling Date Object&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/mmazzarolo/react-native-modal-datetime-picker" rel="noopener noreferrer"&gt;react-native-modal-datetime-picker&lt;/a&gt; - A React-Native datetime-picker for Android and iOS&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's start with adding the required imports:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Component&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Platform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;StyleSheet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SafeAreaView&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;View&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-native&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ListItem&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-native-elements&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;firebase&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-native-firebase&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;DateTimePicker&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-native-modal-datetime-picker&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;moment&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;moment&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we'll create and default export &lt;code&gt;Dashboard&lt;/code&gt; class component.&lt;/p&gt;

&lt;p&gt;Now, add some local states:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;enableNotification&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;isDateTimePickerVisible&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;notificationTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;moment&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;hour&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;17&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we want to set reminder as soon as user lands on the dashboard, so we'll create and call a &lt;code&gt;setReminder()&lt;/code&gt; method on &lt;code&gt;componentDidMount()&lt;/code&gt; lifecycle method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;componentDidMount&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setReminder&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;setReminder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;notificationTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;enableNotification&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;enableNotification&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// schedule notification&lt;/span&gt;
    &lt;span class="nx"&gt;firebase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;notifications&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;scheduleNotification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;buildNotification&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;fireDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;notificationTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;valueOf&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="na"&gt;repeatInterval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;day&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;exact&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;code&gt;setReminder()&lt;/code&gt; method first we use &lt;code&gt;notificationTime&lt;/code&gt; and &lt;code&gt;enableNotification&lt;/code&gt; state.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;enableNotification&lt;/code&gt; is true we schedule the notification using &lt;a href="https://rnfirebase.io/docs/v5.x.x/notifications/reference/Notifications#scheduleNotification" rel="noopener noreferrer"&gt;&lt;code&gt;scheduleNotification()&lt;/code&gt;&lt;/a&gt; method.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;scheduleNotification()&lt;/code&gt; has two parameters, &lt;code&gt;notification&lt;/code&gt; and &lt;code&gt;schedule&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For &lt;code&gt;notification&lt;/code&gt; we'll call another custom method &lt;code&gt;buildNotification()&lt;/code&gt; which will return notification and, in &lt;code&gt;schedule&lt;/code&gt; we'll pass an object with &lt;code&gt;fireDate&lt;/code&gt;, &lt;code&gt;repeatInterval&lt;/code&gt; and &lt;code&gt;exact&lt;/code&gt; keys.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;fireDate&lt;/code&gt; is the date when the notification should first be shown, in milliseconds. For &lt;code&gt;repeatInterval&lt;/code&gt; we'll use &lt;code&gt;day&lt;/code&gt;, as we want the notification to be shown everyday. The &lt;code&gt;exact&lt;/code&gt; option is Android only and it denotes if the &lt;code&gt;fireDate&lt;/code&gt; should be respected exactly, i.e. for reminders, or if it can deviate slightly to save battery.&lt;/p&gt;

&lt;p&gt;In else we'll just return &lt;code&gt;false&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now, we'll add &lt;code&gt;buildNotification()&lt;/code&gt; method which will build a notification when a notification is received:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;buildNotification&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Platform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OS&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;android&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Daily Reminder&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;notification&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;firebase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Notification&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setNotificationId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Any random ID&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setTitle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Title of the notification&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setBody&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;This is a notification&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// body of notification&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;android&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setPriority&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;firebase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Android&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Priority&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;High&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// set priority in Android&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;android&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setChannelId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;reminder&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// should be the same when creating channel for Android&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;android&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAutoCancel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// To remove notification when tapped on it&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;notification&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's add some helper method for the notification &lt;code&gt;switch&lt;/code&gt; and time picker. These will be used by their respective components to set local states which will then later be used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;enableNotification&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;enableNotification&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;showDateTimePicker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;isDateTimePickerVisible&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;hideDateTimePicker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;isDateTimePickerVisible&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;handleDatePicked&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hideDateTimePicker&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;notificationTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;moment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, the &lt;code&gt;render()&lt;/code&gt; which we'll return the UI of the dashboard:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;enableNotification&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isDateTimePickerVisible&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;notificationTime&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SafeAreaView&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;View&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cardTitleView&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Text&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cardTitle&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Add&lt;/span&gt; &lt;span class="nx"&gt;Reminder&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Text&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/View&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ListItem&lt;/span&gt;
        &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Notification&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="nx"&gt;bottomDivider&lt;/span&gt;
        &lt;span class="nx"&gt;titleStyle&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;titleStyle&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;onValueChange&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;enableNotification&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;enableNotification&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;
      &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ListItem&lt;/span&gt;
        &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Time&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="nx"&gt;titleStyle&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;titleStyle&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;onPress&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;showDateTimePicker&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;rightElement&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Text&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.7&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;moment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;notificationTime&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;LT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Text&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;}
&lt;/span&gt;      &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;DateTimePicker&lt;/span&gt;
        &lt;span class="nx"&gt;isVisible&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isDateTimePickerVisible&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;onConfirm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handleDatePicked&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;onCancel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hideDateTimePicker&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;time&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="c1"&gt;// show only time picker&lt;/span&gt;
        &lt;span class="nx"&gt;is24Hour&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;notificationTime&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
        &lt;span class="nx"&gt;titleIOS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Pick your Notification time&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/SafeAreaView&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Time to sprinkle in some styles to make UI more appealing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;styles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;StyleSheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;container&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;backgroundColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#EEEFF0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;cardTitleView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;paddingHorizontal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;paddingTop&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;paddingBottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;cardTitle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;fontSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#585858&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;fontWeight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;600&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;titleStyle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;fontSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#585858&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. We are done with development. In the next section, we'll test it out in an iOS simulator.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running the app
&lt;/h2&gt;

&lt;p&gt;Test the app by enabling notification and setting time 1 min in the future.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fcwsdsu81n6vud8uw3g36.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fcwsdsu81n6vud8uw3g36.gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: Sometimes the notification may not show at the exact time. Please wait for a whole minute for notification to appear.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;We can now schedule daily local push notification in our React Native app for both iOS and Android.&lt;/p&gt;

&lt;p&gt;Apart from firebase, there are other libraries you can use to implement local push notification like &lt;a href="https://github.com/zo0r/react-native-push-notification" rel="noopener noreferrer"&gt;react-native-push-notification&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here is the Github repo &lt;a href="https://github.com/vikrantnegi/local-reminders" rel="noopener noreferrer"&gt;local-reminders&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Originally published on &lt;a href="https://medium.com/better-programming/react-native-local-scheduled-push-notification-with-firebase-8c775b71c35c" rel="noopener noreferrer"&gt;Medium&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;PS: Cover image by &lt;a href="https://unsplash.com/@jamie452?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Jamie Street&lt;/a&gt; on Unsplash&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>firebase</category>
      <category>pushnotification</category>
      <category>localpushnotification</category>
    </item>
    <item>
      <title>React Native Location Tracking</title>
      <dc:creator>Vikrant Negi</dc:creator>
      <pubDate>Mon, 21 May 2018 18:09:06 +0000</pubDate>
      <link>https://dev.to/vikrantnegi/react-native-location-tracking-32o7</link>
      <guid>https://dev.to/vikrantnegi/react-native-location-tracking-32o7</guid>
      <description>&lt;p&gt;&lt;a href="https://media.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%2Fbwvylez1sszgpo1qvez1.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fbwvylez1sszgpo1qvez1.gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you’ve been working with React Native you must have used or heard about react-native-maps. React native maps is a react native package that provide Google Maps API for React Native. Using static google maps is very common but today we are going to use one of the widely used feature of google maps, location tracking using React native.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Find the project repo &lt;a href="https://github.com/vikrantnegi/react-native-location-tracking" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;Start by creating a React Native project and name it whatever you want. I’m using the react-native-cli but &lt;a href="https://github.com/react-community/create-react-native-app" rel="noopener noreferrer"&gt;create-react-native-app&lt;/a&gt; should also work just fine.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;react-native init locationTracking&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing react-native-maps
&lt;/h2&gt;

&lt;p&gt;First, download the library from npm:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npm install react-native-maps --save&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Installing react-native-maps requires adding code to native iOS and Android files. Follow the installation instructions provided by the react-native-maps. After installation make sure that the project builds successfully before moving forward.&lt;/p&gt;

&lt;p&gt;Installing react-native-maps is not a simple task. If you are doing it for the first time I would suggest going through the &lt;a href="https://github.com/react-community/react-native-maps/blob/master/docs/installation.md" rel="noopener noreferrer"&gt;installation&lt;/a&gt; instructions carefully. Believe me this will be the toughest task of this tutorial. Use google if you face any errors.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using react-native-maps
&lt;/h2&gt;

&lt;p&gt;Great, now that you’ve successfully installed the react-native-maps. Let’s move to the juicy part, where the real magic happens.&lt;br&gt;
Let first set some initial states that will be used for this project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;constructor(props) {
  super(props);
  this.state = {
    latitude: LATITUDE,
    longitude: LONGITUDE,
    routeCoordinates: [],
    distanceTravelled: 0,
    prevLatLng: {},
    coordinate: new AnimatedRegion({
     latitude: LATITUDE,
     longitude: LONGITUDE
    })
  };
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We’ll use these states later in the app, the only thing of interest here is the new &lt;code&gt;AnimatedRegion&lt;/code&gt; which will help us to animate our markers when the location updates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Watch for location changes
&lt;/h2&gt;

&lt;p&gt;Now we’ll need to get the location coordinates every time the user moves. Google maps geolocation API has &lt;code&gt;watchPosition&lt;/code&gt; method which will hep us get the location coordinates whenever they gets changed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;componentDidMount() {
  this.watchID = navigator.geolocation.watchPosition(
    position =&amp;gt; {
      const { coordinate, routeCoordinates, distanceTravelled } =   this.state;
      const { latitude, longitude } = position.coords;

      const newCoordinate = {
        latitude,
        longitude
      };
      if (Platform.OS === "android") {
        if (this.marker) {
          this.marker._component.animateMarkerToCoordinate(
            newCoordinate,
            500
          );
         }
       } else {
         coordinate.timing(newCoordinate).start();
       }
       this.setState({
         latitude,
         longitude,
         routeCoordinates: routeCoordinates.concat([newCoordinate]),
         distanceTravelled:
         distanceTravelled + this.calcDistance(newCoordinate),
         prevLatLng: newCoordinate
       });
     },
     error =&amp;gt; console.log(error),
     { enableHighAccuracy: true, timeout: 20000, maximumAge: 1000 }
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The watchPosition gives us the information about user’s location whenever it get’s changed. We then use ES6 &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment" rel="noopener noreferrer"&gt;Destructuring&lt;/a&gt; to get latitude &amp;amp; longitude from the position.coords. Also, we’ll get coordinate, routeCoordinates &amp;amp; distanceTravelled from initial state.&lt;/p&gt;

&lt;p&gt;We’ll than create newCoordinate variable that will store these new updated location coordinates that we received from position.coords. Now that we have got the updates coordinates we’ll animate the marker to these new coordinates. Both Android and iOS have different way of handling this so we’ll use &lt;a href="https://facebook.github.io/react-native/docs/platform-specific-code.html" rel="noopener noreferrer"&gt;Platform Specific Code&lt;/a&gt; to handle this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (Platform.OS === "android") {
  if (this.marker) {
  this.marker._component.animateMarkerToCoordinate(
    newCoordinate,
    500
   );
  }
} else {
  coordinate.timing(newCoordinate).start();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now its time to update our initial states with the new one.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;this.setState({
  latitude,
  longitude,
  routeCoordinates: routeCoordinates.concat([newCoordinate]),
  distanceTravelled: distanceTravelled + this.calcDistance(newCoordinate),
  prevLatLng: newCoordinate
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Calculating Distance Travelled
&lt;/h2&gt;

&lt;p&gt;We have used distanceTravelled state variable to store the distance travelled by the user. To calculate this distance we’ll create a new function calcDistance that will take newLatLng as parameter and prevLatLng as state variable and will return distance.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;calcDistance = newLatLng =&amp;gt; {
  const { prevLatLng } = this.state;
  return haversine(prevLatLng, newLatLng) || 0;
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looking at this function you maybe wondering when did calculating the distance got so complex. Apparently due to the curvature of the earth calculating distance with the help of latitude and longitude is not so straightforward. Earth has got some curves.&lt;/p&gt;

&lt;p&gt;To calculate the distance using latitude and longitude we need to use &lt;a href="https://en.wikipedia.org/wiki/Haversine_formula" rel="noopener noreferrer"&gt;Haversine&lt;/a&gt; formula. If you are like me who struggles with mathematics 😅, there is a hope in the form of a haversine npm package that can help us calculate distance using latitude and longitude. Install haversine npm package using the following command.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npm install haversine&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Rendering MapView
&lt;/h2&gt;

&lt;p&gt;Since  component need region prop which accepts an object with location coordinates. We are going to create a function that returns all necessary information needed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;getMapRegion = () =&amp;gt; ({
  latitude: this.state.latitude,
  longitude: this.state.longitude,
  latitudeDelta: LATITUDE_DELTA,
  longitudeDelta: LONGITUDE_DELTA
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we’ve all the information need to render the map.&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;MapView
  style={styles.map}
  showUserLocation
  followUserLocation
  loadingEnabled
  region={this.getMapRegion()}
&amp;gt;
  &amp;lt;Polyline coordinates={this.state.routeCoordinates} strokeWidth={5} /&amp;gt;
  &amp;lt;Marker.Animated
    ref={marker =&amp;gt; {
      this.marker = marker;
    }}
    coordinate={this.state.coordinate}
  /&amp;gt;
&amp;lt;/MapView&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We’ve also used the Google Maps Polyline to draw the path as the user moves. Polyline has acoordinate props which accepts an array of coordinates which we can get from our routeCoordinates. We’ve also set the strokeWidth to so we can see the path clearly.&lt;/p&gt;

&lt;p&gt;Next to show the Animated marker we’ll use Marker.Animated component to show marker at user current position. It has coordinate props which will get the coordinate object from the state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Show Distance Travelled
&lt;/h2&gt;

&lt;p&gt;Finally to show the distance travelled by the user we’ll set up a View with the proper styling applied.&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;View style={styles.buttonContainer}&amp;gt;
  &amp;lt;TouchableOpacity style={[styles.bubble, styles.button]}&amp;gt;
    &amp;lt;Text style={styles.bottomBarContent}&amp;gt;
      {parseFloat(this.state.distanceTravelled).toFixed(2)} km
    &amp;lt;/Text&amp;gt;
  &amp;lt;/TouchableOpacity&amp;gt;
&amp;lt;/View&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Testing
&lt;/h2&gt;

&lt;p&gt;Now that we’ve all the necessary pieces in place we can finally run our app.&lt;/p&gt;

&lt;p&gt;To test the app on iOS simulator we’ll use the location mode available in iOS simulators. After running the app go to &lt;code&gt;Debug&lt;/code&gt; &amp;gt; &lt;code&gt;Location&lt;/code&gt; &amp;gt; &lt;code&gt;Freeway Drive&lt;/code&gt; setting in simulator to turn on this feature. Doing so should result in something similar as below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fbwvylez1sszgpo1qvez1.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fbwvylez1sszgpo1qvez1.gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For Android, it’s not that straightforward as Android emulators doesn’t have any in built feature to test the dynamic location changes. To test it, either you can manually change your position by walking some distance or you can use 3rd party mock location apps. I was able to test this using &lt;a href="https://play.google.com/store/apps/details?id=com.theappninjas.gpsjoystick&amp;amp;hl=en_IN" rel="noopener noreferrer"&gt;GPS Joystick&lt;/a&gt; app on Android.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;We have successfully created a React Native app that tracks an user location and draws the path. It can also calculate the distance travelled by the user.&lt;/p&gt;

&lt;p&gt;I’ve skipped the styles and some other boilerplate code needed for this app but you can find that in the github repo &lt;a href="https://github.com/vikrantnegi/react-native-location-tracking" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I hope this helps you to understand the Google maps API. Maybe you can take inspiration from this to build something amazing. Please feel free to leave any feedback, I’m always looking for better solutions!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Originally published on &lt;a href="https://medium.com/@vikrantnegi/react-native-location-tracking-14ab2c9e2db8" rel="noopener noreferrer"&gt;Medium&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>reactnative</category>
      <category>react</category>
      <category>googlemaps</category>
      <category>locationtracking</category>
    </item>
  </channel>
</rss>
