front-end Articles / Blogs / Perficient https://blogs.perficient.com/tag/front-end/ Expert Digital Insights Tue, 16 Jul 2024 20:50:31 +0000 en-US hourly 1 https://blogs.perficient.com/files/favicon-194x194-1-150x150.png front-end Articles / Blogs / Perficient https://blogs.perficient.com/tag/front-end/ 32 32 30508587 Adobe Sites: Migrating from Webpack to Vite https://blogs.perficient.com/2024/07/16/adobe-sites-migrating-from-webpack-to-vite/ https://blogs.perficient.com/2024/07/16/adobe-sites-migrating-from-webpack-to-vite/#comments Tue, 16 Jul 2024 20:50:31 +0000 https://blogs.perficient.com/?p=365994

Webpack is an amazing bundler for JavaScript and, with the correct loader, it can also transform CSS, HTML, and other assets.  When a new AEM project is created via the AEM Project Archetype and the front-end module is set to general, Adobe provides a Webpack configuration to generate the project’s client libraries.

Introducing Vite

Vite is a new build tool that has recently come onto the scene.  You can check the NPM trends here.

Compared to Webpack,

  • Vite provides significantly faster build times and hot reloading during development.
  • Vite utilizes Rollup.  Rollup generates small bundles by utilizing optimizations like tree shaking, ES6 modules, scope hoisting, minification, code splitting, and a plugin ecosystem.

Avoid Configuration Challenges With Vite

If you have any experience with Webpack, you know the challenges of configuring different loaders to preprocess your files.  Many of these configurations are unnecessary with Vite.  Vite supports TypeScript out of the box.  Vite provides built-in support for .scss, .sass, .less, .styl, and .stylus files.  There is no need to install Vite-specific plugins for them.  If the project contains a valid PostCSS configuration, it will automatically apply to all imported CSS.  It is truly a game-changer. 

Project “Jete”

“Vite” comes from the French word for “fast”.  In music, the term “Vite” refers to playing at a quickened pace.  For the following tutorial, I have chosen the music term “Jete” for the name of our project.  “Jete” refers to a bowing technique in which the player is instructed to let the bow bounce or jump off the strings.  Let us take a cue from this musical term and “bounce” into our tutorial. 

Migrating From Webpack to Vite Tutorial

Create an AEM Project via the AEM Project Archetype: 

mvn -B archetype:generate -D archetypeGroupId=com.adobe.aem -D archetypeArtifactId=aem-project-archetype -D archetypeVersion=49 -D aemVersion=cloud -D appTitle="Jete" -D appId="jete" -D groupId="com.jete" -D frontendModule=general -D includeExamples=n

Once your project has been created, install your project within your AEM instance:

mvn clean install -PautoInstallSinglePackage

After verifying the Jete site in AEM, we can start migrating our frontend project to Vite. 

Backup the existing ui.frontend directory: 

cd jete/ 

mv ui.frontend ../JeteFrontend 

From within “jete” run: 

npm create vite@latest

Use “aem-maven-archetype” for the project name, select Vanilla for the framework, and “TypeScript” for the variant. 

Rename the directory “aem-maven-archetype” to “ui.frontend”.  We chose that project name to match the name generated by the AEM Archetype. 

mv aem-maven-archetype ui.frontend

Let’s put the pom.xml file back into the frontend directory: 

mv ../JeteFrontend/pom.xml ui.frontend

Since we are updating the POM files, let’s update the Node and NPM versions in the parent.

pom.xml file. 

<configuration>  

  <nodeVersion>v20.14.0</nodeVersion>  

  <npmVersion>10.7.0</npmVersion>  

</configuration>

We will be using various Node utilities within our TypeScript filesLet us install the Node Types package. 

npm install @types/node --save-dev 

Add the following compiler options to our tsconfig.json file: 

"outDir": "dist", 

"baseUrl": ".", 

"paths": { 

  "@/*": [ 

    "src/*" 

  ] 

}, 

"types": [ 

  "node" 

]

These options set the output directory to “dist”, the base url to the current directory: “ui.frontend”, create an alias of “@” to the src directory, and add the Node types to the global scope. 

Let’s move our “public” directory and the index.html file into the “src” directory. 

Create a file named “vite.config.ts” within “ui.frontend” project. 

Add the following vite configurations: 

import path from 'path'; 

import { defineConfig } from 'vite'; 

export default defineConfig({ 

  build: { 

    emptyOutDir: true, 

    outDir: 'dist', 

  }, 

  root: path.join(__dirname, 'src'), 

  plugins: [], 

  server: { 

    port: 3000, 

  }, 

});

Update the index.html file within the “src” directoryChange the reference of the main.ts file from “/src/main.ts” to “./main.ts. 

<script type="module" src="./main.ts"></script>

Run the Vite dev server with the following command: 

npm run dev

You should see the following page: 

AEM Vite + Typescript

We are making progress! 

Let us make some AEM-specific changes to our Vite configuration. 

Change outDir to: 

path.join(__dirname, 'dist/clientlib-site')

Add the following within the build section: 

lib: { 

  entry: path.resolve(__dirname, 'src/main.ts'), 

  formats: ['iife'], 

  name: 'site.bundle', 

}, 

rollupOptions: { 

  output: { 

    assetFileNames: (file) => { 

      if (file.name?.endsWith('.css')) { 

        return 'site.bundle.[ext]'; 

      } 

      return `resources/[name].[ext]`; 

    }, 

    entryFileNames: `site.bundle.js`, 

  }, 

},

These configurations set the entry file, wrap the output within an immediately invoked function expression (to protect against polluting the global namespace), set the JavaScript and CSS bundle names to site.bundle.js and site.bundle.css, and set the output path for assets to a directory named “resources”.  Using the “iife” format requires setting the “process.env.NODE_ENV” variable. 

Add a “define” section at the same level as “build” with the following option: 

define: { 

  'process.env.NODE_ENV': '"production"', 

}, 

Add a “resolve” section at the same level as “define” and “build” to use our “@” alias: 

resolve: { 

  alias: { 

    '@': path.resolve(__dirname, './src'), 

  }, 

}, 

Add the following “proxy” section within the “server” section: 

proxy: { 

  '^/etc.clientlibs/.*': { 

      changeOrigin: true, 

      target: 'http://localhost:4502', 

  }, 

},

These options inform the dev server to proxy all requests starting with /etc.clientlibs to localhost:4502. 

It is time to remove the generated code.  Remove “index.html”, “conter.ts”, “style.css”, “typescript.svg”, “public/vite.svg” from within the “src” directory.  Remove everything from “main.ts”. 

Move the backup of index.html file to the src directory: 

cp ../JeteFrontend/src/main/webpack/static/index.html ui.frontend/src/

Edit the index.html file.  Replace the script including the “clientlib-site.js” with the following: 

<script type="module" src="./main.ts"></script>

Save the following image to “src/public/resources/images/”: 

https://raw.githubusercontent.com/PRFTAdobe/jete/main/ui.frontend/src/public/resources/images/favicon.ico 

Add the following element within the head section of the index.html file: 

<link rel="icon" href="./resources/images/favicon.ico" type="image/x-icon" />

While we are updating favicons, edit the

ui.apps/src/main/content/jcr_root/apps/jete/components/page/customheaderlibs.html file.

Add the following to the end of the file: 

<link rel="icon" href="/etc.clientlibs/jete/clientlibs/clientlib-site/resources/images/favicon.ico" type="image/x-icon" />

Run the Vite dev server once more … 

npm run dev

You should see the following: 

Project Jete With AEM Vite

It is not very attractiveLet us add some stylingRun the following command to install “sass”. 

npm i -D sass

Create a “main.scss” file under the “src” directory. 

touch main.scss

Edit the main.ts file and add the following line to the top of the file: 

import '@/main.scss'

Copy the variables stylesheet from the frontend backup to the “src” directory: 

cp ../JeteFrontend/src/main/webpack/site/_variables.scss ./ui.frontend/src/

Edit the _variables.scss file and add the following: 

$color-foreground-rgb: rgb(32 32 32);

Copy the base stylesheet from the frontend backup to the “src” directory: 

cp ../JeteFrontend/src/main/webpack/site/_base.scss ./ui.frontend/src/

Include references to these files within main.scss: 

@import 'variables'; 

@import 'base';

Run the Vite dev server once more … 

npm run dev

You should see the following: 

Project Jete With AEM Vite Version 2

Things are getting better, but there is still more work to do! 

Copy the component and site stylesheets from the frontend backup to the “src” directory: 

cp -R ../JeteFrontend/src/main/webpack/components ./ui.frontend/src/ 

 

cp -R ../JeteFrontend/src/main/webpack/site/styles ./ui.frontend/src/

Add the following to the main.scss file: 

@import './components/**/*.scss'; 

@import './styles/**/*.scss';

Run the Vite dev server … 

npm run dev

No luck this timeYou will probably see this error: 

Project Jete With AEM Vite Error

Vite doesn’t understand “splat imports”, “wildcard imports”, or “glob imports”.  We can fix this by installing a package and updating the Vite configuration file. 

Install the following package: 

npm i -D vite-plugin-sass-glob-import

Update the vite.config.ts fileAdd the following to the import statements: 

import sassGlobImports from 'vite-plugin-sass-glob-import';

Add “sassGlobImports” to the plugins section: 

plugins: [sassGlobImports()],

Now, let’s run the Vite dev server again. 

npm run dev

You should see the following: 

Project Jete With Aem Vite Version 3

Much better.  The front end is looking great!  Time to work on the JavaScript imports! 

TypeScript has been working well for us so far, so there’s no need to switch back to JavaScript. 

Remove the “helloworld” JavaScript file: 

rm -rf src/components/_helloworld.js

Grab the TypeScript from this URL and save it as src/components/_helloworld.ts: https://raw.githubusercontent.com/PRFTAdobe/jete/main/ui.frontend/src/components/_helloworld.ts 

To see the results of this script within our browser, we have to include this file within main.ts.  Importing splats won’t work on a TypeScript file.  So we can’t write: “import ‘@/components/**/*.ts’”.  Instead, we will write:

import.meta.glob('@/components/**/*.ts', { eager: true });

Now, let’s run the Vite dev server. 

npm run dev

You should see the following in Chrome DevTools: 

Aem Vite Javascript Example

Very good!  The JavaScript is working as well! 

The following section is optional, but it is good practice to add some linting rules. 

Install the following: 

npm i -D @typescript-eslint/eslint-plugin @typescript-eslint/parser autoprefixer eslint eslint-config-airbnb-base eslint-config-airbnb-typescript eslint-config-prettier eslint-import-resolver-typescript eslint-plugin-import eslint-plugin-prettier eslint-plugin-sort-keys eslint-plugin-typescript-sort-keys postcss postcss-dir-pseudo-class postcss-html postcss-logical prettier stylelint stylelint-config-recommended stylelint-config-standard stylelint-config-standard-scss stylelint-order stylelint-use-logical tsx

Save the following URLs to ui.frontend:

https://raw.githubusercontent.com/PRFTAdobe/jete/main/ui.frontend/.eslintrc.json

https://raw.githubusercontent.com/PRFTAdobe/jete/main/ui.frontend/.postcssrc.json 

https://raw.githubusercontent.com/PRFTAdobe/jete/main/ui.frontend/.prettierrc.json 

