<?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: noire.munich</title>
    <description>The latest articles on DEV Community by noire.munich (@noiremunich).</description>
    <link>https://dev.to/noiremunich</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%2F356305%2F6a3314ab-fa87-4870-aaaf-be127ac069b3.jpeg</url>
      <title>DEV Community: noire.munich</title>
      <link>https://dev.to/noiremunich</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/noiremunich"/>
    <language>en</language>
    <item>
      <title>How-to organize your cells for flexibility</title>
      <dc:creator>noire.munich</dc:creator>
      <pubDate>Thu, 22 Jun 2023 09:36:18 +0000</pubDate>
      <link>https://dev.to/noiremunich/how-to-organize-your-cells-for-flexibility-amj</link>
      <guid>https://dev.to/noiremunich/how-to-organize-your-cells-for-flexibility-amj</guid>
      <description>&lt;p&gt;One of the early defining feature of #redwoodJS was Cells: React components designed to help you organise a Graphql's request lifecycle with frontend components for each of its states. RWJS is rather flexible, which leaves a lot of freedom for creative design and spectacular mistakes (I've had my share). Organising your code can become a challenge, so this post is meant to help you.&lt;/p&gt;

&lt;p&gt;Over my past 3 years full time on RWJS I have been repeatedly confronted to the feeling that Cells were useful but were difficult to scale with. At the end of the day, I feel like the only real immutable part of a Cell, thus defining its nature, being the QUERY, there was no reason to feel constrained by a Cell's other parts, namely:&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;QUERY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;beforeQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;afterQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isEmpty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Loading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Failure&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Success&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;displayName&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;Most of the time the only thing I had to change was the Success component, which created the itch, but was not big enough to bring forward a clearer solution. I used &lt;code&gt;as&lt;/code&gt; and &lt;code&gt;variant&lt;/code&gt; props to change the &lt;code&gt;Success&lt;/code&gt; component, but felt unsatisfied.&lt;/p&gt;

&lt;h2&gt;
  
  
  Better variants with createCell
&lt;/h2&gt;

&lt;p&gt;The following proposition is far from being perfect, but it's the one version that allowed me to feel confident in a design based on RedwoodJS, with minor tweaks to take cells to another level. I admit I might have been the only one in need of this and that it may be for lack of better engineering skills, but hey, it's a journey, we're all learning.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/redwoodjs/redwood/blob/main/packages/web/src/components/createCell.tsx"&gt;createCell&lt;/a&gt; powers the transformation of your &lt;code&gt;XCell.tsx&lt;/code&gt;. The framework exposes it, so you can reuse it and get creative.&lt;/p&gt;

&lt;p&gt;To address my code organisation problem with a 'variant' solution, I'm fulling relying on &lt;code&gt;createCell&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step by step
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Create variants
&lt;/h3&gt;

&lt;p&gt;In the same directory as the cell you are working on, create a &lt;code&gt;variants&lt;/code&gt; directory and add the following as a base for &lt;code&gt;index.ts&lt;/code&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="c1"&gt;// ./variants/index.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;List&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;./List&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Select&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;./Select&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Variants&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;list&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;select&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;variants&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Variants&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;list&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;List&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;select&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Select&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 declares only two variants, let's keep things simple and light. The exported type &lt;code&gt;Variants&lt;/code&gt; helps with the Cell's &lt;code&gt;variant&lt;/code&gt; prop typing and &lt;code&gt;variants&lt;/code&gt; is the map we will use in the default export.&lt;/p&gt;

&lt;h4&gt;
  
  
  Add the variant code
&lt;/h4&gt;

&lt;p&gt;The imported modules, &lt;code&gt;List&lt;/code&gt; and &lt;code&gt;Select&lt;/code&gt;, should have at least one Cell part different from what we have defined in our original cell. In my case, I neutralized the &lt;code&gt;Loading&lt;/code&gt;, &lt;code&gt;Failure&lt;/code&gt; and &lt;code&gt;Empty&lt;/code&gt; components in the &lt;code&gt;Select&lt;/code&gt; variant and added a different &lt;code&gt;afterQuery&lt;/code&gt; in the &lt;code&gt;List&lt;/code&gt; variant.&lt;br&gt;
I didn't do it, but a better way to handle the specificity and complexity here would be to rely on &lt;code&gt;yarn rw g cell X&lt;/code&gt; to create the boilerplate code for each variant. You'd be set with tests &amp;amp; stories upfront.&lt;/p&gt;
&lt;h3&gt;
  
  
  Neutralize the import transformer
