<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Matt Ruiz</title>
    <description>The latest articles on Forem by Matt Ruiz (@matthewzruiz).</description>
    <link>https://forem.com/matthewzruiz</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%2F865943%2F61fe8372-b76c-4616-aa18-090c2520c647.jpg</url>
      <title>Forem: Matt Ruiz</title>
      <link>https://forem.com/matthewzruiz</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/matthewzruiz"/>
    <language>en</language>
    <item>
      <title>Using the Three Table Pattern to Track Your AI Costs</title>
      <dc:creator>Matt Ruiz</dc:creator>
      <pubDate>Tue, 07 Apr 2026 20:04:13 +0000</pubDate>
      <link>https://forem.com/matthewzruiz/using-the-three-table-pattern-to-track-your-ai-costs-15ed</link>
      <guid>https://forem.com/matthewzruiz/using-the-three-table-pattern-to-track-your-ai-costs-15ed</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; When you add AI to an app, persist the request, persist the response, and link them with an exchange. That one structure makes every round-trip traceable — and token costs fall out naturally.&lt;/p&gt;




&lt;h2&gt;
  
  
  My default when adding AI to an app
&lt;/h2&gt;

&lt;p&gt;A pattern I keep coming back to: &lt;strong&gt;The Three Table Pattern&lt;/strong&gt; — one table for the request, one for the response, and one exchange table that links them and holds the meter.&lt;/p&gt;

&lt;p&gt;My default is to persist what I'm sending, persist what comes back, and tie both to a single exchange id so one user↔model round-trip is traceable end to end.&lt;/p&gt;

&lt;p&gt;That id is what lets me answer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What did we actually send?&lt;/li&gt;
&lt;li&gt;What did we get back?&lt;/li&gt;
&lt;li&gt;What did this turn cost?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without reconstructing anything from logs or guessing from the UI.&lt;/p&gt;

&lt;p&gt;Not a doctrine. Just the shape I keep reaching for.&lt;/p&gt;




&lt;h2&gt;
  
  
  The three tables
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Requests&lt;/strong&gt; — what went out&lt;/p&gt;

&lt;p&gt;Stores the user's input, a reference to the user, any context the request needs (session id, document id, etc.), and a &lt;code&gt;status&lt;/code&gt; of &lt;code&gt;pending&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Responses&lt;/strong&gt; — what came back&lt;/p&gt;

&lt;p&gt;Stores the model's structured output. This is where token counts live in some setups, though I sometimes put them on the exchange instead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Exchanges&lt;/strong&gt; — the unit that links them&lt;/p&gt;

&lt;p&gt;Stores &lt;code&gt;request_id&lt;/code&gt;, &lt;code&gt;response_id&lt;/code&gt;, &lt;code&gt;input_tokens&lt;/code&gt;, &lt;code&gt;output_tokens&lt;/code&gt;, &lt;code&gt;model_used&lt;/code&gt;, and &lt;code&gt;status&lt;/code&gt;. One exchange = one round-trip. This is the row you query when you want cost.&lt;/p&gt;




