<?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: Žiga Patačko Koderman</title>
    <description>The latest articles on DEV Community by Žiga Patačko Koderman (@zigapk).</description>
    <link>https://dev.to/zigapk</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1416019%2Fe6b057e6-a1c4-43a8-b6c4-5dec4d978016.jpeg</url>
      <title>DEV Community: Žiga Patačko Koderman</title>
      <link>https://dev.to/zigapk</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/zigapk"/>
    <language>en</language>
    <item>
      <title>Lighthouse vs. PageSpeed Insights: The Lack of Correlation</title>
      <dc:creator>Žiga Patačko Koderman</dc:creator>
      <pubDate>Mon, 05 Aug 2024 12:23:00 +0000</pubDate>
      <link>https://dev.to/zerodays/lighthouse-vs-pagespeed-insights-the-lack-of-correlation-26d4</link>
      <guid>https://dev.to/zerodays/lighthouse-vs-pagespeed-insights-the-lack-of-correlation-26d4</guid>
      <description>&lt;h2&gt;
  
  
  What's the problem?
&lt;/h2&gt;

&lt;p&gt;Have you ever run &lt;a href="https://pagespeed.web.dev/" rel="noopener noreferrer"&gt;PageSpeed Insights (PSI)&lt;/a&gt; on your website only to receive a wildly different performance score than when running &lt;a href="https://developer.chrome.com/docs/lighthouse/overview/" rel="noopener noreferrer"&gt;Lighthouse&lt;/a&gt; via Chrome Developer Tools? Let me show you what I mean:&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%2F60p85qw22e5wf5kirf7k.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%2F60p85qw22e5wf5kirf7k.png" alt="Comparison between Lighthouse and PageSpeed tests"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The overall scores are relatively similar, but there is &lt;strong&gt;almost no correlation&lt;/strong&gt; between the metrics that make them up. This is a problem because &lt;strong&gt;you cannot run PageSpeed Insights on your local build of the website&lt;/strong&gt;, forcing you to publish any changes and test them online, which is slow, inconvenient, and a bad practice. Let's take a look at where the difference between Lighthouse and PageSpeed comes from.&lt;/p&gt;

&lt;h2&gt;
  
  
  The reason for the discrepancy
&lt;/h2&gt;

&lt;p&gt;PageSpeed uses Lighthouse under the hood, so what is the deal here? Googling this tells you that PageSpeed also includes some real-world data and so on, but this does not address the problem at stake. After some lengthy deep dives into the issue, we found the reason for it &lt;a href="https://github.com/GoogleChrome/lighthouse/blob/main/docs/throttling.md" rel="noopener noreferrer"&gt;here&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PageSpeed Insights run Lighthouse using a relative 4x CPU slowdown.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The fact that it is &lt;strong&gt;relative&lt;/strong&gt; results in a different score based on the device running the benchmark.&lt;/p&gt;

&lt;p&gt;You might be tempted to say "just multiply the times here by X to compensate for having a faster computer." However, this won't work. We won't go into the depths of why this is the case beyond saying that those metrics are complex and don't scale in that way. You can find out more &lt;a href="https://developer.chrome.com/docs/lighthouse/performance/performance-scoring/" rel="noopener noreferrer"&gt;here&lt;/a&gt; (TBT is perhaps the simplest example of that).&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution
&lt;/h2&gt;

&lt;p&gt;Let us start by installing the tools we'll need further down the line.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Install the Lighthouse CLI
&lt;/h3&gt;

&lt;p&gt;You cannot tweak the CPU slowdown of a Lighthouse simulation through the Chrome Dev Tools. Hence the need for &lt;a href="https://github.com/GoogleChrome/lighthouse" rel="noopener noreferrer"&gt;Lighthouse CLI&lt;/a&gt;. You can install it using&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; &lt;span class="nt"&gt;-g&lt;/span&gt; lighthouse


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

&lt;/div&gt;
&lt;h3&gt;
  
  
  2. Estimate the slowdown factor (optional)
&lt;/h3&gt;

&lt;p&gt;Here we will show how to extract the &lt;code&gt;benchmarkIndex&lt;/code&gt; for your device and then calculate an approximate slowdown factor that you need to use. This is the procedure found in the Chrome documentation. However, you will probably need to readjust the slowdown even after that. Therefore, &lt;strong&gt;we prefer to skip this step and find a suitable constant using trial and error&lt;/strong&gt;.&lt;/p&gt;
&lt;h4&gt;
  
  
  2.1 Extract &lt;code&gt;benchmarkIndex&lt;/code&gt; from the report
&lt;/h4&gt;

&lt;p&gt;Here is a command that extracts the &lt;code&gt;benchmarkIndex&lt;/code&gt; from the report. It presumes that you have &lt;code&gt;jq&lt;/code&gt; installed on your system.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