&lt;/h3&gt;

&lt;p&gt;Back in the cell's directory, rename your &lt;code&gt;XCell.tsx&lt;/code&gt; to drop the &lt;code&gt;Cell&lt;/code&gt; suffix, otherwise you'll have import conflicts. This remains your entry point and should have all your default Cell parts.&lt;/p&gt;
&lt;h3&gt;
  
  
  Activate variants with default support
&lt;/h3&gt;

&lt;p&gt;Your default export in this file should look like the following:&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;type&lt;/span&gt; &lt;span class="nx"&gt;Params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;random&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="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;memo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Variants&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;CellSuccessProps&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;MyQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Exact&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Params&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;createCell&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;
    &lt;span class="nx"&gt;CellSuccessProps&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;MyQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;MyQueryVariables&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;MyQueryVariables&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;QUERY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;afterQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;Loading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;Failure&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;Success&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;...(&lt;/span&gt;&lt;span class="nx"&gt;variant&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;variants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{}),&lt;/span&gt;
  &lt;span class="p"&gt;})(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's about it.&lt;/p&gt;

&lt;p&gt;Now the reason I prefer this over the Cell boilerplate is that the generated code helped me develop fast but on longer term I found myself more confused when facing other cases for my cells, as I didn't necessarily anticipate the need for a cell to be way more flexible. For a long time I thought only working on a different &lt;code&gt;Success&lt;/code&gt; component was enough - and in some way it is, but enabling the entire module to be altered in variants is helping me &lt;strong&gt;pushing design and attention to details a lot further&lt;/strong&gt;.&lt;br&gt;
Another solution would be to generate the same cell with a different name and the same query over and over, but this rings to me as a trap to bloat the codebase and face more and more the dreaded problem of naming things.&lt;/p&gt;

&lt;p&gt;So I'll stick to this &lt;code&gt;variants&lt;/code&gt; implementation.&lt;/p&gt;

</description>
      <category>redwoodjs</category>
      <category>rwjs</category>
      <category>javascript</category>
      <category>graphql</category>
    </item>
    <item>
      <title>Lightsail to deploy your first RedwoodJS App</title>
      <dc:creator>noire.munich</dc:creator>
      <pubDate>Mon, 31 Oct 2022 08:28:22 +0000</pubDate>
      <link>https://dev.to/noiremunich/lightsail-to-deploy-your-first-redwoodjs-app-4j1p</link>
      <guid>https://dev.to/noiremunich/lightsail-to-deploy-your-first-redwoodjs-app-4j1p</guid>
      <description>&lt;p&gt;So you've been trying out RedwoodJS and are looking for some easy AWS hosting solution.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/fr/lightsail/"&gt;AWS Lightsail&lt;/a&gt; and &lt;a href="https://redwoodjs.com/docs/deploy/baremetal#deployment-lifecycle"&gt;RedwoodJS Baremetal&lt;/a&gt; have you covered.&lt;/p&gt;

&lt;p&gt;For that we will assume you have your repo with your RedwoodJS app ready to be deployed.&lt;br&gt;
This short tutorial should set you on the right path, however I have not yet covered the &lt;code&gt;api&lt;/code&gt; side. Come back later, as I intend to explore this as well.&lt;/p&gt;

&lt;p&gt;At the end of this tutorial you will get an instance of Lightsail up &amp;amp; running with the web side of your application's current version served to a public static IP and bound to your domain.&lt;/p&gt;

&lt;p&gt;Let's get this thing started:&lt;/p&gt;
&lt;h3&gt;
  
  
  Sign in AWS
&lt;/h3&gt;
&lt;h3&gt;
  
  
  Go to Lightsail and create your first instance
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Pick the Linux/Unix platform&lt;/li&gt;
&lt;li&gt;Select the Apps + OS blueprint category&lt;/li&gt;
&lt;li&gt;Tick the Node.js blueprint&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Make sure your ssh key is configured properly as we are not going to cover that here. You can change this later though.&lt;/p&gt;

&lt;p&gt;Select your &lt;em&gt;instance plan&lt;/em&gt;, but be aware that you will need at least &lt;em&gt;1GB of RAM&lt;/em&gt; otherwise the deploy will fail to heap usage.&lt;/p&gt;

&lt;p&gt;Name your instance and tag it if that's how you work.&lt;br&gt;
Your instance is being created.&lt;/p&gt;
&lt;h3&gt;
  
  
  Create and assign a static IP
&lt;/h3&gt;

&lt;p&gt;Go to the Lightsail &amp;gt; Networking tab and create a Static IP. Into the IP page, attach your newly created instance.&lt;br&gt;
Copy the new IP, you will need it in a minute.&lt;br&gt;
&lt;strong&gt;Disable IPv6&lt;/strong&gt; - this is not ideal but it will help you install a certificate later on with minimal effort.&lt;/p&gt;
&lt;h3&gt;
  
  
  Create a domain
&lt;/h3&gt;

&lt;p&gt;If you don't have one, create a domain on &lt;a href="https://console.aws.amazon.com/route53/home"&gt;AWS Route 53&lt;/a&gt;.&lt;br&gt;
If you already have one, you will need to update the DNS of your domain provider to add the records.&lt;br&gt;
Remove any IPv6 record from your domain's configuration that may be lingering - it would have to be an AAAA record.&lt;/p&gt;
&lt;h3&gt;
  
  
  Configure the DNS
&lt;/h3&gt;

&lt;p&gt;Go to Route 53 &amp;gt; Hosted zones and select your domain.&lt;br&gt;
Create a new Record of type 'A', leave its name empty and change its value to add the IP address you should still have in your clipboard - the static IP we created earlier in Lightsail.&lt;br&gt;
Set the TTL at 60 if it is not already and save.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This solution doesn't feel ideal but getting it to work with Lightsail's DNS wasn't getting me anywhere. It is valid though and documented by AWS &lt;a href="https://lightsail.aws.amazon.com/ls/docs/en_us/articles/amazon-lightsail-using-route-53-to-point-a-domain-to-an-instance"&gt;here&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;



&lt;p&gt;&lt;em&gt;What happens next should happen in your instance, so please, SSH there now&lt;/em&gt;&lt;/p&gt;


&lt;h3&gt;
  
  
  Create a ssh key and add it to Github
&lt;/h3&gt;

&lt;p&gt;In terminal use &lt;code&gt;ssh-keygen&lt;/code&gt; - passphrase is optional but recommended.&lt;br&gt;
In &lt;code&gt;~/.ssh&lt;/code&gt; use &lt;code&gt;cat&lt;/code&gt; on the &lt;code&gt;[your_keyname_file].pub&lt;/code&gt; (hence on the &lt;code&gt;.pub&lt;/code&gt;) and add it to your Github settings. This will help your instance clone and update your app.&lt;/p&gt;
&lt;h3&gt;
  
  
  Manually add pm2
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;pm2&lt;/code&gt; is necessary to deploy RedwoodJS baremetal and it is not included in the instance. That's going to be easy enough to fix, still in your &lt;code&gt;ssh&lt;/code&gt; terminal:&lt;br&gt;
&lt;code&gt;sudo npm install pm2 -g&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Create your certificate for HTTPS
&lt;/h3&gt;

&lt;p&gt;You will use Bitnami's installed bncert tool, which will set you up with a Let's encrypt certificate in no time. The tool is preinstalled, so just run the following command. Make sure your domains are comma separated. We do not cover &lt;code&gt;www.[domain]&lt;/code&gt; in this tutorial so you are on your own for that.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;$ sudo /opt/bitnami/bncert-tool --domains [domain1,domain2,...]&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And follow on the instructions. It will install a certificate thanks to &lt;a href="https://letsencrypt.org/fr/"&gt;https://letsencrypt.org/fr/&lt;/a&gt;, so it's free.&lt;/p&gt;
&lt;h3&gt;
  
  
  Update Apache hosts
&lt;/h3&gt;

&lt;p&gt;Now we need to fix our Apache hosts to make sure they will serve your app.&lt;br&gt;
In terminal, open the file and update the path of the app to add the suffix &lt;code&gt;/current/web/dist&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;$ vim /opt/bitnami/apache2/conf/bitnami/bitnami.conf 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's good. For port &lt;code&gt;:80&lt;/code&gt;, HTTP protocol.&lt;br&gt;
You should report the same modification in here to support HTTPS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ sudo vim /opt/bitnami/apache2/conf/bitnami/bitnami-ssl.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save and restart apache:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ sudo /opt/bitnami/ctlscript.sh restart apache
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;em&gt;Please leave your instance now and go back to your local terminal, astutely positioned to your app's root directory. We should stay local as of now.&lt;/em&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Setup the baremetal deploy for your RedwoodJS app
&lt;/h3&gt;

&lt;p&gt;Enter the command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn rw setup deploy baremetal
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It should create you the following files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;deploy.toml&lt;/li&gt;
&lt;li&gt;ecosystem.config.js&lt;/li&gt;
&lt;li&gt;web/src/maintenance.html&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Update &lt;code&gt;deploy.toml&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Mine looks roughly like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[[production.servers]]
host = "[static ip configured in Lightsail]"
username = "bitnami"
privateKeyPath = "[location to the default Lightsail SSH key file you would have configured while creating your Lightstail instance]"
agentForward = true
sides = ["api","web"]
packageManagerCommand = "yarn"
monitorCommand = "pm2"
path = "/home/bitnami/htdocs"
processNames = ["serve"]
repo = "[path to your git repo]"
branch = "main"
keepReleases = 5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That should do the trick. &lt;code&gt;host&lt;/code&gt;, &lt;code&gt;privateKeyPath&lt;/code&gt;, &lt;code&gt;path&lt;/code&gt; and &lt;code&gt;repo&lt;/code&gt; should be updated accordingly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Change your redwood.toml to update the web port
&lt;/h3&gt;

&lt;p&gt;Update &lt;code&gt;redwood.toml&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;[web]
  title = "Redwood App"
-  port = 8910
+  port = 80
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Should be enough.&lt;/p&gt;

&lt;p&gt;Now commit and push!&lt;/p&gt;

&lt;h3&gt;
  
  
  Run your first dpeloy
&lt;/h3&gt;

&lt;p&gt;Back to terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ yarn rw deploy baremetal production --first-run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's about it. Check your static IP, you should be good to go. If you just created your domain you're going to have to wait for a couple of days before you can check - AWS should notify you of its availability though and it can be way quicker.&lt;/p&gt;

</description>
      <category>redwoodjs</category>
      <category>lightsail</category>
      <category>hosting</category>
      <category>jamstack</category>
    </item>
    <item>
      <title>Flex your cells</title>
      <dc:creator>noire.munich</dc:creator>
      <pubDate>Thu, 17 Feb 2022 10:23:02 +0000</pubDate>
      <link>https://dev.to/noiremunich/flex-your-cells-23ph</link>
      <guid>https://dev.to/noiremunich/flex-your-cells-23ph</guid>
      <description>&lt;h2&gt;
  
  
  Some context
&lt;/h2&gt;

&lt;p&gt;We've been building SportOffice on &lt;a href="https://redwoodjs.com"&gt;RedwoodJS&lt;/a&gt; for almost a year now and we've made a point to use the framework as it comes - with little to no exotic stuff sprayed over.&lt;/p&gt;

&lt;p&gt;This helped us go live in December and today we are hitting numbers ( € ), with a CRM built entirely with RedwoodJS ( and, yeah, Stripe, AWS, we're in an ecosystem anyway ). RW's not in v1 yet, but there's no looking back for us.&lt;/p&gt;

&lt;p&gt;Now with all its power in its standard setup, couple of things could need some exposition online to help people better understand what's possible with it.&lt;/p&gt;

&lt;p&gt;Today, I will precisely talk about Cells.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reminding you of Cells
&lt;/h2&gt;

&lt;p&gt;In a standard Redwood app, you'd have a &lt;code&gt;web&lt;/code&gt; and an &lt;code&gt;api&lt;/code&gt; side, both self explanatory. The &lt;code&gt;api&lt;/code&gt; would be powered by Redwood itself - but it could be anything else, really, from an Express served api to a stitched graphql schema and beyond ( sky's the limit to Redwoods ).&lt;/p&gt;

&lt;p&gt;Cells are components that manage the whole fetch-some-data-display-in-front cycle, errors and empty payloads included. A typical Cell will at the very least be a module with no &lt;code&gt;default&lt;/code&gt; export, exporting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a &lt;code&gt;const QUERY = gql[...]&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;a &lt;code&gt;const Success: React.FC&amp;lt;SuccessProps&amp;gt; = [...]&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sample below.&lt;/p&gt;

&lt;p&gt;It's clean and easy, I've been using them for so long I don't even know if it ever felt difficult. Certainly it felt great to leave &lt;code&gt;fetch&lt;/code&gt; calls in React components behind.&lt;/p&gt;

&lt;p&gt;So, cells in themselves are very convenient, but sometimes you need a bit more flexibility. What if you needed to call entirely different queries but the rest of the component should stay the same? It's doable with a standard cell, but not in a very clean way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Some code
&lt;/h2&gt;

&lt;p&gt;What I'm about to show you is not neat and shiny, it's unpolished code extracted to demonstrate the point - please forgive me if your eyes do bleed. This is the price of knowledge ( for some of us ).&lt;/p&gt;

&lt;p&gt;We needed a &lt;code&gt;Select&lt;/code&gt; for all our &lt;code&gt;Users&lt;/code&gt; where &lt;code&gt;roles.include('student')&lt;/code&gt;. This has been enough for about ten months:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;userToOption&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;src/components/Model/User&lt;/span&gt;&lt;span class="dl"&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;Input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Select&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;src/ui&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;  

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;QUERY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;gql&lt;/span&gt;&lt;span class="s2"&gt;`  
 query SELECT_STUDENT($where: WhereUserInput) {  
     options: students(where: $where) {  
         id  
         firstname
         lastname  
     }
}`&lt;/span&gt;  

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Loading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;  
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Input&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;student&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;disabled&lt;/span&gt; &lt;span class="na"&gt;pointer&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;model:student.label&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;gt;&lt;/span&gt;  
&lt;span class="p"&gt;)&lt;/span&gt;  

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;Select&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;  
  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;students&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
  &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
  &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;selectProps&lt;/span&gt;  