https://raw.githubusercontent.com/PRFTAdobe/jete/main/ui.frontend/.stylelintrc.json 

Add the following to the “script” section of package.json: 

"lint": "stylelint src/**/*.scss --fix && eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0"

Let’s try out our new script by running: 

npm run lint

You should see a fair amount of sass linting errors.  You can fix the errors manually or overwrite your local versions with the ones from the git repo: https://github.com/PRFTAdobe/jete/tree/main/ui.frontend/src 

We are ready to move on from linting.  Let’s work on the AEM build. 

Install the following: 

npm i -D aem-clientlib-generator aemsync

Save the following URLs to ui.frontend: 

https://github.com/PRFTAdobe/jete/blob/main/ui.frontend/aem-sync-push.ts 

https://github.com/PRFTAdobe/jete/blob/main/ui.frontend/clientlib.config.ts 

https://github.com/PRFTAdobe/jete/blob/main/ui.frontend/aem-clientlib-generator.d.ts 

https://github.com/PRFTAdobe/jete/blob/main/ui.frontend/aemsync.d.ts 

The files with the “d.ts” extensions are used to provide typescript type information about the referenced packages. 

The “clientlib.config.ts” script, creates a client library based on the JS and CSS artifacts created during the build process.  It also copies the artifacts to the “clientlib” directory within “ui.apps”. 

The “aem-sync-push.ts” script takes the clientlib created above and pushes it to a running AEM instance. 

It is time to update the “script” section of package.json. 

Remove the existing “build” and “preview” commands.  Add the following commands: 

"build": "tsc && npm run lint && vite build && tsx ./clientlib.config.ts && tsx ./aem-sync-push.ts", 

"prod": "tsc && npm run lint && vite build && tsx ./clientlib.config.ts",

Let’s try out the build command first: 

npm run build

If the command has been completed successfully, you will see messages indicating that the “generator has finished” and the “aem sync has finished”.  You will also notice the creation of a “dist” directory under “ui.frontend”. 

Our last step is to copy over the “assembly.xml” file from the backup we made earlier. 

cp ../JeteFrontend/assembly.xml ui.frontend/

With that file in place, we are ready to rerun the AEM build: 

mvn clean install -PautoInstallSinglePackage

Congratulations!

The build should be complete without errors.  You have successfully migrated from Webpack to Vite! 

Make sure to follow our Adobe blog for more Adobe solution tips and tricks!  

]]>
https://blogs.perficient.com/2024/07/16/adobe-sites-migrating-from-webpack-to-vite/feed/ 1 365994
Angular 17: Maximize Your Development Efficiency and Performance https://blogs.perficient.com/2024/06/13/elevate-your-development-with-angular-17/ https://blogs.perficient.com/2024/06/13/elevate-your-development-with-angular-17/#respond Thu, 13 Jun 2024 14:32:59 +0000 https://blogs.perficient.com/?p=364242

Angular has long been a leading web development framework, powering countless dynamic websites with its robust features and scalable architecture. However, recent advances in technology have seen other frameworks offering faster, more efficient solutions. With the release of Angular 17, the framework responds with a transformative update, enhancing performance and flexibility. This post will examine those features and their impact on web development.

Angular 17 

This latest version was released in November 2023, and marked a significant evolution from its predecessors, introducing substantial changes and new features. 

These are its major features: 

  • Block Template Syntax: changes the structure of Angular templates for more intuitive coding to the developer. 
  • Defer: Optimizing the loading strategy to improve application performance. 
  • SSR (Server-Side Rendering) Configuration: Enhancing SEO (Search Engine Optimization) and load times with advanced server-side rendering options. 
  • Vite: Integrating a faster, more efficient interpreter tool to speed up development. 
  • Signals: Introducing a new way to manage state changes in applications, for more responsive interfaces. 
  • Reactivity and Zoneless: Offering improved state management and performance by reducing reliance on Angular’s zone mechanism. 

These updates collectively aim to refine the development experience and elevate the capabilities of Angular applications, let’s look into it!

Block Template Syntax 

Angular completely changed the way we can use template directives, now we have a syntax that is more intuitive and JavaScript-like to improve the developer experience and code readability.

@if block: Conditional block.

//--file.component.html
public showContent = signal(false);

public toggleContent() {
  this.showContent.update(value => !value)
}

//--file.component.html
<button (click)="toggleContent()">
  Click Me!
</button>

@if(showContent()) {
  <p>Hello World!</p>
} @else {
  <p>*******</p>
}

@Switch block: Multi-conditional block.

//--file.component.ts
type Grade = 'A'|'B'|'F';
public grade= signal<Grade>('A');

//--file.component.html
@switch (grade()) {
  @case ('A'){
    <p>Up to 90, Excellent! 😍</p>
  }
  @case ('B') {
    <p>Up to 80, Great! 😁</p>
  }
  @default {
    <p>Sorry, Try again! 😞</p>
  }
}

@for and @empty block: Iterative block and default empty option block.

public frameworks2 = signal([]);
//-------------------------------------
<ul>
  @For (framework of frameworks2(); track $index) {
    <li>{{framework}}</li>
  }
  @empty {
    <li>There's no added values!</li>
  }
</ul>

Other performance improvements are included during its construction. Although we could perceive the difference in performance with Angular 17, we tried to confirm these changes by analyzing the creation times of scripts and DOM(Document Object Model) loading, taking as a basis a code that generates some tags iteratively 3000 times validating if they are displayed based on their index is even, finding the following results:

CommonModule

<ng-container
  *ngFor="let item of items; let i = index">
  <p *ngIf="i % 2 === 0">
    {{item}}
  </p>
</ng-container>

New Angular 17 Directives

@for (item of items; track $index) {
  @if ( $index %2 === 0) {
    <p>{{item}}</p>
  }
}

Scripting Average: 180-220ms
Dom Loading Average: 400ms

Angular 17 - 1

Angular 17 - 2

Angular 17 - 3

Image (1)

Average Scripting: 105-110ms
Average Dom Loading: 295ms

Angular 17 - 4

Angular 17 - 5

Angular 17 - 6

Image (9)

Defer 

Angular now offers a new and advanced way to handle Lazy Loading with the template block @defer. This block allows developers to specify components that should load lazily, directly within the template. 

To be able to use this feature, the component must be standalone, which means that the component is not going to be in a module. 

This approach not only simplifies the structure of Angular applications but also enhances performance by reducing the initial load time. Components marked with @defer are loaded only when needed, thereby improving the application’s efficiency and user experience. This selective lazy loading can significantly optimize resource utilization and accelerate rendering speeds, especially in complex applications with numerous components. 

This lazy loading mechanism is further refined with additional directives like @placeholder, @error, and @loading, which manage the component’s loading states. 

@placeholder: provides a temporary display element until the component loads 

@error: handles any loading errors 

@loading: indicates the loading process. 

An example of a defer block in code can be the following: 

<section class="col-start-1">
  @defer (on interaction) { 
    <app-heavy-loaders-slow cssClass="bg-blue-500 h-20"> 
    </app-heavy-loaders-slow>

  } @loading { 
    <div class="w-full h-20 bg-yellow-100"> 
      Loading component (loading) 
    </div> 

  } @placeholder { 
    <div class="w-full h-20 bg-purple-100"> 
      Click the div (placeholder) 
    </div> 

  } @error { 
    <div class="w-full h-20 bg-red-100"> 
      An error happened 
    </div> 
  }
</section>

Doing a comparative performance on loading a Heavy Component, a person can see a clear difference on Core Web Vitals when interacting with a page that lazy loads these kinds of components vs loading components on page load.

Performance without Defer:

Performancewithoutdefer

Performance with Defer:

Performancewithdefer

 

SSR

Angular 17 allows you to create a new application with Server-Side Rendering (SSR) from the start, eliminating the need to separately install the Angular Universal package. Previously, Angular Universal required a double bundle to render the HTML, leading to issues in SEO, performance, and user experience. Angular 17 streamlines this by implementing SSR by default, creating server and client bundles simultaneously, and avoiding the double-bundling issue.

This enhancement boosts SEO and improves Core Web Vitals metrics such as First Contentful Paint (FCP) and Largest Contentful Paint (LCP). It also enhances the overall user experience by delivering content more quickly and consistently from server to client, especially beneficial for users with slower internet connections. By including the HTML template directly in the initial server response while waiting for the JavaScript to load, Angular 17 ensures that users see meaningful content faster. This approach simplifies the development process and streamlines rendering, allowing for more efficient management of dynamic content. As a result, websites become more interactive and responsive from the initial load, increasing user engagement and satisfaction. 

Vite

The usage of Vite in the Angular CLI (Command Line Interface) is currently only within a development server capacity only.

The current development server process uses the new build system to generate a development build of the application in memory and passes the results to Vite to serve the application.  

The usage of Vite, much like the Webpack-based development server, is encapsulated within the Angular CLI dev-server builder and currently cannot be directly configured. 

Signals  

Signals in Angular 17 bring a major improvement to how state and reactivity are managed. Developers can now more precisely monitor and react to state changes, making UI (User Interface) updates faster and more streamlined. ‘Signals’ leverage a reactive programming approach to simplify code structure and cut down on the redundant code typical in complex state handling. This efficiency boost means the system only updates when it is absolutely needed, lowering the complexity, and enhancing performance. Consequently, ‘Signals’ in Angular 17 steers the framework towards more agile, powerful, and easy-to-maintain web applications, improving the development process and user engagement. 

With Signals a person can control when a single part of an application needs to be updated, not triggering updates on the whole tree of components, but into a single component instead. This has the advantage of allowing component-based reactivity on any Angular-based application.  

There are important takeaways to consider. The first is that angular signals can work with RXJS, and they do not have to exclude each other, as RXJS offers a rich environment for advanced reactivity. The second is that angular signals are not supported out of the box yet and that a person has to be pretty careful in the implementation to check that angular ZoneJs is not being used without notice.  

Reactivity and Zoneless 

In the Change Detection processes, Angular has sought an approach to the Concept of Fine Grain Reactivity that achieves that each time the performance in this process decreases in time.  

Zoneless is a feature of previous versions of Angular 17 that has applied the detection process considering the entire tree of components, but even the most recent versions have used the On Push method which tries to keep in mind only the components that change for Inputs and Outputs. 

Now with the use of signals in Angular 17, it seeks to reduce much more the Change Detection process approximating a more minimal expression within the components and corresponding only to the change of the signal. 

Conclusion 

With the new Angular 17 features, it provides the capabilities to keep up with other front-end development frameworks such as Vue or React, as it has managed to reduce performance costs and complexity in development.

Written in collaboration with Carlos Borrero and Sebastian Echeverry.

]]>
https://blogs.perficient.com/2024/06/13/elevate-your-development-with-angular-17/feed/ 0 364242
useOptimistic: Powering Next.js https://blogs.perficient.com/2024/05/02/useoptimistic-powering-next-js/ https://blogs.perficient.com/2024/05/02/useoptimistic-powering-next-js/#respond Thu, 02 May 2024 14:25:21 +0000 https://blogs.perficient.com/?p=360284

In today’s blog, we will examine an experimental hook that helps us display optimized results when we display data we fetch from the server. What can we do to optimize such a use case? Is there a way to show updated data as it is being fetched from the server? We will explore precisely that with the new useOptimistic hook.