lighthouse YOUR_WEBSITE_URL &lt;span class="nt"&gt;--output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;json &lt;span class="nt"&gt;--chrome-flags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"--headless"&lt;/span&gt; &lt;span class="nt"&gt;--form-factor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mobile | jq &lt;span class="s1"&gt;'.environment.benchmarkIndex'&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;On a baseline 2021 M1 MacBook Pro, this number is around 2900.&lt;/p&gt;

&lt;h4&gt;
  
  
  2.2 Convert &lt;code&gt;benchmarkIndex&lt;/code&gt; into a slowdown estimate
&lt;/h4&gt;

&lt;p&gt;You can use the &lt;a href="https://lighthouse-cpu-throttling-calculator.vercel.app/" rel="noopener noreferrer"&gt;CPU Throttling Calculator&lt;/a&gt; to estimate the slowdown factor you need. For &lt;code&gt;benchmarkIndex&lt;/code&gt; 2900, this yields 9.9. However, it later turns out that this is not enough by almost a factor of two. &lt;/p&gt;

&lt;p&gt;Let's take a look at why this might be the case. By reverse engineering the website code, we found that this calculator uses a linear estimation based on a few calibrated points. Let's take a look at a chart of their estimation function:&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%2Fql6rhyjgnpt9kqr6wvic.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%2Fql6rhyjgnpt9kqr6wvic.png" alt="CPU Throttling Calculator Estimation Function"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The last calibrated point is around benchmark index 1300, more than two times lower than what a modern laptop is capable of. This explains why the estimated CPU slowdown of around 10 does not produce satisfactory results. In other words, it is not (yet) calibrated in the range that matters to us.&lt;/p&gt;

&lt;p&gt;For a baseline 2021 M1 MacBook Pro, the slowdown factor that produces reasonable results sits around 19.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Run Lighthouse
&lt;/h3&gt;

&lt;p&gt;You can run Lighthouse with a desired CPU slowdown like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

lighthouse YOUR_WEBSITE_URL &lt;span class="nt"&gt;--throttling&lt;/span&gt;.cpuSlowdownMultiplier YOUT_CPU_SLOWDOWN &lt;span class="nt"&gt;--output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;html &lt;span class="nt"&gt;--chrome-flags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"--headless"&lt;/span&gt; &lt;span class="nt"&gt;--form-factor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mobile &lt;span class="nt"&gt;--output-path&lt;/span&gt; report.html


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

&lt;/div&gt;

&lt;p&gt;and then open up the generated HTML report.&lt;/p&gt;

&lt;p&gt;This produces somewhat more relevant results:&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%2F9plweix99qqurcmcpzb9.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%2F9plweix99qqurcmcpzb9.png" alt="Comparison between PageSpeed Insight and local Lighthouse run using CPU slowdown"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The values are not the same, but we should not expect them to be — this is a complex benchmark running on a simulated device, after all. Go ahead and &lt;strong&gt;tweak your slowdown factor&lt;/strong&gt; until it resembles what online PageSpeed Index test is telling you.&lt;/p&gt;

&lt;p&gt;Here is a one-liner using a JSON report that also extracts the metrics in question:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

lighthouse YOUR_WEBSITE_URL &lt;span class="nt"&gt;--throttling&lt;/span&gt;.cpuSlowdownMultiplier YOUT_CPU_SLOWDOWN &lt;span class="nt"&gt;--output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;json &lt;span class="nt"&gt;--chrome-flags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"--headless"&lt;/span&gt; &lt;span class="nt"&gt;--form-factor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mobile | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'{LCP: .audits["largest-contentful-paint"].numericValue, "LCP Score": .audits["largest-contentful-paint"].score, FCP: .audits["first-contentful-paint"].numericValue, "FCP Score": .audits["first-contentful-paint"].score, SI: .audits["speed-index"].numericValue, "SI Score": .audits["speed-index"].score, TBT: .audits["total-blocking-time"].numericValue, "TBT Score": .audits["total-blocking-time"].score, CLS: .audits["cumulative-layout-shift"].numericValue, "CLS Score": .audits["cumulative-layout-shift"].score, "TOTAL": .categories.performance.score} | to_entries[] | "\(.key): \(.value)"'&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;It is not the prettiest, but it can be useful.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Repeat multiple times
&lt;/h3&gt;

&lt;p&gt;The last thing we wanted to touch on is repeatability. Lighthouse will produce vastly different performance results each run. Therefore, we usually like to &lt;strong&gt;run it at least 5 times&lt;/strong&gt; and compute the averages. This allows us to have a better sense of whether we are moving in the right direction.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://gist.github.com/zigapk/4c627c25d9de61d701e674c7bc89ad57" rel="noopener noreferrer"&gt;gist here&lt;/a&gt; contains a script that does just that for you.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Dive deeper
&lt;/h3&gt;

&lt;p&gt;The Lighthouse JSON report contains much more information that the HTML one, so it is worth examining a bit. It contains all the reasons why the webpage might not be performing well. One of the more useful things we found is the &lt;code&gt;largest-contentful-paint-element&lt;/code&gt;, which tells you which element you should optimize in order to achieve a better LCP score.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's get back to work
&lt;/h2&gt;

