<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Sergey's Developer Notes]]></title><description><![CDATA[Sergey's Developer Notes]]></description><link>https://notes.dunaevskiy.dev</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1718485170154/254830b8-c628-4745-8a9d-56de6c1cb9cd.png</url><title>Sergey&apos;s Developer Notes</title><link>https://notes.dunaevskiy.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Tue, 12 May 2026 04:18:23 GMT</lastBuildDate><atom:link href="https://notes.dunaevskiy.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[TanStack Query and Mutation templates for React RSC/SSR/RCC]]></title><description><![CDATA[Recommendations on how to structure TanStack queries and mutations. Provided templates define hooks with extensible interfaces, supporting custom fetch functions per request. RSC/SSR/RCC compatible.
Expectations

Compatible with some RSC/SSR framewor...]]></description><link>https://notes.dunaevskiy.dev/tanstack-query-and-mutation-templates-for-react-rscssrrcc</link><guid isPermaLink="true">https://notes.dunaevskiy.dev/tanstack-query-and-mutation-templates-for-react-rscssrrcc</guid><category><![CDATA[React]]></category><category><![CDATA[tanstack-query]]></category><category><![CDATA[convention]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[template]]></category><dc:creator><![CDATA[Sergey Dunaevskiy]]></dc:creator><pubDate>Fri, 27 Sep 2024 18:33:52 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1727458913371/29c3cfe1-01f7-495e-964b-402be21ee35e.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>Recommendations on how to structure TanStack queries and mutations. Provided templates define hooks with extensible interfaces, supporting custom fetch functions per request. RSC/SSR/RCC compatible.</em></p>
<h2 id="heading-expectations">Expectations</h2>
<ul>
<li><p>Compatible with some <strong>RSC/SSR</strong> frameworks - <strong>fetch requests should exist independently</strong>.</p>
</li>
<li><p>Good <strong>invalidation possibilities</strong> - prefix and predicate statements defined in one place.</p>
</li>
<li><p><a target="_blank" href="https://github.com/axios/axios"><strong>Axios</strong></a> library (with a simple replacement with native fetch, e.g. in Next.js case).</p>
</li>
<li><p>There is no way to generate hooks for Your project, e.g. via GraphQL <a target="_blank" href="https://the-guild.dev/graphql/codegen">Codegen</a>. See <a target="_blank" href="https://tanstack.com/query/latest/docs/framework/react/community/community-projects">TanStack Docs</a>.</p>
</li>
</ul>
<h2 id="heading-project-structure">Project structure</h2>
<ul>
<li><p><strong>Globally shared hooks</strong></p>
<ul>
<li><p>One folder to rule them all: <code>~/src/queries</code> (or separate mutations into their own <code>~/src/mutations</code> folder).</p>
</li>
<li><p>It is somehow similar to the result of a code generator.</p>
</li>
</ul>
</li>
<li><p><strong>Component-based</strong> hooks</p>
<ul>
<li><p>The main concept is high cohesion - hooks are placed according to usage as near as possible to the components that import them.</p>
</li>
<li><p>For more details, see <a target="_blank" href="https://notes.dunaevskiy.dev/react-folder-structure-recommendations-based-on-nextjs-approuter">React folder structure</a>.</p>
</li>
</ul>
</li>
</ul>
<p>Each file is named according to its react hook and exports all types, fetch requests and the hook itself (or query option). For example, we have a REST endpoint <code>GET /api/v1/users</code>. Method <code>GET</code> and unique entity name <code>Users</code> are included in the final hook name.</p>
<center><h3>useQuery(HttpMethod)(Entity).tsx</h3><h3>useQueryGetUsers.tsx</h3></center>

<p>The same naming convention is applied to mutations:</p>
<center><h3>useMutation(HttpMethod)(Entity).tsx</h3><h3>useMutationPatchUser.tsx</h3></center>

<p>We also need to manage and store query keys and invalidation groups in case of queries. <strong>Never define them inside hooks.</strong> The whole set of keys EOT will become a mess in this case. Query keys are similar to primary keys in databases, so it is good to normalize them. See database normal forms - <a target="_blank" href="https://www.geeksforgeeks.org/normal-forms-in-dbms/">1NF, 2NF, and 3NF</a> (normalization is not required, but it helps a lot).</p>
<h2 id="heading-query-keys-and-invalidation-groups">Query keys and invalidation groups</h2>
<p>It is a good idea to define one file that will manage all query keys across an app.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// ~/src/configs/tsq.ts</span>

<span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { Query, QueryKey } <span class="hljs-keyword">from</span> <span class="hljs-string">'@tanstack/react-query'</span>;

<span class="hljs-keyword">type</span> Filters = { [key: <span class="hljs-built_in">string</span>]: <span class="hljs-built_in">string</span> | <span class="hljs-built_in">number</span> | <span class="hljs-literal">undefined</span> };
<span class="hljs-keyword">type</span> QueryKeyList = { [key: <span class="hljs-built_in">string</span>]: <span class="hljs-function">(<span class="hljs-params">...args: <span class="hljs-built_in">never</span>[]</span>) =&gt;</span> QueryKey };
<span class="hljs-keyword">type</span> QueryKeyPredicate = <span class="hljs-function">(<span class="hljs-params">query: Query</span>) =&gt;</span> <span class="hljs-built_in">boolean</span>;
<span class="hljs-keyword">type</span> QueryKeyInvalidationList = {
  [key: <span class="hljs-built_in">string</span>]: QueryKey | QueryKeyPredicate | (<span class="hljs-function">(<span class="hljs-params">...args: <span class="hljs-built_in">never</span>[]</span>) =&gt;</span> QueryKeyPredicate);
};

<span class="hljs-comment">/**
 * List of QueryKeys
 */</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> QK = {
  <span class="hljs-comment">// Examples</span>
  <span class="hljs-comment">// getSomeEntity:                  ()                                =&gt; ['group', 'key'],</span>
  <span class="hljs-comment">// getSomeEntityWithFilter:        (filters: Filters)                =&gt; ['group', 'key', filters],</span>
} <span class="hljs-keyword">as</span> <span class="hljs-keyword">const</span> satisfies QueryKeyList;

<span class="hljs-comment">/**
 * Query invalidators (by prefix and by predicate)
 * See https://tanstack.com/query/latest/docs/framework/react/guides/query-invalidation
 */</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> QK_INVALIDATION = {
  <span class="hljs-comment">// Examples</span>
  <span class="hljs-comment">// prefixKeyFromQK:   QK.getSomeEntity(),</span>
  <span class="hljs-comment">// prefixKeyCustom:   ['group'],</span>
  <span class="hljs-comment">// predicateKey:      (query: Query) =&gt; query.queryKey.includes('organizationContext'),</span>
  <span class="hljs-comment">// genPredicateKey:   (pageSize: number) =&gt; (query: Query) =&gt; (query.queryKey[1] as {[key: string]: unknown})?.pageSize === pageSize,</span>
} <span class="hljs-keyword">as</span> <span class="hljs-keyword">const</span> satisfies QueryKeyInvalidationList;
</code></pre>
<h2 id="heading-usequery-template">useQuery template</h2>
<pre><code class="lang-typescript"><span class="hljs-comment">// ~/src/queries/useQueryGetEntity.ts</span>