This hook from React gives us a copy of our data, which we pass to it, and a function like how useState works. This function is then utilized to manipulate the copied data we show in our application until the new data is fetched from the server or API call.

Implementing the “useOptimistic” Hook in Your Project

Like useFormStatus and useFormState in earlier blogs, this is an experimental hook by react-dom; therefore, it will not be available with the usual npm install package command. To accomplish this, run the following command in your terminal to install the experimental version of react and react-dom:

npm install react@experimental react-dom@experimental

After installing the experimental packages, your package.json should contain the dependencies listed below:

useOptimistic

After doing this, if you want to use typescript in your project, you should additionally create a file informing your project that you will be utilizing the experimental utilities:
useOptimistic

Following that, you should be able to import useFormState into your files and use it appropriately. If you use TypeScript, you may need to add “//@ts-ignore” above the import. TypeScript will not recognize this experimental import but will continue functioning as intended.

//@ts-ignore
import { useFormState } from "react-dom";

Developing a Simple User Interface

Let’s now create a simple react component showing an input and a button. You can use a form here as well; I have simply used the onClick property of the button to recreate the API service call scenario.

return (
    <div className="bg-dark text-white py-5">
      <div className="row mx-0">
        <h2 className="text-center">useOptimistic</h2>
        <div className="col-md-6 p-5">
          <input
            ref={inputRef}
            type="text"
            className="form-control my-3"
            placeholder="Enter item name"
          />
          <button
            disabled={isLoading}
            onClick={() => startTransition(() => onClick())}
            className="btn btn-warning form-control"
          >
            Add Item
          </button>
        </div>
      </div>
    </div>
  );

We now have a simple input and button component. The input component is passed with an inputRef created by using the useRef hook of react, and the button’s onClick function is wrapped in a startTransition function to simulate loading.

useOptimistic

The Utilities Listed in Form

Next, we look at the useful hooks we need to import into our app and some other useful utilities we will use to demonstrate our new hook.

import { useState, useOptimistic, useTransition, useRef } from "react";
import "bootstrap/dist/css/bootstrap.css";

function App() {
  const createItem = (item) => {
    return new Promise((resolve) => setTimeout(() => resolve(item), 2000));
  };
  const inputRef = useRef();
  const [isLoading, startTransition] = useTransition();

  const [itemList, setItemList] = useState([]);
  const [optimisticItems, setOptimisticItems] = useOptimistic(itemList);

As shown in the code above, we import all our hooks from React. As I mentioned earlier, we are using the “startTransition” function to simulate loading here. We are taking this function as the second value provided to us from the useTransition hook in React, the first being the loading state for the function we use startTransition with.

I have created a function here to replicate the API service call behavior of fetching data. It is the createItem function. It basically returns me a value with a 2-second delay. Other than this, we just have the inputRef, which we will use to get the data from input and useState to put the data in an array.

The last line of the code above is what we are looking for. We use the useOptimistic hook to duplicate our itemList, storing it in the variable optimisticItems. The second item we get from this hook is a function that updates the same copied state. We will use this method to initially update our copied state until the service call is complete and we have the correct data in our itemList variable. So, let’s examine the function where we will make this call.

const onClick = async () => {
   const inputValue = inputRef.current.value;
   if (inputRef.current == null || !inputValue) return;
   setOptimisticItems((list) => [
     ...list,
     {
       itemName: inputValue,
       key: crypto.randomUUID(),
       loading: true,
     },
   ]);
   const newItem = await createItem(inputValue);
   setItemList((list) => [
     ...list,
     {
       key: crypto.randomUUID(),
       itemName: newItem,
     },
   ]);
 };

setOptimisticItems Function

Let’s go through this code one by one. First, we take the value of input from inputRef and check to see if it is empty. If it is empty, we will simply return from the function. Next, we have the setOptimisticItems function, which is basically the main purpose of this blog.

Here, we are keeping it as simple as it could get. We get the previous state as the first parameter of this function, which would be the existing list of items, so we will spread that array and add our new value to its end. This now creates our data array and puts the latest data with it before the service call starts in the following line. We are also giving it a loading value which we will use to indicate this is not the complete data, more on that later.

In the following line, we are making the service call (or mocking a service call in this case), getting actual data from the service, and updating our state with that exact data. Therefore, until we perform the await operation, we have already displayed the desired data using the optimistic value. Let’s see how we can implement using the loading indicator I mentioned earlier.

<div className="col-md-6 p-5 ms-4">
  <div>Item's List:</div>
  <ul className="my-3">
    {optimisticItems.map(({ itemName, loading, key }) => (
      <li key={key} className={loading ? "opacity-50" : "opacity-100"}>
        {itemName}
        {loading ? "..." : null}
      </li>
    ))}
  </ul>
</div>

In the above code, we show that the value that is not loaded yet will be displayed with half opacity and will have an ellipsis at the end to indicate it is loading or waiting for the service call to be completed.

Output

Initial form state:

useOptimistic

The user enters a value in the input field:

Picture5

 

The Add button can be clicked to start a service call:
Picture9

The value appears less visible than standard text and has an ellipsis at the end to indicate the loading state. When the loading is complete or the API call successfully completes, it will appear in its normal state, as shown in the image below.

Picture7

This is basically how we can show the data before it is ultimately part of the database and indicate that proper data is loading using this hook. Another screenshot to show the same.

Picture10

One simple yet powerful way to optimize user interactions in Next.js apps is to include the ‘useOptimistic’ hook. Developers may guarantee improved user pleasure and better performance by implementing optimistic updates. ‘useOptimistic’ is a helpful utility for Next.js developers, offering better user experiences with less work thanks to its effect and simplicity.

]]>
https://blogs.perficient.com/2024/05/02/useoptimistic-powering-next-js/feed/ 0 360284
Advanced Array Methods in JavaScript: Part 3 https://blogs.perficient.com/2024/04/08/advanced-array-methods-in-javascript-part-3/ https://blogs.perficient.com/2024/04/08/advanced-array-methods-in-javascript-part-3/#respond Mon, 08 Apr 2024 10:36:53 +0000 https://blogs.perficient.com/?p=359525

Welcome back to the third part of our series on elevating your JavaScript skills through array methods. Having established a solid foundation with simple array methods, we’re now poised to tackle more advanced methods. In this blog, we will discover sophisticated array methods that offer greater flexibility and power in manipulating data. Prepare to unlock new ranges of programming prowess as we continue our deep dive into JavaScript’s array methods. If you haven’t yet, make sure to explore essential array methods in Part 1 and Part 2 of this series.

Advanced array methods encompass a diverse range of categories, each serving specific purposes in data manipulation. These include:

  • Array Find and Search Methods
  • Array Sort Methods and Tricks
  • Array Iteration Methods

These categories provide developers with powerful tools for locating elements, organizing data, and iterating through arrays efficiently, enhancing the capabilities of JavaScript programming.

Array Find and Search Methods

indexOf() and lastIndexOf()

These advanced array methods are like searchlights in the dark, helping you pinpoint the exact location of a specific element within an array. If the element is found, it reveals its index. indexOf() uncovers the first occurrence, while lastIndexOf() reveals the last. However, if the element is nowhere to be found, they report back -1, indicating that the search was unsuccessful.

Example:

const animals = ["Cheetah", "Lion", "Zebra", "Horse", "Cheetah", "Deer"];
const firstIndex = animals.indexOf("Cheetah");
console.log("firstIndex", firstIndex);
const lastIndex = animals.lastIndexOf("Cheetah");
console.log("lastIndex", lastIndex);

Output:
Advanced Array 1

includes()

It is used to determine whether a value is included in the system entry and returns true or false as appropriate.

Example:

const colors = ['red', 'green', 'blue'];
console.log("includes method:")
console.log(colors.includes('green'));
console.log(colors.includes('yellow'));

Output:
Advanced Array 2

find() and findIndex()

find()

This function helps find the first array element that meets a condition. If found, it returns the element; otherwise, it is undefined.

Example:

const movies = ["The Lion King", "Aladdin", "The Jungle Book", "Moana"];
const foundMovie = movies.find((movie) => movie === "Aladdin");
console.log("find method:\n", foundMovie);

Output:
Advanced Array 3

findIndex()

It returns the index of the first element in the array that satisfies the given testing function. If the function does not satisfy any element, it will return -1.

Example:

const movies = ["The Lion King", "Aladdin", "The Jungle Book", "Moana"];
const index = movies.findIndex((movie) => movie === "Moana");
console.log("findIndex method:\n", index);

Output:
Advanced Array 4

findLast()

This method fetches the last array element that meets the condition set by the provided testing function.

Example:

const numbers = [10, 20, 30, 40, 50];
const lastNumber = numbers.findLast(num => num > 20);
console.log("Output:",lastNumber);

Output:
Advanced Array 5

findLastIndex()

It retrieves the index of the last array element that fulfills the conditions set by the testing function.

Example:

const numbers = [10, 20, 30, 40, 50];
const lastIndex = numbers.findLastIndex(num => num > 20);
console.log("Last index of matched condition:",lastIndex); // Output: 4 (index of 50)

Output:
Advanced Array 6

Array Iteration Methods

forEach()

It’s like having a guide show you around a museum, stopping at each exhibit along the way. method executes a function for every element within an array.

Example:

const numbers = [1, 2, 3, 4, 5];
console.log("forEach method:")
numbers.forEach((num) => console.log(num * 2));

Output:
Advanced Array 7

flat() and flatMap()

flat()

Imagine you have a stack of nested trays, each containing some items. flat() is like taking out all the items from those trays and putting them into a single tray, simplifying the organization.

Example:

const nestedArray = [["Peter Pan", "Aladdin"], ["Mulan", "Maleficent"], ["Moana", "Tangled"]];
const flattenedArray = nestedArray.flat();
console.log("flat method:\n",flattenedArray);

Output:
Advanced Array 8

flatMap()

It’s like having a stack of notebooks, and you need to examine each page, write something on it, and then gather all those pages into a new notebook. flatMap() first maps over every element inside the array using a function you offer and then flattens the result into a new array, making it easier to deal with.

Example:

const numbers = [1, 2, 3];
const mappedAndFlattened = numbers.flatMap(num => [num * 2, num * 3]);
console.log("flatMap:\n",mappedAndFlattened);

Output:
Advanced Array 9

filter()

Think of it as a filter on a coffee machine that separates ground coffee from brewed coffee, ensuring that only pure water flows in. Filter() in JavaScript searches each element of an array to establish a condition specifically, storing only those elements that satisfy the condition and using those elements to create a new array.

Example:

const numbers = [1, 2, 3, 4, 5];
const evenNumbers = numbers.filter((num) => num % 2 === 0);
console.log("filter method:\n", evenNumbers);

Output:
Advanced Array 10

every() and some()

These methods are like gatekeepers, checking each element against a condition.

every():

The condition is checked if all elements are met.

Example:

const numbers = [2, 4, 6, 7, 8];
const allEven = numbers.every((num) => num % 2 === 0);
console.log("every method:\n", allEven);

output:
Advanced Array 11

some():

Checks if at least one element meets a condition.

Example:

const numbers = [2, 4, 6, 7, 8];
const anyEven = numbers.some((num) => num % 2 === 0);
console.log("some method:\n", anyEven);