&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;  
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Select&lt;/span&gt;  
    &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;selectProps&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;  
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;  
    &lt;span class="na"&gt;pointer&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;model:student.label&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;  
    &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;student&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;userToOption&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;student&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;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It uses a &lt;code&gt;students&lt;/code&gt; service with a &lt;code&gt;where&lt;/code&gt; parameter, it's safe for you to assume that this should fit straight into a &lt;strong&gt;prisma&lt;/strong&gt; query.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Problem now is that we need the same &lt;code&gt;Select&lt;/code&gt;, targeting the same &lt;code&gt;role&lt;/code&gt;, but in different contexts that will actually require different db queries.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One way to do that could be to pass an argument to our &lt;code&gt;graphql query&lt;/code&gt; and then, on the &lt;code&gt;api&lt;/code&gt; side, &lt;code&gt;switch&lt;/code&gt; over it to trigger different method calls.&lt;br&gt;
Though it's a valid way to handle this in some cases, I wasn't too keen this time on doing so. I'd prefer to keep my methods and endpoints explicit and focused, which I found to be more scalable.&lt;/p&gt;

&lt;p&gt;To do so I created 3 endpoints, each with their own api services &amp;amp; separate methods, to fetch my students in their different context. And to make sure this would be used properly in front, I relied on &lt;code&gt;createCell&lt;/code&gt; ( formerly &lt;code&gt;withCell&lt;/code&gt;), to select the query I'd need to call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createCell&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;@redwoodjs/web&lt;/span&gt;&lt;span class="dl"&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;userToOption&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;src/components/Model/User&lt;/span&gt;&lt;span class="dl"&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;Input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Select&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;src/ui&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;  

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;CellProps&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
  &lt;span class="nl"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;  
  &lt;span class="nx"&gt;courseId&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;  

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;QUERY_ALL_STUDENTS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;gql&lt;/span&gt;&lt;span class="s2"&gt;`  
 query QUERY_ALL_STUDENTS($where: WhereUserInput) {  
   options: students(where: $where) {  
     id  
     firstname
     lastname  
   }
}`&lt;/span&gt;  

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;QUERY_SESSION_STUDENTS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;gql&lt;/span&gt;&lt;span class="s2"&gt;`  
 query QUERY_SESSION_STUDENTS($id: Int) {  
   options: getSessionStudents(id: $id) {  
     id  
     firstname
     lastname  
   }
}`&lt;/span&gt;  

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;QUERY_COURSE_STUDENTS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;gql&lt;/span&gt;&lt;span class="s2"&gt;`  
 query QUERY_COURSE_STUDENTS($id: Int) {  
   options: getCourseStudents(id: $id) {
     id  
     firstname
     lastname  
   }
}`&lt;/span&gt;  

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Loading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;  
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Input&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;student&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;disabled&lt;/span&gt; &lt;span class="na"&gt;pointer&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;model:student.label&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;gt;&lt;/span&gt;  
&lt;span class="p"&gt;)&lt;/span&gt;  

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Success&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;selectProps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&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;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;Select&lt;/span&gt;  
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;selectProps&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;  
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;  
      &lt;span class="na"&gt;pointer&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;model:student.label&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;  
      &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;student&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;userToOption&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;student&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;)&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&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="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;courseId&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;CellProps&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;query&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;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;useMemo&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;switch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nb"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sessionId&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;courseId&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;sessionId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;QUERY_SESSION_STUDENTS&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;  
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nb"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;sessionId&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;courseId&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;courseId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;QUERY_COURSE_STUDENTS&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;  
      &lt;span class="nl"&gt;default&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;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;QUERY_ALL_STUDENTS&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;  
    &lt;span class="p"&gt;}&lt;/span&gt;  
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;courseId&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;createCell&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;  
    &lt;span class="na"&gt;QUERY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
    &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;StudentsSelect&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
    &lt;span class="nx"&gt;Loading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
    &lt;span class="nx"&gt;Success&lt;/span&gt;&lt;span class="p"&gt;,&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;I think this is the cleanest way I've found so far to deal with this.&lt;/p&gt;

