<?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: Radu Brehar👨‍💻</title>
    <description>The latest articles on Forem by Radu Brehar👨‍💻 (@radubrehar).</description>
    <link>https://forem.com/radubrehar</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%2F199492%2F81b6a489-214e-4099-87a5-8aaf86b7cfc6.jpg</url>
      <title>Forem: Radu Brehar👨‍💻</title>
      <link>https://forem.com/radubrehar</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/radubrehar"/>
    <language>en</language>
    <item>
      <title>How to use Excel-like editing in your DataGrid</title>
      <dc:creator>Radu Brehar👨‍💻</dc:creator>
      <pubDate>Thu, 13 Jun 2024 08:39:08 +0000</pubDate>
      <link>https://forem.com/radubrehar/how-to-use-excel-like-editing-in-your-datagrid-2j0d</link>
      <guid>https://forem.com/radubrehar/how-to-use-excel-like-editing-in-your-datagrid-2j0d</guid>
      <description>&lt;p&gt;Excel-like editing is a very popular request we had. In this short article, we show you how to configure Excel-like editing in the Infinite React DataGrid.&lt;/p&gt;

&lt;p&gt;Click on a cell and start typing&lt;br&gt;
&lt;iframe src="https://codesandbox.io/embed/y6xtw6"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;This behavior is achieved by using the &lt;a href="https://infinite-table.com/docs/learn/keyboard-navigation/keyboard-shortcuts#instant-edit"&gt;Instant Edit keyboard shorcut&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring keyboard shortcuts
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;DataSource&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;InfiniteTable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;keyboardShortcuts&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;@infinite-table/infinite-react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

 &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;DataSource&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Developer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;primaryKey&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;dataSource&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;InfiniteTable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Developer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nx"&gt;columns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;keyboardShortcuts&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{[&lt;/span&gt;
        &lt;span class="nx"&gt;keyboardShortcuts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instantEdit&lt;/span&gt;
      &lt;span class="p"&gt;]}&lt;/span&gt;
    &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/DataSource&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;instantEdit&lt;/code&gt; &lt;a href="https://infinite-table.com/docs/learn/keyboard-navigation/keyboard-shorcuts"&gt;keyboard shorcut&lt;/a&gt; is configured (by default) to respond to any key (via the special &lt;code&gt;*&lt;/code&gt; identifier which matches anything) and will start editing the cell as soon as a key is pressed. This behavior is the same as in Excel, Google Sheets, Numbers or other spreadsheet software.&lt;/p&gt;

&lt;p&gt;To enable editing globally, you can use the &lt;a href="https://infinite-table.com/docs/reference/infinite-table-props#columnDefaultEditable"&gt;columnDefaultEditable&lt;/a&gt; boolean prop on the &lt;code&gt;InfiniteTable&lt;/code&gt; DataGrid component. This will make all columns editable.&lt;/p&gt;

&lt;p&gt;Or you can be more specific and choose to make individual columns editable via the &lt;a href="https://infinite-table.com/docs/reference/infinite-table-props#columns.defaultEditable"&gt;column.defaultEditable&lt;/a&gt; prop. This overrides the global &lt;a href="https://infinite-table.com/docs/reference/infinite-table-props#columnDefaultEditable"&gt;columnDefaultEditable&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Column Editors
&lt;/h3&gt;

&lt;p&gt;Read about how you can configure various &lt;a href="https://infinite-table.com/docs/learn/editing/column-editors"&gt;editors&lt;/a&gt; for your columns.&lt;/p&gt;

&lt;h3&gt;
  
  
  Editing Flow Chart
&lt;/h3&gt;

&lt;p&gt;A picture is worth a thousand words - &lt;a href="https://infinite-table.com/docs/learn/editing/inline-edit-flow"&gt;see a chart for the editing flow&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finishing an Edit
&lt;/h2&gt;

&lt;p&gt;An edit is generally finished by user interaction - either the user confirms the edit by pressing the &lt;code&gt;Enter&lt;/code&gt; key or cancels it by pressing the &lt;code&gt;Escape&lt;/code&gt; key.&lt;/p&gt;

&lt;p&gt;As soon as the edit is confirmed by the user, &lt;code&gt;InfiniteTable&lt;/code&gt; needs to decide whether the edit should be accepted or not.&lt;/p&gt;

&lt;p&gt;In order to decide (either synchronously or asynchronously) whether an edit should be accepted or not, you can use the global &lt;a href="https://infinite-table.com/docs/reference/infinite-table-props#shouldAcceptEdit"&gt;shouldAcceptEdit&lt;/a&gt; prop or the column-level &lt;a href="https://infinite-table.com/docs/reference/infinite-table-props#columns.shouldAcceptEdit"&gt;column.shouldAcceptEdit&lt;/a&gt; alternative.&lt;/p&gt;

&lt;p&gt;When neither the global &lt;a href="https://infinite-table.com/docs/reference/infinite-table-props#shouldAcceptEdit"&gt;shouldAcceptEdit&lt;/a&gt; nor the column-level &lt;a href="https://infinite-table.com/docs/reference/infinite-table-props#columns.shouldAcceptEdit"&gt;column.shouldAcceptEdit&lt;/a&gt; are defined, all edits are accepted by default.&lt;/p&gt;

&lt;p&gt;Once an edit is accepted, the &lt;a href="https://infinite-table.com/docs/reference/infinite-table-props#onEditAccepted"&gt;onEditAccepted&lt;/a&gt; callback prop is called, if defined.&lt;/p&gt;

&lt;p&gt;When an edit is rejected, the &lt;a href="https://infinite-table.com/docs/reference/infinite-table-props#onEditRejected"&gt;onEditRejected&lt;/a&gt; callback prop is called instead.&lt;/p&gt;

&lt;p&gt;The accept/reject status of an edit is decided by using the &lt;code&gt;shouldAcceptEdit&lt;/code&gt; props described above. However an edit can also be cancelled by the user pressing the &lt;code&gt;Escape&lt;/code&gt; key in the cell editor - to be notified of this, use the &lt;a href="https://infinite-table.com/docs/reference/infinite-table-props#onEditCancelled"&gt;onEditCancelled&lt;/a&gt; callback prop.&lt;/p&gt;

&lt;p&gt;Using shouldAcceptEdit to decide whether a value is acceptable or not&lt;/p&gt;

&lt;p&gt;In this example, the &lt;code&gt;salary&lt;/code&gt; column is configured with a &lt;a href="https://infinite-table.com/docs/reference/infinite-table-props#columns.shouldAcceptEdit"&gt;shouldAcceptEdit&lt;/a&gt; function property that rejects non-numeric values.&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://codesandbox.io/embed/2x7nrw"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Persisting an Edit
&lt;/h2&gt;

&lt;p&gt;By default, accepted edits are persisted to the &lt;code&gt;DataSource&lt;/code&gt; via the &lt;a href="https://infinite-table.com/docs/reference/datasource-api#updateData"&gt;DataSourceAPI.updateData&lt;/a&gt; method.&lt;/p&gt;

&lt;p&gt;To change how you persist values (which might include persisting to remote locations), use the &lt;a href="https://infinite-table.com/docs/reference/infinite-table-props#persistEdit"&gt;persistEdit&lt;/a&gt; function prop on the &lt;code&gt;InfiniteTable&lt;/code&gt; component.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://infinite-table.com/docs/reference/infinite-table-props#persistEdit"&gt;persistEdit&lt;/a&gt; function prop can return a &lt;code&gt;Promise&lt;/code&gt; for async persistence. To signal that the persisting failed, reject the promise or resolve it with an &lt;code&gt;Error&lt;/code&gt; object.&lt;/p&gt;

&lt;p&gt;After persisting the edit, if all went well, the &lt;a href="https://infinite-table.com/docs/reference/infinite-table-props#onEditPersistSuccess"&gt;onEditPersistSuccess&lt;/a&gt; callback prop is called. If the persisting failed (was rejected), the &lt;a href="https://infinite-table.com/docs/reference/infinite-table-props#onEditPersistError"&gt;onEditPersistError&lt;/a&gt; callback prop is called instead.&lt;/p&gt;

</description>
      <category>frontend</category>
      <category>javascript</category>
      <category>html</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Master-Detail React DataGrid with Charts</title>
      <dc:creator>Radu Brehar👨‍💻</dc:creator>
      <pubDate>Mon, 10 Jun 2024 15:12:28 +0000</pubDate>
      <link>https://forem.com/radubrehar/master-detail-react-datagrid-with-charts-15a3</link>
      <guid>https://forem.com/radubrehar/master-detail-react-datagrid-with-charts-15a3</guid>
      <description>&lt;p&gt;In this article, I want to show you how easy it is to leverage the master-detail support in the &lt;a href="https://infinite-table.com"&gt;Infinite Table React DataGrid&lt;/a&gt; in order to toggle between a table and a chart view in the row detail.&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://codesandbox.io/embed/gg7h4f"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;In the &lt;a href="https://infinite-table.com/docs/reference/infinite-table-props#components.RowDetail"&gt;RowDetail&lt;/a&gt; component of Infinite Table, we render a &lt;code&gt;&amp;lt;DataSource /&amp;gt;&lt;/code&gt;, which in turn will render either an &lt;code&gt;&amp;lt;InfiniteTable /&amp;gt;&lt;/code&gt; component or a chart.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;&amp;lt;DataSource /&amp;gt;&lt;/code&gt; in InfiniteTable is very powerful and does all the data processing the grid needs. All the row grouping, sorting, filtering, aggregations, pivoting are done in the &lt;code&gt;&amp;lt;DataSource /&amp;gt;&lt;/code&gt; - so you can use it standalone, or with InfiniteTable - it's totally up to you.&lt;/p&gt;