Output:
Advanced Array 12

reduce():

It’s like having a calculator that provides all the numbers in a list for you. You provide a function that tells the calculator a way to combine every range with the running total.

Syntax:

array.reduce(function(total, currentValue, currentIndex, arr), initialValue)

Example:

const numbers = [1, 2, 3, 4, 5];
const sum = numbers.reduce((total, current) => total + current, 0);
console.log("reduce method:\n",sum);

Output:
Advanced Array 13

Example 2:

const items = [
  { name: "Shirt", price: 20 },
  { name: "Pants", price: 30 },
  { name: "Shoes", price: 50 },
];

const totalPrice = items.reduce((acc, item) => acc + item.price, 0);
console.log("reduce method:");
console.log("Total Price:", totalPrice);

Output:
Advanced Array 14

reduceRight()

The function reduces each value of the array (from right to left) against an accumulator to produce a single value.

Example:

const arr = [1, 2, 3, 4];
const sum = arr.reduceRight((accumulator, currentValue) => accumulator + currentValue);
console.log("Sum of all numbers:",sum); // Output: 10 (4 + 3 + 2 + 1)

Output:
reduceRight

Array Sort Methods

1. Array Alphabetic Sort

sort():

The elements of an array are sorted in place by the sort() method, and the sorted array is returned. It rearranges elements either in place or by creating a new sorted array.

Example:

const numbers = [36, 17, 84, 01, 65, 19, 22, 16];
const sortedNumbers = numbers.sort();
console.log("sort method:\n",sortedNumbers)

output:
sort

reverse()

reverse() The order of the elements in the array is reversed. It’s like looking at a mirror image of your array. Moves an array to its location and returns a reference to the same array; the first array element is now the last, and the last array element is the first.

Example:

const animals = ["Cheetah", "Lion", "Zebra", "Horse", "Deer"];
animals.reverse();
console.log("reverse method:");
console.log("Array in reverse order:", animals);

Output:
reverse

toSorted()

The array is returned with elements sorted in ascending order.

Example:

const numbers = [5, 2, 8, 1, 4];
const sortedNumbers = numbers.toSorted();
console.log("Sorted in ascending order",sortedNumbers);

Output:
toSorted

toReversed()

Returns the array with elements in reverse order.

Example:

const numbers = [1, 2, 3, 4, 5];
const reversedNumbers = numbers.toReversed();
console.log("Elements in reverse order:",reversedNumbers);

Output:
toReversed

2. Array Numeric Sort

Math.min()

The smallest number among the provided arguments is returned.

Example:

const minNumber = Math.min(22,15,34); 
console.log("Smallest number in the series:",minNumber);

Output:
Math.min

Math.max()

The function determines the largest number among the arguments provided.

Example:

const maxNumber = Math.max(10, 45, 20); 
console.log("Largest number in the series:",maxNumber);

Output:
Math.max

Conclusion

Our exploration of JavaScript array methods has led us from fundamental operations to more advanced techniques. These tools empower developers to manipulate data efficiently and think critically about problem-solving in JavaScript. By mastering these methods, you’ll enhance your coding skills and uncover deeper layers of JavaScript’s potential. Keep practicing and experimenting to unlock even greater possibilities in your coding journey. For those who started here, consider revisiting Essential Methods Part 1 and Part 2 to ensure a comprehensive understanding of array basics.

]]>
https://blogs.perficient.com/2024/04/08/advanced-array-methods-in-javascript-part-3/feed/ 0 359525
Exploring AngularJS Routing: A Practical Guide https://blogs.perficient.com/2024/04/05/exploring-angularjs-routing-a-practical-guide/ https://blogs.perficient.com/2024/04/05/exploring-angularjs-routing-a-practical-guide/#respond Fri, 05 Apr 2024 06:56:35 +0000 https://blogs.perficient.com/?p=360619

AngularJS is a widely adopted JavaScript framework, arming developers with a rich arsenal of tools to engineer dynamic and captivating web applications. Notably, its robust routing capabilities emerge as a key pillar for constructing Single Page Applications (SPAs). Effectively orchestrating navigation and dynamically presenting diverse content sans the need for complete page refreshes, AngularJS routing emerges as a cornerstone in contemporary web development.

In this comprehensive guide, we will embark on a detailed exploration of AngularJS routing, meticulously dissecting its foundational principles. Moreover, we will equip you with tangible instances to foster a more profound comprehension and proficiency in navigating this vital element. Nevertheless, before delving into the complexities of AngularJS routing, it’s crucial to take a moment to explore the benefits inherent in Single Page Applications (SPAs). Then, we’ll explore how to implement both an about route as dynamic content and a product route as a dynamic route with practical code examples.

Benefits of Building SPAs with AngularJS Routing

Building SPAs with AngularJS brings a myriad of benefits to both developers and end-users. Some of these include:

Enhanced User Experience

By requesting only necessary information and resources from the server, Single-Page Applications (SPAs) maximize performance by reducing latency and enhancing overall speed and efficiency. Our optimized Method guarantees quick loading times and improves user experience by prioritizing relevant content delivery.

Improved Performance

SPAs fetch just the vital records and assets from the server, reducing latency and enhancing performance.

Simplified Development

AngularJS streamlines development by supplying a modular, element-based design that makes large-scale applications easier to manipulate and preserve.

Cross-Platform Compatibility

SPAs built with AngularJS are inherently cell-pleasant and can be easily adapted for diverse devices and display sizes.

Now that we’ve covered the blessings of SPAs, let’s investigate AngularJS routing and how it improves dynamic and attractive internet apps.

Understanding AngularJS Routing

AngularJS routing is based on the concept of mapping URLs to different views or templates within the application. It enables developers to define routes and associate each route with a specific template and controller. When a user navigates to a particular URL, AngularJS loads the associated template and controller, updating the view dynamically without requiring a full page reload.

Understanding Dynamic Content

Dynamic content refers to website elements or data that can change dynamically without requiring a full page reload. This dynamism enables developers to create more engaging and personalized user experiences by updating content in real-time based on user interactions or other factors. In AngularJS, dynamic content is typically achieved through data binding and the manipulation of model data.

Understanding Dynamic Routes

However, dynamic routes allow developers to outline routes with dynamic parameters that could be exchanged primarily based on person entry or other situations. These dynamic parameters act as placeholders in the route path, capturing values from the URL and allowing for the dynamic rendering of content. Dynamic routes provide a flexible and powerful mechanism for handling different types of content within an application.

Key Concepts

1. ngRoute

ngRoute is a module provided by AngularJS that enables routing capabilities in an application. Including this module as a dependency when defining an AngularJS application that utilizes routing is essential.

2. $routeProvider

The $routeProvider service is a crucial component of critical routing. It allows developers to configure routes within an application by defining URL paths and associating them with specific templates and controllers.

3. ng-view Directive

The ng-view directive plays a crucial role in AngularJS, indicating precisely where within an HTML template AngularJS should inject the contents of the current route’s template. It is a placeholder for rendering dynamic perspectives based on the cutting-edge route.

4. Controllers

Controllers in AngularJS are JavaScript functions that are responsible for defining the behavior and data associated with a particular view.It plays a crucial role in separating concerns within an application by handling view logic and interactions.

5. Template

In AngularJS routing, a template denotes an HTML file representing a distinct view within the application. These templates are linked with routes and dynamically loaded and rendered based on the current accessed route.

6. otherwise Method

The otherwise() Method specifies a default route to navigate if the requested route doesn’t match any of the defined routes. It ensures users are redirected to a designated route when accessing unrecognized URLs.

Example AngularJS Routing Implementation

Let’s dive into a practical example to demonstrate AngularJS routing in action.

Setting Up AngularJS Routing

To use routing in an AngularJS application, you first need to include the AngularJS and ngRoute libraries for your undertaking (index.html). You can download them from the official website or include them via a CDN link in your HTML file.

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.8.2/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.8.2/angular-route.js"></script>

You can also leverage package managers like NPM to manage your project dependencies more efficiently.

npm install angular angular-route

By going for walks with the above command, you may set up AngularJS and the angular-path module, which offers routing competencies, as dependencies in your project.

Once you’ve included AngularJS, you can define your application module and configure routing using the $routeProvider service provided by AngularJS.

routeProvider.js

var app = angular.module("myApp", ["ngRoute"]);
app.config(function ($routeProvider, $locationProvider) {
  $locationProvider.hashPrefix("");
  $routeProvider
    .when("/", {
      templateUrl: "views/home.html",
      controller: "HomeController",
    })
    .when("/about", {
      templateUrl: "views/about.html",
      controller: "AboutController",
    })
    .when("/product/:id", {
      templateUrl: "views/product.html",
      controller: "ProductController",
    })
    .otherwise({ redirectTo: "/" });
});

app.controller("MainController", function ($scope) {
  // MainController logic here
});

In the above code:

  • We define the application module `myApp` and specify the dependency on the `ngRoute` module, which provides routing capabilities.
  • We configure routes using the `$routeProvider.when()` Method. Each route definition consists of a URL path, a template URL, and, optionally, a controller.
  • We’ve added the $locationProvider.hashPrefix(”) configuration to remove the default hashbang (#!) from the URL.
  • The `otherwise()` Method specifies the default route to navigate if the requested route doesn’t match any defined routes.

Creating Views and Controllers

Now that we’ve configured routing let’s create the views and controllers for our routes.

Home View and Controller

Create a file named home.html inside a directory called views. This will be the template for the home route.

home.html

<div ng-controller="HomeController">
  <h1>Welcome to the Home Page</h1>
  <p>This is the home page of our AngularJS application.</p>
</div>

Next, create a controller named HomeController in the routeProvider.js file.

routeProvider.js

app.controller("HomeController", function ($scope) {
  //Home Controller logic here
});

Implementing Dynamic Content and Routes

Let’s now see how we can implement both an about route as dynamic content and a product route as a dynamic route in an AngularJS application.

About View and Controller as Dynamic Content

Let’s observe how we can enforce dynamic content in AngularJS. Similarly, create a report named about.html in the perspectives listing.

about.html

<div ng-controller="AboutController">
  <h1>{{ pageTitle }}</h1>
  <p>{{ pageContent }}</p>
</div>

In this about.html template:

  • We use AngularJS expressions ({{ }}) to dynamically render the page title and content, which are bound to the corresponding scope variables (pageTitle and pageContent) set in the AboutController.
  • When navigating the About page, users will see the dynamically populated title and content.

In the AboutController, we can dynamically set the page title and content based on route parameters or any other logic specific to the about page. Then, define the <strong>AboutController</strong> by adding the mentioned code in the routeProvider.js file.

routeProvider.js

app.controller("AboutController", function ($scope) {
  $scope.pageTitle = "About Us";
  $scope.pageContent = "Learn more about our company and mission here.";
});

Product View and Controller as Dynamic Route

Create the product view template (product.html) and define the corresponding controller (ProductController) to handle the dynamic product data.

product.html

<div ng-controller="ProductController">
  <h1>Product Details</h1>
  <p>ID: {{ productId }}</p>
</div>

In the above product.html template, we display the product details, including the product ID obtained from the $routeParams service.

Then, define the ‘ProductController’ by adding the mentioned code in the routeProvider.js file.

routeProvider.js

app.controller('ProductController', function($scope, $routeParams) {
  $scope.productId = $routeParams.id;
  // Fetch product data based on the ID and update the view
});

The ProductController extracts the product ID from the route parameters and can then use it to fetch the corresponding product data from the server or another data source.

Linking Routes to Navigation

For seamless navigation between various routes, leverage the ng-href directive within your HTML to establish links to different paths specified in your route definitions.

index.html

<div ng-controller="MainController">
  <ul>
    <li><a ng-href="#/">Home</a></li>
    <li><a ng-href="#/about">About</a></li>
    <li><a ng-href="#/product/1">Product 1</a></li>
    <li><a ng-href="#/product/2">Product 2</a></li>
  </ul>
  <div ng-view></div>
</div>

Note: When implementing AngularJS routing, remember to add the ng-app= “myApp” attribute to the opening <html> tag in your index.html file. Additionally, link the routeProvider.js file in your index.html to enable routing functionality.

The ng-view directive is a placeholder where AngularJS will inject the templates associated with the current route.

Output

Output

Conclusion

AngularJS routing provides a powerful mechanism for building SPAs by enabling navigation between different views without page reloads. Developers can create dynamic and interactive web applications by configuring routes, defining templates, and linking them to controllers. With the examples and explanations provided in this guide, you should now understand AngularJS routing and be well-equipped to leverage it in your projects.

Remember to explore further documentation and best practices to enhance your routing implementation and create seamless user experiences in your AngularJS applications. Happy coding!

]]>
https://blogs.perficient.com/2024/04/05/exploring-angularjs-routing-a-practical-guide/feed/ 0 360619
Exploring Basics of React’s useReducer and useRef Hooks https://blogs.perficient.com/2024/04/04/exploring-basics-of-reacts-usereducer-and-useref-hooks/ https://blogs.perficient.com/2024/04/04/exploring-basics-of-reacts-usereducer-and-useref-hooks/#respond Thu, 04 Apr 2024 10:00:56 +0000 https://blogs.perficient.com/?p=360923