&lt;p&gt;It let's me keep a very clean API - which I'm going to really need further on as this is an important part of our business, and it lets me avoid creating dozens of the same component with only one prop to differentiate them.&lt;/p&gt;

&lt;p&gt;So at the end of the day, I feel like it's clean enough on the &lt;code&gt;web&lt;/code&gt; and &lt;code&gt;api&lt;/code&gt; sides of my lawn.&lt;/p&gt;

&lt;p&gt;Cheers,&lt;/p&gt;




&lt;p&gt;Notes&lt;/p&gt;




&lt;ul&gt;
&lt;li&gt;Notice how each query has their own name? Whichever way you want to tackle such issue, always keep in mind graphql client will require you to use query names as if they were ids. RedwoodJS will let some warning show if you don't comply.&lt;/li&gt;
&lt;li&gt;Cells are documented &lt;a href="https://learn-redwood.netlify.app/docs/tutorial/cells"&gt;here&lt;/a&gt;, &lt;a href="https://redwoodjs.com/docs/cells"&gt;here as well&lt;/a&gt; and &lt;a href="https://github.com/redwoodjs/redwood/blob/main/packages/web/src/components/createCell.tsx"&gt;here&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




</description>
      <category>redwoodjs</category>
      <category>graphql</category>
      <category>react</category>
    </item>
    <item>
      <title>Testing with react-i18next in RedwoodJS</title>
      <dc:creator>noire.munich</dc:creator>
      <pubDate>Tue, 25 Jan 2022 16:15:35 +0000</pubDate>
      <link>https://dev.to/noiremunich/testing-with-react-i18next-in-redwoodjs-2958</link>
      <guid>https://dev.to/noiremunich/testing-with-react-i18next-in-redwoodjs-2958</guid>
      <description>&lt;p&gt;This post assumes you've already fiddled with &lt;a href="https://redwoodjs.com/"&gt;RedwoodJS&lt;/a&gt; and are looking further into it for something you'd show the world. It is about testing &amp;amp; translation, hence the title. If you have experience with both topics, the article might not be very relevant to you, instead I'd suggest checking out RedwoodJS.&lt;/p&gt;