&lt;p&gt;Well, now the hard part begins - improving the webpage performance. However, do not forget that Lighthouse is a (very relevant) made-up metric - in reality, you need to optimize your webpage for real-world users. And web crawlers, but we will get into the SEO debate some other time.&lt;/p&gt;

&lt;p&gt;This blog post was written by the awesome team at &lt;a href="https://zerodays.dev/" rel="noopener noreferrer"&gt;zerodays.dev&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>performance</category>
      <category>tutorial</category>
      <category>web</category>
    </item>
    <item>
      <title>How Our Infrastructure Supports Last-Minute Studying</title>
      <dc:creator>Žiga Patačko Koderman</dc:creator>
      <pubDate>Thu, 04 Jul 2024 08:15:21 +0000</pubDate>
      <link>https://dev.to/zerodays/how-our-infrastructure-supports-last-minute-studying-2c5d</link>
      <guid>https://dev.to/zerodays/how-our-infrastructure-supports-last-minute-studying-2c5d</guid>
      <description>&lt;p&gt;The past few weeks, one of our clients, &lt;a href="https://astra.si/en/astra-ai/" rel="noopener noreferrer"&gt;Astra AI&lt;/a&gt; - an AI powered math tutor, has been recording a steep increase in traffic. This makes sense, as in June, students were frantically studying to improve their final grades right before the school year ended and preparing for the Slovenian national high school final exam - Matura.&lt;/p&gt;

&lt;p&gt;Although it’s been years since our teachers warned us not to study in the last days and avoid cramming, the data shows that this is still the case. Well, little has changed since our study days (not that we listened to teachers back then either, of course).&lt;/p&gt;

&lt;p&gt;But this time we are experiencing this phenomenon from a completely new perspective - intensely studying in the last few days before the exam means a sudden increase in traffic. The chart below represents the number of &lt;a href="https://platform.openai.com/tokenizer" rel="noopener noreferrer"&gt;OpenAI tokens&lt;/a&gt; used by Astra per day:&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%2Fuploads%2Farticles%2Fap66flywkqll97g9xr19.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%2Fuploads%2Farticles%2Fap66flywkqll97g9xr19.png" alt="Chart showing a big spike in token usage." width="526" height="294"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We won’t be sharing the absolute numbers of course, but suffice to say that we believe this to be one of the biggest usages in our region.&lt;/p&gt;

&lt;p&gt;This was Astra’s first time through such (admittedly expected) load increase. However, we were fairly confident in our infrastructure and did nothing special in advance to handle this. And in fact no technical issues arose.&lt;/p&gt;

&lt;p&gt;Since the peak, we’ve analyzed the logs, reviewed the data, and attributed this to a few key factors:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Some clever load balancing between multiple API keys in order to climb &lt;a href="https://platform.openai.com/docs/guides/rate-limits/usage-tiers" rel="noopener noreferrer"&gt;OpenAI's tier ladder&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Great choice of hosting providers allowing for easy elastic scaling, namely:

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://vercel.com/" rel="noopener noreferrer"&gt;Vercel&lt;/a&gt; for the &lt;a href="http://Next.js" rel="noopener noreferrer"&gt;Next.js&lt;/a&gt; frontend and part of the backend. The killer features for us include autoscaling, CDN and instant rollbacks out of the box 📦.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://railway.app/" rel="noopener noreferrer"&gt;Railway.app&lt;/a&gt; for the background machinery handling the more complicated requests.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Great choice of monitoring and analytics tools (&lt;a href="https://sentry.io/welcome/" rel="noopener noreferrer"&gt;Sentry&lt;/a&gt;, &lt;a href="https://axiom.co/" rel="noopener noreferrer"&gt;Axiom&lt;/a&gt;, &lt;a href="https://posthog.com/" rel="noopener noreferrer"&gt;Posthog&lt;/a&gt; and &lt;a href="https://github.com/louislam/uptime-kuma" rel="noopener noreferrer"&gt;Uptime Kuma&lt;/a&gt;) coupled with amazing &lt;a href="https://slack.com/" rel="noopener noreferrer"&gt;Slack&lt;/a&gt; integrations that allowed us to iron out any issues way before the traffic spike while the troubling features were still fresh from the oven.&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;As we could talk about our infrastructure choices for days, we decided to keep this post short and simple but are planning on doing more of a deep dive in one of the future posts, so keep an eye out for that.&lt;/p&gt;

&lt;p&gt;This blog post was written by the &lt;a href="https://zerodays.dev/" rel="noopener noreferrer"&gt;zerodays.dev&lt;/a&gt; team.&lt;/p&gt;