In the vast landscape of React development, developers are armed with powerful tools to navigate challenges effortlessly: the useReducer and useRef hooks. This guide offers a deep dive into these hooks, unveiling their functionalities, adaptable use cases, and advanced methodologies.

What is `useReducer`?

The <strong>useReducer</strong> hook in React is a cornerstone for managing state, offering a more nuanced approach than its counterpart, <strong>useState.</strong> Its value shines brightest when grappling with intricate state logic, especially in scenarios where the state comprises multiple sub-values or the next state hinges on its predecessor.

Basic Usage

Let’s start with a basic example. Suppose we have a counter component. Instead of resorting to <strong>useState,</strong> developers can employ <strong>useReducer</strong> to orchestrate state management within their React applications.

Counter.js

import React, { useReducer } from "react";

const initialState = { count: 0 };

function reducer(state, action) {
  switch (action.type) {
    case "increment":
      return { count: state.count + 1 };
    case "decrement":
      return { count: state.count - 1 };
    default:
      throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <div>
      Count: {state.count}
      <button onClick={() => dispatch({ type: "increment" })}>+</button>
      <button onClick={() => dispatch({ type: "decrement" })}>-</button>
    </div>
  );
}

export default Counter;

Within this demonstration, the reducer function operates as a pivotal entity responsible for processing both the current state and an action, ultimately yielding a fresh state contingent upon the nature of the action. Through the utilization of the useReducer hook, we seamlessly integrate this reducer function alongside the initial state. This amalgamation furnishes us with access to the current state and a potent dispatch function, empowering us to propagate actions directly to the reducer, thereby orchestrating state transformations with precision and efficiency.

Output

Counter

Advanced Usage

useReducer can handle more complex state objects. You can use it with objects, arrays, or any other data structure. Additionally, you can combine it with useContext for global state management or optimize performance with useMemo and useCallback.

Now, let’s explore an advanced usage scenario of useReducer with a more complex state object:

AdvancedCounter.js

// AdvancedCounter.js
import React, { useReducer } from 'react';

const initialState = {
  count: 0,
  showText: false
};

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { ...state, count: state.count + 1 };
    case 'toggleText':
      return { ...state, showText: !state.showText };
    default:
      throw new Error();
  }
}

function AdvancedCounter() {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <div>
      <div>
        Count: {state.count}
        <button onClick={() => dispatch({ type: 'increment' })}>Increment</button>
      </div>
      <div>
        <button onClick={() => dispatch({ type: 'toggleText' })}>
          {state.showText ? 'Hide Text' : 'Show Text'}
        </button>
        {state.showText && <p>This is a dynamic text!</p>}
      </div>
    </div>
  );
}

export default AdvancedCounter;

In this example, our state object comprises both a counter and a Boolean value to toggle text visibility. The reducer function now handles both incrementing the count and toggling the text display.

Output

Advancecounter

What is `useRef`?

The useRef hook is another essential hook in React. It is primarily used for accessing and manipulating DOM elements directly. Unlike useState or useReducer, changes to a ref don’t cause a re-render. Such versatility makes it apt for various tasks, ranging from overseeing focus management and instigating imperative animations to seamlessly integrating with third-party DOM libraries.

Basic Usage

Let’s create a simple instance to apprehend how useRef works. Suppose we have a form component that requires focusing on an input field when it mounts.

Form.js

import React, { useRef, useEffect } from 'react';

function Form() {
  const inputRef = useRef(null);

  useEffect(() => {
    inputRef.current.focus();
  }, []);

  return (
    <div>
      <input ref={inputRef} type="text" />
      <button>Submit</button>
    </div>
  );
}

export default Form;

In the illustration provided, useRef emerges as the linchpin, facilitating the establishment of a reference to the input element within the component’s scope. Subsequently, leveraging the effect hook, we orchestrate the focus onto the input element upon the component’s initial rendering, meticulously ensuring that this operation occurs precisely once through the judicious utilization of an empty dependency array [].

Output

Form

Advanced Usage

useRef is not limited to DOM elements, it can also persist mutable values across renders without causing re-renders. This property makes it useful for storing previous values, caching values between renders, or interacting with imperative APIs.

Now, let’s discover a complicated utilization scenario where useRef is hired to persist mutable values:

AdvancedForm.js

// AdvancedForm.js
import React, { useRef, useEffect } from "react";

function AdvancedForm() {
  const renderCount = useRef(0);

  useEffect(() => {
    renderCount.current++;
  });

  return (
    <div>
      <p>This component has rendered {renderCount.current} times.</p>
    </div>
  );
}

export default AdvancedForm;

Within this instance, useRef is ingeniously harnessed to maintain the tally of component renderings throughout successive re-renders, all the while circumventing the unnecessary triggering of additional renders. By employing this tactic, we ensure the seamless preservation of the render counts across iterations, thus exemplifying the versatile capabilities of the useRef hook in React development.

Output

Advancedform

Conclusion

In this guide, we’ve explored two effective hooks supplied by React: useReducer and useRef. useReducer is a versatile tool for dealing with complicated state logic, while useRef gives direct get entry to DOM elements and permits for staying power of mutable values. Understanding and mastering those hooks will notably enhance your React development skills, allowing you to construct greater green and robust packages. Start integrating them into your projects and explore their full potential!

]]>
https://blogs.perficient.com/2024/04/04/exploring-basics-of-reacts-usereducer-and-useref-hooks/feed/ 0 360923
Essential Array Methods Guide: Part 2 https://blogs.perficient.com/2024/03/28/essential-array-methods-guide-part-2/ https://blogs.perficient.com/2024/03/28/essential-array-methods-guide-part-2/#respond Thu, 28 Mar 2024 13:07:31 +0000 https://blogs.perficient.com/?p=359523

In continuation of Part 1, we will now delve into essential array methods that enhance your JavaScript toolkit. These methods allow you to effortlessly handle complex data manipulation tasks, opening new possibilities for your projects.

isArray()

isArray() is your trusty detector for identifying arrays. Just pass any value to Array.isArray(), and it quickly tells you whether it’s an array or not. Simple, yet incredibly useful for handling different types of data.

Example:

const animals = ["Cheetah", "Lion", "Zebra", "Horse", "Deer"];
const isArray = Array.isArray(animals);
console.log("Is passed value an array: ", isArray);

Output:
Essential Array 1

keys(), values(), and entries()

These methods give you iterators for keys, values, and key-value pairs within an array. Essentially, they provide different ways for you to move through the elements of an array.

Key

const fruits = ["apple", "banana", "orange"];
for (const key of fruits.keys()) {
  console.log("Keys of an array: ", key);
}

Output:
Essential Array 2

value

const fruits = ["apple", "banana", "orange"];
for (const value of fruits.values()) {
  console.log("Values of an array: ", value);
}

Output:

entries

const fruits = ["apple", "banana", "orange"];
for (const [index, fruit] of fruits.entries()) {
  console.log("Values of array with its index: ", index, fruit);
}

Output:
Array 4

from()

Array.from(): It’s like a magic wand that turns almost anything into an array. Whether you have scattered pieces of data or an iterable object, Array.from() neatly organizes them into a new array, ready for action.

Example:

const arrayLike = { length: 3, 0: "a", 1: "b", 2: "c" };
const newArray = Array.from(arrayLike);
console.log(newArray);

Output:
from()

fill()

The fill() method is used to change all elements in an array to a static value.

Example:

const numbers = [1, 2, 3, 4, 5];
numbers.fill(0, 2, 4); // [1, 2, 0, 0, 5]
console.log(numbers)

Output:
fill()

flat()

Imagine you have a stack of nested trays, each containing some items. flat() is like taking out all the items from those trays and putting them into a single tray, simplifying the organization.

Example:

const nestedArray = [["Peter Pan", "Aladdin"], ["Mulan", "Maleficent"], ["Moana", "Tangled"]];
const flattenedArray = nestedArray.flat();
console.log("flat method:\n",flattenedArray);

Output:
flat()

with()

The with() method is a tool for arrays that lets you change one specific item without touching the original array. It creates a new array reflecting the update.

Syntax

newArray = array.with(index, newValue);

Parameters

  • index: This represents the position of the item that needs to be updated.
  • newValue: The new value for the specified item.

Example:

const originalArray = ['apple', 'banana', 'cherry'];
const newArray = originalArray.with(1, 'grape');

console.log("New Array:",newArray); 
console.log("Orignal Array:",originalArray);

Output:
with()

length

Length tells you how many items are in an array or characters in a string.

Example:

const languages = ["Urdu", "English", "Hindi", "Spanish", "Italian"];

console.log("Length of array:",languages.length);

Output:
length

at()

Get the item located at a particular position within an array or a string, considering positive and negative indexes.

Example:

const languages = ["Urdu", "English", "Hindi", "Spanish", "Italian"];

console.log("Element at end of the array:",languages.at(-1));

Output:
Essential Array 10

join()

The function combines all elements of an array into a single string, separating them with a specified delimiter.

Example:

const series = ["Array", "Methods", "In", "JavaScript"];

console.log("Series:",series.join(' '));

Output:
join()

delete()

Removes a property from an object; it is not typically used for arrays as it leaves undefined holes.

Example:

const languages = ["Urdu", "English", "Hindi", "Spanish", "Italian"];

delete languages[1];

console.log("After deleting element at 1",languages);

Output:
Essential Array 12

spread (…)