&lt;p&gt;In practice, this means that you can use the &lt;code&gt;&amp;lt;DataSource /&amp;gt;&lt;/code&gt; to process your data and then simply pass that to a charting library like &lt;code&gt;ag-charts-react&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;detailGroupBy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DataSourcePropGroupBy&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Developer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;field&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stack&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}];&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;detailAggregationReducers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DataSourcePropAggregationReducers&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Developer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;salary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;field&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;salary&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;initialValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;reducer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;acc&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;done&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;RowDetail&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rowInfo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useMasterRowInfo&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;City&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;!&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;showChart&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setShowChart&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="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rowInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&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="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{...}&lt;/span&gt;&lt;span class="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;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setShowChart&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;showChart&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;!&lt;/span&gt;&lt;span class="nx"&gt;showChart&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;
        Click to see &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;showChart&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;grid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;chart&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/**
       * In this example, we leverage the DataSource aggregation and grouping feature to
       * calculate the average salary by stack for the selected city.
       */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;DataSource&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Developer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;detailDataSource&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;primaryKey&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="nx"&gt;groupBy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;detailGroupBy&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;aggregationReducers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;detailAggregationReducers&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="cm"&gt;/**
         * Notice here we're not rendering an InfiniteTable component
         * but rather we use a render function to access the aggregated data.
         */&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;{(&lt;/span&gt;&lt;span class="nx"&gt;params&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;// here we decide if we need to show the chart or the grid&lt;/span&gt;
          &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;showChart&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;InfiniteTable&lt;/span&gt;
                &lt;span class="na"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;detailColumns&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
                &lt;span class="na"&gt;domProps&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                  &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;paddingTop&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
              &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;

          &lt;span class="c1"&gt;// the dataArray has all the aggregations and groupings done for us, &lt;/span&gt;
          &lt;span class="c1"&gt;// so we need to retrieve the correct rows and pass it to the charting library&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;groups&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataArray&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;rowInfo&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;rowInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isGroupRow&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;groupData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;group&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="na"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;group&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;stack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;avgSalary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reducerData&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;salary&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;AgChartsReact&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="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;autoSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                  &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Avg salary by stack in &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;rowInfo&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;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;rowInfo&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;country&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="p"&gt;},&lt;/span&gt;
                &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;groupData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;series&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                  &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bar&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="na"&gt;xKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stack&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="na"&gt;yKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;avgSalary&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="na"&gt;yName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Average Salary&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="p"&gt;],&lt;/span&gt;
              &lt;span class="p"&gt;}&lt;/span&gt;&lt;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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="na"&gt;DataSource&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The demo above is using the &lt;code&gt;ag-charts-react&lt;/code&gt; package to render the charts.&lt;/p&gt;

&lt;p&gt;Read more about the &lt;a href="https://infinite-table.com/docs/learn/master-detail/custom-row-detail-content"&gt;rendering custom content in a master-detail setup&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>frontend</category>
      <category>typescript</category>
    </item>
    <item>
      <title>The best testing strategies for frontends</title>
      <dc:creator>Radu Brehar👨‍💻</dc:creator>
      <pubDate>Mon, 22 Apr 2024 13:28:59 +0000</pubDate>
      <link>https://forem.com/radubrehar/the-best-testing-strategies-for-frontends-14k2</link>
      <guid>https://forem.com/radubrehar/the-best-testing-strategies-for-frontends-14k2</guid>
      <description>&lt;p&gt;In our previous article, we focused on documenting &lt;a href="https://infinite-table/blog/2024/04/18/the-best-testing-setup-for-frontends-playwright-nextjs"&gt;the best testing setup for frontends&lt;/a&gt;, which used Playwright and Next.js. You can check out the repository &lt;a href="https://github.com/infinite-table/testing-setup-nextjs-playwright"&gt;here&lt;/a&gt; where you can find the full setup.&lt;/p&gt;

&lt;p&gt;We consider the combination described above, Playwright + NextJS being the best combo around for testing frontends. Ok, you can switch out NextJS with other meta-framework that offers file-system routing, but the idea is the same: every test is made of 2 sibling files, with the same name but different extension.&lt;/p&gt;

&lt;p&gt;In the test files, Playwright is configured to navigate automatically to the page being tested, so no need for adjustments if you move files around. This saves you a lot of time and hustle and makes your tests more robust and focused.&lt;/p&gt;

&lt;p&gt;But in addition to end-to-end testing with Playwright and NextJS, there are other forms of testing out there which are available and can be used to complement your testing strategy. In this article, we'll focus on what we think are the best testing strategies for frontends. So here are a few options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;E2E testing&lt;/li&gt;
&lt;li&gt;Component testing &lt;/li&gt;
&lt;li&gt;Visual regression testing&lt;/li&gt;
&lt;li&gt;Unit testing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For each of those options there are plenty of tools you can use, each with its own pros and cons.&lt;/p&gt;

&lt;h2&gt;
  
  
  E2E testing
&lt;/h2&gt;

&lt;p&gt;With the advent of tools like &lt;a href="https://pptr.dev/"&gt;Puppeteer&lt;/a&gt; and now &lt;a href="https://playwright.dev/"&gt;Playwright&lt;/a&gt;, end-to-end testing has become much easier and more reliable. For anyone who's used Selenium in the past, you know what I'm talking about. &lt;br&gt;
Puppeteer has opened the way in terms of E2E tooling, but Playwright has taken it to the next level and made it easier to await for certain selectors or conditions to be fulfilled (via &lt;a href="https://playwright.dev/docs/locators"&gt;locators&lt;/a&gt;), thus making tests more reliable and less flaky.&lt;br&gt;
Also, it's a game changer that it introduced a test-runner - this made the integration between the headless browser and the actual test code much smoother.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reasons to use E2E
&lt;/h3&gt;

&lt;p&gt;End to end testing is actually a real browser, so the closest possible environment to what your app will be using.&lt;/p&gt;

&lt;p&gt;No need to fake the page with JSDOM, no need to only do shallow rendering in React. Just use the platform!&lt;/p&gt;

&lt;h2&gt;
  
  
  Component testing
&lt;/h2&gt;

&lt;p&gt;Probably &lt;a href="https://enzymejs.github.io/enzyme/"&gt;Enzyme&lt;/a&gt; was the first to popularize component testing in React by doing shallow rendering and expecting some things to be there in the React component tree. Then &lt;a href="https://testing-library.com/"&gt;React Testing library&lt;/a&gt; came and took component testing to a whole new level.&lt;/p&gt;

&lt;p&gt;The tools are great for what they're doing, but with the advent of better tooling, we should move on to better ways of testing. With the tools we have now in 2024, there's no more need to use JSDOM and simulate a browser enviroment. It used to be very cumbersome to start a headless browser back in the day, but now with Playwright/Puppeteer, it's a breeze.&lt;/p&gt;

&lt;h2&gt;
  
  
  Visual regression testing
&lt;/h2&gt;

&lt;p&gt;Also in the days before Playwright was around, there was much hype about visual regression testing. It was very very tempting to use it - who wouldn't want to have a tool that automatically checks if the UI has (mistakenly) changed? It might fit a few use-cases, but in general, it's not worth the effort of maintaining all those tests for any little change in the UI. True, you can set thresholds for the differences, but it's still a lot of work to maintain it, especially in highly dynamic frontends and teams.&lt;/p&gt;