<span class="hljs-keyword">import</span> { keepPreviousData, queryOptions } <span class="hljs-keyword">from</span> <span class="hljs-string">'@tanstack/react-query'</span>;
<span class="hljs-keyword">import</span> { AxiosInstance } <span class="hljs-keyword">from</span> <span class="hljs-string">'axios'</span>;
<span class="hljs-keyword">import</span> { QK } <span class="hljs-keyword">from</span> <span class="hljs-string">'~/src/configs/tsq.ts'</span>;

<span class="hljs-comment">/**
 * useQuery&lt;MethodEntity&gt;.ts
 *
 * &lt;MethodEntity&gt; | &lt;methodEntity&gt;
 * Choose a method from API: GET, POST, PUT, DELETE, PATCH, ... that represents action.
 * Choose an entity name: User, OrderList, Orders, InstitutionEnum, ... that represents a resource.
 * These names are used to create a name &lt;MethodEntity&gt; for the API endpoints: GetInstitutionEnum, PostUser, ...
 */</span>

<span class="hljs-comment">/**
 * &lt;MethodEntity&gt;RequestData (optional)
 * Request data - used for a request creation.
 */</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">type</span> GetEntityRequestData = {
  id: <span class="hljs-built_in">number</span>;
  queryParams: {
    <span class="hljs-comment">// Pagination</span>
    page: <span class="hljs-built_in">number</span>;
    limit: <span class="hljs-number">10</span> | <span class="hljs-number">25</span> | <span class="hljs-number">50</span> | <span class="hljs-number">100</span>;
    <span class="hljs-comment">// Filters</span>
    search?: <span class="hljs-built_in">string</span>;
  };
};

<span class="hljs-comment">/**
 * &lt;MethodEntity&gt;ResponseData (required)
 * Response data - returned from the server (inside body).
 * This type should not be declared anywhere else and should not use any external type declarations (out of this file).
 * This file is a single source of truth that defines a behavior of the endpoint.
 */</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">type</span> GetEntityResponseData = {
  id: <span class="hljs-built_in">number</span>;
  name: <span class="hljs-built_in">string</span>;
  quantity: <span class="hljs-built_in">number</span>;
};

<span class="hljs-comment">/**
 * &lt;methodEntity&gt;Request (required)
 * Request - stand-alone request. For SSR and RSC pre-fetch purposes.
 * @param axios - Axios client. It is parametrized to allow SSR and RSC to use different Axios instances.
 * @param requestData - Static data used for request creation.
 * @returns Callable function for a request.
 */</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getEntityRequest =
  <span class="hljs-function">(<span class="hljs-params">{ axios, requestData }: { axios: AxiosInstance; requestData: GetEntityRequestData }</span>) =&gt;</span>
  () =&gt;
    axios.get&lt;GetEntityResponseData&gt;(<span class="hljs-string">`/api/mock/users/<span class="hljs-subst">${requestData.id}</span>`</span>).then(<span class="hljs-function">(<span class="hljs-params">res</span>) =&gt;</span> res.data);

<span class="hljs-comment">/**
 * &lt;MethodEntity&gt;QueryRequestData (optional)
 * Query options - used for query config creation (server prefetch and client fetch).
 */</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">type</span> GetEntityQueryRequestData = {
  requestData: GetEntityRequestData; <span class="hljs-comment">// required if request is parametrized</span>
};

<span class="hljs-comment">/**
 * &lt;methodEntity&gt;QueryOptions (required)
 * Query options - used for query creation (server prefetch and client fetch).
 * @param axios - Axios client.
 * @param queryRequestData - Static data used for query creation. Includes request data.
 * @param select - Custom selector.
 */</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getEntityQueryOptions = &lt;T&gt;({
  axios,
  queryRequestData,
  select,
}: {
  axios: AxiosInstance;
  queryRequestData: GetEntityQueryRequestData;
  select?: <span class="hljs-function">(<span class="hljs-params">data: GetEntityResponseData</span>) =&gt;</span> T;
}) =&gt;
  queryOptions({
    queryKey: QK.enumProducts(),
    queryFn: getEntityRequest({ axios, requestData: queryRequestData.requestData }),
    placeholderData: keepPreviousData,
    select,
  });

<span class="hljs-comment">// Custom selectors that can be used with query options.</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> selectGetEntityItemsQuantity = <span class="hljs-function">(<span class="hljs-params">data: GetEntityResponseData</span>) =&gt;</span> data.quantity;
</code></pre>
<h2 id="heading-usemutation-template">useMutation template</h2>
<pre><code class="lang-typescript"><span class="hljs-comment">// ~/src/mutations/useMutationPatchEntity.ts</span>

<span class="hljs-keyword">import</span> { useMutation } <span class="hljs-keyword">from</span> <span class="hljs-string">'@tanstack/react-query'</span>;
<span class="hljs-keyword">import</span> { AxiosInstance } <span class="hljs-keyword">from</span> <span class="hljs-string">'axios'</span>;

<span class="hljs-comment">/**
 * useQuery&lt;MethodEntity&gt;.ts
 *
 * &lt;MethodEntity&gt; | &lt;methodEntity&gt;
 * Choose a method from API: GET, POST, PUT, DELETE, PATCH, ... that represents action.
 * Choose an entity name: User, OrderList, Orders, InstitutionEnum, ... that represents a resource.
 * These names are used to create a name &lt;MethodEntity&gt; for the API endpoints: GetInstitutionEnum, PostUser, ...
 */</span>

<span class="hljs-comment">/**
 * &lt;MethodEntity&gt;RequestStaticData (optional)
 * Request static data - used for a request creation. Data not changing with each request (e.g. ID of the entity).
 */</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">type</span> PatchEntityRequestStaticData = {
  id: <span class="hljs-built_in">number</span>;
};

<span class="hljs-comment">/**
 * &lt;MethodEntity&gt;RequestData (optional)
 * Request dynamic data - used for a request call (e.g. user input).
 */</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">type</span> PatchEntityRequestDynamicData = {
  name: <span class="hljs-built_in">string</span>;
};