This method enables an array or other iterable object to be spread out in locations where functions or arrays anticipate zero or more arguments or elements.

Example:

const languagesOne = ["Urdu", "English"];
const languagesTwo = ["Spanish", "Italian"];
const allLanguages = [...languagesOne, ...languagesTwo]; // Combines languagesOne and languagesTwo into allLanguages
console.log("Combined array:",allLanguages);

Output:
Essential Array 13

toString()

converts all elements of an array into a single string, separating them with commas.

Example:

const languages = ["Urdu", "English", "Hindi", "Spanish", "Italian"];

let languagesString = languages.toString();
console.log("Array to String:",languagesString);

Output:
Essential Array 14

Conclusion:

As we wrap up the essential and primary part of our exploration into JavaScript array methods, remember that these foundational techniques serve as the building blocks for more complex operations. Mastery of adding, removing, and manipulating elements within arrays opens a myriad of possibilities for data handling in your JavaScript projects. Stay tuned for the next part of our series, where we’ll delve into advanced array methods and further expand our JavaScript toolkit.

]]>
https://blogs.perficient.com/2024/03/28/essential-array-methods-guide-part-2/feed/ 0 359523
Essential Array Methods Guide: Part 1 https://blogs.perficient.com/2024/03/27/essential-array-methods-guide-part-1/ https://blogs.perficient.com/2024/03/27/essential-array-methods-guide-part-1/#respond Wed, 27 Mar 2024 16:26:05 +0000 https://blogs.perficient.com/?p=359501