&lt;h2&gt;
  
  
  Context
&lt;/h2&gt;

&lt;p&gt;A while back as I was trying to set up my current project, I tried to follow the Redwood Way ( no definitive specification on that one as of yet, AFAIK ) and implement frontend tests. I had never worked with frontend tests before and soon enough I stumbled onto &lt;a href="https://github.com/redwoodjs/redwood/issues/2397"&gt;performance issues&lt;/a&gt;. The ticket doesn't show the real story, but what happened was that I had errors thrown and no way to gather enough information to understand what was going on - and having to wait 90 seconds for any file update quickly became an obstacle I couldn't deal with.&lt;/p&gt;

&lt;p&gt;I left the frontend tests aside until &lt;a class="mentioned-user" href="https://dev.to/jtoar"&gt;@jtoar&lt;/a&gt; pinged me on the original issue this week. &lt;a href="https://github.com/redwoodjs/redwood/releases/tag/v0.42.1"&gt;v0.42&lt;/a&gt; ships with improvements on testing, I had to try it and see where we were at now.&lt;/p&gt;

&lt;p&gt;To do so I met my following problem: tests were still failing, but in 10s, which is way better and let me investigate a bit more the core of my problem - translation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Hands on
&lt;/h2&gt;

&lt;p&gt;If your application supports translation, you may find yourself with many components relying on &lt;code&gt;i18n&lt;/code&gt; 's ( or your library of choice to handle that ) functions or components. Those may impair your tests if you do not use proper mocks.&lt;br&gt;
Fortunately this is rather straightforward. This example works with &lt;code&gt;react-i18next&lt;/code&gt;.&lt;br&gt;
First, let's define a component that will use the library. Let's KISS this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useTranslation&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-i18next&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;  

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;pointer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useTranslation&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="nt"&gt;div&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="nt"&gt;label&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;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pointer&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="nt"&gt;label&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="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;name&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="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;  
 &lt;span class="p"&gt;)&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;  

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;Input&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Second, we need to define a test configuration for the library:&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="err"&gt;#&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;configuration&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;i18nForTests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;i18n&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;i18next&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;  