</description>
      <category>openai</category>
      <category>monitoring</category>
      <category>startup</category>
      <category>vercel</category>
    </item>
    <item>
      <title>The Hunt for a Perfect Headless CMS</title>
      <dc:creator>Žiga Patačko Koderman</dc:creator>
      <pubDate>Wed, 03 Jul 2024 06:57:50 +0000</pubDate>
      <link>https://dev.to/zerodays/the-hunt-for-a-perfect-headless-cms-123h</link>
      <guid>https://dev.to/zerodays/the-hunt-for-a-perfect-headless-cms-123h</guid>
      <description>&lt;p&gt;We recently decided to leave Webflow (the reasons why will be further explained in our next blog) and migrate our company website to a headless content management system. For that reason we decided to thoroughly research the options available and select the best solution for us.&lt;/p&gt;

&lt;h2&gt;
  
  
  🗒️ Requirements (and wishes ✨)
&lt;/h2&gt;

&lt;p&gt;Our main goal was to select a suitable headless CMS for managing the content of a NextJS-based landing page, allowing for full customization and seamless integration. Based on that we developed a list of requirements for the system we wanted, not just for our website, but also for the majority of other landing pages we develop for our clients.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Must have&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Localization support (&lt;a href="https://en.wikipedia.org/wiki/Right-to-left_script" rel="noopener noreferrer"&gt;RTL&lt;/a&gt; support in editor is a bonus)&lt;/li&gt;
&lt;li&gt;Good integration with &lt;a href="https://nextjs.org/" rel="noopener noreferrer"&gt;NextJS&lt;/a&gt;:&lt;/li&gt;
&lt;li&gt;Good &lt;a href="https://react.dev/" rel="noopener noreferrer"&gt;React&lt;/a&gt; integration.&lt;/li&gt;
&lt;li&gt;Possible server-side (pre)rendering.&lt;/li&gt;
&lt;li&gt;Supports custom sections or components to emulate a purposely primitive website builder.&lt;/li&gt;
&lt;li&gt;Rich text editor support.&lt;/li&gt;
&lt;li&gt;User-friendly admin interface.&lt;/li&gt;
&lt;li&gt;SEO support.&lt;/li&gt;
&lt;li&gt;Multi-user support.&lt;/li&gt;
&lt;li&gt;Media hosting.&lt;/li&gt;
&lt;li&gt;Easy backup and restore.&lt;/li&gt;
&lt;li&gt;Good reputation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Nice to have&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open source.&lt;/li&gt;
&lt;li&gt;Ability to self-host.&lt;/li&gt;
&lt;li&gt;Support for drafts and versioning&lt;/li&gt;
&lt;li&gt;Live preview inside the content editor.&lt;/li&gt;
&lt;li&gt;Admin panel customization options.&lt;/li&gt;
&lt;li&gt;A/B testing support.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Other considerations&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Migration challenges from the existing website.&lt;/li&gt;
&lt;li&gt;Multi-region hosting and caching (can be done on NextJS side).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Based on those requirements we tested and considered lots of CMS options. Let’s jump straight into why the Payload ended up our first choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  🥇 The winner - &lt;a href="https://payloadcms.com/" rel="noopener noreferrer"&gt;Payload CMS&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Reasons for Selection&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Excellent integration with NextJS. Latest version (3.0) is now a part of a NextJS application.&lt;/li&gt;
&lt;li&gt;Native localization support where localization can be done per field, instead of only per-block.&lt;/li&gt;
&lt;li&gt;Open source and widely used.&lt;/li&gt;
&lt;li&gt;Ability to be self-hosted.&lt;/li&gt;
&lt;li&gt;Admin dashboard is easy to extend with custom React components.&lt;/li&gt;
&lt;li&gt;Easy to extend with custom functionalities since CMS code is co-located with the frontend.&lt;/li&gt;
&lt;li&gt;Native TypeScript support.&lt;/li&gt;
&lt;li&gt;Live editing preview option.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; The version 3 that includes all the sweet features we desire is still in beta - this is why we are using it only for our website at the moment and not experimenting on our clients.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🥈 Other CMS systems considered
&lt;/h2&gt;