<span class="hljs-comment">/**
 * &lt;MethodEntity&gt;ResponseData (required)
 * Response data - returned from the server (inside body).
 * This type should not be declared anywhere else and should not use any external type declarations (out of this file).
 * This file is a single source of truth that defines a behavior of the endpoint.
 */</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">type</span> PatchEntityResponseData = {
  id: <span class="hljs-built_in">number</span>;
  name: <span class="hljs-built_in">string</span>;
};

<span class="hljs-comment">/**
 * &lt;methodEntity&gt;Request (required)
 * Request - stand-alone fetch.
 * @param axios - Axios client
 * @param requestStaticData - Request static data
 * @param () =&gt; requestDynamicData - Request dynamic data
 */</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> patchEntityRequest =
  <span class="hljs-function">(<span class="hljs-params">{
    axios,
    requestStaticData,
  }: {
    axios: AxiosInstance;
    requestStaticData: PatchEntityRequestStaticData;
  }</span>) =&gt;</span>
  (requestDynamicData: PatchEntityRequestDynamicData) =&gt;
    axios
      .patch&lt;PatchEntityResponseData&gt;(<span class="hljs-string">`/api/mock/users/<span class="hljs-subst">${requestStaticData.id}</span>`</span>, requestDynamicData)
      .then(<span class="hljs-function">(<span class="hljs-params">res</span>) =&gt;</span> res.data);

<span class="hljs-comment">/**
 *
 * &lt;methodEntity&gt;MutationStaticData (optional)
 * Mutation static data - used for mutation and request creation.
 */</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">type</span> PatchEntityMutationRequestData = {
  requestStaticData: PatchEntityRequestStaticData;
};

<span class="hljs-comment">/**
 * useMutation&lt;MethodEntity&gt; (required)
 * React hook - used for mutation creation.
 * @param axios - Axios client.
 * @param mutationStaticData - Static data used for mutation creation.
 */</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> useMutationPatchEntity = <span class="hljs-function">(<span class="hljs-params">{
  axios,
  mutationStaticData,
}: {
  axios: AxiosInstance;
  mutationStaticData: PatchEntityMutationRequestData;
}</span>) =&gt;</span>
  useMutation({
    mutationFn: patchEntityRequest({
      axios,
      requestStaticData: mutationStaticData.requestStaticData,
    }),
  });
</code></pre>
<h2 id="heading-usage">Usage</h2>
<p>Both templates are almost agnostic and should be suited for each request. Let's say that we have an endpoint for user private projects:</p>
<p><code>GET /api/v2/users/project?type=private</code></p>
<p>Our hook is called <code>useQueryUserProjects</code> to make it universal because of the possibility of some public projects, etc. We need static data with a project type, a response type is generated from OpenAPI definition, and there is no need to configure fetch request with each hook call (only request itself, because of server calls):</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// ~/src/configs/tsq.ts</span>

<span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { Query, QueryKey } <span class="hljs-keyword">from</span> <span class="hljs-string">'@tanstack/react-query'</span>;

<span class="hljs-keyword">type</span> Filters = { [key: <span class="hljs-built_in">string</span>]: <span class="hljs-built_in">string</span> | <span class="hljs-built_in">number</span> | <span class="hljs-literal">undefined</span> };
<span class="hljs-keyword">type</span> QueryKeyList = { [key: <span class="hljs-built_in">string</span>]: <span class="hljs-function">(<span class="hljs-params">...args: <span class="hljs-built_in">never</span>[]</span>) =&gt;</span> QueryKey };
<span class="hljs-keyword">type</span> QueryKeyPredicate = <span class="hljs-function">(<span class="hljs-params">query: Query</span>) =&gt;</span> <span class="hljs-built_in">boolean</span>;
<span class="hljs-keyword">type</span> QueryKeyInvalidationList = {
  [key: <span class="hljs-built_in">string</span>]: QueryKey | QueryKeyPredicate | (<span class="hljs-function">(<span class="hljs-params">...args: <span class="hljs-built_in">never</span>[]</span>) =&gt;</span> QueryKeyPredicate);
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> QK = {
  <span class="hljs-comment">// ...</span>
  getUserProjects:  <span class="hljs-function">(<span class="hljs-params">filters: Filters</span>) =&gt;</span> [<span class="hljs-string">'users'</span>, <span class="hljs-string">'projects'</span>, filters],
} <span class="hljs-keyword">as</span> <span class="hljs-keyword">const</span> satisfies QueryKeyList;

<span class="hljs-comment">// optional</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> QK_INVALIDATION = {
  prefixUsers: [<span class="hljs-string">'users'</span>],
  prefixUserProjects: [<span class="hljs-string">'users'</span>, <span class="hljs-string">'projects'</span>],
} <span class="hljs-keyword">as</span> <span class="hljs-keyword">const</span> satisfies QueryKeyInvalidationList;
</code></pre>
<pre><code class="lang-typescript"><span class="hljs-comment">// ~/src/queries/useQueryGetUserProjects.ts</span>

<span class="hljs-keyword">import</span> { keepPreviousData, queryOptions } <span class="hljs-keyword">from</span> <span class="hljs-string">'@tanstack/react-query'</span>;
<span class="hljs-keyword">import</span> { AxiosInstance } <span class="hljs-keyword">from</span> <span class="hljs-string">'axios'</span>;
<span class="hljs-keyword">import</span> ApiTypes <span class="hljs-keyword">from</span> <span class="hljs-string">'~/swagger-types'</span>;
<span class="hljs-keyword">import</span> { QK } <span class="hljs-keyword">from</span> <span class="hljs-string">'~/src/configs/tsq'</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">type</span> GetUserProjectsRequestData = {
    <span class="hljs-keyword">type</span>?: <span class="hljs-built_in">string</span>;
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">type</span> GetUserProjectsResponseData =
    ApiTypes.paths[<span class="hljs-string">'/api/v2/users/project'</span>][<span class="hljs-string">'get'</span>][<span class="hljs-string">'responses'</span>][<span class="hljs-string">'200'</span>][<span class="hljs-string">'content'</span>][<span class="hljs-string">'application/json'</span>];

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getUserProjectsRequest =
    <span class="hljs-function">(<span class="hljs-params">{ axios, requestData }: { axios: AxiosInstance; requestData: GetUserProjectsRequestData }</span>) =&gt;</span>
    () =&gt;
        axios
            .get&lt;GetUserProjectsResponseData&gt;(<span class="hljs-string">'/api/v2/users/project'</span>, {
                params: {
                    <span class="hljs-keyword">type</span>: requestData.type,
                },
            })
            .then(<span class="hljs-function">(<span class="hljs-params">res</span>) =&gt;</span> res.data);