&lt;p&gt;With better CSS approaches like &lt;a href="https://tailwindcss.com/"&gt;TailwindCSS&lt;/a&gt; and &lt;a href="https://vanilla-extract.style/"&gt;Vanilla Extract&lt;/a&gt; (which we're heavily using) it's much easier to maintain the UI and make sure it doesn't change unexpectedly. No more conflicting CSS classes, much less CSS specificity issues and much less CSS code in general.&lt;/p&gt;

&lt;p&gt;One of the troubles in large and tangled CSS codebases is that it's write-only. Well, not write-only per se, but teams are generally afraid to remove a line of CSS cause it might break someone else's code or it might still be used.&lt;/p&gt;

&lt;p&gt;With &lt;a href="https://vanilla-extract.style/"&gt;Vanilla Extract&lt;/a&gt; you can be sure that if you remove a CSS class, it's not used anywhere else in the codebase. It's been a game changer in terms of CSS maintainability and productivity for us at &lt;a href="https://infinite-table.com/"&gt;Infinite Table&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So with all those tools to make styling easier, the need for visual regression testing has dropped significantly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Unit testing
&lt;/h2&gt;

&lt;p&gt;Unit testing will be here to stay - at least if besides your UI, your app has some significant business logic. We're using it in combination with E2E testing to make sure complex use-cases work as expected.&lt;/p&gt;

&lt;p&gt;For example, our &lt;a href="https://infinite-table.com/docs/learn/grouping-and-pivoting/grouping-rows"&gt;logic for row grouping&lt;/a&gt; is fully tested with unit tests. We do have E2E tests for it, but with unit tests we can have full coverage of all the nitty-gritty details of the grouping logic. We do the same for pivoting and aggregating. Column sizing and column grouping are also covered with unit tests.&lt;/p&gt;

&lt;p&gt;We think there's always going to be a place for unit testing to ensure robustness and reliability of the app under even the most complex use-cases and user inputs.&lt;/p&gt;

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

&lt;p&gt;In our experience, the best testing strategy for modern frontends is a combination of E2E testing (using Playwright+NextJS), and unit testing. Visual regression testing is not worth the effort in our opinion, especially with the advent of better CSS tooling like TailwindCSS and &lt;a href="https://vanilla-extract.style/"&gt;Vanilla Extract&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Though we used shallow component testing in the past, we're not going back to it - mocking the DOM and the browser is no longer worth it when you can use a real browser with Playwright.&lt;/p&gt;

&lt;p&gt;We hope this article has been helpful in guiding you towards the best testing strategy for your frontend. If you have any questions or comments, feel free to reach out to us at &lt;a href="//mailto:admin@infinite-table.com"&gt;admin@infinite-table.com&lt;/a&gt;. We're always happy to help! &lt;/p&gt;

</description>
      <category>testing</category>
      <category>frontend</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>The best testing setup for frontends, with Playwright and NextJS</title>
      <dc:creator>Radu Brehar👨‍💻</dc:creator>
      <pubDate>Thu, 18 Apr 2024 09:36:38 +0000</pubDate>
      <link>https://forem.com/radubrehar/the-best-testing-setup-for-frontends-with-playwright-and-nextjs-lfc</link>
      <guid>https://forem.com/radubrehar/the-best-testing-setup-for-frontends-with-playwright-and-nextjs-lfc</guid>
      <description>&lt;p&gt;We want to share with you the best testing setup we've experienced - and this includes using &lt;a href="https://playwright.dev/" rel="noopener noreferrer"&gt;Playwright&lt;/a&gt; and &lt;a href="https://nextjs.org/" rel="noopener noreferrer"&gt;NextJS&lt;/a&gt;. It's a setup we've come up with for &lt;a href="https://infinite-table.com" rel="noopener noreferrer"&gt;Infinite React DataGrid&lt;/a&gt;, which is a complex component, with lots of things to test, but this configuration has helped us ship with more confidence and speed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you should expect from a testing setup
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Fast feedback
&lt;/h3&gt;

&lt;p&gt;⚡️ Quick ⚡️ feedback is a no-brainer, since without a fast turnaround, devs will not have the patience to run the tests and will move on to the next "burning" issue or to the next cup of coffee.&lt;/p&gt;

&lt;p&gt;Also, you can't run all the test suite at once, so you need to be able to run only the tests that are relevant to the changes you've made. This has long been available in unit-testing frameworks, but it's not so common in end-to-end testing, when loading a webpage and rendering an actual component is involved.&lt;/p&gt;

&lt;p&gt;In this article we want to show you how we achieved fast feedback that allows rapid developer iterations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stability and predictability
&lt;/h3&gt;

&lt;p&gt;You don't need flaky tests that fail randomly - it's the last thing you want when doing a release, or even during development.&lt;br&gt;
Waiting for an element to appear on page or an animation to finish or an interaction to complete is a common source of flakiness in end-to-end tests, but Playwright gives you the tools to address these issues - thank you &lt;a href="https://playwright.dev/docs/locators" rel="noopener noreferrer"&gt;Playwright locators&lt;/a&gt; 🙏 and other playwright testing framework features.&lt;/p&gt;
&lt;h3&gt;
  
  
  Ease of maintenance and debugging
&lt;/h3&gt;

&lt;p&gt;Another crucial point when you setup a testing framework and start writing tests is how easy is to write a new test, to inspect what is being tested and to reproduce failing tests. All these should be as easy as opening loading a URL in a browser - this is exactly what this setup gives you, with NextJS and Playwright playing very well together.&lt;br&gt;
When one of your tests fails, Playwright outputs a command you can run to reproduce the exact failure and actually see the UI at the moment of the failure, with the ability to navigate through the test timeline and see what happened before the failure.&lt;/p&gt;
&lt;h2&gt;
  
  
  Setting up NextJS and Playwright
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Step 1 - creating the NextJS app
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npx create-next-app@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;You're being asked a few questions. For &lt;code&gt;Would you like to use src/ directory?&lt;/code&gt; we chose &lt;code&gt;Yes&lt;/code&gt;. Also, we're using TypeScript.&lt;/p&gt;

&lt;p&gt;When you run this command, make sure for this question &lt;code&gt;Would you like to use App Router?&lt;/code&gt; you reply &lt;code&gt;No&lt;/code&gt;, as you want to use file-system routing to make it very easy and intuitive to add new pages and tests.&lt;/p&gt;

&lt;p&gt;Check out our repo for this stage of the setup - &lt;a href="https://github.com/infinite-table/testing-setup-nextjs-playwright/tree/01-setup-nextjs" rel="noopener noreferrer"&gt;Step 1 - setting up NextJS&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Before you go to the next step, you can configure your &lt;code&gt;next.config.mjs&lt;/code&gt; to use the &lt;code&gt;.page&lt;/code&gt; extension for your pages.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nextConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;reactStrictMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;pageExtensions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;page.tsx&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;page.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;page.js&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="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;nextConfig&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is useful so NextJS will only compile those files as pages that your tests will be targeting, and not all the files in the &lt;code&gt;pages&lt;/code&gt; folder, which will also contain your tests.&lt;/p&gt;

&lt;p&gt;So you know all your &lt;code&gt;.page&lt;/code&gt; files are pages that your tests will be run against and all your &lt;code&gt;.spec&lt;/code&gt; files are tests (see next step).&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2 - setting up Playwright
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npm init playwright@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again a few questions about your setup.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Where to put your end-to-end tests?&lt;/code&gt; - choose &lt;code&gt;src/pages&lt;/code&gt; - which makes your NextJS pages folder the place where you put your end-to-end tests.&lt;/p&gt;

&lt;p&gt;This script installs &lt;code&gt;@playwright/test&lt;/code&gt; and creates a &lt;code&gt;playwright.config.ts&lt;/code&gt; file with the default configuration. Most importantly, the &lt;code&gt;testDir&lt;/code&gt; is configured to &lt;code&gt;./src/pages&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;By default, all &lt;code&gt;.spec&lt;/code&gt; files in the &lt;code&gt;testDir&lt;/code&gt; (which is set to &lt;code&gt;src/pages&lt;/code&gt;) will be run as tests.&lt;/p&gt;

&lt;p&gt;Check out our repo for this stage of the setup - &lt;a href="https://github.com/infinite-table/testing-setup-nextjs-playwright/tree/02-setup-playwright" rel="noopener noreferrer"&gt;Step 2 - setting up Playwright&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There are some additional configurations you might want to do in this step.&lt;br&gt;
You probably want to change the default &lt;code&gt;reporter&lt;/code&gt; from &lt;code&gt;'html'&lt;/code&gt; to &lt;code&gt;'list'&lt;/code&gt; in your &lt;code&gt;playwright.config.ts&lt;/code&gt; - the &lt;code&gt;'html'&lt;/code&gt; reporter will open a browser window with the test results, which you might not prefer. You'd rather see the results in the terminal.&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;// playwright.config.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;testDir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./src/pages&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;reporter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;list&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// the 'html' reporter will open a browser window with the test results&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For now, you might want to only run your tests in one browser, so comment out any additional entries in the &lt;code&gt;projects&lt;/code&gt; array in your &lt;code&gt;playwright.config.ts&lt;/code&gt; file - that controls the devices that will be used in your tests.&lt;/p&gt;

&lt;p&gt;The last piece of the puzzle before running your first test with Playwright is defining the &lt;code&gt;test&lt;/code&gt; script in your &lt;code&gt;package.json&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"testing-setup-nextjs-playwright"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx playwright test"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"next dev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"next build"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Executing the &lt;code&gt;npm run test&lt;/code&gt; command will run the tests in the &lt;code&gt;src/pages&lt;/code&gt; folder - for now, you should have a single file, &lt;code&gt;example.spec.ts&lt;/code&gt;, which was generated by the &lt;code&gt;npm init playwright&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Finfinite-table.com%2Fblog-images%2Fstep-2-initial-results.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Finfinite-table.com%2Fblog-images%2Fstep-2-initial-results.png" alt="Playwright test output"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Your initial test file was something very basic. This file is importing the &lt;code&gt;test&lt;/code&gt; (and &lt;code&gt;expect&lt;/code&gt;) function from &lt;code&gt;@playwright/test&lt;/code&gt; - and this is what you're using to define tests (and write assertions).&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;// example.spec.ts&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;test&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@playwright/test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;has title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&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;page&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;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://playwright.dev/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Expect a title "to contain" a substring.&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveTitle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/Playwright/&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;h3&gt;
  
  
  Step 3 - configuring the naming convention in Playwright to open the right pages
&lt;/h3&gt;

&lt;p&gt;This step is probably the most important one in your configuration. Normally your tests will open webpages before you start testing - but this is not something you want to do explicitly in your project. Rather, you want your tests to automatically navigate to the corresponding page for the test. This is what this step is achieving - and we're using &lt;a href="https://playwright.dev/docs/test-fixtures" rel="noopener noreferrer"&gt;Playwright fixtures&lt;/a&gt; to do this.&lt;/p&gt;

&lt;p&gt;Think of a fixture as some code that's configuring the testing environment for each of your tests.&lt;br&gt;
A fixture will extend the &lt;code&gt;test&lt;/code&gt; function from &lt;code&gt;@playwright/test&lt;/code&gt; with additional functionalities. Mainly, we want before every test to open the correct page, without writing this explicitly in every test. Based on the location of the test file in the file system, we want to navigate to a webpage for it and we assume it will have the same path as the test file. This is possible because NextJS is configured to use file-system routing.&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;// Defining the fixture file - test-fixtures.ts&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;test&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&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;PlaywrightTestArgs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;PlaywrightTestOptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@playwright/test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@playwright/test&lt;/span&gt;&lt;span class="dl"&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;test&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;extend&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;
  &lt;span class="nx"&gt;PlaywrightTestArgs&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;
    &lt;span class="nx"&gt;PlaywrightTestOptions&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;//@ts-ignore&lt;/span&gt;
  &lt;span class="na"&gt;page&lt;/span&gt;&lt;span class="p"&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;baseURL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;testInfo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;testFilePath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;testInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;titlePath&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&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;fileName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;testFilePath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.spec.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;baseURL&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;fileName&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="c1"&gt;// navigate to the corresponding page for this test&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We'll give this fixture file the name &lt;code&gt;test-fixtures.ts&lt;/code&gt; and put it in the root of the project.&lt;/p&gt;

&lt;p&gt;Now instead of importing the &lt;code&gt;test&lt;/code&gt; function from &lt;code&gt;@playwright/test&lt;/code&gt; we want to import it from the &lt;code&gt;test-fixtures.ts&lt;/code&gt; file - we'll do this in all our tests. To make this easier, let's also define a path alias in the &lt;code&gt;tsconfig.json&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"compilerOptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"paths"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"@playwright/test"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"test-fixtures.ts"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We're ready to write our first test page in NextJS and use the new fixture in the Playwright test.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/pages/example.page.tsx&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="nf"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&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;Hello world&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/pages/example.spec.ts&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;test&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@testing&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// notice the import&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Main example has corrent content&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&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;page&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;// notice we don't need to navigate to the page, this is done by the fixture&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;innerText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;body&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toContain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello world&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For our tests against the NextJS app, we obviously need to start the app.&lt;/p&gt;

&lt;p&gt;Let's configure a custom port of &lt;code&gt;5432&lt;/code&gt; in the package.json &lt;code&gt;dev&lt;/code&gt; script.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"next dev --port 5432"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx playwright test"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;//...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We need to use the same port in the Playwright configuration file.&lt;br&gt;
Also we'll use a smaller test &lt;code&gt;timeout&lt;/code&gt; (the default is 30s).&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;// playwright.config.ts&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;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@playwright/test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="cm"&gt;/**
 * See https://playwright.dev/docs/test-configuration.
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;testDir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./src/pages&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;reporter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;list&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;use&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;baseURL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:5432/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CI&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// ... more options&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;We're now ready to roll!&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npm run dev&lt;/code&gt; will run NextJS and &lt;code&gt;npm run test&lt;/code&gt; will run the tests against your NextJS app.&lt;/p&gt;

&lt;p&gt;To make the setup easier, avoid using &lt;code&gt;index.page.tsx&lt;/code&gt; pages in NextJS - give your pages another name, to avoid issues with directory index pages in tests. This can easily be solved in the test fixture, but for the sake of clarity and brevity we're not doing it now.&lt;/p&gt;

&lt;p&gt;Check out our repo for this stage of the setup - &lt;a href="https://github.com/infinite-table/testing-setup-nextjs-playwright/tree/03-configure-naming-convention" rel="noopener noreferrer"&gt;Step 3 - configuring the Playwright fixture and naming convention&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4 - adding watch mode
&lt;/h3&gt;

&lt;p&gt;As we mentioned initially, no testing setup is great unless it gives you very fast feedback. For this, we obviously need watch mode.&lt;/p&gt;

&lt;p&gt;We want to be able to re-run tests when our test code has changed, but even better, when our NextJS page has changed - so the page the test is running against.&lt;br&gt;
NextJS has watch mode built-in in dev mode, so whenever a page is changed, it's recompiled and the browser is served the updated page. We'll use this in our advantage, so tests will always see the latest version of the page.&lt;br&gt;
This means the last piece of the puzzle is to make Playwright re-run the tests when the page has changed or the test itself has changed.&lt;/p&gt;

&lt;p&gt;For this, we'll use &lt;a href="https://www.npmjs.com/package/chokidar" rel="noopener noreferrer"&gt;&lt;code&gt;chokidar&lt;/code&gt;&lt;/a&gt;  - more specifically the &lt;a href="https://www.npmjs.com/package/chokidar-cli" rel="noopener noreferrer"&gt;&lt;code&gt;chokidar-cli&lt;/code&gt;&lt;/a&gt; package. &lt;code&gt;chokidar&lt;/code&gt; is probably the most useful file watching library for the nodejs ecosystem and it will serve us well.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx playwright test"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"test:watch"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"chokidar '**/*.spec.ts' '**/*.page.tsx' -c 'test_file_path=$(echo {path} | sed s/page.tsx/spec.ts/) &amp;amp;&amp;amp; npm run test -- --retries=0 ${test_file_path}'"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;test:watch&lt;/code&gt; script is watching for changes in &lt;code&gt;.spec.ts&lt;/code&gt; files and &lt;code&gt;.page.tsx&lt;/code&gt; files and whenever there's a change in one of those files, it's re-running the respective test. (When a change was found in a &lt;code&gt;.page.tsx&lt;/code&gt; file, we're using &lt;code&gt;sed&lt;/code&gt; to replace the &lt;code&gt;.page.tsx&lt;/code&gt; extension with &lt;code&gt;.spec.ts&lt;/code&gt;, because we want to pass the test file to the &lt;code&gt;npm run test&lt;/code&gt; command so it knows what test to re-run.)&lt;/p&gt;