&lt;h2&gt;
  
  
  The sequence
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Insert request (&lt;code&gt;status: pending&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Insert exchange (&lt;code&gt;request_id&lt;/code&gt; set, &lt;code&gt;response_id: null&lt;/code&gt;, &lt;code&gt;status: pending&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Call the model&lt;/li&gt;
&lt;li&gt;Insert response (structured output)&lt;/li&gt;
&lt;li&gt;Update exchange (&lt;code&gt;response_id&lt;/code&gt;, token counts, &lt;code&gt;model_used&lt;/code&gt;, &lt;code&gt;status: completed&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the model call fails, the exchange updates to &lt;code&gt;failed&lt;/code&gt;. You always have a record of the attempt.&lt;/p&gt;




&lt;h2&gt;
  
  
  Example 1 — Inline chat (knowledge-transcript-chat)
&lt;/h2&gt;

&lt;p&gt;This is a chat UI where a user asks questions about a YouTube transcript.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;File: &lt;code&gt;insert-knowledge-transcript-chat-request.ts&lt;/code&gt;&lt;/strong&gt;&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;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;insertKnowledgeTranscriptChatRequest&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="nx"&gt;supabase&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SupabaseClient&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;InsertKnowledgeTranscriptChatRequestInput&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&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="kr"&gt;string&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;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;data&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="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;knowledge_transcript_chat_requests&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="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;user_id&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;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;youtube_transcript_id&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;youtubeTranscriptId&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="nx"&gt;input&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="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pending&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="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id&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="nf"&gt;single&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;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Failed to insert chat request: &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;message&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="k"&gt;return&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;data&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;File: &lt;code&gt;insert-knowledge-transcript-chat-exchange.ts&lt;/code&gt;&lt;/strong&gt;&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;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;insertKnowledgeTranscriptChatExchange&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="nx"&gt;supabase&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SupabaseClient&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;InsertKnowledgeTranscriptChatExchangeInput&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&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="kr"&gt;string&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;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;data&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="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;knowledge_transcript_chat_exchanges&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="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;user_id&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;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;request_id&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;requestId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pending&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="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id&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="nf"&gt;single&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;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Failed to insert chat exchange: &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;message&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="k"&gt;return&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;data&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the model responds, &lt;code&gt;updateKnowledgeTranscriptChatExchange&lt;/code&gt; closes it out with &lt;code&gt;input_tokens&lt;/code&gt;, &lt;code&gt;output_tokens&lt;/code&gt;, &lt;code&gt;model_used&lt;/code&gt;, and &lt;code&gt;status: completed&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The client receives &lt;code&gt;responseId&lt;/code&gt;, &lt;code&gt;inputTokens&lt;/code&gt;, &lt;code&gt;outputTokens&lt;/code&gt;, and &lt;code&gt;modelUsed&lt;/code&gt; back from the server and appends them to the session slice. The costs table reads directly from those session messages — no separate cost-tracking service needed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Example 2 — One-off AI job (marketing-blog-ideation)
&lt;/h2&gt;

&lt;p&gt;This is a background job that generates blog ideas for a given topic and ICP context.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;File: &lt;code&gt;create-request.ts&lt;/code&gt;&lt;/strong&gt;&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;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;createMarketingBlogIdeationRequest&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="nx"&gt;supabase&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SupabaseClient&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;CreateMarketingBlogIdeationRequestInput&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;MarketingBlogIdeationRequestRow&lt;/span&gt;&lt;span class="o"&gt;&amp;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;data&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="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;marketing_blog_ideation_requests&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="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;user_id&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;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;topic&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;topic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;context&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;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;icp_context&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;icpContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;mode&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;mode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pending&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="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;single&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;error&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Failed to create request: &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;message&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;data&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;strong&gt;File: &lt;code&gt;create-response.ts&lt;/code&gt;&lt;/strong&gt;&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;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;createMarketingBlogIdeationResponse&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="nx"&gt;supabase&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SupabaseClient&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;CreateMarketingBlogIdeationResponseInput&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;MarketingBlogIdeationResponseRow&lt;/span&gt;&lt;span class="o"&gt;&amp;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;data&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="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;marketing_blog_ideation_responses&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="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;structured&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;structured&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;model_used&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;modelUsed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;input_tokens&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;inputTokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;output_tokens&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;outputTokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;total_tokens&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;totalTokens&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="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;single&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;error&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Failed to create response: &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;message&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;data&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;Same three-table shape, different domain. The request carries topic and ICP context. The response carries structured output and token counts. The exchange links them.&lt;/p&gt;




&lt;h2&gt;
  
  
  Example 3 — Surfacing it in the client (classification-requests)
&lt;/h2&gt;

&lt;p&gt;On the client side in luckee-web, request rows are normalized into a Redux dump slice by id:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;File: &lt;code&gt;classification-requests.ts&lt;/code&gt;&lt;/strong&gt;&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;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;classificationRequestsSlice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createSlice&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;classificationRequests&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;initialState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ClassificationRequest&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;reducers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;setClassificationRequests&lt;/span&gt;&lt;span class="p"&gt;:&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="nx"&gt;action&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;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;addClassificationRequest&lt;/span&gt;&lt;span class="p"&gt;:&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="nx"&gt;action&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;state&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;payload&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="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;payload&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;updateClassificationRequest&lt;/span&gt;&lt;span class="p"&gt;:&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="nx"&gt;action&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;state&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;payload&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="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;payload&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;deleteClassificationRequest&lt;/span&gt;&lt;span class="p"&gt;:&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="nx"&gt;action&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;delete&lt;/span&gt; &lt;span class="nx"&gt;state&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;payload&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;Requests, responses, and exchanges load into their own slices. The UI reconstructs the round-trip from what it has. The three-table shape stays consistent — it just shows up in Redux instead of raw SQL at this layer.&lt;/p&gt;




&lt;h2&gt;
  
  
  Cost is a side effect of having the rows
&lt;/h2&gt;

&lt;p&gt;Once &lt;code&gt;input_tokens&lt;/code&gt;, &lt;code&gt;output_tokens&lt;/code&gt;, and &lt;code&gt;model_used&lt;/code&gt; are on the exchange or response, the math is one formula:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(inputTokens / 1_000_000) * inputRateUsd
+ (outputTokens / 1_000_000) * outputRateUsd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sum per exchange for one turn. Sum per thread for a session. Sum per user for a billing period. You don't need a separate cost-tracking service — you just need the rows.&lt;/p&gt;




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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Three tables:&lt;/strong&gt; requests, responses, exchanges — one per round-trip&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Insert in order:&lt;/strong&gt; request → exchange → call model → response → update exchange&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Status on every row:&lt;/strong&gt; &lt;code&gt;pending&lt;/code&gt; / &lt;code&gt;completed&lt;/code&gt; / &lt;code&gt;failed&lt;/code&gt; gives you retry and audit for free&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Token counts on the exchange:&lt;/strong&gt; makes cost a query, not a guess&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Same shape, different domains:&lt;/strong&gt; chat UI, background job, classification — the pattern transfers&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>supabase</category>
      <category>express</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>Using React Navigation instead of Expo Router</title>
      <dc:creator>Matt Ruiz</dc:creator>
      <pubDate>Mon, 10 Feb 2025 20:43:46 +0000</pubDate>
      <link>https://forem.com/matthewzruiz/using-react-navigation-instead-of-expo-router-49bl</link>
      <guid>https://forem.com/matthewzruiz/using-react-navigation-instead-of-expo-router-49bl</guid>
      <description>&lt;p&gt;Hola hola,&lt;/p&gt;

&lt;p&gt;If you're looking to use React Navigation instead of Expo Router, then this quick guide should be helpful.&lt;/p&gt;

&lt;p&gt;There are two steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Update the &lt;code&gt;main&lt;/code&gt; prop of your &lt;code&gt;package.json&lt;/code&gt; to point to your new entry file
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "name": "pcexpo",
  "main": "./src/App.tsx",
  "version": "1.0.0",
  ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2.Export your entry file using the &lt;a href="https://docs.expo.dev/versions/latest/sdk/register-root-component/" rel="noopener noreferrer"&gt;registerRootComponent&lt;/a&gt; provided by Expo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React from 'react';
import { registerRootComponent } from 'expo';
import { View, Text, StyleSheet } from 'react-native';

export const App = () =&amp;gt; {
  return (
    &amp;lt;View style={styles.container}&amp;gt;
      &amp;lt;Text style={styles.text}&amp;gt;Welcome to my App!&amp;lt;/Text&amp;gt;
    &amp;lt;/View&amp;gt;
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
  text: {
    fontSize: 20,
    fontWeight: 'bold',
  },
});

export default registerRootComponent(App);

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

&lt;/div&gt;



&lt;p&gt;That should be all you need!&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>rnexpo</category>
    </item>
    <item>
      <title>Entry Level Project Management in 2025</title>
      <dc:creator>Matt Ruiz</dc:creator>
      <pubDate>Mon, 10 Feb 2025 14:40:14 +0000</pubDate>
      <link>https://forem.com/matthewzruiz/entry-level-project-management-in-2025-4d5c</link>
      <guid>https://forem.com/matthewzruiz/entry-level-project-management-in-2025-4d5c</guid>
      <description>&lt;h2&gt;
  
  
  How much can you make as a Entry Project Manager?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F58yylubfr6pfedyfbavd.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.amazonaws.com%2Fuploads%2Farticles%2F58yylubfr6pfedyfbavd.png" alt="A Junior Project Manager job is shown. This job is posted by the City of New York QUEENS and shows a salary range of $56,313.00-$64.760.00 and posted 9/4/2024." width="800" height="197"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cityjobs.nyc.gov/job/junior-project-manager-in-queens-jid-19986?utm_campaign=google_jobs_apply&amp;amp;utm_source=google_jobs_apply&amp;amp;utm_medium=organic" rel="noopener noreferrer"&gt;New York City is offering up to ~$65k for their Junior Project Manager role.&lt;/a&gt; If you’re working 2,080 hours per year, that comes out to about $31 per hour.&lt;/p&gt;

&lt;p&gt;I’m not sure, from the job listing, if there are health benefits included but working for them does provide access to programs such as the &lt;a href="https://studentaid.gov/pslf/" rel="noopener noreferrer"&gt;Public Service Loan Forgiveness&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;How much would access to that program be worth? For some, a lot more than others — let’s say an extra $10/hr. That would make this position worth (in our imaginary world) ~$45/hr.&lt;/p&gt;

&lt;h2&gt;
  
  
  Can I make $45/hr as an Entry Level Project Manager?
&lt;/h2&gt;

&lt;p&gt;I wouldn’t get your hopes up. The $20-$30 per hour range is a better expectation. For the NYC post, they also require at least a master’s degree.&lt;/p&gt;

&lt;p&gt;Let’s say you don’t have a master’s, or even a bachelor’s, degree…I would expect a lower hourly rate than someone who does.&lt;/p&gt;

&lt;p&gt;The NYC post lists a range of $56k-$65k. Therefore, the lower-experienced Entry Level Project Manager (who is already scraping for real-world experience) would only be adding about $27 worth of value per hour.&lt;/p&gt;

&lt;p&gt;The NYC job listing is just one post but it serves as good benchmark from a public institution.&lt;/p&gt;

&lt;h2&gt;
  
  
  How can I find an Entry Level Project Management role?
&lt;/h2&gt;

&lt;p&gt;To start, you’ll need to understand the different elements of being a Project Manager. You can read more about the tools I use as a Project Manager with 7 other stakeholders (2 CEOs, 2 EA/QA, 1 Jr Dev, 1 Sr Dev, and 1 Designer).&lt;/p&gt;

&lt;p&gt;Here are some of the responsibilities listed by NYC:&lt;/p&gt;

&lt;p&gt;Junior Project Manager duties will include assisting with the following tasks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;maintaining a project management information system to provide data for the planning and control of project development;&lt;/li&gt;
&lt;li&gt;establishing project timeframes and cost schedules;&lt;/li&gt;
&lt;li&gt;determining and coordinating project activities required between the client agencies, contractors, and departments responsible for project completion;&lt;/li&gt;
&lt;li&gt;reviewing all schedules, reports, and orders prepared by consultants, contractors, and client agencies to assure compliance with project completion dates;&lt;/li&gt;
&lt;li&gt;checking contractors’ work performance via field visits and on-site inspections&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I think this job listing does a great job at highlighting what it means to be an Entry Level Project Manager. Let’s go through each of them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Maintaining a project management information system to provide data for the planning and control of project development
&lt;/h3&gt;

&lt;p&gt;Data is a most important word on this sentence. What data does a Project Manager care for?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Who is available today? Who is available this week?&lt;/li&gt;
&lt;li&gt;What blockers are new? What blockers have been open for a minute?&lt;/li&gt;
&lt;li&gt;Who is holding the team back?&lt;/li&gt;
&lt;li&gt;etc…&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All of this data is important to decision makers of all sizes. As an Entry Level Project Manager, you should continue asking yourself these questions at the beginning and end of your work day.&lt;/p&gt;

&lt;h3&gt;
  
  
  Establishing project timeframes and cost schedules
&lt;/h3&gt;

&lt;p&gt;With the data mentioned above, you’ll start should aim to get a feel for the delivery cadence of each stakeholder in your team. This will give you a sense of how ‘quickly’ your team delivers.&lt;/p&gt;

&lt;p&gt;Once you understand the speed of your team, you begin to make more realistic time estimates. The longer something takes is often related to how much it will cost.&lt;/p&gt;

&lt;p&gt;By remaining confident that your team, and therefore decision makers, have as much relevant data as possible, you will excel as a Project Manager (not just at the Entry/Junior level).&lt;/p&gt;

&lt;h3&gt;
  
  
  Determining and coordinating project activities required between the client agencies, contractors, and departments responsible for project completion
&lt;/h3&gt;

&lt;p&gt;This one is simple. As the meetings or communication units get pushed around, it’s your job to make sure that your team maintains a steady flow of information.&lt;/p&gt;

&lt;p&gt;If a meeting gets cancelled last minute, are you sending a follow-up email with main topics for your team to discuss through text. Given what you knew prior to the meeting, are you able to ask specific stakeholders for specific feedback/direction/etc?&lt;/p&gt;

&lt;p&gt;A lot of the time, information is kept to a single stakeholder. As a Project Manager, connecting the dots is a key job responsibility — and you need that withheld information to do so.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reviewing all schedules, reports, and orders prepared by consultants, contractors, and client agencies to assure compliance with project completion dates
&lt;/h3&gt;

&lt;p&gt;As your project(s) take shape and matures, many changes will be made to everything. Designs, technical requirements, User journeys, and etc are all fluid during project progress.&lt;/p&gt;

&lt;p&gt;When there are blockers, how does your team react to those blockers? Are there trade-offs that need to be made? Does your team need more help? If so, how will the time spent on recruiting/interviewing/etc change the schedule of your team.&lt;/p&gt;

&lt;p&gt;By collecting a constant stream of project management data, you will best serve your team and it’s stakeholders.&lt;/p&gt;

&lt;h3&gt;
  
  
  Checking contractors’ work performance via field visits and on-site inspections
&lt;/h3&gt;

&lt;p&gt;You may be thinking to yourself: “This is what stand-ups are for”. Yes. Stand-ups help your team stay on track. These meetings are for you and you only. At the end of this meeting, Project Managers should have clear next steps and updated schedules/timelines for all stakeholders.&lt;/p&gt;

&lt;p&gt;If you don’t have stand-ups with your team, like us, then you should make use of common management tools like &lt;a href="https://www.trello.com" rel="noopener noreferrer"&gt;Trello&lt;/a&gt;, &lt;a href="https://www.slack.com" rel="noopener noreferrer"&gt;Slack&lt;/a&gt;, &lt;a href="https://www.asyncmanage.com" rel="noopener noreferrer"&gt;Async Management&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I wrote more about how you can use these project tracking tools to maximize your remote team.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;A Project Manager should aim to “connect the dots”. By remaining in sync with the delivery cadence of each stakeholder, you can reallocate resources where needed. Written documentation/communication is always your friend.&lt;/p&gt;

&lt;p&gt;Entry Level Project Manager jobs are slim. If you’re really looking to get your foot in the door, I recommend DM’ing some business owners and project manager to pitch coming in with a part-time role.&lt;/p&gt;

&lt;p&gt;It's gonna' take time but if you stick with it you should see more opportunities open.&lt;/p&gt;

</description>
      <category>career</category>
      <category>architecture</category>
      <category>startup</category>
    </item>
    <item>
      <title>Simple and Reusable React Native Radio Button</title>
      <dc:creator>Matt Ruiz</dc:creator>
      <pubDate>Mon, 03 Feb 2025 01:19:51 +0000</pubDate>
      <link>https://forem.com/matthewzruiz/simple-and-reusable-react-native-radio-button-37ao</link>
      <guid>https://forem.com/matthewzruiz/simple-and-reusable-react-native-radio-button-37ao</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyg2ebvypnzsw7uxkztkp.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.amazonaws.com%2Fuploads%2Farticles%2Fyg2ebvypnzsw7uxkztkp.png" alt=" " width="800" height="169"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Creating the &lt;code&gt;RadioButton.tsx&lt;/code&gt; component
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React from "react";
import {View, Text, SafeAreaView, StyleSheet, TouchableOpacity} from "react-native";

type Props = {
  isSelected: boolean;
  onPress: () =&amp;gt; void;
};

export const RadioButton = (props: Props) =&amp;gt; {
  const {isSelected, onPress} = props;

  return (
    &amp;lt;TouchableOpacity style={styles.outer} onPress={onPress}&amp;gt;
      {isSelected &amp;amp;&amp;amp; &amp;lt;View style={styles.inner} /&amp;gt;}
    &amp;lt;/TouchableOpacity&amp;gt;
  )
};

const styles = StyleSheet.create({
  outer: {
    height: 24,
    width: 24,
    borderRadius: 12,
    borderWidth: 2,
    borderColor: "#000",
    alignItems: "center",
    justifyContent: "center",
  },
  inner: {
    height: 12,
    width: 12,
    borderRadius: 6,
    backgroundColor: "#000",
  },
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's an example of using the &lt;code&gt;&amp;lt;RadioButton&amp;gt;&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;import {registerRootComponent} from "expo";
import React from "react";
import {Text, SafeAreaView, StyleSheet} from "react-native";
import {RadioButton} from "./components/RadioButton";

const App = () =&amp;gt; {
  const [isSelected, setIsSelected] = React.useState(false);

  return (
    &amp;lt;SafeAreaView&amp;gt;
      &amp;lt;Text style={styles.header}&amp;gt;Welcome to One Minute Coding!&amp;lt;/Text&amp;gt;

      &amp;lt;RadioButton isSelected={isSelected} onPress={() =&amp;gt; setIsSelected(true)} /&amp;gt;
    &amp;lt;/SafeAreaView&amp;gt;
  );
};

export default registerRootComponent(App);

const styles = StyleSheet.create({
  header: {
    fontSize: 24,
    fontWeight: "bold",
    textAlign: "center",
    height: 50,
    width: '100%',
  },
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hope this helps! I would love to see how y'all make use of this - please share.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Matt&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>reactnative</category>
      <category>radio</category>
      <category>coding</category>
    </item>
    <item>
      <title>How do I store my data in Redux?</title>
      <dc:creator>Matt Ruiz</dc:creator>
      <pubDate>Fri, 31 Jan 2025 21:00:22 +0000</pubDate>
      <link>https://forem.com/matthewzruiz/redux-state-management-dumps-4lk9</link>
      <guid>https://forem.com/matthewzruiz/redux-state-management-dumps-4lk9</guid>
      <description>&lt;h2&gt;
  
  
  Storing your data in one of many redux 'dump' slices.
&lt;/h2&gt;

&lt;p&gt;Redux slices can be used to easily interact with your data through one of the following &lt;a href="https://redux-toolkit.js.org/api/createSlice" rel="noopener noreferrer"&gt;slice&lt;/a&gt; types:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;dump - this is where we store all retrieved documents;&lt;/li&gt;
&lt;li&gt;current X - this is where we store the current document the User is interacting with;&lt;/li&gt;
&lt;li&gt;builders - this is where we track common environment flags like filters/sorting/etc;&lt;/li&gt;
&lt;li&gt;changes - this is where we keep track of the changes made so that we can perform a partial transaction updates;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In this article, we will talk through the redux &lt;em&gt;dump&lt;/em&gt;. It will be React Native focused but the concepts are extremely similar when using redux in your &lt;em&gt;Next.js&lt;/em&gt; app.&lt;/p&gt;

&lt;p&gt;For this article, I will assume:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You've worked with Redux before and have a solid grasp of the basics;&lt;/li&gt;
&lt;li&gt;You have an existing React Native project setup using TypeScript;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Steps for creating the Redux store with &lt;strong&gt;&lt;code&gt;src/store.ts&lt;/code&gt;&lt;/strong&gt;.
&lt;/h2&gt;

&lt;p&gt;To start, let's imagine that you've got the following &lt;code&gt;store.ts&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Comment: The following code imports configure store from redux toolkit;
import {configureStore} from '@reduxjs/toolkit';

// Comment: The following code declares the reducer for our redux store using our redux slices (atm just *users*);
export const store = configureStore({
  reducer: {
    users,
  },
});

// Comment: The following code grants UI-logic the ability to update redux;
export const useAppDispatch = () =&amp;gt; useDispatch&amp;lt;AppDispatch&amp;gt;();

// Comment: The following code grants UI-logic the ability to read from redux;
export const useAppSelector: TypedUseSelectorHook&amp;lt;RootState&amp;gt; = useSelector;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above code snippet, we're creating one Redux slice and calling it &lt;code&gt;users&lt;/code&gt;. We're also creating two hooks call &lt;code&gt;useAppDispatch&lt;/code&gt; and &lt;code&gt;useAppSelector&lt;/code&gt; which will allow us to perform both read and write operations on our redux data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Storing all retrieved documents in &lt;strong&gt;&lt;em&gt;dump&lt;/em&gt;&lt;/strong&gt; slice.
&lt;/h2&gt;

&lt;p&gt;The &lt;em&gt;dump&lt;/em&gt; is where we keep all of the retrieved documents for one collection of data. For example, if you retrieved all Users within 10km, you would store them inside of a &lt;code&gt;.users&lt;/code&gt; slice. &lt;/p&gt;

&lt;p&gt;This &lt;code&gt;.users&lt;/code&gt; slice would be an object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Comment: The following code imports the Users type, helper functions from redux toolkit and react-redux
import {User} from '@/model/index';
import {createSlice, PayloadAction} from '@reduxjs/toolkit';
import {TypedUseSelectorHook, useSelector} from 'react-redux';

// Comment: Declares the type for UsersSlice which is an Object of key:value pairs with key being the `User.id` and value being the User
export type UsersSlice = {[id: string]: User;};

const initialState: InitialState = {};

export const users = createSlice({
  name: 'users',
  initialState,
  reducers: {
    add: (state, action: PayloadAction&amp;lt;User[]&amp;gt;) =&amp;gt; {
      const users = action.payload;

      users.forEach(user =&amp;gt; {
        state[user.email] = user;
      });
    },
    remove: (state, action: PayloadAction&amp;lt;string&amp;gt;) =&amp;gt; {
      const id = action.payload;

      delete state[id];
    },
  },
});

// The following export will be used the UI-logic via; `dispatch(UsersActions.add([...]);`
export const UsersActions = users.actions;

// The following default export will be used by the reducer/store.ts;
export default users.reducer;

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Using the new &lt;em&gt;users&lt;/em&gt; slice;
&lt;/h3&gt;

&lt;p&gt;When first loading an app, we sometimes load multiple Users based on the UX.&lt;/p&gt;

&lt;p&gt;Imagine an initial app load for a Social Media App. On app load, we might load the 10 most recent Posts and the Users to created them.&lt;/p&gt;

&lt;p&gt;We store all of these Users inside of the &lt;code&gt;users&lt;/code&gt; slice. We also store all of the Posts we've retrieved (i.e., the 10 most recent) inside of the &lt;code&gt;posts&lt;/code&gt; slice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benefits of storing your data inside of a &lt;em&gt;dump&lt;/em&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Easy access &lt;em&gt;Related Documents&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;Let's imagine we have the following &lt;code&gt;Post&lt;/code&gt; type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type Post = {
 id: string;
 createdDate: number;
 /**
 * The user prop is set using User.id value of the User that created the Post
 */
 user: string;
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We're looking at a Post in the UI and on this Post there is a section that has some User information such as their Name and Profile Photo.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa4lgoxp96wvfnjxuton4.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.amazonaws.com%2Fuploads%2Farticles%2Fa4lgoxp96wvfnjxuton4.png" alt="An image of a mobile app User who has their name, profile image, and their rating out of 5 stars. It is meant to display the intended UI - picture on the left and the name on the right overtop of the 5 star rating." width="286" height="88"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's how we would show this information:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
type Props = {
  post: Post;
};

const PostComponent = (props: Props) =&amp;gt; {
  const {post} = props;

  const users = useAppSelector(state =&amp;gt; state.users);

  const userForPost = useMemo(() =&amp;gt; users[post.user], [user, post.user]);

  return (
    &amp;lt;View style={styles.container}&amp;gt;
     &amp;lt;Image src={userForPost.image} style={styles.image} /&amp;gt;
     &amp;lt;View&amp;gt;
       &amp;lt;Text style={styles.name}&amp;gt;{userForPost.name}&amp;lt;/Text&amp;gt;
       &amp;lt;Ratings rating={5} /&amp;gt;
     &amp;lt;/View&amp;gt;
    &amp;lt;/View&amp;gt;
  );
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As needed, developers can use the &lt;code&gt;id&lt;/code&gt; of the selected User to retrieve the entirety of the User document.&lt;/p&gt;

&lt;p&gt;This type of redux state management makes is easy and extremely consistent across data types. &lt;/p&gt;

&lt;p&gt;We have dumps for all different type of data.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Matt&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>Apple Auth Errors - React Native</title>
      <dc:creator>Matt Ruiz</dc:creator>
      <pubDate>Fri, 24 Jan 2025 13:52:55 +0000</pubDate>
      <link>https://forem.com/matthewzruiz/apple-auth-errors-react-native-17h5</link>
      <guid>https://forem.com/matthewzruiz/apple-auth-errors-react-native-17h5</guid>
      <description>&lt;p&gt;Hola hola,&lt;/p&gt;

&lt;p&gt;If you’re React Native app is using &lt;a href="https://github.com/invertase/react-native-apple-authentication" rel="noopener noreferrer"&gt;react-native-apple-authentication&lt;/a&gt; and &lt;a href="https://www.sentry.com" rel="noopener noreferrer"&gt;Sentry&lt;/a&gt;, you may find very vague errors logs such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;com.apple.AuthenticationServices.AuthorizationError error 1000&lt;/li&gt;
&lt;li&gt;com.apple.AuthenticationServices.AuthorizationError error 1001&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After doing some digging into the codebase, I found &lt;a href="https://github.com/invertase/react-native-apple-authentication/blob/d45d086c3ac0306f3594164501575e06578f8733/lib/AppleAuthModule.js#L40" rel="noopener noreferrer"&gt;these Error codes&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It turns out that 1000 means an unknown issue has occurred (which could be an issue so check it out).&lt;/p&gt;

&lt;p&gt;1001 is a low-stress issue of the user cancelling their purchase attempt. Of course we don’t want this, but it’s more of a UX/marketing/copy/CTA/etc issue than a bug.&lt;/p&gt;

&lt;p&gt;Hope this helps!&lt;/p&gt;

&lt;p&gt;— Matt&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>coding</category>
    </item>
    <item>
      <title>React and React Native coding conventions for a successful cross-platform team in 2025</title>
      <dc:creator>Matt Ruiz</dc:creator>
      <pubDate>Fri, 24 Jan 2025 13:50:47 +0000</pubDate>
      <link>https://forem.com/matthewzruiz/react-and-react-native-coding-conventions-for-a-successful-cross-platform-team-in-2025-48oa</link>
      <guid>https://forem.com/matthewzruiz/react-and-react-native-coding-conventions-for-a-successful-cross-platform-team-in-2025-48oa</guid>
      <description>&lt;p&gt;Hola hola,&lt;/p&gt;

&lt;p&gt;I’ve been building cross-platform apps since 2019. What started as just React Native has expanded into the React/NextJS realm.&lt;/p&gt;

&lt;p&gt;React and React Native make for a great team — especially in the modern AI-coder era.&lt;/p&gt;

&lt;p&gt;Underneath-the-hood, I’m sure there are million things that are different between React and React Native. But, from a developer POV, or more specifically from a codebase lead POV, there are many similarities between the two “frameworks”.&lt;/p&gt;

&lt;p&gt;Here are some priorities that our team focuses on when delivering React and React Native components:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Code structure consistency across React and React Native is number one priority&lt;/li&gt;
&lt;li&gt;The more files the better — just name them consistently&lt;/li&gt;
&lt;li&gt;Remove unnecessary &lt;code&gt;View&lt;/code&gt; and &lt;code&gt;div&lt;/code&gt; components&lt;/li&gt;
&lt;li&gt;Minimize prop drilling

&lt;ul&gt;
&lt;li&gt;We have a purposefully large &lt;code&gt;redux&lt;/code&gt; setup&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Inline onclick logic should be extracted into a function if more than one line&lt;/li&gt;
&lt;li&gt;More &lt;code&gt;useMemo&lt;/code&gt; and &lt;code&gt;useCallback&lt;/code&gt;
7.Static consts declared locally outside of the component&lt;/li&gt;
&lt;li&gt;Do not use an array index as a &lt;code&gt;key&lt;/code&gt; value on list items.&lt;/li&gt;
&lt;li&gt;Import names should always match the export name&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There are many others that we chat about but don’t have in Notion.&lt;/p&gt;

&lt;p&gt;What conventions do you hold yourself or your team accountable for?&lt;/p&gt;

&lt;p&gt;— Matt&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>react</category>
    </item>
    <item>
      <title>[!] CocoaPods could not find compatible versions for pod</title>
      <dc:creator>Matt Ruiz</dc:creator>
      <pubDate>Tue, 21 Jan 2025 14:48:47 +0000</pubDate>
      <link>https://forem.com/matthewzruiz/-cocoapods-could-not-find-compatible-versions-for-pod-27g</link>
      <guid>https://forem.com/matthewzruiz/-cocoapods-could-not-find-compatible-versions-for-pod-27g</guid>
      <description>&lt;p&gt;Hola hola,&lt;/p&gt;

&lt;p&gt;When working with a bare CLI React Native app, I often run into issues during the &lt;code&gt;pod install&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;Currently, I’m getting this CocoaPods could not find compatible versions for pod with: when working on the React Native version of &lt;a href="https://www.amplinks.app" rel="noopener noreferrer"&gt;Amplinks&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here is the error that I’m getting. Yours could be for non-firebase packages — I’ve had it happen with Sentry/Stripe/etc.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[!] CocoaPods could not find compatible versions for pod "Firebase/Analytics":
  In snapshot (Podfile.lock):
    Firebase/Analytics (= 11.7.0)

  In Podfile:
    RNFBAnalytics (from `../node_modules/@react-native-firebase/analytics`) was resolved to 21.6.1, which depends on
      Firebase/Analytics (= 11.5.0)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CLI then gives us the following message highlighting potential issues and their fix.&lt;/p&gt;

&lt;p&gt;You have either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;out-of-date source repos which you can update with &lt;code&gt;pod repo update&lt;/code&gt; or with &lt;code&gt;pod install —-repo-update&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;changed the constraints of dependency &lt;code&gt;Firebase/Analytics&lt;/code&gt; inside your development pod &lt;code&gt;RNFBAnalytics&lt;/code&gt;. You should run &lt;code&gt;pod update Firebase/Analytics&lt;/code&gt; to apply changes you’ve made.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  How to fix CocoaPods could not find compatible versions for pod?
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Option 1: Run the command as it says
&lt;/h2&gt;

&lt;p&gt;For me, the pod install — repo-update command somtimes work’s by itself. Note: you will have to run cd ios before running any pod ... command since it will look for a local ./Podfile . Here is an article from Gant at Infinite Red labeled &lt;em&gt;&lt;a href="https://shift.infinite.red/beginner-s-guide-to-using-cocoapods-with-react-native-46cb4d372995" rel="noopener noreferrer"&gt;Beginner’s Guide to Using CocoaPods with React Native&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 2: Remove some extra files/cache/etc
&lt;/h2&gt;

&lt;p&gt;Other times, I have to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Delete derived data for the app I’m currently working on: &lt;a href="https://stackoverflow.com/questions/38016143/how-can-i-delete-derived-data-in-xcode-8" rel="noopener noreferrer"&gt;StackOverflow&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;run pod deintegrate to remove the bad Pods: &lt;a href="https://stackoverflow.com/questions/16427421/how-to-remove-cocoapods-from-a-project" rel="noopener noreferrer"&gt;StackOverflow&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Remove node_modules in root of React Native project&lt;/li&gt;
&lt;li&gt;Remove ios/Pods , ios/build , and ios/Podfile.lock (maybe even ./yarn.lock or package-lock.json&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;The React Native ←→ Xcode environment can be really taxing and can take up hours of your day. I call them config days and they can really dampen momentum.&lt;/p&gt;

&lt;p&gt;I would recommend that you take extra time:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;To read, and then re-read, the error messages every time. Don’t try to copy and past into Google/ChatGPT right away — you’ve got to know what to look for.&lt;/li&gt;
&lt;li&gt;To read through documentation for these base level cli commands and the related environments - try and make sense of those StackOverflow links I shared. They might not make sense now but they will eventually.&lt;/li&gt;
&lt;li&gt;Learn about Pods and how they they mirror node_modules behavior but on a native iOS/Xcode/Swift/Objective C level — basic understanding will take you far.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Good luck — hope this was helpful!&lt;/p&gt;

&lt;p&gt;Please give a follow on &lt;a href="https://x.com/MatthewZRuiz" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; if you’re in the space!&lt;/p&gt;

&lt;p&gt;— Matt&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>ios</category>
      <category>xcode</category>
      <category>cocoapods</category>
    </item>
    <item>
      <title>Disabling ESLint in Firebase Cloud Functions using Node</title>
      <dc:creator>Matt Ruiz</dc:creator>
      <pubDate>Sun, 20 Aug 2023 15:37:20 +0000</pubDate>
      <link>https://forem.com/matthewzruiz/disabling-eslint-in-firebase-cloud-functions-using-node-5go7</link>
      <guid>https://forem.com/matthewzruiz/disabling-eslint-in-firebase-cloud-functions-using-node-5go7</guid>
      <description>&lt;p&gt;Hola hola,&lt;/p&gt;

&lt;p&gt;I was working inside of a Firebase Cloud Functions project and my builds were failing because ESLint didn't like me using &lt;code&gt;any&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Ideally, we're not using &lt;code&gt;any&lt;/code&gt; in our code but it's not a real priority for an early stage product.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem
&lt;/h2&gt;

&lt;p&gt;If you're receiving an ESLint error then you can copy and paste the warning(s)/error(s) listed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/Users/matthewruiz/github/golfassistai/golf-assist-ai-cloud-functions/functions/src/api/firestore/DocumentRetriever.ts
   63:13  warning  Unexpected any. Specify a different type  @typescript-eslint/no-explicit-any
   92:13  warning  Unexpected any. Specify a different type  @typescript-eslint/no-explicit-any
  250:10  warning  Unexpected any. Specify a different type  @typescript-eslint/no-explicit-any
  278:18  warning  Unexpected any. Specify a different type  @typescript-eslint/no-explicit-any
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Add to &lt;code&gt;rules&lt;/code&gt; object of &lt;code&gt;.eslintrc.js&lt;/code&gt; along with a &lt;code&gt;off&lt;/code&gt; value.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"@typescript-eslint/no-explicit-any": "off"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hope that helps.&lt;/p&gt;

&lt;p&gt;-Matt&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>tutorial</category>
      <category>programming</category>
      <category>mobile</category>
    </item>
    <item>
      <title>command not found: gsutil</title>
      <dc:creator>Matt Ruiz</dc:creator>
      <pubDate>Sat, 19 Aug 2023 19:50:50 +0000</pubDate>
      <link>https://forem.com/matthewzruiz/command-not-found-gsutil-gm6</link>
      <guid>https://forem.com/matthewzruiz/command-not-found-gsutil-gm6</guid>
      <description>&lt;p&gt;Hola hola,&lt;/p&gt;

&lt;p&gt;I ran into this error when trying to retrieve the most recent backup we have for one of our apps that use Firebase Cloud Functions. &lt;/p&gt;

&lt;p&gt;Here's the command I was trying to run from our Cloud Function Node project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gsutil ls -l gs://{APP_FIRESTORE}-backup/backups/ | sort -k2 | tail -n1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Looking to Google
&lt;/h2&gt;

&lt;p&gt;I typed &lt;code&gt;command not found: gsutil cloud functions nodejs&lt;/code&gt; into Google and found &lt;a href="https://cloud.google.com/storage/docs/gsutil_install#sdk-install" rel="noopener noreferrer"&gt;this Google Cloud documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;After downloading and extracting the &lt;code&gt;.tar.gz&lt;/code&gt; file from step 2, I ran the following two commands from the folder that contains the extracted &lt;code&gt;.tar.gz&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;./google-cloud-sdk/install.sh&lt;/code&gt; in the folder where the extracted &lt;code&gt;.tar.gz&lt;/code&gt; existed (which was Downloads for me).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;./google-cloud-sdk/bin/gcloud init&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;This part can happen fast and you may chose the wrong project like I did. &lt;em&gt;&lt;a href="https://dev.to/matthewzruiz/changing-your-gcloud-cli-project-6cb-temp-slug-5170063"&gt;Here's a quick post&lt;/a&gt; about switching projects.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Note: You may need to restart your terminal after installing &lt;code&gt;gcloud&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;That worked for my situation and I hope it helps others getting the &lt;code&gt;command not found: gsutil&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Matt&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>firebase</category>
      <category>cloudfunctions</category>
      <category>tutorial</category>
      <category>googlecloud</category>
    </item>
    <item>
      <title>Changing your gcloud CLI project</title>
      <dc:creator>Matt Ruiz</dc:creator>
      <pubDate>Sat, 19 Aug 2023 19:50:12 +0000</pubDate>
      <link>https://forem.com/matthewzruiz/changing-your-gcloud-cli-project-2fbn</link>
      <guid>https://forem.com/matthewzruiz/changing-your-gcloud-cli-project-2fbn</guid>
      <description>&lt;p&gt;Hola hola,&lt;/p&gt;

&lt;p&gt;I was running into issues because I did not have &lt;code&gt;gcloud&lt;/code&gt; or &lt;code&gt;gsutil&lt;/code&gt; installed on our macs. Therefore, I could not run any related commands in our Firebase Cloud Functions project.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/matthewzruiz/command-not-found-gsutil-5fj2-temp-slug-8968256"&gt;After installing them&lt;/a&gt; I needed to switch the project.&lt;/p&gt;

&lt;p&gt;First, I needed to retrieve the &lt;code&gt;PROJECT_ID&lt;/code&gt; for the project that I wanted to switch to. I did that by running this command:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;gcloud projects list&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once I got the &lt;code&gt;PROJECT_ID&lt;/code&gt; value, I then ran this command:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;gcloud config set project {PROJECT_ID}&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If successful, you should see a message like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Updated property [core/project].&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;-Matt&lt;/p&gt;

</description>
      <category>firebase</category>
      <category>node</category>
      <category>cloudfunctions</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Changing your Firebase CLI project</title>
      <dc:creator>Matt Ruiz</dc:creator>
      <pubDate>Sat, 19 Aug 2023 19:47:23 +0000</pubDate>
      <link>https://forem.com/matthewzruiz/changing-your-firebase-cli-project-3156</link>
      <guid>https://forem.com/matthewzruiz/changing-your-firebase-cli-project-3156</guid>
      <description>&lt;p&gt;Hola hola,&lt;/p&gt;

&lt;h2&gt;
  
  
  TLDR
&lt;/h2&gt;

&lt;p&gt;Retrieve &lt;code&gt;PROJECT ID&lt;/code&gt; by running:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;firebase projects:list&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once you have that id, run the following command:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;firebase use {PROJECT ID}&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Problem
&lt;/h2&gt;

&lt;p&gt;As you work with projects, you'll notice that you will often have a &lt;code&gt;dev&lt;/code&gt; and a &lt;code&gt;prod&lt;/code&gt; database instance.&lt;/p&gt;

&lt;p&gt;You don't want to test new features on live user data so you create a &lt;em&gt;duplicate&lt;/em&gt; of your &lt;code&gt;prod&lt;/code&gt; database.&lt;/p&gt;

&lt;p&gt;Now that you have two projects, you're gonna' need to switch between them as you test and deploy your Cloud Cunctions (CF).&lt;/p&gt;

&lt;p&gt;Imagine the following scenario:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;New CF feature A is being developed, tested, and deployed to the &lt;code&gt;dev&lt;/code&gt; database/CF instances.&lt;/li&gt;
&lt;li&gt;Feature A is now ready to release to our Users.&lt;/li&gt;
&lt;li&gt;We need to now deploy the feature A updates to our &lt;code&gt;prod&lt;/code&gt; instances. &lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;&lt;em&gt;At this point, I will assume that you have installed &lt;a href="https://firebaseopensource.com/projects/firebase/firebase-tools/" rel="noopener noreferrer"&gt;Firebase CLI&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;These projects have a unique &lt;code&gt;PROJECT ID&lt;/code&gt; that we use in our commands to switch projects.&lt;/p&gt;

&lt;p&gt;First, we want to make get that &lt;code&gt;PROJECT ID&lt;/code&gt;. Do that by running:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;firebase projects:list&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once you have that id, run the following command:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;firebase use {PROJECT ID}&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If successful, you should see a message like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Now using project {PROJECT_ID}&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I hope that's helpful to some.&lt;/p&gt;

&lt;p&gt;-Matt&lt;/p&gt;

</description>
      <category>firebase</category>
      <category>cli</category>
      <category>cloudfunctions</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