&lt;p&gt;Below is the list of all the other options we’ve tested. This is not to say that none of those could work for us - Strapi was extremely close too, and others might be best suited for your specific use case.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://strapi.io/" rel="noopener noreferrer"&gt;Strapi&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open source.&lt;/li&gt;
&lt;li&gt;(Probably) the most popular headless CMS option.&lt;/li&gt;
&lt;li&gt;Self-hosted easily.&lt;/li&gt;
&lt;li&gt;Official plugin to generate Swagger documentation so NextJS integration is trivial (although generated schema doesn’t include localization parameters).&lt;/li&gt;
&lt;li&gt;Native localization support&lt;/li&gt;
&lt;li&gt;Customizable admin dashboard.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Locales are still separate, when translating content all fields need to be set in all locales instead of preserving some fields (images, slugs, etc.). This might cause some friction for editors.&lt;/li&gt;
&lt;li&gt;Poor TypeScript support.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://wordpress.org/" rel="noopener noreferrer"&gt;WordPress&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;: Very popular option, familiarity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;: Requires additional plugins and configuration to achieve full headless functionality. Worse NextJS integration and development experience. Forces you to use different technologies on frontend and backend.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://www.sanity.io/" rel="noopener noreferrer"&gt;Sanity&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;: NextJS support, UI editor integration, flexible localization methods.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;: No self hosting, high price (per user, per request and bandwidth).&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://prismic.io/" rel="noopener noreferrer"&gt;Prismic&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;: NextJS support, live preview editor.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;: No self-hosting, clunky localization (all locales are totally separate), restrictive pricing plans (max 8 locales in platinum plan).&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://tina.io/" rel="noopener noreferrer"&gt;Tina.io&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;: Simple, developer friendly, has live editing capabilities.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;: Poor i18n support, needs to be handled by developers.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://directus.io/" rel="noopener noreferrer"&gt;Directus&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;: More a dataset editor than a CMS.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://www.contentful.com/" rel="noopener noreferrer"&gt;Contentful&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;: Limited language support without enterprise tier.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://keystonejs.com/" rel="noopener noreferrer"&gt;KeystoneJS&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;: Seems inactive, development progress on the official website was not updated since 2022.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://www.contentstack.com/" rel="noopener noreferrer"&gt;Contentstack&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;: CMS is a minor part of the product. Company’s focus is elsewhere.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://www.storyblok.com/" rel="noopener noreferrer"&gt;Storyblok&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;: Expensive. Subpar developer experience.&lt;/p&gt;

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

&lt;p&gt;In the end, Payload took the crown for us. What ultimately sold us on it, was its superb integration with Next.js - our technology of choice for web development.&lt;/p&gt;

&lt;p&gt;This blog and its underlying research was made by the awesome team at &lt;a href="https://zerodays.dev/" rel="noopener noreferrer"&gt;zerodays.dev&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>cms</category>
      <category>payload</category>
      <category>webdev</category>
    </item>
    <item>
      <title>All Paths Lead To Jira</title>
      <dc:creator>Žiga Patačko Koderman</dc:creator>
      <pubDate>Wed, 22 May 2024 07:03:24 +0000</pubDate>
      <link>https://dev.to/zerodays/all-paths-lead-to-jira-2b43</link>
      <guid>https://dev.to/zerodays/all-paths-lead-to-jira-2b43</guid>
      <description>&lt;h2&gt;
  
  
  zerodays’ journey through the world of project management tools
&lt;/h2&gt;

&lt;p&gt;Right after we started our company in late 2021, we knew a good project management tool will be absolutely essential for our work, but we had some specific requirements. We do a lot of agency work, developing for multiple different clients at the same time so the tool had to be amazing for cross project planning, as well as enable some type of transparent sharing of the project progress with the client. &lt;/p&gt;

&lt;p&gt;Now, almost two and a half years later and with four tools tested in detail, few others considered and most of them tossed, we feel like we finally landed on a good option for us - Jira. But, as this was definitely not a smooth path, we wanted to do an overlook of everything we have tried, why it worked and why not, in hopes it eases the process for anyone else trying to navigate the world of project management tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  What methodology do we follow?
&lt;/h2&gt;

&lt;p&gt;The methodology we follow of course defines the tools needed for our project management, so let’s start with that. We follow agile principles, but no specific methodology strictly, however, we use Kanban boards. So, we were looking for a tool that would offer us flexible and user-friendly Kanban boards and would allow us to plan as well as track the time spent on various tasks/tickets.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trying out the Tools
&lt;/h2&gt;

&lt;h3&gt;
  
  
  GitHub Issues and Projects
&lt;/h3&gt;

&lt;p&gt;Our first project management tool we took on was GitHub Projects. We knew and loved GitHub Issues from the open source projects and believed that Projects would be the tool that would fit us. However, unfortunately, it left quite a lot to be desired.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What we disliked&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All the project management is connected only to the repository. So if your project exists in two separate repositories (frontend and backend for example), you have to make two separate tickets instead of one and cross linking through the repositories is not a practical solution.&lt;/li&gt;
&lt;li&gt;It isn’t really a fit for our office team, as the non-development tasks don’t fit in, and it’s expensive as a project management tool.&lt;/li&gt;
&lt;li&gt;Every ticket you create is a draft by default (this isn’t really a deal-breaker on its own, but it definitely makes the user experience a lot less enjoyable).&lt;/li&gt;
&lt;li&gt;There is no hierarchy when it comes to tickets, which makes having an overview of the issue timeline, the main issue, the sub issues etc. difficult.&lt;/li&gt;
&lt;li&gt;Poor time tracking.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So after a while, we made the decision to move on and try out Clickup 2.0.&lt;/p&gt;

&lt;h3&gt;
  
  
  ClickUp
&lt;/h3&gt;