&lt;p&gt;The above &lt;code&gt;test:watch&lt;/code&gt; script was written for MacOS (and Unix-like systems). If you're using Windows, you might need to adjust the command to achieve the same result.&lt;/p&gt;

&lt;p&gt;Don't forget to run &lt;code&gt;npm run dev&lt;/code&gt; before running &lt;code&gt;npm run test&lt;/code&gt; or &lt;code&gt;npm run test:watch&lt;/code&gt; - you need the NextJS app running to be able to run the tests. After all, that's what you're testing 😅.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5 - running tests on production build
&lt;/h3&gt;

&lt;p&gt;In the last step, we want to build a production build of the NextJS app and run the tests against it. &lt;/p&gt;

&lt;p&gt;So first let's configure the &lt;code&gt;next.config.mjs&lt;/code&gt; file to build a static site when &lt;code&gt;npm run build&lt;/code&gt; is run.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// next.config.mjs - configured to export a static site&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nextConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;reactStrictMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;export&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;pageExtensions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;page.tsx&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;page.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;page.js&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="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;nextConfig&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the &lt;code&gt;"output": "export"&lt;/code&gt; property. Having configured this, the &lt;code&gt;npm run build&lt;/code&gt; will create an &lt;code&gt;/out&lt;/code&gt; folder with the compiled assets and pages of the app.&lt;/p&gt;

&lt;p&gt;Next we need an NPM script to serve the compiled app with a static server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"serve"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx http-server --port 5432 out"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"//..."&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"// other scripts"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We could either run this &lt;code&gt;serve&lt;/code&gt; script ourselves to start the webserver before running our tests or even better, we can instruct Playwright to &lt;a href="https://playwright.dev/docs/test-webserver#configuring-a-web-server" rel="noopener noreferrer"&gt;use this webserver automatically&lt;/a&gt;. So let's do that in our &lt;code&gt;playwright.config.ts&lt;/code&gt; file.&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;// playwright.config.ts - configured to use a custom server&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="c1"&gt;//... other options&lt;/span&gt;

  &lt;span class="c1"&gt;// on CI, run the static server to serve the built app&lt;/span&gt;
  &lt;span class="na"&gt;webServer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CI&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;npm run serve&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:5432&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;reuseExistingServer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&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="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In order for Playwright to correctly detect the webserver is running ok, we need to make sure we have a valid index page at that address, so we need to add a &lt;code&gt;index.page.tsx&lt;/code&gt; file in the &lt;code&gt;pages&lt;/code&gt; folder.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/pages/index.page.tsx&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="nf"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&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;Index page&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is just useful in the CI environment so that Playwright can detect the server is running and the app is served correctly.&lt;/p&gt;

&lt;p&gt;Next, in order to run our tests as if we're in the CI environment, let's add a &lt;code&gt;test:ci&lt;/code&gt; script, which is basically calling the &lt;code&gt;test&lt;/code&gt; script but setting the &lt;code&gt;CI&lt;/code&gt; environment variable to &lt;code&gt;true&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"test:ci"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CI=true npm run test"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx playwright test"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"serve"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx http-server --port 5432 out"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"//..."&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"// other scripts"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We're now ready to run our tests against the production build of the NextJS app.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run build &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm run &lt;span class="nb"&gt;test&lt;/span&gt;:ci
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This script first builds the NextJS static app and then runs the tests against it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring CI github actions
&lt;/h2&gt;

&lt;p&gt;We're now ready to integrate our &lt;a href="https://playwright.dev/docs/ci-intro" rel="noopener noreferrer"&gt;testing workflow into CI via Github actions&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Create a YAML file &lt;code&gt;.github/workflows/test.yml&lt;/code&gt; in the root of your project with the following content.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Playwright Tests&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;master&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;master&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;timeout-minutes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;60&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;lts/*&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install dependencies&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm ci&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build app&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run build&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install Playwright Browsers&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npx playwright install --with-deps&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run Playwright tests&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run test&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/upload-artifact@v4&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always()&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;playwright-report&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;playwright-report/&lt;/span&gt;
          &lt;span class="na"&gt;retention-days&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this, you're ready to go! Push your changes to the main branch and see your tests running and passing in the CI environment. Go green! 🟢&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo repository
&lt;/h2&gt;

&lt;p&gt;You can find the full setup in our &lt;a href="https://github.com/infinite-table/testing-setup-nextjs-playwright/tree/main?tab=readme-ov-file" rel="noopener noreferrer"&gt;testing-setup-nextjs-playwright repo&lt;/a&gt;. Check it out and give it a star if you find it useful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Profit 🚀
&lt;/h2&gt;

&lt;p&gt;With this setup, you have a very convenient way to write your tests against real pages, loaded in a real browser, just like the end user experiences. And with the watch mode giving you instant feedback, you no longer have an excuse to not write tests.&lt;/p&gt;

&lt;p&gt;This is the same setup we've been using for developing and testing the &lt;a href="https://infinite-table.com" rel="noopener noreferrer"&gt;Infinite Table React DataGrid&lt;/a&gt; and it has been serving us really well.&lt;/p&gt;

&lt;p&gt;DataGrids are some of the most complex UI components one can build, so having a reliable tool that allowed us to iterate very quickly was crucial to us. This helped us add new features, while being confident that all of the existing core functionalities like row/column grouping, filtering, sorting, pagination, pivoting still work as expected.&lt;/p&gt;