&lt;span class="nx"&gt;i18n&lt;/span&gt;      
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;init&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;  
    &lt;span class="na"&gt;lng&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
    &lt;span class="na"&gt;fallbackLng&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  

    &lt;span class="c1"&gt;// have a common namespace used around the full app    &lt;/span&gt;
&lt;span class="na"&gt;ns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;site&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;  
    &lt;span class="na"&gt;defaultNS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;site&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  

    &lt;span class="na"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  

    &lt;span class="na"&gt;interpolation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
      &lt;span class="na"&gt;escapeValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// not needed for react!!    &lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;  

    &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;fr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;site&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="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;i18n&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, we can run some tests:&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;src/configuration/i18nForTests&lt;/span&gt;&lt;span class="dl"&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;fireEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;screen&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;@redwoodjs/testing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;  
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Input&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;./Input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;  

&lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-i18next&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;  
  &lt;span class="c1"&gt;// this mock makes sure any components using the translate hook can use it without a warning being shown  &lt;/span&gt;
 &lt;span class="na"&gt;useTranslation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="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;t&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;str&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;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
      &lt;span class="na"&gt;i18n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
        &lt;span class="na"&gt;changeLanguage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{}),&lt;/span&gt;  
      &lt;span class="p"&gt;},&lt;/span&gt;  
    &lt;span class="p"&gt;}&lt;/span&gt;  
  &lt;span class="p"&gt;},&lt;/span&gt;  
&lt;span class="p"&gt;}))&lt;/span&gt;  

&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;UI/Input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
  &lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Renders successfully&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Input&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;someInput&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;pointer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;site:input.text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt; &lt;/span&gt;&lt;span class="err"&gt; 
&lt;/span&gt;
    &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;textbox&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;toBeInstanceOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;HTMLInputElement&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;That should do the trick. 10s of success, now!&lt;/p&gt;

&lt;p&gt;There's a &lt;code&gt;debug&lt;/code&gt; prop in the configuration object for &lt;code&gt;i18n&lt;/code&gt;, if set to true you'll get information about missing keys. I haven't played with that, but if I had time, that could be valuable.&lt;/p&gt;

&lt;p&gt;Happy coding. &lt;/p&gt;




&lt;h5&gt;
  
  
  Notes
&lt;/h5&gt;




&lt;p&gt;RedwoodJS let's you setup translation with an out of the box cli command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn rw setup i18n
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you pickup RedwoodJS and don't have strong preferences on translation libs, there's a solution for you.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>redwoodjs</category>
      <category>translation</category>
      <category>testing</category>
    </item>
  </channel>
</rss>