&lt;p&gt;ClickUp is advertised as an “all in one productivity platform” which drew us to it. It definitely solved most of the issues we mentioned with GitHub, such as cross project tracking and ticket hierarchy, but unfortunately we still weren’t completely satisfied.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What we disliked&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Firstly, the app had a lot of bugs and issues, which made it difficult to use. For example, often when creating a ticket, it didn’t show up until a couple of minutes later, when two same tickets popped up. Slow loading times were also a common issue.&lt;/li&gt;
&lt;li&gt;The layout and organization of the platform was in our opinion a bit cluttered, so the project tracking and overlook of the projects is messy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We have to mention that the bugs mentioned above are supposedly resolved in the 3.0 version, however as the app didn’t convince us, we decided not to stay aboard, and jumped ship to Notion.&lt;/p&gt;

&lt;h3&gt;
  
  
  Notion
&lt;/h3&gt;

&lt;p&gt;Notion is an everyday organization tool, combining project management tools with wikis, docs, planning tools, etc. Its customizability and the lure of having one place for the documentation and project management are what convinced us to try it. We used it for most of our development and non-development documentation and tried out two Kanban boards - a free Kanban template offered by the platform, and our own customizable Kanban board. What we liked about the platform was the easy addition of external guests and sharing the projects with people outside of the company, and good tools for non-development related team collaboration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What we disliked&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Although Notion is quite known to be very customizable for everyday use, it was too limited for our development team. We specifically had issues with the Kanban boards - they were too rigid. Both the template and our own version lacked the ability to move, rename columns, add filters, automatically hide done tickets, etc. &lt;/li&gt;
&lt;li&gt;Poor GitHub integration.&lt;/li&gt;
&lt;li&gt;Sometimes it doesn't work - some tickets are not saved on the first try and other bugs.&lt;/li&gt;
&lt;li&gt;Similar to GitHub - there was no ticket hierarchy, which made the project overlook difficult.&lt;/li&gt;
&lt;li&gt;Columns don’t have an option to hide tickets, so it gets cluttered.&lt;/li&gt;
&lt;li&gt;Project planning and filter options were quite limited. There was also no option to integrate it with our time-tracking tool.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With enough experience to finally know what we like and what not, we started considering our final move to either Linear or Jira.&lt;/p&gt;

&lt;h3&gt;
  
  
  Jira
&lt;/h3&gt;

&lt;p&gt;In the end we settled on Jira, because of the configurability of the tool and integrations it offers, specifically with Slack, Figma and GitHub, the last one being absolutely amazing. So far, it solves the majority of the issues we had with other tools, most notably, it is automated where it can be, which makes a more hassle-free user experience for the development team.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What do we still dislike&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Although we love the configurability it offers, it is quite difficult to set up everything - it is confusing, cluttered in different places, and the learning curve is very steep. On the good side, it is only a pain for the admins, not the developers.&lt;/li&gt;
&lt;li&gt;It doesn’t really offer good template options. The way around we found is that we have a project that we named template and we then initiate new projects based on it.. The downside is we still have to manually set up filters and settings, so it takes a while. Again, thankfully it is the issue only for the admins.&lt;/li&gt;
&lt;li&gt;We had to make our own integration for the time tracking software we use - Clockify. There are some apps that offer some integration, but we wanted time tracking to be synced with Jira’s own time tracking. However, with Jira, we at least had an option to do this ourselves.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrapping it up
&lt;/h2&gt;

&lt;p&gt;We also considered Linear and although it seemed to cover most of our requirements, we decided to go for Jira because of the integrations it offers. In all honesty, we have to admit that we might have not considered it enough and will maybe return back to it at some point, but a decision had to be made.&lt;/p&gt;

&lt;p&gt;So, here we are at last - Jira. Is this the best solution for eternity? Probably not. But, we are happy at the moment, as we configured it to our specific needs and it doesn’t restrict our developers, while being fairly automated, so the hassle of moving tickets etc. is minimized. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;zerodays is a custom software development team based in Ljubljana, Slovenia. We focus on AI development and consulting, web and mobile app development, business digitalization, and process automation solutions. If you’re interested in what we do and want to keep up with us, visit our &lt;a href="https://zerodays.dev/" rel="noopener noreferrer"&gt;website&lt;/a&gt; or follow us on &lt;a href="https://si.linkedin.com/company/zerodays" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>projectmanagement</category>
      <category>jira</category>
      <category>projectmanagementtools</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>Generative UI in React Native</title>
      <dc:creator>Žiga Patačko Koderman</dc:creator>
      <pubDate>Thu, 11 Apr 2024 11:09:47 +0000</pubDate>
      <link>https://dev.to/zerodays/generative-ui-in-react-native-180f</link>
      <guid>https://dev.to/zerodays/generative-ui-in-react-native-180f</guid>
      <description>&lt;p&gt;Let's build a generative UI Weather Chatbot!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExZHJ4NDJndHk5NnRwNzI5dmZ0ODdoMms1bGMwZzUyc2Q4M2t3NzBsbyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/ENgvOtmRyu7VSuOY7r/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExZHJ4NDJndHk5NnRwNzI5dmZ0ODdoMms1bGMwZzUyc2Q4M2t3NzBsbyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/ENgvOtmRyu7VSuOY7r/giphy.gif" width="222" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This article is inspired by &lt;a href="https://sdk.vercel.ai/docs/concepts/ai-rsc"&gt;Vercel's Generative UI&lt;/a&gt;. It expands on &lt;a href="https://platform.openai.com/docs/guides/function-calling"&gt;OpenAI's functions/tools&lt;/a&gt; by allowing the model to visualize data to the user.&lt;/p&gt;