<span class="hljs-keyword">export</span> <span class="hljs-keyword">type</span> GetUserProjectsQueryRequestData = {
    requestData: GetUserProjectsRequestData;
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getUserProjectsQueryOptions = &lt;T&gt;({
  axios,
  queryRequestData,
  select,
}: {
  axios: AxiosInstance;
  queryRequestData: GetUserProjectsQueryRequestData;
  select?: <span class="hljs-function">(<span class="hljs-params">data: GetUserProjectsResponseData</span>) =&gt;</span> T;
}) =&gt;
  queryOptions({
    queryKey: QK.getUserProjects({ <span class="hljs-keyword">type</span>: queryRequestData.requestData.type }), 
    queryFn: getUserProjectsRequest({ axios, requestData: queryRequestData.requestData }),
    placeholderData: keepPreviousData,
    select,
  });
</code></pre>
<pre><code class="lang-typescript"><span class="hljs-comment">// Component.tsx</span>

<span class="hljs-keyword">import</span> axiosClient = <span class="hljs-string">'~/axios'</span>;
<span class="hljs-keyword">import</span> { getUserProjectsQueryOptions } <span class="hljs-keyword">from</span> <span class="hljs-string">'~/src/queries/useQueryGetUserProjects.ts'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> Component = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> { data: dataGetUserProjects, isFetching: isFetchingGetUserProjects } = useQuery(
    getUserProjectsQueryOptions({
      axios: axiosClient,
      queryRequestData: {
        requestData: {
          <span class="hljs-keyword">type</span>: <span class="hljs-string">'private'</span>
        },
      },
    }),
  );

  <span class="hljs-keyword">return</span> &lt;&gt;...&lt;/&gt;;
};
</code></pre>
<p>The original interface is preserved - it has consistency, and it is easy to extend. At first, it may feel like there is a lot of code for just one simple request - yes, deal with it. It may be shorter, but then it is less extendable, and EOT each request is going to have its own special way of calling it.</p>
]]></content:encoded></item><item><title><![CDATA[Cheat Sheet: ni - universal CLI alias for npm/yarn/pnpm/bun]]></title><description><![CDATA[Tool ni is a utility that removes the need to recognize a type of package manager in JS projects (npm, yarn, pnpm, bun). It provides a unified interface and uses appropriate commands instead (calls yarn in yarn projects, npm in npm projects, etc.).
L...]]></description><link>https://notes.dunaevskiy.dev/cheat-sheet-ni-universal-cli-alias-for-npmyarnpnpmbun</link><guid isPermaLink="true">https://notes.dunaevskiy.dev/cheat-sheet-ni-universal-cli-alias-for-npmyarnpnpmbun</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[package manager]]></category><category><![CDATA[TypeScript]]></category><dc:creator><![CDATA[Sergey Dunaevskiy]]></dc:creator><pubDate>Sun, 11 Aug 2024 21:19:47 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1723409473383/47228671-cdad-4326-939f-156746b1a370.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>Tool</em> <code>ni</code> <em>is a utility that removes the need to recognize a type of package manager in JS projects (npm, yarn, pnpm, bun). It provides a unified interface and uses appropriate commands instead (calls</em> <code>yarn</code> in yarn projects, <code>npm</code> in npm projects, etc.<em>).</em></p>
<p>Let's assume getting a project with an unknown manager (at least for the first 5 seconds) or just a situation when we work with multiple projects having different managers. It may become a little bit annoying to pay attention to which manager we need.</p>
<p>The <code>ni</code> is a CLI utility that recognizes a project package manager according to its lock file (<code>yarn.lock</code>, <code>package-lock.json</code>, etc.) and uses appropriate commands. At least basic ones that could be unified.</p>
<h2 id="heading-installation">Installation</h2>
<pre><code class="lang-bash">npm i -g @antfu/ni
</code></pre>
<p>Also, it is better to create a config <code>~/.nirc</code> with default values.</p>
<pre><code class="lang-bash">defaultAgent=npm
globalAgent=npm
</code></pre>
<h2 id="heading-usage">Usage</h2>
<p>Examples below are based on the <code>yarn</code> commands.</p>
<pre><code class="lang-bash">ni      <span class="hljs-comment"># yarn install</span>
ni X    <span class="hljs-comment"># yarn add X</span>
ni -D X <span class="hljs-comment"># yarn add -D X</span>
nr dev  <span class="hljs-comment"># yarn run dev</span>
na      <span class="hljs-comment"># yarn</span>
</code></pre>
<p>A full list is defined at <a target="_blank" href="https://github.com/antfu-collective/ni">README.md</a> of the library.</p>
<p>It is nothing too special, but it removes a tiny annoying part during app development.</p>
]]></content:encoded></item><item><title><![CDATA[React folder structure recommendations (based on Next.js AppRouter)]]></title><description><![CDATA[Various React applications require various structures. Let's define a set of advice on how to think about project organisation and apply it to a medium-sized project.
These recommendations aren't a silver bullet for every project. Apart from the "cas...]]></description><link>https://notes.dunaevskiy.dev/react-folder-structure-recommendations-based-on-nextjs-approuter</link><guid isPermaLink="true">https://notes.dunaevskiy.dev/react-folder-structure-recommendations-based-on-nextjs-approuter</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[Next.js]]></category><category><![CDATA[React]]></category><category><![CDATA[Folder Structure]]></category><dc:creator><![CDATA[Sergey Dunaevskiy]]></dc:creator><pubDate>Sat, 22 Jun 2024 22:55:04 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1719093474128/ec3b1f8e-c786-4543-85e7-7d63954fe6c0.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>Various React applications require various structures. Let's define a set of advice on how to think about project organisation and apply it to a medium-sized project.</em></p>
<p>These recommendations aren't a silver bullet for every project. Apart from the "casual one", which is described below, library-structured monorepo projects, micro frontends, etc., have their own issues and requirements. <strong>Each project needs to consider complexity, future development, available budget and other aspects</strong>.</p>
<p>Let's discuss a project that could become a medium-sized app with hundreds of components. Consider Next.js, AppRouter, without workspaces and a comfortable budget:</p>
<ul>
<li><p><strong>define potential issues to be avoided or reduced,</strong></p>
</li>
<li><p><strong>define axioms, rules and suitable design patterns,</strong></p>
</li>
<li><p><strong>go through different use cases with implementation.</strong></p>
</li>
</ul>
<h2 id="heading-issues-to-be-avoided-or-reduced">Issues to be avoided or reduced</h2>
<ul>
<li><p>Variables/functions/components are imported from "random" places in the project, which makes it hard to understand <strong>which parts of the app may be affected by changes</strong>.</p>
</li>
<li><p>An unreasonable number of files in one directory makes the whole project messy.</p>
</li>
<li><p>Exporting everything from everywhere makes it unclear which components can be used without context (e.g. <code>NavItem</code> is dependent on <code>Nav</code>).</p>
</li>
<li><p>Too high or too low granularity in terms of code per file.</p>
</li>
</ul>
<h2 id="heading-expectations">Expectations</h2>
<ul>
<li><p>A project can contain hundreds of pages, and <strong>it is obvious where components are used</strong>.</p>
</li>
<li><p>Folder structure may be used with or without Next.js.</p>
</li>
<li><p>Understandable for juniors - a clear structure of folders, files and file naming.</p>
</li>
</ul>
<h2 id="heading-design-patterns-amp-concepts">Design patterns &amp; concepts</h2>
<ul>
<li><p><strong>Component-based principle</strong> – components emphasise a separation of concerns and distribute themselves as a <strong>black-box</strong> functionality with the possibility to extend.</p>
</li>
<li><p><strong>Component High cohesion</strong> – a component has everything in one place (its file/folder) except shared dependencies or special cases.</p>
</li>
<li><p><strong>Compound pattern</strong> – a JSX component that has its own optional structure (e.g. <code>Card</code>, <code>CardBody</code>) and uses a compound structure (e.g. <code>Card</code>, <code>Card.Body</code>) to prevent confusing exports.</p>
</li>
</ul>
<h2 id="heading-structural-principles">Structural principles</h2>
<ul>
<li><p><strong>Application code</strong> related to the functionality is inside <code>./src</code> folder aliased by <code>@/</code> path (src root).</p>
<ul>
<li><p><code>_(.*)</code> are special Next.js folders, <code>_</code> could be avoided without the framework.</p>
</li>
<li><p><code>@/app</code> is the AppRouter folder containing pages, see Next.js docs.</p>
</li>
<li><p><code>@/**/*</code> files could be divided into 2 groups - <strong>React components</strong> and <strong>"secondary variables"</strong> (= constants, utils, hooks, ...).</p>
</li>
</ul>
</li>
<li><p><strong>ReactComponent is a <em>file</em> or <em>folder</em></strong> having exactly the same interface/behaviour as a file. Both export 1 main component ± secondary variables.</p>
<ul>
<li><p><strong>File</strong> – <code>ComponentName.tsx</code> contains exactly 1 React component and may have secondary stuff. Everything uses named export. However, secondary variables cannot be used without a relation to the component. Otherwise, see Shared Code and Implementation below.</p>
</li>
<li><p><strong>Folder</strong> – <code>ComponentName</code> has a similar interface as a file – exposes 1 React component and secondary variables via <code>ComponentName/index.ts</code> so that it can replace a file <code>ComponentName.tsx</code> without redefining imports across a project. Besides the component itself, inside the folder, we can define other files/folders that are required for its instance. <em>All imports within the folder are relative.</em></p>
</li>
</ul>
</li>
<li><p><strong>Shared Code</strong> – everything that needs to be used in multiple parts of the project.</p>
<ul>
<li><p><strong>Component-related</strong> – secondary variables can exported from the file/folder with the main component. These variables should be used only with this component. E.g. <code>Nav</code> also provides <code>NavItem</code>, or some special hooks.</p>
</li>
<li><p><strong>Shared between ReactComponents</strong> – the shared variable is separated into a stand-alone file, placed into <code>_(.*)</code> folder according to its type and put into the <em>nearest common parent folder</em>. Since separation, all imports of this variable should be absolute <code>@/</code>.</p>
</li>
<li><p><strong>Global modules</strong> – some items expected to be used everywhere – typically form fields, buttons, auth hooks, GraphQL Fragments, etc. They are placed in root folders <code>./src/_(.*)/</code> according to their type.</p>
</li>
</ul>
</li>
</ul>
<p>The project follows the naming conventions described in the <a target="_blank" href="https://notes.dunaevskiy.dev/cheat-sheet-javascript-variable-name-conventions">cheat sheet</a>.</p>
<h2 id="heading-implementation">Implementation</h2>
<p>Let's assume that we need to create a blog page. We start with one file and extend a structure step by step.</p>
<h3 id="heading-pages">Pages</h3>
<p>Let's begin with a homepage – a list of articles. URI <code>http://localhost/blog</code>, a page file always has <code>page.tsx</code> filename. We assume that routes are auto-detected by Next.js or manually declared with a router library.</p>
<pre><code class="lang-plaintext">./src
└── app
   └── blog
      └── page.tsx
</code></pre>
<p>URI <code>http://localhost/blog/article/unique-slug</code> contains chosen article:</p>
<pre><code class="lang-plaintext">./src
└── app
   └── blog
      ├── articles
      │  └── [slug]
      │     └── page.tsx
      └── page.tsx
</code></pre>
<p>Pages use default export - Next.js requirement and React Router lazy loading compatibility.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/app/blog/page.tsx</span>
<span class="hljs-keyword">const</span> BlogPage = <span class="hljs-function">() =&gt;</span> &lt;&gt;&lt;/&gt;;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> BlogPage;
</code></pre>
<h3 id="heading-simple-react-component">Simple React component</h3>
<p>The blog page renders multiple <code>ArticlePreview</code> components. An article is rendered by <code>Article</code> component.</p>
<pre><code class="lang-plaintext">./src
└── app
   └── blog
      ├── _components
      │  └── ArticlePreview.tsx
      ├── articles
      │  └── [slug]
      │     ├── _components
      │     │  └── Article.tsx
      │     └── page.tsx
      └── page.tsx
</code></pre>
<p>All components use named export, which ensures the same component name within an application.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/app/blog/articles/[slug]/_components/Article.tsx</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> Article = <span class="hljs-function">() =&gt;</span> &lt;&gt;&lt;/&gt;;
</code></pre>
<h3 id="heading-shared-react-component">Shared React component</h3>
<p>We discovered that <code>ArticlePreview</code> and <code>Article</code> need to use the same UI component <code>Rating</code> - let's find the nearest parent folder of both components and create <code>Rating.tsx</code> inside type-related folder (<code>_components</code>).</p>
<pre><code class="lang-plaintext">./src
└── app
   └── blog
      ├── _components
      │  ├── ArticlePreview.tsx
      │  └── Rating.tsx
      ├── articles
      │  └── [slug]
      │     ├── _components
      │     │  └── Article.tsx
      │     └── page.tsx
      └── page.tsx
</code></pre>
<p><code>Rating</code> is an independent component. For <code>ArticlePreview</code> it is a sibling node within <code>/blog/_components</code> folder (main component inside <code>page.tsx</code>) – <strong><em>relative import</em></strong>. For <code>Article</code> it is a component in the upper structure – <strong><em>absolute import</em></strong>.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/app/blog/_components/ArticlePreview.tsx</span>
<span class="hljs-keyword">import</span> { Rating } <span class="hljs-keyword">from</span> <span class="hljs-string">'./Rating'</span>;
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> ArticlePreview = <span class="hljs-function">() =&gt;</span> &lt;Rating /&gt;;
</code></pre>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/app/blog/articles/[slug]/_components/Article.tsx</span>
<span class="hljs-keyword">import</span> { Rating } <span class="hljs-keyword">from</span> <span class="hljs-string">'@/app/blog/_components/Rating'</span>;
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> Article = <span class="hljs-function">() =&gt;</span> &lt;Rating /&gt;;
</code></pre>
<h3 id="heading-complex-react-component">Complex React component</h3>
<p><code>Article</code> seems to be too long and needs to be divided into smaller parts – <code>ArticleHeader</code>, <code>ArticleBody</code>. Both of them cannot be used without <code>Article</code>.</p>
<p>At first, we transform the file component into a folder component.</p>
<pre><code class="lang-plaintext">./src
└── app
   └── blog
      └── articles
         └── [slug]
            └── _components
               └── Article
                  ├── Article.tsx
                  └── index.ts
</code></pre>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/app/blog/articles/[slug]/_components/Article/index.ts</span>
<span class="hljs-keyword">export</span> { Article } <span class="hljs-keyword">from</span> <span class="hljs-string">'./Article'</span>;
</code></pre>
<p>Interfaces were preserved. Then we add <code>Article</code>-related components.</p>
<pre><code class="lang-plaintext">./src
└── app
   └── blog
      ├── _components
      │  ├── ArticlePreview.tsx
      │  └── Rating.tsx
      ├── articles
      │  └── [slug]
      │     ├── _components
      │     │  └── Article
      │     │     ├── Article.tsx
      │     │     ├── ArticleBody.tsx
      │     │     ├── ArticleHeader.tsx
      │     │     └── index.ts
      │     └── page.tsx
      └── page.tsx
</code></pre>
<p>Usually, the main component should be the only one exported from the folder. In case that <code>ArticleHeader</code> and <code>ArticleBody</code> need to be used outside <code>Article</code>, we may export them with the Compound Pattern (recommended way) or directly from the <code>Article</code> folder:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/app/blog/articles/[slug]/_components/Article/index.ts</span>
<span class="hljs-keyword">export</span> { Article } <span class="hljs-keyword">from</span> <span class="hljs-string">'./Article'</span>;
<span class="hljs-keyword">export</span> { ArticleBody } <span class="hljs-keyword">from</span> <span class="hljs-string">'./ArticleBody'</span>;
<span class="hljs-keyword">export</span> { ArticleHeader } <span class="hljs-keyword">from</span> <span class="hljs-string">'./ArticleHeader'</span>;
</code></pre>
<p>In this case, we need to handle them as strictly bounded and not use them without Article or, even worse, inside another React Component as a shared one, as we do with <code>Ratings</code>.</p>
<h3 id="heading-complex-react-component-compound-pattern">Complex React component - Compound Pattern</h3>
<p>To prevent exporting useless (on their own) components:</p>
<pre><code class="lang-plaintext">./src
└── app
   └── blog
      └── articles
         └── [slug]
            └── _components
               └── Article
                  ├── Article.tsx
                  ├── ArticleBody.tsx
                  ├── ArticleHeader.tsx
                  └── index.ts
</code></pre>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/app/blog/articles/[slug]/_components/Article/Article.tsx</span>
<span class="hljs-keyword">import</span> { Rating } <span class="hljs-keyword">from</span> <span class="hljs-string">'@/app/blog/_components/Rating'</span>;
<span class="hljs-keyword">import</span> { ArticleBody } <span class="hljs-keyword">from</span> <span class="hljs-string">'./ArticleBody'</span>
<span class="hljs-keyword">import</span> { ArticleHeader } <span class="hljs-keyword">from</span> <span class="hljs-string">'./ArticleHeader'</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> Article = <span class="hljs-function">() =&gt;</span> &lt;Rating /&gt;;

Article.Body = ArticleBody;
Article.Header = ArticleHeader;
</code></pre>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/app/blog/articles/[slug]/page.tsx</span>
<span class="hljs-keyword">import</span> { Article } <span class="hljs-keyword">from</span> <span class="hljs-string">'./_components/Article'</span>;

<span class="hljs-keyword">const</span> Page = <span class="hljs-function">() =&gt;</span> (
   &lt;Article&gt;
     &lt;Article.Header /&gt;
     &lt;Article.Body /&gt;
   &lt;/Article&gt;
);
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Page;
</code></pre>
<h3 id="heading-additional-secondary-items-hooks-validations-etc">Additional secondary items (hooks, validations, etc.)</h3>
<p><code>ArticleHeader</code> needs to have a <code>useColoredPart</code> hook. It isn't shared with <code>Article</code> and belongs only to the <code>ArticleHeader</code>. <code>ArticleHeader</code> stops to be a simple file component and imports the hook with a relative path (we are inside a folder component).</p>
<pre><code class="lang-javascript">./src
└── app
   └── blog
      └── articles
         └── [slug]
            └── _components
               └── Article
                  ├── Article.tsx
                  ├── ArticleBody.tsx
                  ├── ArticleHeader
                  │  ├── ArticleHeader.tsx
                  │  ├── index.ts
                  │  └── useColoredPart.ts
                  └── index.ts
</code></pre>
<p>It is possible to create a special folder for same-type components. E.g. <code>/hooks</code>, <code>/validations</code>, <code>/constants</code>, etc.</p>
<pre><code class="lang-javascript">./src
└── app
   └── blog
      └── articles
         └── [slug]
            └── _components
               └── Article
                  ├── Article.tsx
                  ├── ArticleBody.tsx
                  ├── ArticleHeader
                  │  ├── ArticleHeader.tsx
                  │  ├── hooks
                  │  │  ├── useColoredPart.ts
                  │  │  └── useSomethingSpecial.ts
                  │  └── index.ts
                  └── index.ts
</code></pre>
<h3 id="heading-global-components">Global components</h3>
<p><code>Article</code> now needs <code>useColoredPart.ts</code> and the hook is expected to be used in many, many others. This type of module could be declared as a global one. The same logic has, e.g., UI JSX components. We move them to the root.</p>
<pre><code class="lang-javascript">./src
├── _components
│  ├── Button.tsx
│  └── FormFieldText.tsx
├── _hooks
│  └── useColoredPart.ts
└── app
   └── blog
      ├── _components
      │  ├── ArticlePreview.tsx
      │  └── Rating.tsx
      ├── articles
      │  └── [slug]
      │     ├── _components
      │     │  └── Article
      │     │     ├── Article.tsx
      │     │     ├── ArticleBody.tsx
      │     │     ├── ArticleHeader
      │     │     │  ├── ArticleHeader.tsx
      │     │     │  ├── index.ts
      │     │     │  └── useSomethingSpecial.ts
      │     │     └── index.ts
      │     └── page.tsx
      └── page.tsx
</code></pre>
<p>These components theoretically may be used across projects. Absolute imports inside nested folders, relative within the same level.</p>
<p><em>A similar concept is used, e.g., by the npm package manager, which places installed dependencies into a file system tree. The only difference is that npm considers all dependencies root-first, not leaf-first.</em></p>
<h2 id="heading-conclusion">Conclusion</h2>
<h3 id="heading-pros">Pros</h3>
<ul>
<li><p>It is evident which part of an application we can affect after changes in any file.</p>
</li>
<li><p>Rules for imports reduce the number of required import changes across an app in cases of moving or adding complexity to a component.</p>
</li>
</ul>
<h3 id="heading-cons">Cons</h3>
<ul>
<li>We continually need to check if all rules are fulfilled, especially the rule about the location of the shared component in the nearest common parent folder.</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Cheat Sheet: JavaScript (TypeScript) variable naming convention]]></title><description><![CDATA[Recommendations on how to unify naming convention within a code – formats, acronyms and abbreviations, value-based prefixes, event handlers, etc. Mainly for vanilla TypeScript with a bit of React.
Variable name formats
It is good to restrict name for...]]></description><link>https://notes.dunaevskiy.dev/cheat-sheet-javascript-variable-name-conventions</link><guid isPermaLink="true">https://notes.dunaevskiy.dev/cheat-sheet-javascript-variable-name-conventions</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[Naming Conventions]]></category><category><![CDATA[easy]]></category><dc:creator><![CDATA[Sergey Dunaevskiy]]></dc:creator><pubDate>Sat, 01 Jun 2024 12:25:57 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1718553248630/df52745e-01a1-446d-9b32-a376b235c7f3.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>Recommendations on how to unify naming convention within a code – formats, acronyms and abbreviations, value-based prefixes, event handlers, etc. Mainly for vanilla TypeScript with a bit of React.</em></p>
<h2 id="heading-variable-name-formats">Variable name formats</h2>
<p>It is good to restrict name formats to those listed below unless there is an extraordinary reason (e.g. pairing with database column names):</p>
<ul>
<li><p><code>camelCase</code> – default name format,</p>
</li>
<li><p><code>PascalCase</code> – format of classes, types, enum-like structures, interfaces and special structures (e.g. React component names),</p>
</li>
<li><p><code>SCREAMING_SNAKE_CASE</code> – global constants.</p>
</li>
</ul>
<p>We can use full words (<code>properties</code>), abbreviations (<code>props</code>) and acronyms within variable names.</p>
<ul>
<li><p>2 letter acronyms are uppercase.</p>
</li>
<li><p>3+ letter acronyms are lowercase.</p>
</li>
</ul>
<pre><code class="lang-typescript"><span class="hljs-comment">// 2 letters</span>
<span class="hljs-keyword">const</span> TranslationsEn = {};  <span class="hljs-comment">// abbreviation</span>
<span class="hljs-keyword">const</span> enTranslations = {};  <span class="hljs-comment">// abbreviation</span>
<span class="hljs-keyword">const</span> adminUI = {};         <span class="hljs-comment">// acronym</span>
<span class="hljs-keyword">const</span> adminUIAdvanced = {}; <span class="hljs-comment">// acronym</span>
<span class="hljs-keyword">const</span> UIButton = {};        <span class="hljs-comment">// acronym</span>