&lt;p&gt;The setup was a pivotal point in our development process and it's what gives us and our enterprise customers the peace of mind that the product is stable and reliable, both now and in the future.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>nextjs</category>
      <category>testing</category>
    </item>
    <item>
      <title>Master detail is now available in Infinite React DataGrid</title>
      <dc:creator>Radu Brehar👨‍💻</dc:creator>
      <pubDate>Tue, 05 Mar 2024 11:50:35 +0000</pubDate>
      <link>https://forem.com/radubrehar/master-detail-is-now-available-in-infinite-react-datagrid-5fe1</link>
      <guid>https://forem.com/radubrehar/master-detail-is-now-available-in-infinite-react-datagrid-5fe1</guid>
      <description>&lt;p&gt;Today is a big day for the Infinite React DataGrid - we're excited to announce that the master detail feature is now available!&lt;/p&gt;

&lt;p&gt;With this addition, our DataGrid is now enterprise-ready! We know master-detail scenarios are needed in many business applications, and we're happy to provide this feature to our users starting today!&lt;/p&gt;

&lt;h2&gt;
  
  
  Master-detail highlights 🎉
&lt;/h2&gt;

&lt;p&gt;1️⃣ support for multiple levels of master-detail &amp;amp; rendering custom content&lt;br&gt;
 2️⃣ configurable detail height&lt;br&gt;
 3️⃣ control over expand/collapse state&lt;br&gt;
 4️⃣ caching mechanism for detail DataGrids&lt;/p&gt;
&lt;h2&gt;
  
  
  What can you do with master-detail?
&lt;/h2&gt;

&lt;p&gt;Master-detail allows you to have rows in the DataGrid that expand to show more details. This can be used to show more information about the row, or even to show another DataGrid with related data.&lt;/p&gt;

&lt;p&gt;You can render basically anything in the detail row - it doesn't need to be another DataGrid. However, if you do want to show another DataGrid, you can, and you can do that at any level of depth.&lt;/p&gt;

&lt;p&gt;In the detail &lt;code&gt;&amp;lt;DataSource /&amp;gt;&lt;/code&gt; component, you have access to the master row, so it will be very easy to load related data based on the master row the user expands.&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://codesandbox.io/embed/tender-cdn-9cpznx?module=%2FApp.tsx"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Configurable detail height
&lt;/h2&gt;

&lt;p&gt;Our master-detail implementation is very configurable - you can control the height of the row details, the expand/collapse state, and much more.&lt;/p&gt;

&lt;p&gt;The height of the row details is fully adjustable - see the &lt;a href="https://infinite-table.com/docs/reference/infinite-table-props#rowDetailHeight"&gt;rowDetailHeight&lt;/a&gt; prop to learn about all the options you have.&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://codesandbox.io/embed/beautiful-sammet-3gkwn9?module=%2FApp.tsx"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;As seen in the snippet above, it's also really easy to control the expand/collapse state of the row details. You can choose to have some rows expanded by default so details of those rows will be visible from the start.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configurable expand/collapse state
&lt;/h2&gt;

&lt;p&gt;Using the &lt;a href="https://infinite-table.com/docs/reference/infinite-table-props#rowDetailState"&gt;rowDetailState&lt;/a&gt;, you can control (in a declarative way) which rows are expanded and which are collapsed. In addition, if you prefer the imperative approach, we also have an &lt;a href="https://infinite-table.com/docs/reference/row-detail-api"&gt;API to work with row details&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you have some rows with details and some without, that's also covered. Use the &lt;a href="https://infinite-table.com/docs/reference/infinite-table-props#isRowDetailEnabled"&gt;isRowDetailEnabled&lt;/a&gt; to control which rows will have details and which will not.&lt;/p&gt;

&lt;p&gt;Another important configuration is choosing the column that has the row detail expand/collapse icon. Use the &lt;a href="https://infinite-table.com/docs/reference/infinite-table-props#columns.renderRowDetailIcon"&gt;columns.renderRowDetailIcon&lt;/a&gt; prop on the column that needs to display the expand/collapse icon. If this prop is a function, it can be used to customize the icon rendered for expanding/collapsing the row detail.&lt;/p&gt;

&lt;h2&gt;
  
  
  Master detail caching
&lt;/h2&gt;

&lt;p&gt;By far the most common scenario will be to render another DataGrid in the detail row.&lt;/p&gt;

&lt;p&gt;For such cases we offer a caching mechanism that will keep the state of the detail DataGrid when the user collapses and then expands the row again.&lt;/p&gt;

&lt;p&gt;To enable caching, use the &lt;a href="https://infinite-table.com/docs/reference/infinite-table-props#rowDetailCache"&gt;rowDetailCache&lt;/a&gt; prop.&lt;/p&gt;

&lt;p&gt;It can be one of the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;false&lt;/code&gt; - caching is disabled - this is the default&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;true&lt;/code&gt; - enables caching for all detail DataGrids&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;number&lt;/code&gt; - the maximum number of detail DataGrids to keep in the cache. When the limit is reached, the oldest detail DataGrid will be removed from the cache.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This example will cache the last 5 detail DataGrids - meaning they won't reload when you expand them again.&lt;br&gt;
You can try collapsing a row and then expanding it again to see the caching in action - it won't reload the data.&lt;br&gt;
But when you open up a row that hasn't been opened before, it will load the data from the remote location.&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://codesandbox.io/embed/thirsty-browser-xxf6wf?module=%2FApp.tsx"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Read our docs on &lt;a href="https://infinite-table.com/docs/learn/master-detail/caching-detail-datagrid"&gt;caching detail DataGrids&lt;/a&gt; to learn more how you can use this feature to improve the user experience.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>react</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Write-only social media?</title>
      <dc:creator>Radu Brehar👨‍💻</dc:creator>
      <pubDate>Thu, 05 Oct 2023 13:02:57 +0000</pubDate>
      <link>https://forem.com/radubrehar/write-only-social-media-3bgj</link>
      <guid>https://forem.com/radubrehar/write-only-social-media-3bgj</guid>
      <description>&lt;p&gt;How do you all manage your digital interactions?&lt;/p&gt;

&lt;p&gt;As developers, we're tempted to spend countless hours reading the latest trends, following the latest tech, keeping up-to-date with the latest news, and so on. And all of this can be very draining.&lt;/p&gt;

&lt;p&gt;I've been there myself and I can't say I'm now immune to this - I'm still learning how to keep my sanity, remain informed but be productive at the same time.&lt;/p&gt;

&lt;p&gt;One of the most important things is to realise life is too short to keep up with everything. You can't know it all and you can't be everything. Be content with who you are! And be a professional at the same time. Know your tools and sharpen them, but don't try to learn every single tool out there - it's not gonna work. Sure, experimentation has its place, but don't let it take over your life.&lt;/p&gt;

&lt;p&gt;Rather than being a consumer, become a producer. Rather than being a reader, become a writer. Sharpen your tools by doing not by seeing.&lt;br&gt;
It's so easy to keep scrolling - but stop it and start writing! Writing will make you a better developer and a happier individual. It's happier to give rather than to receive.&lt;/p&gt;

&lt;p&gt;If you start writing, it will make you stand out from the crowd. Even if you write poorly at the beginning, you won't be in the same position in 1 month from now or in a year from now. You'll be better. And you'll be happier. Your writing will attract and your content will make people want to work with you and get to know you.&lt;/p&gt;

&lt;p&gt;So stop being just a reader and become a writer! Get off your read-only mode and start putting content out there. And you'll be better off!&lt;/p&gt;

</description>
      <category>socialmedia</category>
      <category>contentwriting</category>
      <category>software</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Building a DataGrid with the right tools</title>
      <dc:creator>Radu Brehar👨‍💻</dc:creator>
      <pubDate>Wed, 04 Oct 2023 20:59:18 +0000</pubDate>
      <link>https://forem.com/radubrehar/building-a-datagrid-with-the-right-tools-dil</link>
      <guid>https://forem.com/radubrehar/building-a-datagrid-with-the-right-tools-dil</guid>
      <description>&lt;p&gt;Building for the browser has historically been very tedious. In the old days you had to resort to all sorts of hacks for getting the right layout - anyone remembers conditional comments targeting IE6-9? 😅&lt;/p&gt;

&lt;p&gt;Yeah, we don't miss those days either.&lt;/p&gt;

&lt;p&gt;Things have evolved in the last few years, and the amount of goodies JS/CSS/HTML/layout goodies we now take for granted is staggering. New CSS features like flex/grid/custom properties really make a difference. Also browser performance has improved a LOT, and today we can do things in the browser that were unthinkable just a few years ago.&lt;/p&gt;

&lt;p&gt;However, not everything is easier now than it was back in the browser-war days. Handling all kinds of devices, managing changing dependencies, configuring build tools, choosing the right styling approach, proper E2E testing, keeping a small bundle size, CI pipelines, etc. are all things that can (and will) go wrong if you don't have the right tools.&lt;/p&gt;

&lt;p&gt;In this article, we're sharing the tools we used to build &lt;a href="https://infinite-table.com"&gt;the InfiniteTable React DataGrid&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  TypeScript
&lt;/h2&gt;

&lt;p&gt;It's obvious today to just go with &lt;code&gt;TypeScript&lt;/code&gt;, but a few years ago, it was not as obvious. We've been using TypeScript for quite a few years now, and we're very happy with it. We can never imagine going back to plain JS.&lt;/p&gt;

&lt;h2&gt;
  
  
  React
&lt;/h2&gt;

&lt;p&gt;Building on top of &lt;code&gt;React&lt;/code&gt; has given us an amazing component model that's very composable and easy to reason about - and the ecosystem is huge.&lt;/p&gt;