&lt;p&gt;Let's get straight into it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create an Expo Typescript app 📱
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx create-expo-app &lt;span class="nt"&gt;-t&lt;/span&gt; expo-template-blank-typescript demo
&lt;span class="nb"&gt;cd &lt;/span&gt;demo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;yarn add react-native-gen-ui zod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This adds the &lt;a href="https://www.npmjs.com/package/react-native-gen-ui"&gt;&lt;code&gt;react-native-gen-ui&lt;/code&gt;&lt;/a&gt; package that offers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Exposes &lt;code&gt;useChat&lt;/code&gt; hook for easy access to &lt;strong&gt;OpenAI's completions&lt;/strong&gt; api.&lt;/li&gt;
&lt;li&gt;Supports &lt;strong&gt;streaming&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Enables &lt;strong&gt;Generative UI&lt;/strong&gt; via &lt;code&gt;tools&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Is fully &lt;strong&gt;type-safe&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The package was initially developed by us at &lt;a href="https://zerodays.dev/"&gt;zerodays.dev&lt;/a&gt; and was open-sourced for everyone to use.&lt;/p&gt;

&lt;p&gt;We also install &lt;code&gt;zod&lt;/code&gt; here for validation of tool parameters.&lt;/p&gt;

&lt;h3&gt;
  
  
  Import dependencies
&lt;/h3&gt;

&lt;p&gt;At the top of the &lt;code&gt;App.tsx&lt;/code&gt; add&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&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;z&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;zod&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;OpenAI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isReactElement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useChat&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="s1"&gt;react-native-gen-ui&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;h3&gt;
  
  
  Initialize OpenAI
&lt;/h3&gt;

&lt;p&gt;Below imports, write:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;openAi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;EXPO_PUBLIC_OPENAI_API_KEY&lt;/span&gt;&lt;span class="o"&gt;!&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gpt-4&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;Then make sure to add &lt;code&gt;EXPO_PUBLIC_OPENAI_API_KEY&lt;/code&gt; to your environment or in &lt;code&gt;.env&lt;/code&gt; file. You can obtain an API key in the &lt;a href="https://platform.openai.com/account/api-keys"&gt;OpenAI Platform&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Utilize &lt;code&gt;useChat&lt;/code&gt; hook.
&lt;/h3&gt;

&lt;p&gt;At the top of the App function, let's use the &lt;code&gt;useChat&lt;/code&gt; hook.&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;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="nf"&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;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onInputChange&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handleSubmit&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useChat&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;openAi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;initialMessages&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;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 nice little weather chatbot.&lt;/span&gt;&lt;span class="dl"&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="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;&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we are passing initial messages into the &lt;code&gt;useChat&lt;/code&gt; hook, then allow it to manage the state of messages, stream the content from OpenAI and update the UI.&lt;/p&gt;

&lt;p&gt;But we are still missing a way to render this - make the function return the snippet below:&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;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="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&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;#fff&lt;/span&gt;&lt;span class="dl"&gt;"&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="p"&gt;}&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="cm"&gt;/* Iterate over all messages and render them */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;messages&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;msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;index&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;// Message can be either a React component or a string&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isReactElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&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="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Render user messages in blue&lt;/span&gt;
        &lt;span class="k"&gt;case&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="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="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&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;blue&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&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;index&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;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;toString&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="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;assistant&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="c1"&gt;// Render assistant messages&lt;/span&gt;
          &lt;span class="k"&gt;return&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;index&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;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;toString&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="nl"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="c1"&gt;// This includes tool calls, tool results and system messages&lt;/span&gt;
          &lt;span class="c1"&gt;// Those are visible to the model, but here we hide them to the user&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&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="si"&gt;}&lt;/span&gt;
    &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Text input for chatting with the model */&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;TextInput&lt;/span&gt;
      &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;borderColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gray&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;borderWidth&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;textAlign&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;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="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;autoFocus&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;onChangeText&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onInputChange&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;Button&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="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;handleSubmit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&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;disabled&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isLoading&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run the app using &lt;code&gt;npx expo start&lt;/code&gt;. More details about running an Expo app can be found &lt;a href="https://docs.expo.dev/get-started/expo-go/"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Voilà - we can have a basic chat with 🤖 now!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExZnEyOGlqN25heTA3dTIydnNmaWFwcGtuMnFjZjByOGlwNjUxMDZsZSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/031z4f7fdA9RGk2EZE/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExZnEyOGlqN25heTA3dTIydnNmaWFwcGtuMnFjZjByOGlwNjUxMDZsZSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/031z4f7fdA9RGk2EZE/giphy.gif" width="221" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Add a tool