<span class="hljs-comment">// 3+ letters</span>
<span class="hljs-keyword">type</span> ComponentProps = {};  <span class="hljs-comment">// abbreviation</span>
<span class="hljs-keyword">const</span> htmlTags = [];       <span class="hljs-comment">// acronym</span>
<span class="hljs-keyword">const</span> copyHtml = <span class="hljs-function">() =&gt;</span> {}; <span class="hljs-comment">// acronym</span>
</code></pre>
<h2 id="heading-custom-values">Custom values</h2>
<p>All variables must be named to reflect the meaning of the stored value. The name format is chosen according to the list above. In special cases (boolean values, enums, etc.), variable names may use a unique prefix or rule to distinguish themselves from the rest.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// &lt;variableDescription&gt;</span>
<span class="hljs-keyword">const</span> result = <span class="hljs-number">1</span>;
<span class="hljs-keyword">const</span> nextMiddleware = <span class="hljs-string">"logger"</span>;

<span class="hljs-comment">// &lt;VARIABLE_DESCRIPTION&gt;</span>
<span class="hljs-keyword">const</span> ENVIRONMENT_VARIABLE = process.env.ENVIRONMENT_VARIABLE;
</code></pre>
<h2 id="heading-boolean-values-is-are-has">Boolean values - is, are, has</h2>
<p>These variables should only contain <code>true</code>/<code>false</code> boolean (not <code>string</code>) values and thus implicitly declare that they are safe to use in conditions.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// (is|are)[&lt;Noun&gt;]&lt;Adjective&gt;</span>
<span class="hljs-keyword">const</span> isSubmitted = <span class="hljs-literal">false</span>;
<span class="hljs-keyword">const</span> isFormASubmitted = <span class="hljs-literal">false</span>;
<span class="hljs-keyword">const</span> isFormBSubmitted = <span class="hljs-literal">false</span>;
<span class="hljs-keyword">const</span> areFormsSubmitted = isFormASubmitted &amp;&amp; isFormBSubmitted;