Arrays are a cornerstone of JavaScript programming, offering a versatile way to keep and manage data. In this initial segment, we`ll explore the essential array methods that every JavaScript developer should know. If you’re eager to advance further, keep an eye out for advanced array methods in JavaScript.

Introduction to Essential Array Methods

Arrays are fundamental data structures in JavaScript, allowing collections of objects to be stored and manipulated efficiently. This blog builds the foundation for array conversions in JavaScript and explores important array methods.

push() and pop()

push()

The function adds elements to the end of an array.

Example:

const fruits = ["apple", "banana"];
fruits.push("orange"); 
console.log("Push method\n", fruits);

Output:
Essential Array Methods 1

pop()

Only the last person in the queue leaves when someone leaves from the end. Similarly, the last element is removed from an array and returned by pop().

Example:

const fruits = ["apple", "banana", "orange"];
const removedFruit = fruits.pop();
console.log("Pop method", fruits);

Output:
Essential Array Methods 2

shift() and unshift()

Let’s switch gears and talk about the beginning of the array, where folks arrive and leave first.

shift()

This method is like the first person in line leaving. The function removes the first element from an array and returns it.

Example:

const Movies = ['The Lion King', 'Aladdin', 'The Jungle Book', 'Moana']
const firstMovie = Movies.shift();
console.log('Shift method:\n',Movies)

Output:
Essential Array Methods 3

unshift():

It’s like making room for a brand-new character at the front of a line. Unshift() provides new factors at the beginning of an array.

Example:

const Movies = ['The Lion King', 'Aladdin', 'The Jungle Book', 'Moana']
Movies.unshift('maleficent');
console.log('unshift method:\n',Movies)

Output:
Essential Array Methods 4

concat

This combines arrays, creating a fresh array without altering the originals.

Example:

const Movies1 = ["The Lion King", "Aladdin"];
const Movies2 = ["The Jungle Book", "Moana"];

const combinedMovies = Movies1.concat(Movies2);
console.log("concat method:\n", combinedMovies);

Output:
Essential Array Methods 5

slice()

Picture cutting a slice of pizza without disturbing the rest of it. The slice() method takes out a piece of an array and gives you a new array without affecting the original.

Example:

const vegetables = ["cucumbers", "carrots", "potatoes", "Onions"];
const sliceOfVegetable = vegetables.slice(1, 3);
console.log("slice method:\n", sliceOfVegetable);

Output:
Essential Array Methods 6

splice()

Consider getting a necklace with flowers that you can rearrange or add. splice() allows you to make changes to an array by removing or replacing elements and inserting new ones at specific locations.

Example:

const games = ["Archery", "Base ball", "Cricket"];
games.splice(2, 2, "Dodgeball", "Football");
console.log("splice mathods:\n", games);

Output:
Essential Array Methods 7

toSpliced()

The Array.toSpliced() method creates a new array by removing or replacing elements in an existing array without modifying the original array. Since it’s an immutable operation, the source array doesn’t change. When you wish to work with a changed version of the original array but need to preserve its state, this approach comes in handy.

Syntax

array.toSpliced(start, deleteCount, item1, item2,... itemN)

Parameters

start: The index at which to initiate changing the array. A negative index can be used, counting back from the last item.

deleteCount: items will be deleted from the array, starting from the given start.

item1, item2,…, itemN: Items to add to the array starting from the start position.

Original Tasks List

const tasks = ['Email team', 'Meeting at 2 PM', 'Write report'];

Adding Elements Without Removing

Suppose you want to add two new tasks at the end without removing any existing tasks.

const addedTasks = tasks.toSpliced(3, 0, 'Update project plan', 'Review budgets');

console.log(addedTasks);

Output:
Picture9

Removing Elements Without Adding

Let’s say the meeting has been canceled, and you want to remove it from the list without adding anything new.

const removedTasks = tasks.toSpliced(1, 1);

console.log(removedTasks);

Output:
Picture10

Replacing an Element

If you need to replace ‘Write report’ with ‘Prepare presentation’, here’s how you could do it:

const replacedTask = tasks.toSpliced(2, 1, 'Prepare presentation');

console.log(replacedTask);

Output:
Picture11

Using Negative Indexes

Suppose you want to add a task at the end, but use a negative index to specify the position.

const negativeIndexAdd = tasks.toSpliced(-1, 0, 'Check emails');

console.log(negativeIndexAdd);

Output:
Picture12

Removing and Adding with a Negative Index

Lastly, if you want to replace the last task with two new ones using a negative index,

const negativeIndexReplace = tasks.toSpliced(-1, 1, 'Prepare invoices', 'Send updates');

console.log(negativeIndexReplace);

Output:
Picture13

copyWithin()

method copies a sequence of elements within the same array, effectively overwriting existing elements.

Syntax

array.copyWithin(target, start, end)

Parameters

  • target: the index to copy elements to.
  • start: The index to commence copying elements from.
  • end: (optional) index to stop copying, but not including. If omitted, copyUntil() will copy until the end of the array.

Example:

const array = ['apple', 'banana', 'cherry', 'date', 'elderberry'];

// Copy the elements from index 0 to index 3, overwriting elements starting from index 2
array.copyWithin(2, 0, 3);

console.log(array);

Output:
Picture14

Conclusion

As we conclude the first part of our study of JavaScript essential array methods, keep in mind that these fundamental techniques serve as the foundation for more complicated operations. Stay tuned for the second chapter, where we’ll review more important array functions and broaden our JavaScript toolkit.

]]>
https://blogs.perficient.com/2024/03/27/essential-array-methods-guide-part-1/feed/ 0 359501
useFormState in Next.js: Form Management https://blogs.perficient.com/2024/03/26/useformstate-in-next-js-form-management/ https://blogs.perficient.com/2024/03/26/useformstate-in-next-js-form-management/#respond Tue, 26 Mar 2024 16:09:21 +0000 https://blogs.perficient.com/?p=359720

In my previous blog, we had discussed using server actions with our forms and managing the loading state with the new useFormStatus hook from react-dom. In this one, we are going to explore another experimental hook from react-dom: useFormState.

Concisely, useFormState is a hook to which we provide a function to manipulate form data. The function provides us with two values, the first being the form data value or the manipulated data we get from our provided function. As we know, when developing NextJS apps, we prefer to have our code on the server side. This hook is beneficial since the function we provide to it will be a server function. More on that later.

Using useFormState in your Code

As I have mentioned earlier, this is an experimental hook by react-dom, meaning it will not be available with our usual npm install package installing command. For this, we will have to run the command instruction in our terminal meant to install the experimental version of react and react-dom:

npm install react@experimental react-dom@experimental

After installation of experimental packages, your package.json should have the dependencies as follows:

Useformstate6

Once this has been completed, a file should also be created to inform your project that the experimental utilities will be used if typescript is being used in your project:

Useformstate7

After this, you should be able to import useFormState in your files and use them accordingly. //@ts-ignore” may need to be added right above the import as TypeScript will not recognize this experimental import, but it will still function as intended.

//@ts-ignore
import { useFormState } from "react-dom";

Creating a Simple Form

Let’s create a simple form that accepts a name and an age.

<form action={onSubmit} className="form-control py-3 border-primary border-3">
<Input inputName="username" placeholder="Enter Name" />
    <Input inputName="age" placeholder="Enter Age" />
<SaveButton />
</form>

We will be using the “name” property of HTML’s input tag, which is given value here in our Input component by the “inputName” property, for picking data from the form inputs.

const Input = ({
  inputName,
  placeholder
}: {
  inputName: string;
  placeholder: string;
}) => {
  return (
    <div className="my-2">
      <input
        className="form-control"
        name={inputName}
        type="text"
        placeholder={placeholder}
      />
    </div>
  );
};

This is the output of our basic form so far:

Useformstate1

As for the submit button, this is where we will be using an aspect of useFormStatus, another experimental hook I mentioned earlier, that is the loading state. It is a Boolean value by the name “pending,” which we get from this hook. We can use this for disabling the submit button to avoid multiple form submit calls or to change the text on the button like we have done here:

//@ts-ignore
import { useFormStatus } from "react-dom";

const SaveButton = () => {
  const { pending: isLoading } = useFormStatus();
  return (
    <div className="my-3">
      <button
        disabled={isLoading}
        className="btn btn-warning form-control"
        type="submit"
      >
        {isLoading ? "Saving..." : "Save"}
      </button>
    </div>
  );
};

More on this in my previous blog post on this hook.

As for our form, we will use the form’s action attribute for our submit action. But first, let’s take a look at the action we create for the purpose of form submission.

const [{ username, age, error }, updateProfile] = useFormState(
    saveProfile,
    {
      username: "",
      age: "",
      error: {},
    }
  );

As seen in the above code snippet, useFormState takes two values. The first is the action we want to perform when submitting the form, and the second is the initial state values of the form. It then returns two values in the form of an array, as we see with the useState hook from React. When we execute our submit action, we receive the updated value as the first value, and the second value is a function that mirrors our created function, the “saveProfile” function. We will utilize this new function, “updateProfile,” for our form submission. When the form is submitted, we will get the updated values again in the first value of the returned array, which I have deconstructed here for convenience.

Form Submit Action

Now let’s take a look at the server action that we passed to our experimental new hook:

"use server";

import { Profile} from "./types";

export const saveProfile: (_prevState: Profile, profile: Profile) => void = (
  _prevSate,
  profile
) => profile;

As we can observe from the top of this file, the file is a server file, and therefore, it will execute its action on the server side rather than the client side. This is basically the main reason for the use of this process. Now if we look at our function, it is accepting two parameters, but we only should be passing one parameter value, which would be our form data. So where did this extra parameter come from? It is actually due to the useFormState hook.

Earlier, I stated that we pass a function to the useFormState hook and, in return, receive a value and a function that resembles a copy of the function passed to the hook. So the extra parameter is actually from the new “updateProfile” function we have taken from the hook. And as seen from the first parameter’s name, this is the extra parameter which has the previous state data of our form, since we do not have a need for this, I have appended an underscore to its name to denote it as an unused variable.

Now Let’s Use This With our Form

To do so, we just need to pass the new function we got from the useFormState hook to the form’s action property. By passing FormData of the form with the details of the form to our function, we can retrieve the data using the get function of the FormData.

const username = (formData.get("username") as string) || "";
  const age = (formData.get("age") as string) || "";

Let’s add some validation to our form submit function:

export const saveProfile: (_prevState: Profile, formData: FormData) => Profile = (
  _prevSate,
  formData
) => {
  const username = (formData.get("username") as string) || "";
  const age = (formData.get("age") as string) || "";
  const invalidName = username.length && username.length < 4;
  const invalidAge = age.length && parseInt(age) < 18;
  let error: ProfileError = {};
  if (!username) error.username = "Name cannot be empty";
  if (!age) error.age = "Age cannot be empty";
  if (invalidName) error.username = "Username must be at least 4 characters";
  if (invalidAge) error.age = "Age must be above 18 years";
  if (username && !invalidName) error.username = "";
  if (age && !invalidAge) error.age = "";

  return ({ username, age, error });
};

Typically, when a user enters values into form input fields and clicks on the submit button, we expect the submit action to be on the client side as these actions are performed on the client device. But with the useFormState we have managed to move this logic to the server side as a server action. We can add more complex validations than what we see above using regex or service API for more checks if required.

Adding these error messages to our form along with their respective input fields:

<form action={updateProfile} className="form-control py-3 border-primary border-3">
        <Input inputName="username" placeholder="Enter Name" />
        <div className="text-danger fs-6">{error.username}</div>
        <Input inputName="age" placeholder="Enter Age" />
        <div className="text-danger fs-6">{error.age}</div>
        <SaveButton />
      </form>

Let’s see the result of our form with the error validation:

Empty form submission:

Useformstate2

Invalid Username input:

Useformstate3

Invalid Age input:

Useformstate4

Successful form submission:
Useformstate5

Conclusion

In conclusion, mastering the useFormState hook in React enables efficient form management, offering easy access to form data and dynamic state tracking. Experiment with its features to enhance your form-building skills and deliver seamless user experiences.

 

]]>
https://blogs.perficient.com/2024/03/26/useformstate-in-next-js-form-management/feed/ 0 359720
useNavigate: Navigation React Router v6 https://blogs.perficient.com/2024/03/22/usenavigate-navigation-react-router-v6/ https://blogs.perficient.com/2024/03/22/usenavigate-navigation-react-router-v6/#respond Fri, 22 Mar 2024 10:42:20 +0000 https://blogs.perficient.com/?p=359628

In React͏ development, smooth moving between pages is vital for a seamless͏ user experience. React Router v6 brings out the useNavigate hook for advanced navigation, simplifying navigation logic͏ in functional elements. This ͏blog post will delve into the useNavigate hook, its features, functions, and benefits in React Router v6.

Introducing useN͏avigate

In React Router͏ v6, useNavigate͏ is a hook ͏given ͏by͏ the react-͏r͏oute͏r-dom package. It substitutes͏ the useHistory hook from past versions and provides similar features for moving between routes ͏programmatically. The useNavigate hook gives back a navigation function that helps you navigate to various routes within your app. To learn more about navigation in React Router, check out my other blog post on navigation with useHistory.

How to Use useNavigate

Let’s illustrate the usage of useNavigate with an example:

Step 1: Import useNavigate

First, import the useNavigate hook from the ‘react-router-dom’ package at the top of your functional component file:

import { useNavigate } from 'react-router-dom';

Step 2: Access the navigate Function

Within your functional component, call the useNavigate hook to access the navigate function:

const navigate = useNavigate();

Step 3: Use the navigate Function to Navigate

Then, you can use the navigate function to navigate to different paths in your application. This can be triggered by an action such as clicking a button or based on some other reason triggering this:

import React from "react";
import { useNavigate } from 'react-router-dom';
import Heading from "./Heading";
import "./NavigationDemo.css";

const NavigationDemo = () => {
  
  const navigate = useNavigate();

  const navigateToNewPath = () => {
    navigate('/new-path' , { state: { page: 'New Page' } }); 
  };

  const replaceCurrentPath = () => {
    navigate('/updated-path', { state: { page: 'Updated Page' } }); 
  };
  return (
    <div className="container">
      <Heading>React Router Navigation Demo</Heading>
      <button onClick={navigateToNewPath}>Navigate to New Path</button>
      <button onClick={replaceCurrentPath}>Replace Current Path</button>
    </div>
  );
};

export default NavigationDemo;


In this example, when the Navigate to New Path button is clicked, the navigateToNewPath function is called, which then uses the navigate function to navigate to the ‘/new-path’ route. When the Replace Current Path button is clicked, it replaces the current path with an updated path in the history, that is, to ‘/updated-path’.

Output:
Advanced Navigation in React Router v6: useNavigate.

Features and functions of useNavigate

The useNavigate hook provides several features and functions for navigation management:

navigate(to, options): The main function returned by useNavigate. Goes to the specified route with other routing option(s).

location: A read-only property that returns the current location object containing information about the current URL.

status: A read-write property that allows state data to be passed to the destination.

replace(to, state): A function that replaces the current contents of the history stack with a new representation such as history.replace.

preload(to): A function that preloads a specified method’s code asynchronously, useful for improving client-side communication performance.

Advantages of useNavigate Over useHistory in React Router v6

With the upgrade from React Router v5 to v6, the useNavigate hook replaced the useHistory hook as a more flexible option. This enhancement reflects a deliberate effort to simplify navigation logic in React applications. Here are several key advantages of useNavigate over useHistory:

1. Simplified API

useNavigate provides a simpler API than useHistory, making it easier for developers to perform navigation tasks without directly interacting with the history object.

useHistory approach:

const history = useHistory();
history.push('/home');
history.replace('/login');

useNavigate approach:

const navigate = useNavigate();
navigate('/home'); // Equivalent to history.push
navigate('/login', { replace: true }); // Equivalent to history.replace

2. Related transportation assistance

useNavigate natively supports relative navigation, allowing developers to write more maintainable and flexible navigation logic, especially in nested routing scenarios.

  • In useHistory, navigation of the current path is cumbersome and error-prone, requiring manual creation of the target path.
  • useNavigate facilitates by providing navigational instructions about the current path.

3. Direct integration with the navigation environment

Navigating state on the destination path is made even easier with useNavigate, increasing the ability to share state between paths without URL clutter.

useHistory method:

history.push({ pathname: '/dashboard', state: { Login: true } });

useNavigate  Method:

navigate('/dashboard', { state: { Login: true } });

4. Improved routing practices

useNavigate provides additional methods for handling navigation behavior, such as change, touch, preventScrollReset and other advanced methods, which are neither direct nor intuitive in useHistory

5. Future-proof and recommended strategies

Introducing useNavigate to React Router v6 and ignoring useHistory signals a shift towards a simpler and more efficient way of managing traffic Adoption of useNavigate is encouraged to make your application future-proof and conform to the latest best practices in React development.

6. Enhanced programmability and readability

The useNavigate hook increases programmability by allowing developers to easily navigate programmatically, improving code readability and maintainability. It eliminates the difficulties of travel and makes the designer’s thinking clear.

Handling Relative Paths and Advanced Use Cases

React Router v6’s approach to relative paths and advanced navigation scenarios provides developers with increased flexibility. For example, to navigate relative to the current URL instead of the route hierarchy, developers can manipulate the current location with the URL constructor, facilitating more complex navigation patterns. You can also check this doc for more information.

Moreover, React Router v6 introduces experimental options like unstable_flushSync and unstable_viewTransition, offering developers experimental ways to manage navigation and view transitions. While marked as unstable, these features highlight the ongoing development and potential future capabilities of the React Router.

options.unstable_flushSync

The options.unstable_flushSync option is used to control the timing of state updates during navigation. By default, React Router uses React.startTransition for state updates when navigating between routes. This approach allows React to prioritize different types of updates and can help keep the application responsive even during large updates. However, there are situations where a developer might need the state update to happen synchronously, especially if immediate feedback is necessary after a navigation action.

When unstable_flushSync is set to true, React Router DOM wraps the initial state update for the navigation in a ReactDOM.flushSync call. This makes the update synchronous, ensuring that the state change and associated UI update happen immediately without waiting for other updates.

It is important to word that, because of the potential impact on performance and person experience, this selection must be used sparingly and simplest while definitely essential.

options.unstable_viewTransition

The options.unstable_viewTransition option enables the use of view transitions for a navigation action. This is part of an experimental feature set that provides more control over how visual changes are handled when navigating between routes.

Setting unstable_viewTransition to true indicates that the navigation should trigger a view transition. This can be useful in scenarios where you want to apply specific styles or animations during the route transition. It leverages the document.startViewTransition() API, allowing developers to define a period during which elements from the old and new routes can overlap, enabling smoother transitions.

This option enhances your application’s visual continuity during navigation events. Like unstable_flushSync, unstable_viewTransition is marked as unstable and experimental. It’s part of ongoing efforts to improve the routing experience in web applications but should be used with caution, as its behavior may change in future releases.

Conclusion:

useNavigate is a valuable addition to React Router v6, providing an intuitive and efficient way to manage navigation in active objects. Its features and functionality provide developers with powerful tools to manage navigation logic and enhance the user experience in React applications. By using useNavigate, you can streamline navigation performance and create a seamless browsing experience for your users in React Router v6 applications.

]]>
https://blogs.perficient.com/2024/03/22/usenavigate-navigation-react-router-v6/feed/ 0 359628
useFormStatus FormControl in Next.js https://blogs.perficient.com/2024/03/20/form-control-exploring-useformstatus-in-next-js/ https://blogs.perficient.com/2024/03/20/form-control-exploring-useformstatus-in-next-js/#respond Wed, 20 Mar 2024 05:14:41 +0000 https://blogs.perficient.com/?p=359316

In Next JS, developers prefer most of their code to remain on the server side. However, it is quite tricky to accomplish such feats when you are working with forms. Components that require inputs from the user and thus must be client components to perform user operations like button clicks or text inputs. There useFormStatus comes.

So, let’s embark on this journey together to understand an experimental hook from react-dom: useFormStatus. This hook provides us with the details of the form it uses, like its loading state, the data held by the form at the point of submission, and its method and action.

Adding useFormStatus to your Project

As I have mentioned earlier, this is an experimental hook by react-dom. It will not be available with our usual npm install package installing command.

For this, we will have to run the command instruction in our terminal meant to install the experimental version of react and react-dom:

Installation

npm install react@experimental react-dom@experimental

After installation of experimental packages, your package.json should have the dependencies as follows:

Next5
Once this is done, you should also create a file to let your project know you will be using the experimental utilities if you will be using typescript in your project:
Next6

After this, you should be able to import useFormStatus in your files and use them accordingly. You may have to add “//@ts-ignore” right above the import as typescript will not recognize this experimental import, but it will still work as it is intended to.

//@ts-ignore
import { useFormStatus } from "react-dom";

Creating a Simple Form

Let’s create a simple form with basic test inputs and a submit button to use this hook for its intended purpose.

<form action={onSubmit} className="form-control py-3 border-info">
<ProfileInput inputName="firstName" placeholder="First Name" />
    <ProfileInput inputName="lastName" placeholder="Last Name" />
<SubmitProfile />
</form>

Here, the inputName property of the ProfileInput component is important, as we will give this value to the input’s name property. We will then use that property to grab the values the user will enter in the form.

const ProfileInput = ({
  inputName,
  placeholder
}: {
  inputName: string;
  placeholder: string;
}) => {
  return (
    <div className="my-2">
      <input
        className="form-control"
        name={inputName}
        type="text"
        placeholder={placeholder}
      />
    </div>
  );
};

This is the output of our basic form so far:
Next1

As for the submit button, we will use an aspect of useFormStatus, which is the loading state. We get a Boolean value called “pending” from this hook. We can use this for disabling the submit button to avoid multiple forms submit calls or to change the text on the button like we have done here:

//@ts-ignore
import { useFormStatus } from "react-dom";

const SubmitProfile = () => {
  const { pending: isLoading } = useFormStatus();
  return (
    <div className="my-3">
      <button
        disabled={isLoading}
        className="btn btn-warning form-control"
        type="submit"
      >
        {isLoading ? "Loading..." : "Submit"}
      </button>
    </div>
  );
};

Notice how we do not pass any onSubmit or onClick function to the button? That is because although this is the submit button of our form, we are submitting the form through the action attribute of the html form component. In this way, we can execute a server action when we submit our form, meaning the code snippet that runs during the form submission can be from the server side, which, as I mentioned at the start of this post, is what we want for our Next JS project.

Action Attribute to our Form

Let me show you how the form’s action attribute helps us with this.

const InputForm = () => {
  const [profileList, updateProfileList] = useFormState(updateProfiles, []);

  const onSubmit = async (formData: FormData) => {
    const newProfile = await submitProfileAction(formData);
    updateProfileList(newProfile);
  };
  return (
    <>
      <form action={onSubmit} className="form-control py-3 border-info">
        <ProfileInput inputName="firstName" placeholder="First Name" />
        <ProfileInput inputName="lastName" placeholder="Last Name" />
        <SubmitProfile />
      </form>
    </>
);

In the snippet above, we are passing a function to the action attribute of the form, which usually takes a string path. But here, we can see that we are using the same attribute to execute a function with a parameter that supposedly includes the form’s data. The functions within the onSubmit function are the server actions that we want to execute when the form is submitted, so they will be in a file marked “use server.”

"use server";

import { Profile } from "./types";

export const updateProfiles: (
  oldProfiles: Profile[],
  newProfile: Profile
) => void = (oldProfiles, newProfile) => [...oldProfiles, newProfile];

const createProfile = (profile: Profile) => {
  return new Promise((resolve) => setTimeout(() => resolve(profile), 2000));
};

export const submitProfileAction = async (formData: FormData) => {
  const firstName = formData.get("firstName") as string || '';
  const lastName = formData.get("lastName") as string || '';
  return await createProfile({ firstName, lastName });
};

In the submitProfileAction, we are taking input values of our form with the get property in formData (which we got when we passed onSubmit to the action attribute in the form). This is why we gave values to name attributes in our inputs since we are using the same values from that name attribute to pick the data of the form we want.

This is the fascinating part, and the reason why we are using our form component in this way instead of the traditional onSubmit function passed to the submit button. The ability to perform server actions while using forms is quite beneficial to exploit while working on Next JS projects.

Let’s add some output for the form action:

return (
    <>
      <form action={onSubmit} className="form-control py-3 border-info">
        <ProfileInput inputName="firstName" placeholder="First Name" />
        <ProfileInput inputName="lastName" placeholder="Last Name" />
        <SubmitProfile />
      </form>
      {!!profileList.length && (
        <ul className="bg-light border-success form-control border-3 my-3 p-4">
          {profileList.map(({ firstName, lastName }: Profile) => (
            <li key={crypto.randomUUID()}>{`${firstName} ${lastName}`}</li>
          ))}
        </ul>
      )}
    </>

This should allow us to see how our form performs with server actions and a loading state provided by useFormStatus.

Output

 using useFormStatus

Loading state after form submission

Loading state after form submission using useFormStatus

Result after the form is submitted

Result after the form is submitted using useFormStatus

Conclusion

The useFormStatus hook simplifies form management in Next.js applications by abstracting complexity associated with form state management. This tool enables developers to create more maintainable and user-friendly forms. Streamlining the development process and enhancing the user experience in Next.js applications.

]]>
https://blogs.perficient.com/2024/03/20/form-control-exploring-useformstatus-in-next-js/feed/ 0 359316
React Navigation: A Rollercoaster Ride with useHistory https://blogs.perficient.com/2024/03/15/react-navigation-a-rollercoaster-ride-with-usehistory/ https://blogs.perficient.com/2024/03/15/react-navigation-a-rollercoaster-ride-with-usehistory/#respond Fri, 15 Mar 2024 09:51:02 +0000 https://blogs.perficient.com/?p=359079

React applications often require navigation between different components and pages. The useHistory hook, provided by React Router, simplifies navigation by providing access to the browser’s history object. In this blog, we’ll explore the useHistory hook, understand its implementation, and see practical examples of how it enhances navigation in React applications.

Understanding useHistory

The useHistory hook is part of the React Router library, specifically designed for handling navigation in React applications. It allows components to interact with the browser’s history object, enabling programmatic navigation.

Importing useHistory:

To commence using useHistory for your React application, import it from react-router-dom:

import { useHistory } from "react-router-dom";

Accessing the History Object

Once imported, you can call useHistory() within your react component to get the history object:

 const history = useHistory();

Here are some commonly used methods (or attributes) of the history object returned by useHistory and their purposes:

Navigation Methods

push(path, [state])

Logic

Push is used to navigate a new path by adding a new entry to the history stack. It allows you to provide an optional state object associated with the latest history entry.

Example

//JSX
    const history = useHistory();
    history.push({
      pathname: "/new-path",
      state: "New Page",
    });

The above example uses history.push to navigate to /new-path and passes the information provided in the state.

import React from "react";
import { useLocation } from "react-router-dom";
import Heading from "../components/useHistory/Heading";

const New = () => {
  const location = useLocation();
  let data = location.state;
  return (
    <div>
      <Heading>{data}</Heading>
    </div>
  );
};

export default New;

In the above example, when the user arrives at /new-path, you can access the passed state (New page) through the location object. If you’re using functional components, you can access them via the useLocation hook from react-router-dom. To learn about the location, please check out my profile.
This method of passing state is beneficial for sending data across routes that don’t need to be part of the URL.

replace(path, [state])

Logic

You use the function to replace the current entry on the history stack with a new one. This is useful when updating the current path without adding a new history entry.

Example

//JSX
const history = useHistory();
history.replace({
  pathname: "/updated-path",
  state: "Update Page",
});
import React from "react";
import { useLocation } from "react-router-dom";
import Heading from "../components/useHistory/Heading";

const Update = () => {
  const location = useLocation();
  let data = location.state;
  return <Heading>{data}</Heading>;
};

export default Update;

In this example, the ClickHandler triggers history.replace instead of history.push to navigate to /details-page and pass state information in the state. history.replace overwrites the current history entry, eliminating the ability to return to it while history.push adds a new entry, allowing back navigation.

goBack()

Logic

GoBack simulates clicking the back button in the browser. It navigates back one step in the browser’s history.

Example

const goBackInHistory = () => {
  history.goBack();
};

When the button is clicked, clickHandler triggers history.goBack(), simulating the user returning to the browser’s history.

goForward()

Logic

GoForward simulates clicking the forward button in the browser. It navigates forward one step in the browser’s history.

Example

const goForwardInHistory = () => {
  history.goForward();
};

When the button is clicked, clickHandler triggers history.goForward(), simulating the user going forward in the browser’s history.

go(n)

Logic

The go function in JavaScript lets you navigate forwards or backward in the browser’s history by specifying a positive or negative number of steps.

Example

const goTwoStepsForward = () => {
  history.go(2);
};

When the button is clicked, clickHandler triggers history.go(2), moving two steps forward in the browser’s history.

block

Logic

The history.block function in React Router shows users a confirmation dialog when they try to leave the current page. This feature helps prevent users from accidentally navigating away from a page without saving important information or changes.

Example

import React from "react";
import { useEffect } from "react";
import { useHistory } from "react-router-dom";
import Heading from "../components/useHistory/Heading";
function Block() {
  const history = useHistory();
  useEffect(() => {
    const unblock = history.block(
      "Are you sure you want to leave this page? Your changes may not be saved."
    );

    return () => {
      unblock();
    };
  }, [history]);
  return <Heading>Block Page</Heading>;
}

export default Block;

const toBlockPath = () => {
  history.push("/block-path");
};

In this example, when the Block component is rendered, it is called history.block, and a message will be displayed to the user when they try to navigate away from the page. The Block function returns a function to unblock the navigation, which we clean up in the useEffect hook’s cleanup function to avoid memory leaks.

When the user clicks the “Save” button, the toBlockPath function is called, which saves the form data and then allows navigation to the /block-path page by calling history.push.

Additional Properties

length:

This property indicates the number of entries in the history stack.

Example

const numberOfEnteries = history.length

action

This property returns the current navigation action, such as ‘PUSH’, ‘REPLACE’, or ‘POP’.

Example

const currentAction = history.action;

Let’s combine all the methods we have seen and use them to create a React application to better understand them:

import React from "react";
import { useHistory } from "react-router-dom";

import "./NavigationDemo.css";

const NavigationDemo = () => {
  const history = useHistory();

  const currentAction = history.action;

  const navigateToNewPath = () => {
    history.push({
      pathname: "/new-path",
      state: "New Page",
    });
  };

  const replaceCurrentPath = () => {
    history.replace({
      pathname: "/updated-path",
      state: "Update Page",
    });
  };

  const goBackInHistory = () => {
    history.goBack();
  };

  const goForwardInHistory = () => {
    history.goForward();
  };

  const goTwoStepsForward = () => {
    history.go(2);
  };

  const toBlockPath = () => {
    history.push("/block-path");
  };

  
  return (
    <div className="container">
      <h1>React Router Navigation Demo</h1>
      <button onClick={navigateToNewPath}>Navigate to New Path</button>
      <button onClick={replaceCurrentPath}>Replace Current Path</button>
      <button onClick={goBackInHistory}>Go Back</button>
      <button onClick={goForwardInHistory}>Go Forward</button>
      <button onClick={goTwoStepsForward}>Go Two Steps Forward</button>
      <button onClick={toBlockPath}>save</button>
      <button>{currentAction}</button>
      <p>Number of Entries in History: {history.length}</p>
      <p>Current Action: {history.action}</p>
    </div>
  );
};

export default NavigationDemo;

Output
Picture1

Conclusion

 Incorporating the useHistory hook into your React components enables dynamic and navigable user interfaces. Whether you’re adding new entries, updating existing pathways, or moving back and forth across history, useHistory’s functionalities enable developers to create seamless and engaging user experiences.

 

]]>
https://blogs.perficient.com/2024/03/15/react-navigation-a-rollercoaster-ride-with-usehistory/feed/ 0 359079