&lt;/h3&gt;

&lt;p&gt;Now the real magic begins 🪄. A tool is defined by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;its name,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;description&lt;/code&gt; (a simple string for model to understand what this tool does),&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;parameters&lt;/code&gt; (a type-safe zod schema of what this tool accepts as arguments) and&lt;/li&gt;
&lt;li&gt;a &lt;code&gt;render&lt;/code&gt; function for handling what both the user and model see when this tool is called.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's add a “get weather tool“ to tell us what the weather is like at a certain location:&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="kd"&gt;const&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="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useChat&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;getWeather&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Get weather for a location&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="c1"&gt;// Parameters for the tool&lt;/span&gt;
      &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="c1"&gt;// Render component for weather - can yield loading state&lt;/span&gt;
      &lt;span class="na"&gt;render&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Fetch the weather data&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getWeatherData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Return the final result&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="c1"&gt;// The data will be seen by the model&lt;/span&gt;
          &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="c1"&gt;// The component will be rendered to the user&lt;/span&gt;
          &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&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;View&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;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
              &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&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;20&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;40&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;rgba(20, 20, 20, 0.05)&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="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="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&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="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;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;weather&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sunny&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;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="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;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;This allows the model to call the &lt;code&gt;getWeather&lt;/code&gt; function. The model receives the &lt;code&gt;data&lt;/code&gt; and can comment upon it, while the user is presented with an emoji representation of the weather (either ☀️ or 🌧️).&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;getWeatherData&lt;/code&gt; function is still missing, let's add it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getWeatherData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Wait 3 seconds to simulate fetching the data from an API&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="c1"&gt;// Randomly return either `sunny` or `rainy`&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;weather&lt;/span&gt;&lt;span class="p"&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;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sunny&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;rainy&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result is a chat interface that goes beyond just words and can display custom components (even though, this is just emojis in our example).&lt;br&gt;
&lt;a href="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExZXJzYW5lZzRpeGhzNDRoMXVpcWw1dno5MXd4b2hvNXY0ODU5MG5jdyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/EhoBpWpuozdWd99wyO/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExZXJzYW5lZzRpeGhzNDRoMXVpcWw1dno5MXd4b2hvNXY0ODU5MG5jdyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/EhoBpWpuozdWd99wyO/giphy.gif" width="221" height="480"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Show loading states
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;react-native-gen-ui&lt;/code&gt; allows us to yield zero or more components before returning the actual data and the final component. This can be used to display progress to the user until the data is fetched.&lt;/p&gt;

&lt;p&gt;Add the following code at the beginning of &lt;code&gt;render&lt;/code&gt;:&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="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Yield a loading indicator&lt;/span&gt;
  &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ActivityIndicator&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"activity-indicator"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Fetch the weather data&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;render&lt;/code&gt; function is in fact a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator"&gt;generator&lt;/a&gt; - this is denoted by the &lt;code&gt;*&lt;/code&gt; after the &lt;code&gt;function&lt;/code&gt; keyword. The interface will always display the last component that was either yielded or returned, allowing us to swap what the user sees over time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExeXplcnd5cXJoNmR6azhtdXY5cDdzM3NxZGJ0cXo2MmFqOGpmdjRzbSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/l66xLVIZbm3Y6PGrxj/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExeXplcnd5cXJoNmR6azhtdXY5cDdzM3NxZGJ0cXo2MmFqOGpmdjRzbSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/l66xLVIZbm3Y6PGrxj/giphy.gif" width="221" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This can be especially useful if the the render function takes a lot of time and has multiple steps. The user can stay informed of whatever is happening in the background. For example, one could pre-render partial information before the entire data pipeline completes.&lt;/p&gt;

&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;📦 &lt;a href="https://www.npmjs.com/package/react-native-gen-ui"&gt;react-native-gen-ui&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;A &lt;a href="https://github.com/zerodays/react-native-gen-ui-minimal-example"&gt;minimal example&lt;/a&gt; like the one in this post.&lt;/li&gt;
&lt;li&gt;A &lt;a href="https://github.com/zerodays/react-native-gen-ui-weather-example"&gt;complete weather bot example&lt;/a&gt; from the GIF at the top.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This blog post and the &lt;code&gt;react-native-gen-ui&lt;/code&gt; package were made by the awesome team at &lt;a href="https://zerodays.dev/"&gt;zerodays.dev&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>reactnative</category>
      <category>ui</category>
    </item>
  </channel>
</rss>