<span class="hljs-comment">// (is|are)&lt;Action&gt;</span>
<span class="hljs-keyword">const</span> isFetching = <span class="hljs-literal">true</span>;

<span class="hljs-comment">// has&lt;SubjectDescription&gt;</span>
<span class="hljs-keyword">const</span> hasAtLeastOneOddNumber = <span class="hljs-literal">true</span>;

<span class="hljs-comment">// Global constant with IS prefix</span>
<span class="hljs-keyword">const</span> IS_DEBUG_MODE = <span class="hljs-literal">true</span>;
</code></pre>
<p>Prefixes <code>is</code>/<code>are</code> have a priority over <code>has</code> – don't name a variable <code>hasDefinedStatus</code> when you can use <code>isStatusDefined</code>. The prefix <code>has</code> should be used in cases that cannot be comfortably replaced with <code>is</code>/<code>are</code>, e.g. <code>hasTwoElementsFromArray</code>.</p>
<p><strong>Negation</strong></p>
<p>Sometimes, negation is used as frequently as the original value, or we need to operate with negative statements only. In these cases, we can use a secondary <code>Not</code> prefix.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> isFormatted = <span class="hljs-literal">false</span>;
<span class="hljs-keyword">const</span> isNotFormatted = !isFormatted;
</code></pre>
<h2 id="heading-enums">Enums</h2>
<p>Enums have a special behaviour - TypeScript <code>enum</code> as a type is not the best concept, according to the <a target="_blank" href="https://www.youtube.com/watch?v=vBJF0cJ_3G0&amp;t=1012s">TypeScript Team</a>. Therefore it is better to use an object with <code>as const</code> structure (<a target="_blank" href="https://www.youtube.com/watch?v=0fTdCSH_QEU&amp;t=294s">explanation</a>). According to <a target="_blank" href="https://google.github.io/styleguide/jsguide.html#features-objects-enums">Google JS Guide</a>, we can think of this object as a special structure (<code>PascalCase</code>) containing constants (<code>SCREAMING_SNAKE_CASE</code>).</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> RequestStatus = {
   PENDING: <span class="hljs-string">'Pending'</span>,
   SUCCESS: <span class="hljs-string">'Success'</span>,
   ERROR: <span class="hljs-string">'Error'</span>,
} <span class="hljs-keyword">as</span> <span class="hljs-keyword">const</span>;