&lt;p&gt;Read about our journey in the &lt;a href="https://infinite-table.com/blog/2022/11/08/why-another-datagrid"&gt;Why another DataGrid?&lt;/a&gt; blog post. Back when React was launching, many of our team members were writing DataGrids - either in vanilla JS or using some libraries (&lt;code&gt;jQuery&lt;/code&gt; anyone? - we don't miss browser incompatibilities).&lt;/p&gt;

&lt;h2&gt;
  
  
  CSS Variables and Vanilla Extract
&lt;/h2&gt;

&lt;p&gt;As a &lt;code&gt;DataGrid&lt;/code&gt; Infinite Table is built on top of CSS variables - we're going all in with CSS variables. They have a few gotchas in very advanced cases, but all-in-all they're amazing - and especially for performance.&lt;/p&gt;

&lt;p&gt;Infinite Table is not short of &lt;a href="https://infinite-table.com/docs/learn/theming/css-variables"&gt;CSS variables it exposes - see the full list&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Using CSS variables/custom properties has been pivotal not only to the ease of theming, but also to the performance of the DataGrid.&lt;br&gt;
Being able to change a CSS custom property on a single DOM element and then reuse it across many elements that are children of the first one is a huge performance win. Our DataGrid performance would not be the same without CSS variables.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://vanilla-extract.style/"&gt;Vanilla Extract&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;The single tool that has made our life a lot easier working with CSS is &lt;a href="https://vanilla-extract.style/"&gt;Vanilla Extract&lt;/a&gt;. If you're developing a component library, you should definitely use it! Not so much for simple &amp;amp; static apps - there are other styling solutions that are easier to use, like &lt;a href="https://tailwindcss.com/"&gt;tailwindCSS&lt;/a&gt;. But for component libraries, &lt;strong&gt;Vanilla Extract is amazing&lt;/strong&gt;!&lt;/p&gt;

&lt;p&gt;Did we mention it's amazing? 😅&lt;br&gt;
The fact that you can use TypeScript with it, can use "Find All References", see where everything is used is a huge win. You're not writing readonly CSS anymore - because that tends to be the case with most CSS. People are afraid to change it or remove old CSS code, just in case those rules are still being used or referenced somehow. This way, CSS only grows with time, and this is a code smell.&lt;/p&gt;

&lt;p&gt;With Vanilla Extract, you get to forget about that. You know what's being used and what's not.&lt;/p&gt;

&lt;p&gt;Also, hashing class names to avoid collisions is nice - and something now very common in the modern JS ecosystem. It all started with CSS modules, and now it's everywhere, Vanilla Extract included.&lt;/p&gt;

&lt;p&gt;Other great features we use extensively are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;public facing CSS variables - their names are stable&lt;/li&gt;
&lt;li&gt;private CSS variables - their names are hashed&lt;/li&gt;
&lt;li&gt;sharing CSS values with the TS codebase is a dream come true.&lt;/li&gt;
&lt;li&gt;Vanilla Extract recipes - generating and applying CSS classes based on a combination of properties. It's enough that you have 2-3 properties, each with a few values, and managing their combinations can be a pain. Vanilla Extract recipes manage this in a very elegant way.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  End-to-end testing with &lt;a href="https://playwright.dev/"&gt;Playwright&lt;/a&gt; and &lt;a href="https://nextjs.org/"&gt;NextJS&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Remember the days of Selenium? All those flaky tests, the slow execution, the hard to debug issues? They're gone!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://playwright.dev/"&gt;Playwright&lt;/a&gt; all the way! 300+ tests and going strong! Yes, you read that right! We have more than 300 tests making sure the all the DataGrid features are working as expected. Sorting, filtering, row grouping, column groups, pivoting, aggregations, lazy loading, live pagination, keyboard navigation, cell and row selection, theming - they're all tested! And we're not talking about unit tests, but end-to-end tests. We're testing the DataGrid in the browser, with real data just like real users would.&lt;/p&gt;

&lt;p&gt;Playwright is an amazing tool, but we're not using it standalone. Paired with a &lt;a href="https://nextjs.org/"&gt;NextJS&lt;/a&gt; app, with file-system based routing, we've created files/routes for each functionality. Each NextJS file in turn has a Playwright test file with the same name, but a different extension.&lt;/p&gt;

&lt;p&gt;This has the benefit that it's always very obvious which test is running against which page. The test and the route always have the same file name, just the extension is different. The test source-code doesn't explicitly contain code that navigates to a specific page, all this is done under the hood, using this simple convention.&lt;/p&gt;

&lt;p&gt;This way, we have a very clear separation of concerns, and it's very easy to add new tests. We just create a new file in the &lt;code&gt;pages&lt;/code&gt; folder, and a new test file sibling to it. Another amazing benefit is that we can start the NextJS app and point our browser to whatever page we want to see or debug and it's there. We can very easily do the actions the test is doing and see if we get the expected results. This is a huge win for debugging.&lt;/p&gt;

&lt;h2&gt;
  
  
  A tailored state management
&lt;/h2&gt;

&lt;p&gt;We've built a very simple yet highly effective state management solution for our DataGrid. It's built to make updating the internal state of the DataGrid as easy as possible - we want a simple API, with clear actions. Our actions map almost 1-to-1 to the DataGrid properties, which makes it very obvious to know who changed what.&lt;/p&gt;

&lt;p&gt;We can't overstate how important it is to have a clear data flow through the DataGrid. This is because the DataGrid is by far the most complex UI component you'll ever use (and we'll ever build). You can't possibly go beyond that - at least not in common business apps, where you have the normal UI controls you can expect, like inputs, buttons, dropdowns, etc. Just the ComboBox can come near the complexity of the DataGrid, but it's still far behind.&lt;/p&gt;

&lt;p&gt;It's important to be able to tame all this complexity - otherwise it can slow down the development process and bring it to a halt, making it difficult to add new features or fix bugs. With our current model, even though the DataGrid grew in complexity and features, we never felt our velocity dropping! We enjoy that!&lt;/p&gt;

&lt;h2&gt;
  
  
  No dependencies
&lt;/h2&gt;

&lt;p&gt;We're very proud of the fact that we have no dependencies in our DataGrid. When you install our package, you only install our package - and nothing else. Nothing that can go wrong due to version conflicts, missing dependencies, npm issues (&lt;a href="https://www.davidhaney.io/npm-left-pad-have-we-forgotten-how-to-program/"&gt;remember left-pad&lt;/a&gt;?).&lt;/p&gt;

&lt;p&gt;Yes, we still depend on packages in our dev process, but we're striving to keep that small as well. It's already complex enough to keep TS, React, NextJS, npm (with workspaces), aliases, esbuild, tsup, playwright all working together in harmony. But we've got through it, and we're very happy with the result. It was worth it!&lt;/p&gt;

&lt;h2&gt;
  
  
  Separating concerns
&lt;/h2&gt;

&lt;p&gt;We've separated our DataGrid into 2 main parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the &lt;code&gt;&amp;lt;DataSource /&amp;gt;&lt;/code&gt; component - handles data loading and processing&lt;/li&gt;
&lt;li&gt;the &lt;code&gt;&amp;lt;InfiniteTable /&amp;gt;&lt;/code&gt; component - handles the rendering&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This was a brilliant idea! It's new? No! It's not our invention, but we're happy we decided to apply it.&lt;/p&gt;

&lt;p&gt;It adds a better separation between the two big parts of the DataGrid. This also helps tame some of the complexity, while adding clarity to the codebase. It's easier to reason about the code when you know that the &lt;code&gt;&amp;lt;DataSource /&amp;gt;&lt;/code&gt; component is responsible for data loading and processing, while the &lt;code&gt;&amp;lt;InfiniteTable /&amp;gt;&lt;/code&gt; component is ONLY responsible for rendering.&lt;/p&gt;

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

&lt;p&gt;We're not sorry for choosing any of the above tools or approaches when building the InfiniteTable DataGrid component.&lt;/p&gt;

&lt;p&gt;Our developer velocity is high, and we're able to add new features and fix bugs at a fast pace. We're happy with the result and we're confident that we'll be able to keep this pace in the future.&lt;/p&gt;

&lt;p&gt;The right tools get the right job done! They make a lot easier. Looking back, we only regret we didn't have those tools 5 years ago - but hey, things are moving in the right direction, and we're happy to be part of this journey.&lt;/p&gt;

&lt;p&gt;What are your tools for developer productivity?&lt;/p&gt;

</description>
      <category>tooling</category>
      <category>javascript</category>
      <category>typescript</category>
      <category>css</category>
    </item>
    <item>
      <title>Staying focused in a distracted world</title>
      <dc:creator>Radu Brehar👨‍💻</dc:creator>
      <pubDate>Wed, 08 Feb 2023 13:45:58 +0000</pubDate>
      <link>https://forem.com/radubrehar/staying-focused-in-a-distracted-world-jcl</link>
      <guid>https://forem.com/radubrehar/staying-focused-in-a-distracted-world-jcl</guid>
      <description>&lt;p&gt;&lt;strong&gt;What happens when you stay focused on a problem for more than 10 years?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;✅ you understand the problem well enough to know there's no easy solution&lt;br&gt;
✅ you come up with multiple iterations, each better than previous&lt;br&gt;
✅ you see many solutions rising and almost as many failing to solve the problem&lt;br&gt;
✅ you get to sharpen your tools and your abilities like none other&lt;br&gt;
✅ you finally deliver the right product 🛳&lt;/p&gt;

&lt;p&gt;This has been my experience at &lt;a href="https://infinite-table.com" rel="noopener noreferrer"&gt;infinite-table.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>vibecoding</category>
      <category>ai</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Filtering Data with Infinite Table for React</title>
      <dc:creator>Radu Brehar👨‍💻</dc:creator>
      <pubDate>Fri, 27 Jan 2023 13:06:39 +0000</pubDate>
      <link>https://forem.com/radubrehar/filtering-data-with-infinite-table-for-react-3bce</link>
      <guid>https://forem.com/radubrehar/filtering-data-with-infinite-table-for-react-3bce</guid>
      <description>&lt;h2&gt;
  
  
  Why use &lt;a href="https://infinite-table.com"&gt;Infinite Table&lt;/a&gt; filters?
&lt;/h2&gt;

&lt;p&gt;1️⃣ Narrow down your data with your own filter types and operators&lt;/p&gt;

&lt;p&gt;2️⃣ Works both client-side and server-side&lt;/p&gt;

&lt;p&gt;3️⃣ Easy customization of filters and filter editors&lt;/p&gt;

&lt;p&gt;4️⃣ Optimized for performance&lt;/p&gt;

&lt;p&gt;5️⃣ Easy to use across multiple columns&lt;/p&gt;

&lt;p&gt;Filters were, by far, the most requested feature to add to Infinite Table after our initial launch. &lt;/p&gt;

&lt;p&gt;The recently-released version &lt;code&gt;1.1.0&lt;/code&gt; of Infinite Table for React introduces support for column filters, which work both client-side and server-side.&lt;/p&gt;

&lt;p&gt;In order to enable filtering - specify the &lt;a href="https://infinite-table.com/docs/reference/datasource-props#defaultFilterValue"&gt;&lt;code&gt;defaultFilterValue&lt;/code&gt;&lt;/a&gt; property on the &lt;code&gt;&amp;lt;DataSource /&amp;gt;&lt;/code&gt; component, as shown below&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;DataSource&lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="na"&gt;Developer&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  data=&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  primaryKey="id"
  defaultFilterValue=&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&amp;gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;InfiniteTable&lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="na"&gt;Developer&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    columns=&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;columns&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  /&amp;gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;DataSource&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This configures the &lt;code&gt;&amp;lt;DataSource /&amp;gt;&lt;/code&gt; component with an empty array of filters; columns will pick this up and each will display a filter editor in the column header.&lt;/p&gt;

&lt;p&gt;Of course, you can define some initial filters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;defaultFilterValue&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;field&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;age&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;number&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;operator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;40&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;You can see how all of this looks like when we put it all together in the examples below.&lt;/p&gt;

&lt;h2&gt;
  
  
  Local and Remote Filtering
&lt;/h2&gt;

&lt;p&gt;Because the &lt;code&gt;&amp;lt;DataSource /&amp;gt;&lt;/code&gt; &lt;a href="https://infinite-table.com/docs/reference/datasource-props#data"&gt;&lt;code&gt;data&lt;/code&gt;&lt;/a&gt; prop is a function that returns a &lt;code&gt;Promise&lt;/code&gt; with remote data, the filtering will happen server-side by default.&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://codesandbox.io/embed/infinite-table-with-remote-filters-i8b4wx"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;When using remote filtering, it's your responsability to send the DataSource &lt;a href="https://infinite-table.com/docs/reference/datasource-props#filterValue"&gt;&lt;code&gt;filterValue&lt;/code&gt;&lt;/a&gt; to the backend (you get this object as a parameter in your &lt;a href="https://infinite-table.com/docs/reference/datasource-props#data"&gt;&lt;code&gt;data&lt;/code&gt;&lt;/a&gt; function). This value includes for each column the value in the filter editor, the column filter type and the operator in use. In this case, the frontend and the backend need to agree on the operator names and what each one means.&lt;/p&gt;

&lt;blockquote&gt;
&lt;h3&gt;
  
  
  Data reloads when filters change
&lt;/h3&gt;

&lt;p&gt;Whenever filters change, when remote filtering is configured, the &lt;a href="https://infinite-table.com/docs/reference/datasource-props#data"&gt;&lt;code&gt;data&lt;/code&gt;&lt;/a&gt; function prop is called again, with an object that has the &lt;code&gt;filterValue&lt;/code&gt; correctly set to the current filters (together with &lt;code&gt;sortInfo&lt;/code&gt; and other data-related props like &lt;code&gt;groupBy&lt;/code&gt;, etc).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;However, we can use the &lt;a href="https://infinite-table.com/docs/reference/datasource-props#filterMode"&gt;&lt;code&gt;filterMode&lt;/code&gt;&lt;/a&gt; to force client-side filtering:&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;DataSource&lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="na"&gt;Developer&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  filterMode="local"
  filterDelay=&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also specify the &lt;a href="https://infinite-table.com/docs/reference/datasource-props#filterDelay"&gt;&lt;code&gt;filterDelay=0&lt;/code&gt;&lt;/a&gt; in order to perform filtering immediately, without debouncing and batching filter changes, for a quicker response ⚡️ 🏎&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://codesandbox.io/embed/infinite-table-with-client-side-filters-sqbdbu"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;blockquote&gt;
&lt;h3&gt;
  
  
  Using local filtering
&lt;/h3&gt;

&lt;p&gt;Even if your data is loaded from a remote source, using &lt;code&gt;filterMode="local"&lt;/code&gt; will perform all filtering on the client-side - so you don't need to send the &lt;code&gt;filterValue&lt;/code&gt; to the server in your &lt;code&gt;data&lt;/code&gt; function.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Defining Filter Types and Custom Filter Editors
&lt;/h2&gt;

&lt;p&gt;Currently there are 2 filter types available in Infinite Table:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;string&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;number&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Conceptually, you can think of filter types similar to data types - generally if two columns will have the same data type, they will display the same filter.&lt;/p&gt;

&lt;p&gt;Each filter type supports a number of operators and each operator has a name and can define it's own filtering function, which will be used when local filtering is used.&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://codesandbox.io/embed/infinite-table-filters-custom-editor-and-filter-type-3f9911"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;The example above, besides showing how to define &lt;a href="https://infinite-table.com/docs/reference/datasource-props#filterTypes"&gt;a custom filter type&lt;/a&gt;, also shows how to define a custom filter editor.&lt;/p&gt;

&lt;blockquote&gt;
&lt;h3&gt;
  
  
  Providing a Custom Filter Editor
&lt;/h3&gt;

&lt;p&gt;For defining a custom filter editor to be used in a filter type, we need to write a new React component that uses the &lt;a href="https://infinite-table.com/docs/reference/hooks#useInfiniteColumnFilterEditor"&gt;&lt;code&gt;useInfiniteColumnFilterEditor&lt;/code&gt;&lt;/a&gt; hook.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&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;useInfiniteColumnFilterEditor&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;@infinite-table/infinite-react&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;function&lt;/span&gt; &lt;span class="nx"&gt;BoolFilterEditor&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;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setValue&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useInfiniteColumnFilterEditor&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Developer&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;&amp;lt;&amp;gt;&lt;/span&gt;
    &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;This custom hook allows you to get the current &lt;code&gt;value&lt;/code&gt; of the filter and also to retrieve the &lt;code&gt;setValue&lt;/code&gt; function that we need to call when we want to update filtering.&lt;/p&gt;

&lt;p&gt;Read more about this &lt;a href="https://infinite-table.com/docs/learn/filtering/providing-a-custom-filter-editor"&gt;in the docs - how to provide a custom editor&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Customise Filterable Columns and Filter Icons
&lt;/h2&gt;

&lt;p&gt;Maybe you don't want all your columns to be filterable.&lt;/p&gt;

&lt;p&gt;For controlling which columns are filterable and which are not, use the &lt;a href="https://infinite-table.com/docs/reference/infinite-table-props#columns.defaultFilterable"&gt;&lt;code&gt;column.defaultFilterable&lt;/code&gt;&lt;/a&gt; property.&lt;/p&gt;

&lt;p&gt;This overrides the global &lt;a href="https://infinite-table.com/docs/reference/infinite-table-props#columnDefaultFilterable"&gt;&lt;code&gt;columnDefaultFilterable&lt;/code&gt;&lt;/a&gt; prop.&lt;/p&gt;

&lt;p&gt;We have also made it easy for you to customize the filter icon that is displayed in the column header.&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://codesandbox.io/embed/infinite-table-custom-filter-icon-jc7jr8"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;You change the filter icon by using the &lt;a href="https://infinite-table.com/docs/reference/infinite-table-props#columns.renderFilterIcon"&gt;&lt;code&gt;columns.renderFilterIcon&lt;/code&gt;&lt;/a&gt; prop - for full control, it's being called even when the column is not filtered, but you have a &lt;code&gt;filtered&lt;/code&gt; property on the argument the function is called with.&lt;/p&gt;

&lt;p&gt;In the example above, the &lt;code&gt;salary&lt;/code&gt; column is configured to render no filter icon, but the &lt;code&gt;header&lt;/code&gt; is customized to be bolded when the column is filtered.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ready for Your Challenge!
&lt;/h2&gt;

&lt;p&gt;We listened to your requests for advanced filtering.&lt;/p&gt;

&lt;p&gt;And we believe that we've come up with something that's really powerful and customizable.&lt;/p&gt;

&lt;p&gt;Now it's your turn to try it out and show us what you can build with it! 🚀&lt;/p&gt;

&lt;p&gt;If you have any questions, feel free to reach out to us on &lt;a href="https://twitter.com/infinite_table"&gt;Twitter&lt;/a&gt;, in the &lt;a href="https://github.com/infinite-table/infinite-react/discussions"&gt;GitHub Discussions&lt;/a&gt; or in the comments below!&lt;/p&gt;

&lt;p&gt;Make sure you try out filtering in Infinite Table for yourself (&lt;a href="https://infinite-table.com/docs/learn/filtering"&gt;and consult our extensive docs&lt;/a&gt; if required).&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
    <item>
      <title>📣 Infinite Table is Here 🎉</title>
      <dc:creator>Radu Brehar👨‍💻</dc:creator>
      <pubDate>Mon, 16 Jan 2023 09:35:45 +0000</pubDate>
      <link>https://forem.com/radubrehar/infinite-table-is-here-3f6h</link>
      <guid>https://forem.com/radubrehar/infinite-table-is-here-3f6h</guid>
      <description>&lt;h1&gt;
  
  
  Why use Infinite Table?
&lt;/h1&gt;

&lt;p&gt;1️⃣ seriously fast&lt;/p&gt;

&lt;p&gt;2️⃣ no empty or white rows while scrolling&lt;/p&gt;

&lt;p&gt;3️⃣ packed with features&lt;/p&gt;

&lt;p&gt;4️⃣ built from the ground up for React&lt;/p&gt;

&lt;p&gt;5️⃣ clear, concise and easily composable props &amp;amp; API&lt;/p&gt;

&lt;p&gt;We think you'll love Infinite Table. &lt;/p&gt;

&lt;p&gt;This is the DataGrid we would have loved to use more than 15 years ago when &lt;a href="https://infinite-table.com/blog/2022/11/08/why-another-datagrid" rel="noopener noreferrer"&gt;we started working with tables in the browser&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;And now it's finally here 🎉.&lt;/p&gt;

&lt;h2&gt;
  
  
  Built from the Ground Up with React &amp;amp; TypeScript
&lt;/h2&gt;

&lt;h3&gt;
  
  
  React all the Way
&lt;/h3&gt;

&lt;p&gt;Infinite Table feels native to React, not as a after-thought, but built with React fully in mind.&lt;/p&gt;

&lt;p&gt;It's declarative all the way and exposes everything as props, both controlled and uncontrolled.&lt;/p&gt;

&lt;p&gt;If you don't like the default behavior of a prop, use the controlled version and implement your own logic and handling - see for example the &lt;a href="https://infinite-table.com/docs/reference/infinite-table-props#search=columnorder" rel="noopener noreferrer"&gt;following props related to column order&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;columnOrder&lt;/code&gt; - controlled property for managing order of columns&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;defaultColumnOrder&lt;/code&gt; - uncontrolled version of the above&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;onColumnOrderChange&lt;/code&gt; - callback prop for notifications and for updating controlled column order&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Fully Controlled
&lt;/h3&gt;

&lt;p&gt;React introduced controlled components to the wider community and we've been using them for years.&lt;/p&gt;

&lt;p&gt;It's where the power of React lies - giving the developer the flexibility to fully control (when needed) every input point of an app or component.&lt;/p&gt;

&lt;p&gt;All the props which Infinite Table exposes, have both controlled and uncontrolled versions. This allows you to start using the component very quickly and without much effort, but also with the all-important flexibility to fully control the component when needed, as your app grows and you need more control over the DataGrid.&lt;/p&gt;

&lt;h3&gt;
  
  
  TypeScript &amp;amp; Generic Components
&lt;/h3&gt;

&lt;p&gt;Infinite Table is also built with TypeScript, giving you all the benefits of a great type system.&lt;/p&gt;

&lt;p&gt;In addition, the exposed components are exported as generic components, so you can specify the type of the data you're working with, for improved type safety.&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;InfiniteTable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;DataSource&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;@infinite-table/infinite-react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Person&lt;/span&gt; &lt;span class="o"&gt;=&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;number&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;age&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="kd"&gt;const&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;Person&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;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;John&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="c1"&gt;//...&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;columns&lt;/span&gt; &lt;span class="o"&gt;=&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;field&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="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;field&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// ready to render&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;DataSource&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Person&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;primaryKey&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;InfiniteTable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Person&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;columns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/DataSource&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why Use Infinite Table, cont.
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Fast - virtualization
&lt;/h3&gt;

&lt;p&gt;Infinite Table is fast by leveraging &lt;strong&gt;virtualization&lt;/strong&gt; both &lt;strong&gt;vertically&lt;/strong&gt; (for rows) and &lt;strong&gt;horizontally&lt;/strong&gt; (for columns). &lt;/p&gt;

&lt;p&gt;This means DOM nodes are created only for the visible cells, thus reducing the number of DOM nodes and associated memory strain and improving performance.&lt;/p&gt;

&lt;h3&gt;
  
  
  No white space while scrolling - clever layout &amp;amp; rendering
&lt;/h3&gt;

&lt;p&gt;In addition to virtualization, we use clever layout &amp;amp; rendering techniques to avoid white space while scrolling.&lt;/p&gt;

&lt;p&gt;When you scroll, the table will not show any empty rows or white space - no matter how fast you're scrolling!&lt;/p&gt;

&lt;p&gt;We think this is one of the features that sets us apart from other components.&lt;/p&gt;

&lt;p&gt;We've spent a lot of time and effort making sure no whitespace is visible while scrolling the table.&lt;/p&gt;

&lt;h2&gt;
  
  
  Batteries Included
&lt;/h2&gt;

&lt;p&gt;We want you to be productive immediately and stop worrying about the basics. Infinite Table comes with a lot of features out of the box, so you can focus on the important stuff.&lt;/p&gt;

&lt;p&gt;It helps you display huge datasets and get the most out of your data by providing you the right tools to enjoy these features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://infinite-table.com/docs/learn/working-with-data/sorting" rel="noopener noreferrer"&gt;sorting&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://infinite-table.com/docs/learn/grouping-and-pivoting/grouping-rows" rel="noopener noreferrer"&gt;row grouping&lt;/a&gt; - both server-side and client-side&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://infinite-table.com/docs/learn/grouping-and-pivoting/pivoting/overview" rel="noopener noreferrer"&gt;pivoting&lt;/a&gt; - both server-side and client-side&lt;/li&gt;
&lt;li&gt;&lt;a href="https://infinite-table.com/docs/learn/grouping-and-pivoting/grouping-rows#aggregations" rel="noopener noreferrer"&gt;aggregations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://infinite-table.com/docs/learn/working-with-data/live-pagination" rel="noopener noreferrer"&gt;live pagination&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://infinite-table.com/docs/learn/working-with-data/lazy-loading" rel="noopener noreferrer"&gt;lazy loading&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://infinite-table.com/docs/learn/keyboard-navigation/navigating-cells" rel="noopener noreferrer"&gt;keyboard navigation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://infinite-table.com/docs/learn/columns/fixed-and-flexible-size" rel="noopener noreferrer"&gt;fixed and flexible columns&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://infinite-table.com/docs/learn/column-groups" rel="noopener noreferrer"&gt;column grouping&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://infinite-table.com/docs/learn/theming" rel="noopener noreferrer"&gt;theming&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;... and many others&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Infinite Table is built for companies and individuals who want to ship — faster 🏎!&lt;/p&gt;

&lt;h2&gt;
  
  
  (Almost) No External Dependencies
&lt;/h2&gt;

&lt;p&gt;We've implemented everything from scratch and only directly depend on 2 packages (we'll probably get rid of them as well in the future) - all our dependecy graph totals a mere 3 packages.&lt;/p&gt;

&lt;p&gt;We've reduced external dependencies for 2 main reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;avoid security issues with dependencies (or dependencies of dependencies...you know it) - remember left-pad?&lt;/li&gt;
&lt;li&gt;keep the bundle size small&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Composable API - with a small surface
&lt;/h2&gt;

&lt;p&gt;When building a component of this scale, there are two major opposing forces: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;adding functionality&lt;/li&gt;
&lt;li&gt;keeping the component (and the API) simple&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We're continually trying to reconcile both with Infinite Table, so we've built everything with composition in mind.&lt;/p&gt;

&lt;p&gt;A practical example of composition is favouring function props instead of boolean flags or objects. Why implement a feature under a boolean flag or a static object when you can expose a functionality via a function prop? The function prop can be used to handle more cases than any boolean flag could ever handle!&lt;/p&gt;

&lt;p&gt;A good example of composability is the &lt;code&gt;groupColumn&lt;/code&gt; prop which controls the columns that are generated for grouping.&lt;/p&gt;

&lt;p&gt;It can be either a column object or a function:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;when it's a column object, it makes the table render a single column for grouping (as if &lt;code&gt;groupRenderStrategy&lt;/code&gt; was set to &lt;code&gt;"single-column"&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;when it's a function, it behaves like &lt;code&gt;groupRenderStrategy&lt;/code&gt; is set to &lt;code&gt;"multi-column"&lt;/code&gt; and it's being called for each of the generated columns.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;InfiniteTable&lt;/span&gt; 
  &lt;span class="c1"&gt;//...&lt;/span&gt;
  &lt;span class="na"&gt;groupColumn&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;header&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Groups&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;vs&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;InfiniteTable&lt;/span&gt; 
  &lt;span class="c1"&gt;//...&lt;/span&gt;
  &lt;span class="na"&gt;groupColumn&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// this allows you to affect all generated group columns in a single place&lt;/span&gt;
    &lt;span class="c1"&gt;// especially useful when the generated columns are dynamic or generated via a pivot&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;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We've learned from our experience with other DataGrid components that the more features you add, the more complex your API becomes. So we tried to keep the API surface as small as possible, while still offering a rich set of declarative props as building blocks that can be composed to accomplish more complex functionalities.&lt;/p&gt;

&lt;h2&gt;
  
  
  Built for the community, available on NPM
&lt;/h2&gt;

&lt;p&gt;We're thrilled to share Infinite Table with the world.&lt;/p&gt;

&lt;p&gt;We wanted to make it very easy for everyone to &lt;a href="https://infinite-table.com/docs/learn/getting-started" rel="noopener noreferrer"&gt;get started&lt;/a&gt; with it, so all you require is just an npm install:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i @infinite-table/infinite-react
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The component will show a footer with a &lt;a href="https://infinite-table.com" rel="noopener noreferrer"&gt;Powered by Infinite Table&lt;/a&gt; link displayed. However, all the functionalities are still available and fully working. So if you keep the link visible, you can use the component for free in any setup!&lt;/p&gt;

&lt;p&gt;Although you can use Infinite Table for free, we encourage you to &lt;a href="https://infinite-table.com/pricing" rel="noopener noreferrer"&gt;purchase a license&lt;/a&gt; - buying a license will remove the footer link. This will help us keep delivering new features and improvements to the component and support you and your team going forward!&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>api</category>
      <category>tooling</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