<span class="hljs-keyword">type</span> RequestStatus = (<span class="hljs-keyword">typeof</span> RequestStatus)[keyof <span class="hljs-keyword">typeof</span> RequestStatus];

<span class="hljs-comment">// Usage</span>
RequestStatus.SUCCESS;
</code></pre>
<h2 id="heading-types-interfaces-and-generics">Types, interfaces and generics</h2>
<p>Types use <code>PascalCase</code> and do not need a special prefix. Interfaces and generics have one letter prefix because of their special use cases (useful mainly in libraries).</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// &lt;TypeName&gt;</span>
<span class="hljs-keyword">type</span> SystemProps = {};

<span class="hljs-comment">// I&lt;InterfaceName&gt;</span>
<span class="hljs-keyword">interface</span> ISystemProps {}

<span class="hljs-comment">// T&lt;GenericType&gt;</span>
<span class="hljs-keyword">const</span> createValue = &lt;TInputValue&gt;<span class="hljs-function">(<span class="hljs-params">value: TInputValue</span>) =&gt;</span> {
   <span class="hljs-keyword">return</span> value;
};
</code></pre>
<h2 id="heading-event-handlers">Event handlers</h2>
<p>Event handlers (callbacks) are functions that are called after an event – e.g. HTML <code>onclick</code>, <code>oncontextmenu</code>, React events, etc.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// handle[&lt;Object&gt;]&lt;Action&gt;</span>
<span class="hljs-keyword">const</span> handleClick = <span class="hljs-function">() =&gt;</span> {};           <span class="hljs-comment">// click is clearly defined</span>
<span class="hljs-keyword">const</span> handleSendButtonClick = <span class="hljs-function">() =&gt;</span> {}; <span class="hljs-comment">// one of many click events</span>
<span class="hljs-keyword">const</span> handleSignOut = <span class="hljs-function">() =&gt;</span> {}          <span class="hljs-comment">// multi-usage close callback</span>
</code></pre>
<ul>
<li><p><code>Object</code> - defines unique items that we interact with. It is optional if the handler is called from multiple places or if it is unique within the code part.</p>
</li>
<li><p><code>Action</code> - an action caused by a user.</p>
</li>
</ul>
<p><strong>React</strong> applications are a special case using <code>onX={handleX}</code> pairs.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">type</span> ComponentProps = {
   onSignInFormSubmit: <span class="hljs-function">() =&gt;</span> {};
};

<span class="hljs-keyword">const</span> Component = <span class="hljs-function">(<span class="hljs-params">{ onSignInFormSubmit }: ComponentProps</span>) =&gt;</span> {};

<span class="hljs-keyword">const</span> Parent = <span class="hljs-function">() =&gt;</span> {
   <span class="hljs-keyword">const</span> handleSignInFormSubmit = <span class="hljs-function">() =&gt;</span> {};
   <span class="hljs-keyword">return</span> &lt;Component onSignInFormSubmit={handleSignInFormSubmit} /&gt;
}
</code></pre>
<p>It is better to not name handlers with <code>on</code> prefix unless you, for example, want to create an auto-spreading structure of build-in properties (using spread syntax with prop names <code>onClick</code>, etc.). Detailed explanation: <a target="_blank" href="https://jaketrent.com/post/naming-event-handlers-react/">Event Handler Naming in React (by Jake Trent)</a>.</p>
<h2 id="heading-functions">Functions</h2>
<p>A function is always some kind of action. Therefore, its name should begin with a verb. It is a good idea to fix a set of these verbs and describe their purpose.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// transform[Entity]to[Entity]</span>
<span class="hljs-keyword">const</span> transformResultToOrderedArray = <span class="hljs-function">() =&gt;</span> {}

<span class="hljs-comment">// format[Entity]As[Entity]</span>
<span class="hljs-keyword">const</span> formatTimestampAsLocalizedEnString = <span class="hljs-function">() =&gt;</span> {}

<span class="hljs-comment">// (generate|gen)[Entity]</span>
<span class="hljs-keyword">const</span> generateRandomTraceId = <span class="hljs-function">() =&gt;</span> {}
<span class="hljs-keyword">const</span> genRandomTraceId = <span class="hljs-function">() =&gt;</span> {}
</code></pre>
<p>HTTP method functions (return a result of fetch API) should be named with a method that they return:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// &lt;method&gt;&lt;Entity&gt;</span>
<span class="hljs-keyword">const</span> getArticles = <span class="hljs-function">() =&gt;</span> {};
<span class="hljs-keyword">const</span> postArticle = <span class="hljs-function">() =&gt;</span> {};
<span class="hljs-keyword">const</span> putArticle = <span class="hljs-function">() =&gt;</span> {};
<span class="hljs-keyword">const</span> patchArticle = <span class="hljs-function">() =&gt;</span> {};
<span class="hljs-keyword">const</span> deleteArticle = <span class="hljs-function">() =&gt;</span> {};
<span class="hljs-comment">// ...</span>
</code></pre>
<h2 id="heading-class-private-attributes-and-methods">Class private attributes and methods</h2>
<p>According to <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Private_properties">Private Fields</a> we need to use <code>#</code> (hash names) for private methods and attributes.</p>
<pre><code class="lang-javascript">
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PrivateNumber</span> </span>{
    #x;

    <span class="hljs-keyword">constructor</span>(x) {
        <span class="hljs-built_in">this</span>.#x = x;
    }

    get #privateSecretValue() {
        <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.#x + <span class="hljs-number">10</span>;
    }

    <span class="hljs-keyword">get</span> <span class="hljs-title">secretValue</span>() {
        <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.#privateSecretValue;
    }
}
</code></pre>
<h2 id="heading-conclusion">Conclusion</h2>
<p>After all, it is not so important which style, rules or prefixes we use. They just need to be consistent at least within a project. But we make our (and our colleagues) lives easier when we use at least somehow unified conventions.</p>
]]></content:encoded></item></channel></rss>