JavaScript (JS) is an object-oriented programming language that allows engineers to produce and implement complex features within web browsers. JavaScript is popular because of its versatility and is preferred as the primary choice unless a specific function is needed. In this Zone, we provide resources that cover popular JS frameworks, server applications, supported data types, and other useful topics for a front-end engineer.
Performance Optimization Strategies in Highly Scalable Systems
How To Create Isometric Diagrams Using SVG
Seasoned software engineers long for the good old days when web development was simple. You just needed a few files and a server to get up and running. No complicated infrastructure, no endless amount of frameworks and libraries, and no build tools. Just some ideas and some code hacked together to make an app come to life. Whether or not this romanticized past was actually as great as we think it was, developers today agree that software engineering has gotten complicated. There are too many choices with too much setup involved. In response to this sentiment, many products are providing off-the-shelf starter kits and zero-config toolchains to try to abstract away the complexity of software development. One such startup is Zipper, a company that offers an online IDE where you can create applets that run as serverless TypeScript functions in the cloud. With Zipper, you don’t have to spend time worrying about your toolchain — you can just start writing code and deploy your app within minutes. Today, we’ll be looking at a ping pong ranking app I built — once in 2018 with jQuery, MongoDB, Node.js, and Express; and once in 2023 with Zipper. We’ll examine the development process for each and see just how easy it is to build a powerful app using Zipper. Backstory First, a little context: I love to play ping pong. Every office in which I’ve worked has had a ping pong table, and for many years ping pong was an integral part of my afternoon routine. It’s a great game to relax, blow off some steam, strengthen friendships with coworkers, and reset your brain for a half hour. Those who played ping pong every day began to get a feel for who was good and who wasn’t. People would talk. A handful of people were known as the best in the office, and it was always a challenge to take them on. Being both highly competitive and a software engineer, I wanted to build an app to track who was the best ping pong player in the office. This wouldn’t be for bracket-style tournaments, but just for recording the games that were played every day by anybody. With that, we’d have a record of all the games played, and we’d be able to see who was truly the best. This was 2018, and I had a background in the MEAN/MERN stack (MongoDB, Express, Angular, React, and Node.js) and experience with jQuery before that. After dedicating a week’s worth of lunch breaks and nights to this project, I had a working ping-pong ranking app. I didn’t keep close track of my time spent working on the app, but I’d estimate it took about 10–20 hours to build. Here’s what that version of the app looked like. There was a login and signup page: Office Competition Ranking System — Home page The login page asked for your username and password to authenticate: Office Competition Ranking System — Login page Once authenticated, you could record your match by selecting your opponent and who won: Office Competition Ranking System — Record game results page You could view the leaderboard to see the current office rankings. I even included an Elo rating algorithm like they use in chess: Office Competition Ranking System — Leaderboard page Finally, you could click on any of the players to see their specific game history of wins and losses: Office Competition Ranking System — Player history page That was the app I created back in 2018 with jQuery, MongoDB, Node.js, and Express. And, I hosted it on an AWS EC2 server. Now let’s look at my experience recreating this app in 2023 using only Zipper. About Zipper Zipper is an online tool for creating applets. It uses TypeScript and Deno, so JavaScript and TypeScript users will feel right at home. You can use Zipper to build web services, web UIs, scheduled jobs, and even Slack or GitHub integrations. Zipper even includes auth. In short, what I find most appealing about Zipper is how quickly you can take an idea from conception to execution. It’s perfect for side projects or internal-facing apps to quickly improve a business process. Demo App Here’s the ping-pong ranking app I built with Zipper in just three hours. And that includes time reading through the docs and getting up to speed with an unfamiliar platform! First, the app requires authentication. In this case, I’m requiring users to sign in to their Zipper account: Ping pong ranking app — Authentication page Once authenticated, users can record a new ping-pong match: Ping pong ranking app — Record a new match page They can view the leaderboard: Ping pong ranking app — Leaderboard page And they can view the game history for any individual player: Ping pong ranking app — Player history page Not bad! The best part is that I didn’t have to create any of the UI components for this app. All the inputs and table outputs were handled automatically. And, the auth was created for me just by checking a box in the app settings! You can find the working app hosted publicly on Zipper. Ok, now let’s look at how I built this. Creating a New Zipper App First, I created my Zipper account by authenticating with GitHub. Then, on the main dashboard page, I clicked the Create Applet button to create my first applet. Create your first applet Next, I gave my applet a name, which became its URL. I also chose to make my code public and required users to sign in before they could run the applet. Applet configuration Then I chose to generate my app using AI, mostly because I was curious how it would turn out! This was the prompt I gave it: “I’d like to create a leaderboard ranking app for recording wins and losses in ping pong matches. Users should be able to log into the app. Then they should be able to record a match showing who the two players were and who won and who lost. Users should be able to see the leaderboard for all the players, sorted with the best players displayed at the top and the worst players displayed at the bottom. Users should also be able to view a single player to see all of their recorded matches and who they played and who won and who lost.” Applet initialization I might need to get better at prompt engineering because the output didn’t include all the features or pages I wanted. The AI-generated code included two files: a generic “hello world” main.ts file, and a view-player.ts file for viewing the match history of an individual player. main.ts file generated by AI view-player.ts file generated by AI So, the app wasn’t perfect from the get-go, but it was enough to get started. Writing the Ping Pong App Code I knew that Zipper would handle the authentication page for me, so that left three pages to write: A page to record a ping-pong match A page to view the leaderboard A page to view an individual player’s game history Record a New Ping Pong Match I started with the form to record a new ping-pong match. Below is the full main.ts file. We’ll break it down line by line right after this. TypeScript type Inputs = { playerOneID: string; playerTwoID: string; winnerID: string; }; export async function handler(inputs: Inputs) { const { playerOneID, playerTwoID, winnerID } = inputs; if (!playerOneID || !playerTwoID || !winnerID) { return "Error: Please fill out all input fields."; } if (playerOneID === playerTwoID) { return "Error: PlayerOne and PlayerTwo must have different IDs."; } if (winnerID !== playerOneID && winnerID !== playerTwoID) { return "Error: Winner ID must match either PlayerOne ID or PlayerTwo ID"; } const matchID = Date.now().toString(); const matchInfo = { matchID, winnerID, loserID: winnerID === playerOneID ? playerTwoID : playerOneID, }; try { await Zipper.storage.set(matchID, matchInfo); return `Thanks for recording your match. Player ${winnerID} is the winner!`; } catch (e) { return `Error: Information was not written to the database. Please try again later.`; } } export const config: Zipper.HandlerConfig = { description: { title: "Record New Ping Pong Match", subtitle: "Enter who played and who won", }, }; Each file in Zipper exports a handler function that accepts inputs as a parameter. Each of the inputs becomes a form in UI, with the input type being determined by the TypeScript type that you give it. After doing some input validation to ensure that the form was correctly filled out, I stored the match info in Zipper’s key-value storage. Each Zipper applet gets its own storage instance that any of the files in your applet can access. Because it’s a key-value storage, objects work nicely for values since they can be serialized and deserialized as JSON, all of which Zipper handles for you when reading from and writing to the database. At the bottom of the file, I’ve added a HandlerConfig to add some title and instruction text to the top of the page in the UI. With that, the first page is done. Ping pong ranking app — Record a new match page Leaderboard Next up is the leaderboard page. I’ve reproduced the leaderboard.ts file below in full: TypeScript type PlayerRecord = { playerID: string; losses: number; wins: number; }; type PlayerRecords = { [key: string]: PlayerRecord; }; type Match = { matchID: string; winnerID: string; loserID: string; }; type Matches = { [key: string]: Match; }; export async function handler() { const allMatches: Matches = await Zipper.storage.getAll(); const matchesArray: Match[] = Object.values(allMatches); const players: PlayerRecords = {}; matchesArray.forEach((match: Match) => { const { loserID, winnerID } = match; if (players[loserID]) { players[loserID].losses++; } else { players[loserID] = { playerID: loserID, losses: 0, wins: 0, }; } if (players[winnerID]) { players[winnerID].wins++; } else { players[winnerID] = { playerID: winnerID, losses: 0, wins: 0, }; } }); return Object.values(players); } export const config: Zipper.HandlerConfig = { run: true, description: { title: "Leaderboard", subtitle: "See player rankings for all recorded matches", }, }; This file contains a lot more TypeScript types than the first file did. I wanted to make sure my data structures were nice and explicit here. After that, you see our familiar handler function, but this time without any inputs. That’s because the leaderboard page doesn’t need any inputs; it just displays the leaderboard. We get all of our recorded matches from the database, and then we manipulate the data to get it into an array format of our liking. Then, simply by returning the array, Zipper creates the table UI for us, even including search functionality and column sorting. No UI work is needed! Finally, at the bottom of the file, you’ll see a description setup that’s similar to the one on our main page. You’ll also see the run: true property, which tells Zipper to run the handler function right away without waiting for the user to click the Run button in the UI. Ping pong ranking app — Leaderboard page Player History Alright, two down, one to go. Let’s look at the code for the view-player.ts file, which I ended up renaming to player-history.ts: TypeScript type Inputs = { playerID: string; }; type Match = { matchID: string; winnerID: string; loserID: string; }; type Matches = { [key: string]: Match; }; type FormattedMatch = { matchID: string; opponent: string; result: "Won" | "Lost"; }; export async function handler({ playerID }: Inputs) { const allMatches: Matches = await Zipper.storage.getAll(); const matchesArray: Match[] = Object.values(allMatches); const playerMatches = matchesArray.filter((match: Match) => { return playerID === match.winnerID || playerID === match.loserID; }); const formattedPlayerMatches = playerMatches.map((match: Match) => { const formattedMatch: FormattedMatch = { matchID: match.matchID, opponent: playerID === match.winnerID ? match.loserID : match.winnerID, result: playerID === match.winnerID ? "Won" : "Lost", }; return formattedMatch; }); return formattedPlayerMatches; } export const config: Zipper.HandlerConfig = { description: { title: "Player History", subtitle: "See match history for the selected player", }, }; The code for this page looks a lot like the code for the leaderboard page. We include some types for our data structures at the top. Next, we have our handler function which accepts an input for the player ID that we want to view. From there, we fetch all the recorded matches and filter them for only matches in which this player participated. After that, we manipulate the data to get it into an acceptable format to display, and we return that to the UI to get another nice auto-generated table. Ping pong ranking app — Player history page Conclusion That’s it! With just three handler functions, we’ve created a working app for tracking our ping-pong game history. This app does have some shortcomings that we could improve, but we’ll leave that as an exercise for the reader. For example, it would be nice to have a dropdown of users to choose from when recording a new match, rather than entering each player’s ID as text. Maybe we could store each player’s ID in the database and then display those in the UI as a dropdown input type. Or, maybe we’d like to turn this into a Slack integration to allow users to record their matches directly in Slack. That’s an option too! While my ping pong app isn’t perfect, I hope the takeaway here is how easy it is to get up and running with a product like Zipper. You don’t have to spend time agonizing over your app’s infrastructure when you have a simple idea that you just want to see working in production. Just get out there, start building, and deploy!
For many full-stack developers, the combination of Spring Boot and React has become a staple in building dynamic business applications. Yet, while powerful, this pairing has its set of challenges. From type-related errors to collaboration hurdles, developers often find themselves navigating a maze of everyday issues. Enter Hilla, a framework that aims to simplify this landscape. If Hilla hasn't crossed your radar yet, this article will provide an overview of what it offers and how it can potentially streamline your development process when working with Spring Boot and React. Spring Boot, React, and Hilla For full-stack developers, the combination of Java on the backend and React (with TypeScript) on the frontend offers a compelling blend of reliability and dynamism. Java, renowned for its robust type system, ensures data behaves predictably, catching potential errors at compile-time. Meanwhile, TypeScript brings a similar layer of type safety to the JavaScript world, enhancing React's capabilities and ensuring components handle data as expected. However, while both Java and TypeScript offer individual type-safe havens, there's often a missing link: ensuring that this type-safety is consistent from the backend all the way to the frontend. This is where the benefits of Hilla shine, enabling End-to-End Type Safety Direct Communication Between React and Spring Services Consistent Data Validation and Type Safety End-To-End Type Safety Hilla takes type safety a step further by ensuring it spans the entire development spectrum. Developers spend less time perusing API documentation and more time coding. With automatically generated TypeScript services and data types, Hilla allows developers to explore APIs directly within their IDE. This seamless integration means that if any code is altered, whether on the frontend or backend, any inconsistencies will trigger a compile-time error, ensuring that issues are caught early and rectified. Direct Communication Between React and Spring Services With Hilla, the cumbersome process of managing endpoints or deciphering complex queries becomes a thing of the past. Developers can directly call Spring Boot services from their React client, receiving precisely what's needed. This is achieved by making a Spring @Service available to the browser using Hilla's @BrowserCallable annotation. This direct communication streamlines data exchange, ensuring that the frontend gets exactly what it expects without any unnecessary overhead. Here's how it works. First, you add @BrowserCallable annotation to your Spring Service: Java @BrowserCallable @Service public class CustomerService { public List<Customer> getCustomers() { // Fetch customers from DB or API } } Based on this annotation, Hilla auto-generates TypeScript types and clients that enable calling the Java backend in a type-checkable way from the frontend (no need to declare any REST endpoints): TypeScript function CustomerList() { // Customer type is automatically generated by Hilla const [customers, setCustomers] = useState<Customer[]>([]); useEffect(() => { CustomerService.getCustomers().then(setCustomers); }, []); return ( <ComboBox items={customers} ></ComboBox> ) } Consistent Data Validation and Type Safety One of the standout features of Hilla is its ability to maintain data validation consistency across the stack. By defining data validation rules once on the backend, Hilla auto-generates TypeScript validations for the frontend. This not only enhances developer productivity but also ensures that data remains consistent, regardless of where it's being processed. For instance, if a field is marked as @NotBlank in Java, Hilla ensures that the same validation is applied when this data is processed in the React frontend. Java public class Customer { @NotBlank(message = "Name is mandatory") private String name; @NotBlank(message = "Email is mandatory") @Email private String email; // Getters and setters } The Hilla useForm hook uses the generated TypeScript model to apply the validation rules to the form fields. TypeScript function CustomerForm() { const {model, field, read, submit} = useForm(CustomerModel, { onSubmit: CustomerService.saveCustomer }); return ( <div> <TextField label="Name" {...field(model.name)} /> <EmailField label="Email" {...field(model.email)} /> <Button onClick={submit}>Save</Button> </div> ) } Batteries and Guardrails Included Hilla streamlines full-stack development by offering pre-built tools, enhancing real-time capabilities, prioritizing security, and ensuring long-term adaptability. The framework provides a set of pre-built UI components designed specifically for data-intensive applications. These components range from data grids and forms to various select components and editors. Moreover, for those looking to implement real-time features, Hilla simplifies the process with its support for reactive endpoints, removing the need for manual WebSocket management. Another notable aspect of Hilla is its security integration. By default, it's connected with Spring Security, offering robust access control mechanisms to safeguard data exchanges. The framework's stateless design ensures that as user demands increase, the application remains efficient. Hilla's design approach not only streamlines the current development process but also future-proofs your app. It ensures that all components integrate seamlessly, making updates, especially transitioning from one version to another, straightforward and hassle-free. In Conclusion Navigating the complexities of full-stack development in Spring Boot and React can be complex. This article highlighted how Hilla can alleviate many of these challenges. From ensuring seamless type safety to simplifying real-time integrations and bolstering security, Hilla stands out as a comprehensive solution. Its forward-thinking design ensures that as the tech landscape evolves, your applications remain adaptable and updates remain straightforward. For those immersed in the world of Spring Boot and React, considering Hilla might be a step in the right direction. It's more than just a framework; it's a pathway to streamlined and future-ready development.
In today's data-driven world, data visualization simplifies complex information and empowers individuals to make informed decisions. One particularly valuable chart type is the Resource Chart, which facilitates efficient resource allocation. This tutorial will be your essential guide to creating dynamic resource charts using JavaScript. A resource chart is a type of Gantt chart that visualizes data about resource utilization, such as equipment, employees, and so on. It provides a comprehensive overview, making it easier to make informed decisions promptly. As an illustrative example, in this tutorial, I will represent the FIFA World Cup 2022 Qatar schedule by stadium, enabling you to track when and where each game took place. Get your coding boots ready, and by the end of this guide, you'll be well-equipped to create your own JS-based resource chart and have a valuable tool at your disposal for tracking your next favorite tournament, server status, employee project assignments, or anything else of that kind. Resource Chart To Be Crafted Are you excited to see what we're about to create? Keep reading, and you’ll learn how to craft a JavaScript resource chart like the one showcased below. Intrigued? Let's kick off this thrilling journey together! Building Resource Chart The resource chart might seem like a challenging structure at first glance, with horizontal bars representing time periods. However, I assure you that it's quite straightforward once you get the hang of it. You can construct this chart by following these four basic steps: Create a web page in HTML. Include the necessary JavaScript files. Load the data. Write some JS code to visualize the resource chart. Now, let’s delve into each step in detail. 1. Create a Web Page in HTML To begin, create a basic HTML page to host your JavaScript resource chart. Within the body of the HTML document, add an HTML block element such as <div> that will serve as the container for your upcoming chart. Give it an ID, which you'll reference later in your JavaScript code when creating the chart. To ensure the chart utilizes the correct position, define some CSS rules within the <style> block. Below is an example of a simple web page created this way. I’ve named the <div> element as “container” and adjusted its height and width to make the chart utilize the entire screen. HTML <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>JavaScript Resource Gantt Chart</title> <style type="text/css"> html, body, #container { width: 100%; height: 100%; margin: 0; padding: 0; } </style> </head> <body> <div id="container"></div> </body> </html> 2. Include the Necessary JavaScript Files When it comes to data visualization, JavaScript charting libraries are invaluable tools. The key is to find one that not only suits your needs but also supports the specific chart type you're looking for. In this tutorial, I’ll use AnyChart, a long-living JS charting library that supports resource charts and provides comprehensive documentation, and it’s free (unless you integrate it into a commercial application). If you choose to use a different library, the overall process remains essentially the same. You have two primary options for including the necessary JavaScript files of your chosen library: downloading them and using them locally or linking to them directly via a CDN (Content Delivery Network). In this tutorial, I’ll opt for the CDN approach. Below is what it will look like when linking the required scripts in the <head> section of your HTML page. The chart's code will find its home within the <script> tag in the <body> section. Alternatively, you can also place it in the <head> section if that suits your project structure better. HTML <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>JavaScript Resource Gantt Chart</title> <style type="text/css"> html, body, #container { width: 100%; height: 100%; margin: 0; padding: 0; } </style> <script src="https://cdn.anychart.com/releases/8.11.1/js/anychart-core.min.js"></script> <script src="https://cdn.anychart.com/releases/8.11.1/js/anychart-gantt.min.js"></script> <script src="https://cdn.anychart.com/releases/8.11.1/js/anychart-data-adapter.min.js"></script> </head> <body> <div id="container"></div> <script> // The place for the following chart’s code. </script> </body> </html> 3. Load the Data Now, let's load the data. In this tutorial, the schedule of the 2022 FIFA World Cup will be visualized. The data is available in JSON format on the provided GitHub gist. The data consists of a list of objects, with each object representing a stadium. You'll find details such as its name and city inside each stadium object. Additionally, there is a property called "periods," containing a list of matches that have been organized in that stadium. Each match is represented by the names of the two competing countries and the result of the match. To correctly feed this type of data into the resource chart, utilize the anychart.data.loadJsonFile() method. Below is the code snippet that loads the data: JavaScript anychart.data.loadJsonFile("https://gist.githubusercontent.com/awanshrestha/07b9144e8f2539cd192ef9a38f3ff8f5/raw/b4cfb7c27b48a0e92670a87b8f4b1607ca230a11/Fifa%2520World%2520Cup%25202022%2520Qatar%2520Stadium%2520Schedule.json"); 4. Write Some JS Code to Visualize the Resource Chart With the data loaded, you are now ready to move on and see how a few lines of JavaScript code can transform into a fully functional resource chart. Begin by adding the anychart.onDocumentReady() function encapsulates all the necessary code to ensure that your code executes only when the page is fully loaded. HTML <script> anychart.onDocumentReady(function () { // The resource chart data and code will be in this section. }); </script> Next, load the data and create a data tree. JavaScript anychart.onDocumentReady(function () { // load the data anychart.data.loadJsonFile( "https://gist.githubusercontent.com/awanshrestha/07b9144e8f2539cd192ef9a38f3ff8f5/raw/b4cfb7c27b48a0e92670a87b8f4b1607ca230a11/Fifa%2520World%2520Cup%25202022%2520Qatar%2520Stadium%2520Schedule.json", function (data) { // create a data tree var treeData = anychart.data.tree(data, 'as-table’); } ); }); Then, utilize the ganttResource() method to create the resource Gantt chart and set your data tree using the data() method. JavaScript // create a resource gantt chart var chart = anychart.ganttResource(); // set the data for the chart chart.data(treeData); Place the chart within the <div> container introduced in Step 1, and finally, draw the chart using the draw() method. JavaScript // set the container chart.container("container"); // draw the chart chart.draw(); Voila! You've successfully created a simple and fully functional resource chart using JavaScript. Take a look at how it appears in action; the interactive version of this chart is available here. For your convenience, the complete code for the basic resource chart is also provided. With this resource chart, you can easily visualize which matches took place in which stadiums, and you can scroll through the matches section on the right to view all the matches. But there's more to explore, so let's proceed to customize this interactive data visualization. HTML <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>JavaScript Resource Gantt Chart</title> <style type="text/css"> html, body, #container { width: 100%; height: 100%; margin: 0; padding: 0; } </style> <script src="https://cdn.anychart.com/releases/8.11.1/js/anychart-core.min.js"></script> <script data-fr-src="https://cdn.anychart.com/releases/8.11.1/js/anychart-gantt.min.js"></script> <script src="https://cdn.anychart.com/releases/8.11.1/js/anychart-data-adapter.min.js"></script> </head> <body> <div id="container"></div> <script> anychart.onDocumentReady(function () { // load the data anychart.data.loadJsonFile( "https://gist.githubusercontent.com/awanshrestha/07b9144e8f2539cd192ef9a38f3ff8f5/raw/b4cfb7c27b48a0e92670a87b8f4b1607ca230a11/Fifa%2520World%2520Cup%25202022%2520Qatar%2520Stadium%2520Schedule.json", function (data) { // create a data tree var treeData = anychart.data.tree(data, "as-table"); // create a resource gantt chart var chart = anychart.ganttResource(); // set the data for the chart chart.data(treeData); // set the container chart.container("container"); // draw the chart chart.draw(); } ); }); </script> </body> </html> Customizing Resource Chart Now that the basic JavaScript-based resource chart is in place let's explore ways to enhance its visuals and functionality. Configure the Rows and Columns To improve the visual appeal of your resource chart, let's delve into some potential adjustments for the rows and columns. Firstly, you can set custom colors for the selected and hover states of rows and adjust the splitter position for better content visibility. Additionally, consider specifying a default row height for neat presentation and easy readability of row items. JavaScript // customize the rows chart .rowSelectedFill("#D4DFE8") .rowHoverFill("#EAEFF3") .splitterPosition(230); // set the row height chart.defaultRowHeight(50); Now, let's move on to configuring the columns. In the first column, you have the option to include a simple number hashtag "#" as the title and customize its width. For the second column, you can make the stadium name bold to give it prominence and place the city name right below the stadium name. Tailor the column width as needed to accommodate the content comfortably. JavaScript // customize the column settings: // get the data grid var dataGrid = chart.dataGrid(); // set the fixed columns mode dataGrid.fixedColumns(true); // first column dataGrid .column(0) .title("#") .width(30 .labels({ hAlign: "center" }); // second column dataGrid .column(1) .title("Stadium") .width(200) .labels() .useHtml(true) .format(function () { return ( "<strong>" + this.name.toString() + "</strong> <br>" + this.item.get("city") ); }); Add Final Scores to the Bars Now, let's enhance the resource chart by displaying match results directly on the timeline bars. This provides a quick overview without the need to refer elsewhere. To achieve this, enable labels on the periods of the timeline and apply custom styling using the useHtml() method. JavaScript // configure the period labels: // get the period labels var periodLabels = chart.getTimeline().periods().labels(); // set the period labels periodLabels .enabled(true) .useHtml(true) .format( "<span style='color:#fff; font-size: 12px;'>{%result}</span>" ); With this additional information on the resource bars themselves, the chart now delivers a richer set of information at a glance. Personalize the Visual Appearance For an aesthetically pleasing user experience, consider spicing up the visual aspects of the chart. Start by setting the background color of the chart to a light gray shade. JavaScript chart.background("#edeae8 0.8"); Next, access the bars as elements from the timeline and make adjustments to their fill and stroke colors. JavaScript var elements = chart.getTimeline().elements(); elements.normal().fill("#9a1032 0.8"); elements.normal().stroke("#212c68"); To take this one level further, you can use a function to dynamically fill the color of the bars based on a condition. For example, the match result can be such a condition. So, the function checks the match result, and if it's a tie, it paints the bar green; otherwise, it colors it red. This provides an interesting way to instantly discern the outcome of a match from the bar colors themselves. JavaScript // customize the color of the bars: // get the elements var elements = chart.getTimeline().elements(); // check if the current item is a tie, and if yes, color it differently elements.normal().fill(function() { var result = this.period.result; var scores = result.split("-").map(Number); if (scores[0] === scores[1]) { return "#11A055 0.8"; } else { return "#9a1032 0.8"; } }); elements.normal().stroke("#212c68"); Customize the Tooltip Now, it's time to fine-tune the tooltip for an enhanced user experience. To keep the tooltip straightforward, configure it to display the team names and match results whenever you hover over a particular bar. JavaScript // configure the tooltip var tooltip = chart.getTimeline().tooltip(); tooltip .useHtml(true) .format(function(e) { var tooltipText; if (typeof e.period === "undefined") { tooltipText = e.item.la.city; } else { var period = e.period; tooltipText = period.result; } return tooltipText; }); These subtle adjustments significantly improve the visual clarity of the presented data. And now, below is the final version of the resource chart. You can explore the interactive version of this final chart here. Feel free to explore and interact with it. For your convenience, the entire code for the final resource chart is provided below. HTML <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>JavaScript Resource Gantt Chart</title> <style type="text/css"> html, body, #container { width: 100%; height: 100%; margin: 0; padding: 0; } </style> <script src="https://cdn.anychart.com/releases/8.11.1/js/anychart-core.min.js"></script> <script data-fr-src="https://cdn.anychart.com/releases/8.11.1/js/anychart-gantt.min.js"></script> <script data-fr-src="https://cdn.anychart.com/releases/8.11.1/js/anychart-data-adapter.min.js"></script> </head> <body> <div id="container"></div> <script> anychart.onDocumentReady(function () { // load the data anychart.data.loadJsonFile( "https://gist.githubusercontent.com/awanshrestha/07b9144e8f2539cd192ef9a38f3ff8f5/raw/b4cfb7c27b48a0e92670a87b8f4b1607ca230a11/Fifa%2520World%2520Cup%25202022%2520Qatar%2520Stadium%2520Schedule.json", function (data) { // create a data tree var treeData = anychart.data.tree(data, "as-table"); // create a resource gantt chart var chart = anychart.ganttResource(); // set the data for the chart chart.data(treeData); // customize the rows chart .rowSelectedFill("#D4DFE8") .rowHoverFill("#EAEFF3") .splitterPosition(230); // set the row height chart.defaultRowHeight(50); // customize the column settings: // get the data grid var dataGrid = chart.dataGrid(); // first column dataGrid .column(0) .title("#") .width(30) .labels({ hAlign: "center" }); // second column dataGrid .column(1) .title("Stadium") .width(200) .labels() .useHtml(true) .format(function () { return ( "<strong>" + this.name.toString() + "</strong> <br>" + this.item.get("city") ); }); // configure the period labels: // get the period labels var periodLabels = chart.getTimeline().periods().labels(); // set the period labels periodLabels .enabled(true) .useHtml(true) .format( "<span style='color:#fff; font-size: 12px;'>{%result}</span>" ); // configure the background of the chart chart.background("#edeae8 0.8"); // customize the color of the bars: // get the elements var elements = chart.getTimeline().elements(); // check if the current item is a tie, and if yes, color it differently elements.normal().fill(function() { var result = this.period.result; var scores = result.split("-").map(Number); if (scores[0] === scores[1]) { return "#11A055 0.8"; } else { return "#9a1032 0.8"; } }); elements.normal().stroke("#212c68"); // configure the tooltip var tooltip = chart.getTimeline().tooltip(); tooltip .useHtml(true) .format(function(e) { var tooltipText; if (typeof e.period === "undefined") { tooltipText = e.item.la.city; } else { var period = e.period; tooltipText = period.result; } return tooltipText; }); // set the container chart.container("container"); // draw the chart chart.draw(); } ); }); </script> </body> </html> Conclusion Hooray! You’ve come a long way in this journey of crafting a compelling resource chart. I hope this detailed tutorial has provided you with the understanding and skills needed to create your own JavaScript resource charts. Now it's your turn to explore more ways of how you can customize these charts to meet your unique requirements. Why not add some connectors or milestones, for example? Don't hesitate to reach out if you're stuck or have questions, and feel free to share the resource charts that you create following this guide. Let your creativity shine through your work! Happy charting!
Redux is a popular state management library used with React and React Native to manage the application's state efficiently. While Redux provides many benefits, it can also present some challenges, especially when used in the context of React Native mobile app development. In this blog, we'll explore some common problems developers encounter when using Redux with React Native and how to address them. 1. Boilerplate Code Redux is known for its boilerplate code, which can be extensive. React Native projects tend to benefit from lean and concise codebases, so Redux's verbosity can be overwhelming. To mitigate this issue, consider using libraries like Redux Toolkit, which simplifies the setup and reduces boilerplate code. JavaScript javascript // Without Redux Toolkit const initialState = { value: 0 }; function counterReducer(state = initialState, action) { switch (action.type) { case 'increment': return { ...state, value: state.value + 1 }; case 'decrement': return { ...state, value: state.value - 1 }; default: return state; } } // With Redux Toolkit import { createSlice } from '@reduxjs/toolkit'; const counterSlice = createSlice({ name: 'counter', initialState: { value: 0 }, reducers: { increment: (state) => { state.value += 1; }, decrement: (state) => { state.value -= 1; }, }, }); export const { increment, decrement } = counterSlice.actions; export default counterSlice.reducer; 2. Managing Asynchronous Actions Handling asynchronous actions, such as network requests, in Redux can be tricky. Thunks, sagas, or middleware like Redux Thunk and Redux Saga are commonly used to manage asynchronous operations. These tools provide a structured way to perform side effects while maintaining the predictability of Redux actions. JavaScript javascript // Using Redux Thunk for async actions const fetchUserData = (userId) => async (dispatch) => { try { dispatch({ type: 'user/fetch/start' }); const response = await fetch(`https://api.example.com/users/${userId}`); const data = await response.json(); dispatch({ type: 'user/fetch/success', payload: data }); } catch (error) { dispatch({ type: 'user/fetch/error', payload: error }); } }; 3. State Shape Design Designing the shape of your Redux state is crucial, as a poorly designed state can lead to complex selectors and unnecessary re-renders. It's recommended to normalize your state, especially when dealing with relational data, to simplify state management and improve performance. 4. Excessive Re-renders Redux's connect function and useSelector hook can lead to excessive re-renders if not used carefully. Memoization techniques, such as useMemo or libraries like Reselect, can help optimize selectors to prevent unnecessary component re-renders. JavaScript javascript // import { useSelector } from 'react-redux'; const selectedData = useSelector((state) => { // Expensive selector logic return computeSelectedData(state); }, shallowEqual); 5. Debugging Challenges Debugging Redux-related issues can be challenging, especially in larger codebases. Tools like Redux DevTools Extension and React Native Debugger can help you inspect and trace the flow of actions, but understanding the entire Redux flow may require some learning. 6. Learning Curve Redux, with its concepts of actions, reducers, and stores, can have a steep learning curve for beginners. It's important to invest time in understanding Redux thoroughly and practicing its concepts. Conclusion While Redux is a powerful state management library, it does come with its share of challenges when used in React Native development. By addressing these common problems and adopting best practices like using Redux Toolkit, handling asynchronous actions with middleware, and optimizing state shape and selectors, you can harness the full potential of Redux in your React Native projects. Remember that Redux is a valuable tool, but its usage should be tailored to the specific needs of your project.
Previously, I delved into the realm of integrating React Hook Form with Redux, exploring ways to harness the power of these two essential tools for building dynamic forms in React applications. However, my journey didn't end there. In the process of working on that project, I found myself immersed in the intricacies of developing complex form-based applications. This deep dive into form development unveiled a wealth of repeating patterns, invaluable best practices, and insights that significantly influenced my approach to coding, decision-making, and architectural design—especially when tackling large-scale form-based applications. In this follow-up exploration, I'm excited to share the culmination of my experiences and discoveries. We'll dive into a set of best practices that have proven to be invaluable when dealing with the challenges of developing extensive form-based apps using React Hook Form, and we'll emphasize the added benefits of incorporating TypeScript for enhanced type safety and developer productivity. Whether you're embarking on a new form-based project or looking to optimize an existing one, these practices will undoubtedly pave the way for more efficient development and a smoother user experience. So, let's journey into the world of form-based app development and explore the best practices that can transform your approach and outcomes. Developing complex form-based applications can be a challenging endeavor, but with the right tools and practices, it becomes much more manageable. React Hook Form is a powerful library for managing forms in React applications, and when combined with TypeScript, it offers additional benefits in terms of type safety and developer productivity. In this blog post, we will outline some best practices that can help you harness the full potential of React Hook Form while taking advantage of TypeScript for enhanced typechecking. Break the Form Into Small Reusable Components One of the fundamental principles of React is the concept of componentization. Apply this principle to your forms by breaking them down into small, reusable components. Each component should encapsulate a specific piece of the form's functionality. This approach makes your code more modular and easier to maintain, and when combined with TypeScript, it enables strong type-checking for each component. For example, if you have a complex form with multiple sections, create a separate component for each section. This way, you can define TypeScript interfaces for the props of each component, ensuring type safety throughout your codebase. TypeScript-JSX // Example of a TypeScript interface for a form section component's props interface SectionProps { firstName: string; lastName: string; // Other form fields... } function FormSection({ firstName, lastName }: SectionProps) { // Component logic here } Standardize Input Interfaces: "value" and "onChange" To ensure consistency and compatibility, follow the standard input interface of providing "value" and "onChange" handlers for your form inputs. This approach allows React Hook Form to seamlessly integrate with your components while providing TypeScript with the necessary information to perform type checking. TypeScript-JSX <input type="text" name="firstName" value={value} onChange={onChange} // Other input props... /> By adhering to this interface, you make it easier to connect your form inputs to [React Hook Form], as it relies on these properties to manage the form state. TypeScript will also be able to infer types correctly. Use the "name" Prop and TypeScript Interfaces The "name" prop is crucial for [React Hook Form] to interact with your form's context. Each form field should have a unique "name" that corresponds to the field's identity within the form. To leverage TypeScript's type-checking capabilities fully, create TypeScript interfaces for your form data and utilize them in your components. TypeScript-JSX interface FormData { firstName: string; lastName: string; // Other form fields... } // In your component <input type="text" name="firstName" value={formData.firstName} onChange={onChange} // Other input props... /> By using TypeScript interfaces to define your form data structure, you gain the benefits of static type checking throughout your application. Add Agnostic Props as Needed In some cases, you may need to add agnostic props to your form inputs. These props can vary depending on the type of input element you're working with. For instance, when dealing with a `<select>` element, you might need to include options. When working with a `<video>` or `<audio>` element, you might need additional attributes. Ensure TypeScript is aware of these props by defining them in your TypeScript interfaces. TypeScript-JSX // Example of a TypeScript interface for a select input interface MySelectProps { options: string[]; // Other select input props... highlight: boolean; query: (value: Option) => string; } <MySelect name="country" {...props}> {options.map((option) => ( <MyOption key={option} value={option}> {option} </MyOption> ))} </MySelect> TypeScript Support for Form Context To fully leverage TypeScript with React Hook Form, you can create a typed form context that provides type information for the form's context even when components are nested. Here's an example: TypeScript-JSX import { useFormContext } from 'react-hook-form'; export const useMyFormContext = () => useFormContext<MyFormInterface>(); In this example, MyFormInterface is a TypeScript interface that defines the structure of your form data. You can then use this hook within the components you want to interact with this form. These components will be designed to work with the form's interface only, ensuring strong type checking throughout your application. TypeScript-JSX function BookEditor() { const { control, regsiter, ...otherFormApiProps } = useBookFormContext(); // Access and update form state using register and setValue with type safety return ( // JSX for your component ); } Another excellent illustration of reusing the form context arises when dealing with a button that necessitates additional actions before saving the form data. This scenario perfectly aligns with the "ReadM" book editor form. Within this context, the save button leverages the form context to access the current form data. Subsequently, this data undergoes preprocessing before being dispatched to the backend for further handling. TypeScript-JSX export function SaveBookButton() { const formApi = useBookFormContext(); const draft = formApi.watch(); const bookId = draft.id; const { saveBook, saveStatus } = useSaveBook(); return ( <Button leftIcon={<AiOutlineCloudUpload size="24" />} variant="secondary" size="sm" isDisabled={!formApi.formState.isDirty} onClick={async () => { formApi.setValue('fryLevel', getBookLevel(draft)); formApi.reset({ ...draft, [IMAGES_FOR_DELETION]: [] } as any); await saveBook(bookId, draft); } isLoading={saveStatus.isLoading} > Save </Button> ); }
In the fast-paced world of software development, bugs are inevitable. Whether you're a developer working on a small personal project or part of a large development team handling complex enterprise software, you'll encounter bugs. These bugs can range from minor annoyances to critical issues that can impact your application's functionality and user experience. To effectively manage and squash these bugs, you need the right tools. In this blog post, we'll explore the world of React.js bug-tracking tools, how they can streamline your bug-tracking process, and some popular options to consider. Introduction Before we dive into the specifics of React.js bug-tracking tools, let's start with a fundamental question: What exactly is bug-tracking, and why is it essential in the world of software development? Bug tracking, often referred to as issue tracking or defect tracking, is the process of identifying, reporting, prioritizing, and resolving issues or bugs in a software application. These issues can include anything from functional defects and crashes to performance bottlenecks and usability problems. Bug tracking tools, such as those we'll explore in this blog post, provide a structured way to manage these issues efficiently. They streamline the process, ensure clear communication among team members, and ultimately contribute to delivering a higher-quality product. Why Use Bug Tracking Tools With React.js? React.js, a popular JavaScript library for building user interfaces, is widely used in web development projects. While React offers a robust framework for creating dynamic and responsive web applications, it doesn't inherently provide bug-tracking capabilities. That's where bug-tracking tools come in. Here are some reasons why using bug-tracking tools with React.js is crucial: Efficiency: Bug tracking tools streamline the process of reporting, tracking, and resolving issues. They provide a centralized platform for collaboration and communication among team members, making it easier to manage and prioritize bugs. Improved quality: By systematically tracking and addressing bugs, you can deliver a more stable and reliable application. This, in turn, enhances the user experience and builds trust with your audience. Collaboration: React.js bug-tracking tools often offer real-time collaboration features that allow developers, testers, and product managers to work together seamlessly. This collaboration can significantly speed up the debugging process. Data-driven decisions: Bug tracking tools provide valuable data and insights into your application's stability and performance. You can analyze trends, identify recurring issues, and make data-driven decisions to improve your software. Key Features of React.js Bug Tracking Tools When evaluating bug-tracking tools for your React.js projects, consider the following key features: 1. Issue Tracking Bug tracking tools should allow you to create detailed issue reports that include information like the bug's severity, description, steps to reproduce, and the environment in which it occurred. These reports serve as a centralized repository of all known issues. 2. Real-time Collaboration Real-time collaboration features enable team members to discuss issues, share insights, and work together to resolve bugs efficiently. This includes commenting, assigning tasks, and notifying relevant team members when changes occur. 3. Integration with Development Workflow Seamless integration with your development workflow is crucial. Bug tracking tools should work well with React.js and your chosen development tools, such as version control systems (e.g., Git), continuous integration pipelines, and project management platforms. 4. Customization and Flexibility Every development team is unique, and bug-tracking tools should accommodate your specific needs. Look for tools that allow you to customize issue fields, workflows, and notifications to align with your team's processes. Popular React.js Bug Tracking Tools Now, let's explore some of the popular bug-tracking tools that work well with React.js projects: 1. Sentry Sentry is a widely used open-source error-tracking platform that provides real-time visibility into application errors. It supports React.js and offers features like crash reporting, performance monitoring, React code splitting, and issue tracking. Sentry's integration with React allows you to capture and diagnose errors in your application efficiently. 2. Bugsnag Bugsnag is another error-monitoring and bug-tracking platform that works seamlessly with React.js. It provides real-time error reporting, stability scores, and integration with popular development tools. Bugsnag helps you identify and prioritize errors based on their impact on users. 3. Raygun Raygun is an application monitoring and error-tracking solution that supports React.js applications. It offers detailed error reports, real-time monitoring, and user session tracking. Raygun's React.js integration allows you to pinpoint and resolve issues quickly. 4. Airbrake Airbrake is an error-tracking and monitoring tool designed to improve the reliability of web applications. It supports React.js and provides features like error grouping, real-time notifications, and customizable dashboards. Airbrake helps teams discover, diagnose, and fix errors efficiently. 5. Rollbar Rollbar is an error tracking and monitoring platform that offers React.js support. It provides real-time error alerts, error grouping, and integration with popular development and collaboration tools. Rollbar helps you catch and resolve errors before they impact users. Setting Up Bug Tracking With React.js To get started with bug tracking in your React.js projects, follow these general steps: Installation and Configuration Select a bug-tracking tool: Choose a bug-tracking tool that aligns with your project's requirements and budget. Sign up for an account if necessary. Install the tool: Follow the tool's installation instructions. This may involve adding a JavaScript snippet to your application or integrating it with your build process. Configure error reporting: Configure the bug tracking tool to report errors and issues from your React.js application. This typically involves adding a configuration file or making API calls. Integrating With React.js Import the SDK: If the bug tracking tool provides a JavaScript SDK or library, import it into your React.js project. This SDK will enable error tracking and reporting. Initialize the SDK: Initialize the SDK with your project's API key or other required credentials. This step is typically performed in your project's main entry file. Capture errors: Use the bug tracking tool's API to capture and report errors within your React.js components. This may involve wrapping your components with error handlers or using the tool's provided functions for reporting errors. Monitor and debug: Once set up, use the bug tracking tool's dashboard to monitor errors, track their status, and debug issues. Collaborate with your team to prioritize and resolve bugs. Best Practices for Bug Tracking With React.js Efficient bug tracking requires more than just using the right tools. Here are some best practices to consider when managing bugs in your React.js projects: 1. Consistent Bug Reporting Encourage team members to follow a standardized format when reporting bugs. Include essential information like the bug's description, steps to reproduce, expected behavior, and actual behavior. Consistency in bug reports makes it easier to triage and address issues. 2. Prioritization and Workflow Establish clear criteria for prioritizing bugs. Not all bugs are equally critical, and you need a system to determine which ones to address first. Consider factors like the bug's impact on users, frequency, and severity when prioritizing. 3. Regular Bug Triage Hold regular bug triage meetings with your development team to review and prioritize reported issues. During these meetings, assign tasks, discuss potential solutions, and set deadlines for bug resolution. 4. Feedback Loop Maintain a feedback loop with your users. Encourage them to report bugs and provide feedback on their experiences. User feedback is invaluable for identifying issues that may not be immediately apparent. Challenges and Considerations While bug-tracking tools can greatly simplify the process of managing and resolving issues, there are some challenges and considerations to keep in mind: Tool selection: Choose a bug-tracking tool that aligns with your project's needs and budget. Consider factors like scalability, ease of use, and integration capabilities. Privacy and security: Ensure that the bug-tracking tool complies with your organization's data privacy and security policies. Some tools offer on-premises solutions for added control. Learning curve: Depending on the complexity of the bug-tracking tool, there may be a learning curve for your team. Invest time in training to maximize the tool's effectiveness. Cost: Bug tracking tools often come with subscription costs. Evaluate the pricing model and determine if it fits your project's budget. Conclusion React js bug tracking tool are essential for any development team looking to deliver high-quality software applications. These tools streamline the process of identifying, reporting, and resolving bugs, ultimately leading to a more stable and reliable product. When selecting a bug-tracking tool for your React.js projects, consider factors such as issue-tracking capabilities, real-time collaboration features, integration with your development workflow, and customization options. Remember that effective bug tracking goes beyond tools; it involves best practices like consistent bug reporting, prioritization, regular bug triage, and maintaining a feedback loop with users. By embracing bug-tracking tools and practices, you can enhance the quality of your React.js applications, reduce downtime, and create a better user experience. So, start tracking those bugs and ensure your React.js projects are bug-free and user-friendly.
Building components and reusing them across different packages led me to conclude that it is necessary to organize the correct approach for the content of these projects in a single structure. Building tools should be the same, including testing environment, lint rules, and efficient resource allocation for component libraries. I was looking for tools that could bring me efficient and effective ways to build robust, powerful combinations. As a result, a formidable trio emerged. In this article, we will create several packages with all those tools. Tools Before we start, let’s examine what each of these tools does. Lerna: Manages JavaScript projects with multiple packages; It optimizes the workflow around managing multipackage repositories with Git and NPM. Vite: Build tool providing rapid hot module replacement, out-of-the-box ES Module support, extensive feature, and plugin support for React Storybook: An open-source tool for developing and organizing UI components in isolation, which also serves as a platform for visual testing and creating interactive documentation Lerna Initial Setup The first step will be to set up the Lerna project. Create a folder with lerna_vite_monorepo and inside that folder, run through the terminal npx lerna init — this will create an essential for the Lerna project. It generates two files — lerna.json, package.json — and empty folder packages. lerna.json — This file enables Lerna to streamline your monorepo configuration, providing directives on how to link dependencies, locate packages, implement versioning strategies, and execute additional tasks. Vite Initial Setup Once the installation is complete, a packages folder will be available. Our next step involves creating several additional folders inside packages the folder: vite-common footer-components body-components footer-components To create those projects, we have to run npm init vite with the project name. Choose React as a framework and Typescript as a variant. Those projects will use the same lint rules, build process, and React version. This process in each package will generate a bunch of files and folders: ├── .eslintrc.cjs ├── .gitignore ├── index.html ├── package.json ├── public │ └── vite.svg ├── src │ ├── App.css │ ├── App.tsx │ ├── assets │ │ └── react.svg │ ├── index.css │ ├── main.tsx │ └── vite-env.d.ts ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts Storybook Initial Setup Time to set up a Storybook for each of our packages. Go to one of the package folders and run there npx storybook@latest init for Storybook installation. For the question about eslint-plugin-storybook — input Y for installation. After that, the process of installing dependencies will be launched. This will generate .storybook folder with configs and stories in src. Let’s remove the stories folder because we will build our own components. Now, run the installation npx sb init --builder @storybook/builder-vite — it will help you build your stories with Vite for fast startup and HMR. Assume that for each folder, we have the same configurations. If those installation has been accomplished, then you can run yarn storybook inside the package folder and run the Storybook. Initial Configurations The idea is to reuse common settings for all of our packages. Let’s remove some files that we don’t need in each repository. Ultimately, each folder you have should contain the following set of folders and files: ├── package.json ├── src │ └── vite-env.d.ts ├── tsconfig.json └── vite.config.ts Now, let’s take all devDependencies and cut them from package.json in one of our package folders and put them all to devDependenices in the root package.json. Run in root npx storybook@latest init and fix in main.js property: stories: [ "../packages/*/src/**/*..mdx", "../packages/*/src/**/*.stories.@(js|jsx|ts|tsx)" ], And remove from the root in package.json two scripts: "storybook": "storybook dev -p 6006", "build-storybook": "storybook build" Add components folder with index.tsx file to each package folder: ├── package.json ├── src │ ├── components │ │ └── index.tsx │ ├── index.tsx │ └── vite-env.d.ts ├── tsconfig.json └── vite.config.ts We can establish common configurations that apply to all packages. This includes settings for Vite, Storybook, Jest, Babel, and Prettier, which can be universally configured. The root folder has to have the following files: ├── .eslintrc.cjs ├── .gitignore ├── .nvmrc ├── .prettierignore ├── .prettierrc.json ├── .storybook │ ├── main.ts │ ├── preview-head.html │ └── preview.ts ├── README.md ├── babel.config.json ├── jest.config.ts ├── lerna.json ├── package.json ├── packages │ ├── vite-body │ ├── vite-common │ ├── vite-footer │ └── vite-header ├── test.setup.ts ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts We won’t be considering the settings of Babel, Jest, and Prettier in this instance. Lerna Configuration First, let’s examine the Lerna configuration file that helps manage our monorepo project with multiple packages. JSON { "$schema": "node_modules/lerna/schemas/lerna-schema.json", "useWorkspaces": true, "packages": ["packages/*"], "version": "independent" } First of all, "$schema" provides structure and validation for the Lerna configuration. When "useWorkspaces" is true, Lerna will use yarn workspaces for better linkage and management of dependencies across packages. If false, Lerna manages interpackage dependencies in monorepo. "packages" defines where Lerna can find the packages in the project. "version" when set to "independent", Lerna allows each package within the monorepo to have its own version number, providing flexibility in releasing updates for individual packages. Common Vite Configuration Now, let’s examine the necessary elements within the vite.config.ts file. TypeScript import path from "path"; import { defineConfig } from "vite"; import pluginReact from "@vitejs/plugin-react"; const isExternal = (id: string) => !id.startsWith(".") && !path.isAbsolute(id); export const getBaseConfig = ({ plugins = [], lib }) => defineConfig({ plugins: [pluginReact(), ...plugins], build: { lib, rollupOptions: { external: isExternal, output: { globals: { react: "React", }, }, }, }, }); This file will export the common configs for Vite with extra plugins and libraries which we will reuse in each package. defineConfig serves as a utility function in Vite’s configuration file. While it doesn’t directly execute any logic or alter the passed configuration object, its primary role is to enhance type inference and facilitate autocompletion in specific code editors. rollupOptions allows you to specify custom Rollup options. Rollup is the module bundler that Vite uses under the hood for its build process. By providing options directly to Rollup, developers can have more fine-grained control over the build process. The external option within rollupOptions is used to specify which modules should be treated as external dependencies. In general, usage of the external option can help reduce the size of your bundle by excluding dependencies already present in the environment where your code will be run. The output option with globals: { react: "React" } in Rollup's configuration means that in your generated bundle, any import statements for react will be replaced with the global variable React. Essentially, it's assuming that React is already present in the user's environment and should be accessed as a global variable rather than included in the bundle. JSON { "compilerOptions": { "composite": true, "skipLibCheck": true, "module": "ESNext", "moduleResolution": "node", "allowSyntheticDefaultImports": true }, "include": ["vite.config.ts"] } The tsconfig.node.json file is used to specifically control how TypeScript transpiles with vite.config.ts file, ensuring it's compatible with Node.js. Vite, which serves and builds frontend assets, runs in a Node.js environment. This separation is needed because the Vite configuration file may require different TypeScript settings than your frontend code, which is intended to run in a browser. JSON { "compilerOptions": { // ... "types": ["vite/client", "jest", "@testing-library/jest-dom"], // ... }, "references": [{ "path": "./tsconfig.node.json" }] } By including "types": ["vite/client"] in tsconfig.json, is necessary because Vite provides some additional properties on the import.meta object that is not part of the standard JavaScript or TypeScript libraries, such as import.meta.env and import.meta.glob. Common Storybook Configuration The .storybook directory defines Storybook's configuration, add-ons, and decorators. It's essential for customizing and configuring how Storybook behaves. ├── main.ts └── preview.ts For the general configs, here are two files. Let’s check them all. main.ts is the main configuration file for Storybook and allows you to control the behavior of Storybook. As you can see, we’re just exporting common configs, which we’re gonna reuse in each package. TypeScript import type { StorybookConfig } from "@storybook/react-vite"; const config: StorybookConfig = { addons: [ { name: "@storybook/preset-scss", options: { cssLoaderOptions: { importLoaders: 1, modules: { mode: "local", auto: true, localIdentName: "[name]__[local]___[hash:base64:5]", exportGlobals: true, }, }, }, }, { name: "@storybook/addon-styling", options: { postCss: { implementation: require("postcss"), }, }, }, "@storybook/addon-links", "@storybook/addon-essentials", "@storybook/addon-interactions", "storybook-addon-mock", ], framework: { name: "@storybook/react-vite", options: {}, }, docs: { autodocs: "tag", }, }; export default config; File preview.ts allows us to wrap stories with decorators, which we can use to provide context or set styles across our stories globally. We can also use this file to configure global parameters. Also, it will export that general configuration for package usage. TypeScript import type { Preview } from "@storybook/react"; const preview: Preview = { parameters: { actions: { argTypesRegex: "^on[A-Z].*" }, options: { storySort: (a, b) => { return a.title === b.title ? 0 : a.id.localeCompare(b.id, { numeric: true }); }, }, layout: "fullscreen", controls: { matchers: { color: /(background|color)$/i, date: /Date$/, }, }, }, }; export default preview; Root package.json In a Lerna monorepo project, the package.json serves a similar role as in any other JavaScript or TypeScript project. However, some aspects are unique to monorepos. JSON { "name": "root", "private": true, "workspaces": [ "packages/*" ], "scripts": { "start:vite-common": "lerna run --scope vite-common storybook --stream", "build:vite-common": "lerna run --scope vite-common build --stream", "test:vite-common": "lerna run --scope vite-common test --stream", "start:vite-body": "lerna run --scope vite-body storybook --stream", "build": "lerna run build --stream", "test": "NODE_ENV=test jest --coverage" }, "dependencies": { "react": "^18.2.0", "react-dom": "^18.2.0" }, "devDependencies": { "@babel/core": "^7.22.1", "@babel/preset-env": "^7.22.2", "@babel/preset-react": "^7.22.3", "@babel/preset-typescript": "^7.21.5", "@storybook/addon-actions": "^7.0.18", "@storybook/addon-essentials": "^7.0.18", "@storybook/addon-interactions": "^7.0.18", "@storybook/addon-links": "^7.0.18", "@storybook/addon-styling": "^1.0.8", "@storybook/blocks": "^7.0.18", "@storybook/builder-vite": "^7.0.18", "@storybook/preset-scss": "^1.0.3", "@storybook/react": "^7.0.18", "@storybook/react-vite": "^7.0.18", "@storybook/testing-library": "^0.1.0", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^14.0.0", "@types/jest": "^29.5.1", "@types/react": "^18.0.28", "@types/react-dom": "^18.0.11", "@typescript-eslint/eslint-plugin": "^5.57.1", "@typescript-eslint/parser": "^5.57.1", "@vitejs/plugin-react": "^4.0.0", "babel-jest": "^29.5.0", "babel-loader": "^8.3.0", "eslint": "^8.41.0", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.3.4", "eslint-plugin-storybook": "^0.6.12", "jest": "^29.5.0", "jest-environment-jsdom": "^29.5.0", "lerna": "^6.5.1", "path": "^0.12.7", "prettier": "^2.8.8", "prop-types": "^15.8.1", "sass": "^1.62.1", "storybook": "^7.0.18", "storybook-addon-mock": "^4.0.0", "ts-jest": "^29.1.0", "ts-node": "^10.9.1", "typescript": "^5.0.2", "vite": "^4.3.2" } } Scripts will manage the monorepo. Running tests across all packages or building all packages. This package.json also include development dependencies that are shared across multiple packages in the monorepo, such as testing libraries or build tools. The private field is usually set to true in this package.json to prevent it from being accidentally published. Scripts, of course, can be extended with other packages for testing, building, and so on, like: "start:vite-footer": "lerna run --scope vite-footer storybook --stream", Package Level Configuration As far as we exported all configs from the root for reusing those configs, let’s apply them at our package level. Vite configuration will use root vite configuration where we just import getBaseConfig function and provide there lib. This configuration is used to build our component package as a standalone library. It specifies our package's entry point, library name, and output file name. With this configuration, Vite will generate a compiled file that exposes our component package under the specified library name, allowing it to be used in other projects or distributed separately. TypeScript import * as path from "path"; import { getBaseConfig } from "../../vite.config"; export default getBaseConfig({ lib: { entry: path.resolve(__dirname, "src/index.ts"), name: "ViteFooter", fileName: "vite-footer", }, }); For the .storybook, we use the same approach. We just import the commonConfigs. TypeScript import commonConfigs from "../../../.storybook/main"; const config = { ...commonConfigs, stories: ["../src/**/*..mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"], }; export default config; And preview it as well. TypeScript import preview from "../../../.storybook/preview"; export default preview; For the last one from the .storybook folder, we need to add preview-head.html. HTML <script> window.global = window; </script> And the best part is that we have a pretty clean package.json without dependencies, we all use them for all packages from the root. JSON { "name": "vite-footer", "private": true, "version": "1.0.0", "type": "module", "scripts": { "dev": "vite", "build": "tsc && vite build", "lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "preview": "vite preview", "storybook": "storybook dev -p 6006", "build-storybook": "storybook build" }, "dependencies": { "vite-common": "^2.0.0" } } The only difference is vite-common, which is the dependency we’re using in the Footer component. Components By organizing our component packages in this manner, we can easily manage and publish each package independently while sharing common dependencies and infrastructure provided by our monorepo. Let’s look at the folder src of the Footer component. The other components will be identical, but the configuration only makes the difference. ├── assets │ └── flow.svg ├── components │ ├── Footer │ │ ├── Footer.stories.tsx │ │ └── index.tsx │ └── index.ts ├── index.ts └── vite-env.d.ts The vite-env.d.ts file in the src folder helps TypeScript understand and provide accurate type checking for Vite-related code in our project. It ensures that TypeScript can recognize and validate Vite-specific properties, functions, and features. Embedded Javascript /// <reference types="vite/client" /> In the src folder, index.ts has: TypeScript export * from "./components"; And the component that consumes vite-common components look like this: TypeScript-JSX import { Button, Links } from "vite-common"; export interface FooterProps { links: { label: string; href: string; }[]; } export const Footer = ({ links }: FooterProps) => { return ( <footer> <Links links={links} /> <Button label="Click Button" backgroundColor="green" /> </footer> ); }; export default Footer; Here’s what stories looks like for the component: TypeScript-JSX import { StoryFn, Meta } from "@storybook/react"; import { Footer } from "."; export default { title: "Example/Footer", component: Footer, parameters: { layout: "fullscreen", }, } as Meta<typeof Footer>; const mockedLinks = [ { label: "Home", href: "/" }, { label: "About", href: "/about" }, { label: "Contact", href: "/contact" }, ]; const Template: StoryFn<typeof Footer> = (args) => <Footer {...args} />; export const FooterWithLinks = Template.bind({}); FooterWithLinks.args = { links: mockedLinks, }; export const FooterWithOneLink = Template.bind({}); FooterWithOneLink.args = { links: [mockedLinks[0]], }; We use four packages in this example, but the approach is the same. Once you create all the packages, you have to be able to build, run, and test them independently. Before all are in the root level, run yarn install then yarn build to build all packages, or build yarn build:vite-common and you can start using that package in your other packages. Publish To publish all the packages in our monorepo, we can use the npx lerna publish command. This command guides us through versioning and publishing each package based on the changes made. lerna notice cli v6.6.2 lerna info versioning independent lerna info Looking for changed packages since vite-body@1.0.0 ? Select a new version for vite-body (currently 1.0.0) Major (2.0.0) ? Select a new version for vite-common (currently 2.0.0) Patch (2.0.1) ? Select a new version for vite-footer (currently 1.0.0) Minor (1.1.0) ? Select a new version for vite-header (currently 1.0.0) Patch (1.0.1) ❯ Minor (1.1.0) Major (2.0.0) Prepatch (1.0.1-alpha.0) Preminor (1.1.0-alpha.0) Premajor (2.0.0-alpha.0) Custom Prerelease Custom Version Lerna will ask us for each package version, and then you can publish it. lerna info execute Skipping releases lerna info git Pushing tags... lerna info publish Publishing packages to npm... lerna success All packages have already been published. Conclusion I was looking for a solid architecture solution for our front-end components organization in the company I am working for. For each project, we have a powerful, efficient development environment with general rules that help us become independent. This combination gives me streamlined dependency management, isolated component testing, and simplified publishing. References Repository Vite with Storybook
As a computer graphics and programming languages geek, I am delighted to have found myself working on several GPU compilers in the past two years. This began in 2021 when I started to contribute to taichi, a Python library that compiles Python functions into GPU kernels in CUDA, Metal, or Vulkan. Later on, I joined Meta and started working on SparkSL, which is the shader language that powers cross-platform GPU programming for AR effects on Instagram and Facebook. Aside from personal pleasure, I have always believed, or at least hoped, that these frameworks are actually quite useful; they make GPU programming more accessible to non-experts, empowering people to create fascinating graphics content without having to master complex GPU concepts. In my latest installment of compilers, I turned my eyes to WebGPU -- the next-generation graphics API for the web. WebGPU promises to bring high-performance graphics via low CPU overhead and explicit GPU control, aligning with the trend started by Vulkan and D3D12 some seven years ago. Just like Vulkan, the performance benefits of WebGPU come at the cost of a steep learning curve. Although I'm confident that this won't stop talented programmers around the world from building amazing content with WebGPU, I wanted to provide people with a way to play with WebGPU without having to confront its complexity. This is how taichi.js came to be. Under the taichi.js programming model, programmers don't have to reason about WebGPU concepts such as devices, command queues, bind groups, etc. Instead, they write plain Javascript functions, and the compiler translates those functions into WebGPU compute or render pipelines. This means that anyone can write WebGPU code via taichi.js, as long as they are familiar with basic Javascript syntax. The remainder of this article will demonstrate the programming model of taichi.js via a "Game of Life" program. As you will see, with less than 100 lines of code, we will create an fully parallel WebGPU program containing 3 GPU compute pipelines plus a render pipeline. The full source code of the demo can be found here, and if you want to play with the code without having to set-up any local environments, go to this page. The Game The Game of Life is a classic example of a cellular automaton, a system of cells that evolve over time according to simple rules. It was invented by the mathematician John Conway in 1970 and has since become a favorite of computer scientists and mathematicians alike. The game is played on a two-dimensional grid, where each cell can be either alive or dead. The rules for the game are simple: If a living cell has fewer than two or more than three living neighbors, it dies. If a dead cell has exactly three living neighbors, it becomes alive. Despite its simplicity, the Game of Life can exhibit surprising behavior. Starting from any random initial state, the game often converges to a state where a few patterns are dominant as if these are "species" which survived through evolution. Simulation Let's dive into the Game of Life implementation using taichi.js. To begin with, we import the taichi.js library under the shorthand ti and define an async main() function, which will contain all of our logic. Within main(), we begin by calling ti.init(), which initializes the library and its WebGPU contexts. JavaScript import * as ti from "path/to/taichi.js" let main = async () => { await ti.init(); ... }; main() Following ti.init(), let's define the data structures needed by the "Game of Life" simulation: JavaScript let N = 128; let liveness = ti.field(ti.i32, [N, N]) let numNeighbors = ti.field(ti.i32, [N, N]) ti.addToKernelScope({ N, liveness, numNeighbors }); Here, we defined two variables, liveness, and numNeighbors, both of which are ti.fields. In taichi.js, a "field" is essentially an n-dimensional array, whose dimensionality is provided in the 2nd argument to ti.field(). The element type of the array is defined in the first argument. In this case, we have ti.i32, indicating 32-bit integers. However, field elements may also be other more complex types, including vectors, matrices, and even structures. The next line of code, ti.addToKernelScope({...}), ensures that the variables N, liveness, and numNeighbors are visible in taichi.js "kernel"s, which are GPU compute and/or render pipelines, defined in the form of Javascript functions. As an example, the following init kernel is used to populate our grid cells with initial liveness vales, where each cell has a 20% chance of being alive initially: JavaScript let init = ti.kernel(() => { for (let I of ti.ndrange(N, N)) { liveness[I] = 0 let f = ti.random() if (f < 0.2) { liveness[I] = 1 } } }) init() The init() kernel is created by calling ti.kernel() with a Javascript lambda as the argument. Under the hood, taichi.js will look at the JavaScript string representation of this lambda and compile its logic into WebGPU code. Here, the lambda contains a for-loop, whose loop index I iterates through ti.ndrange(N, N). This means that I will take NxN different values, ranging from [0, 0] to [N-1, N-1]. Here comes the magical part -- in taichi.js, all the top-level for-loops in the kernel will be parallelized. More specifically, for each possible value of the loop index, taichi.js will allocate one WebGPU compute shader thread to execute it. In this case, we dedicate one GPU thread to each cell in our "Game of Life" simulation, initializing it to a random liveness state. The randomness comes from a ti.random() function, which is one of the the many functions provided in the taichi.js library for kernel use. A full list of these built-in utilities is available here in the taichi.js documentation. Having created the initial state of the game, let's move on to define how the game evolves. These are the two taichi.js kernels defining this evolution: JavaScript let countNeighbors = ti.kernel(() => { for (let I of ti.ndrange(N, N)) { let neighbors = 0 for (let delta of ti.ndrange(3, 3)) { let J = (I + delta - 1) % N if ((J.x != I.x || J.y != I.y) && liveness[J] == 1) { neighbors = neighbors + 1; } } numNeighbors[I] = neighbors } }); let updateLiveness = ti.kernel(() => { for (let I of ti.ndrange(N, N)) { let neighbors = numNeighbors[I] if (liveness[I] == 1) { if (neighbors < 2 || neighbors > 3) { liveness[I] = 0; } } else { if (neighbors == 3) { liveness[I] = 1; } } } }) Same as the init() kernel we saw before, these two kernels also have top-level for loops iterating over every grid cell, which are parallelized by the compiler. In countNeighbors(), for each cell, we look at the eight neighboring cells and count how many of these neighbors are "alive." The amount of live neighbors is stored into the numNeighbors field. Notice that when iterating through neighbors, the loop for (let delta of ti.ndrange(3, 3)) {...} is not parallelized, because it is not a top-level loop. The loop index delta ranges from [0, 0] to [2, 2] and is used to offset the original cell index I. We avoid out-of-bounds accesses by taking a modulo on N. (For the topologically-inclined reader, this essentially means the game has toroidal boundary conditions). Having counted the amount of neighbors for each cell, we move on to update the their liveness states in the updateLiveness() kernel. This is a simple matter of reading the liveness state of each cell and its current amount of live neighbors and writing back a new liveness value according to the rules of the game. As usual, this process applies to all cells in parallel. This essentially concludes the implementation of the game's simulation logic. Next, we will see how to define a WebGPU render pipeline to draw the game's evolution onto a webpage. Rendering Writing rendering code in taichi.js is slightly more involved than writing general-purpose compute kernels, and it does require some understanding of vertex shaders, fragment shaders, and rasterization pipelines in general. However, you will find that the simple programming model of taichi.js makes these concepts extremely easy to work with and reason about. Before drawing anything, we need access to a piece of canvas that we are drawing onto. Assuming that a canvas named result_canvas exists in the HTML, the following lines of code create a ti.CanvasTexture object, which represents a piece of texture that can be rendered onto by a taichi.js render pipeline. JavaScript let htmlCanvas = document.getElementById('result_canvas'); htmlCanvas.width = 512; htmlCanvas.height = 512; let renderTarget = ti.canvasTexture(htmlCanvas); On our canvas, we will render a square, and we will draw the Game's 2D grid onto this square. In GPUs, geometries to be rendered are represented in the form of triangles. In this case, the square that we are trying to render will be represented as two triangles. These two triangles are defined in a ti.field, which store the coordinates of each of the six vertices of the two triangles: JavaScript let vertices = ti.field(ti.types.vector(ti.f32, 2), [6]); await vertices.fromArray([ [-1, -1], [1, -1], [-1, 1], [1, -1], [1, 1], [-1, 1], ]); As we did with the liveness and numNeighbors fields, we need to explicitly declare the renderTarget and vertices variables to be visible in GPU kernels in taichi.js: JavaScript ti.addToKernelScope({ vertices, renderTarget }); Now, we have all the data we need to implement our render pipeline. Here's the implementation of the pipeline itself: JavaScript let render = ti.kernel(() => { ti.clearColor(renderTarget, [0.0, 0.0, 0.0, 1.0]); for (let v of ti.inputVertices(vertices)) { ti.outputPosition([v.x, v.y, 0.0, 1.0]); ti.outputVertex(v); } for (let f of ti.inputFragments()) { let coord = (f + 1) / 2.0; let texelIndex = ti.i32(coord * (liveness.dimensions - 1)); let live = ti.f32(liveness[texelIndex]); ti.outputColor(renderTarget, [live, live, live, 1.0]); } }); Inside the render() kernel, we begin by clearing the renderTarget with an all-black color, represented in RGBA as [0.0, 0.0, 0.0, 1.0]. Next, we define two top-level for-loops, which, as you already know, are loops that are parallelized in WebGPU. However, unlike the previous loops where we iterate over ti.ndrange objects, these loops iterate over ti.inputVertices(vertices) and ti.inputFragments(), respectively. This indicates that these loops will be compiled into WebGPU "vertex shaders" and "fragment shaders," which work together as a render pipeline. The vertex shader has two responsibilities: For each triangle vertex, compute its final location on the screen (or, more accurately, its "Clip Space" coordinates). In a 3D rendering pipeline, this will normally involve a bunch of matrix multiplications that transforms the vertex's model coordinates into world space, and then into camera space, and then finally into "Clip Space." However, for our simple 2D square, the input coordinates of the vertices are already at their correct values in clip space so that we can avoid all of that. All we have to do is append a fixed z value of 0.0 and a fixed w value of 1.0 (don't worry if you don't know what those are -- not important here!). JavaScript ti.outputPosition([v.x, v.y, 0.0, 1.0]); For each vertex, generate data to be interpolated and then passed into the fragment shader. In a render pipeline, after the vertex shader is executed, a built-in process known as "Rasterization" is executed on all the triangles. This is a hardware-accelerated process which computes, for each triangle, which pixels are covered by this triangle. These pixels are also known as "fragments." For each triangle, the programmer is allowed to generate additional data at each of the three vertices, which will be interpolated during the rasterization stage. For each fragment in the pixel, its corresponding fragment shader thread will receive the interpolated values according to its location within the triangle.In our case, the fragment shader only needs to know the location of the fragment within the 2D square so it can fetch the corresponding liveness values of the game. For this purpose, it suffices to pass the 2D vertex coordinate into the rasterizer, which means the fragment shader will receive the interpolated 2D location of the pixel itself: JavaScript ti.outputVertex(v); Moving on to the fragment shader: JavaScript for (let f of ti.inputFragments()) { let coord = (f + 1) / 2.0; let cellIndex = ti.i32(coord * (liveness.dimensions - 1)); let live = ti.f32(liveness[cellIndex]); ti.outputColor(renderTarget, [live, live, live, 1.0]); } The value f is the interpolated pixel location passed-on from the vertex shader. Using this value, the fragment shader will look-up the liveness state of the cell in the game which covers this pixel. This is done by first converting the pixel coordinates f into the [0, 0] ~ [1, 1] range and storing this coordinate into the coord variable. This is then multiplied with the dimensions of the liveness field, which produces the index of the covering cell. Finally, we fetch the live value of this cell, which is 0 if it is dead and 1 if it is alive. Finally, we output the RGBA value of this pixel onto the renderTarget, where the R,G,B components are all equal to live, and the A component is equal to 1, for full opacity. With the render pipeline defined, all that's left is to put everything together by calling the simulation kernels and the render pipeline every frame: JavaScript async function frame() { countNeighbors() updateLiveness() await render(); requestAnimationFrame(frame); } await frame(); And that's it! We have completed a WebGPU-based "Game of Life" implementation in taichi.js. If you run the program, you should see an animation where 128x128 cells evolve for around 1400 generations before converging to a few species of stabilized organisms. Exercises I hope you found this demo interesting! If you did, then I have a few extra exercises and questions that I invite you to experiment with and think about. (By the way, for quickly experimenting with the code, go to this page.) [Easy] Add a FPS counter to the demo! What FPS value can you obtain with the current setting where N = 128? Try increasing the value of N and see how the framerate changes. Would you be able to write a vanilla Javascript program that obtains this framerate without taichi.js or WebGPU? [Medium] What would happen if we merge countNeighbors() and updateLiveness() into a single kernel and keep the neighbors counter as a local variable? Would the program still work correctly always? [Hard] In taichi.js, a ti.kernel(..) always produces an async function, regardless of whether it contains compute pipelines or render pipelines. If you have to guess, what is the meaning of this async-ness? And what is the meaning of calling await on these async calls? Finally, in the frame function defined above, why did we put await only for the render() function, but not the other two? The last two questions are especially interesting, as they touches onto the inner workings of the compiler and runtime of the taichi.js framework, as well as the principles of GPU programming. Let me know your answer! Resources Of course, this Game of Life example only scratches the surface of what you can do with taichi.js. From real-time fluid simulations to physically based renderers, there are may other taichi.js programs for you to play with, and even more for you to write yourself. For additional examples and learning resources, check out: Github page Docs Playground Happy coding!
JavaScript: the versatile programming language that's at the heart of modern web development. When it comes to both front-end and back-end development, JavaScript plays a pivotal role in shaping the digital landscape. In this article, we'll delve into the significance of JavaScript in these two spheres while also drawing comparisons to another powerhouse, PHP. Let's embark on a journey through the world of web development and explore how JavaScript has redefined the way we create and experience digital content. Introduction to JavaScript and Its Evolution JavaScript, often abbreviated as JS, was introduced in the mid-1990s as a scripting language for web pages. Over the years, it has undergone significant evolution, emerging as a robust and multifunctional language that not only enhances user interfaces but also powers server-side applications. The Front-End Marvel: Enhancing User Experience In the realm of front-end development, JavaScript reigns supreme. It brings life to static web pages by enabling interactivity and dynamic content. From simple form validations to complex animations, JavaScript empowers developers to create seamless and engaging user experiences. Interactive Designs With JavaScript: Leveraging Front-End Frameworks The Power of React React, a JavaScript library developed by Facebook has revolutionized the way front-end applications are built. Its component-based architecture allows developers to create reusable UI elements, leading to efficient development processes and enhanced code maintainability. Angular: More Than Just a Geometric Term Angular, a full-fledged front-end framework maintained by Google, offers a comprehensive set of tools for building dynamic web applications. Its two-way data binding and dependency injection streamline development and testing, making it a favorite among enterprise-level projects. Behind the Scenes: JavaScript in Back-End Development While JavaScript initially found its home in front-end development, its capabilities were extended to the back end with the advent of Node.js. This runtime environment allows developers to execute JavaScript code on the server side, opening doors to high-performance and scalable applications. Node.js: Unifying Front-End and Back-End Node.js, built on Chrome's V8 JavaScript engine, facilitates the creation of real-time applications that operate on both the client and server sides. Its non-blocking, event-driven architecture optimizes throughput and scalability, making it an ideal choice for applications with high concurrent connections. PHP vs. JavaScript: A Comparative Analysis PHP: The Server-Side Scripting Veteran PHP, a server-side scripting language, has been a stalwart in web development for years. It excels in generating dynamic content and interacting with databases, making it a cornerstone of many content management systems and e-commerce platforms. JavaScript: From Browser to Server With Node.js, JavaScript made its grand entrance into the world of server-side programming. This shift eliminated the need for developers to switch between languages for front-end and back-end development, promoting code consistency and easing the learning curve. Full-Stack Prowess: JavaScript's Dominance The emergence of full-stack development has further elevated JavaScript's importance. Full-stack developers proficient in both front-end and back-end JavaScript can seamlessly navigate between layers, streamlining the development process and contributing to cohesive project outcomes. Ensuring Website Performance and Optimization As websites grow in complexity, optimizing performance becomes paramount. JavaScript tools like Webpack and Babel enable developers to bundle, minify, and transpile code, reducing load times and ensuring a smooth user experience. Security Measures in JavaScript Development JavaScript's popularity has attracted both developers and hackers. Ensuring the security of JavaScript applications involves practices like input validation, authentication, and safeguarding against cross-site scripting (XSS) and other vulnerabilities. The Future: WebAssembly and JavaScript WebAssembly, often abbreviated as wasm, presents an exciting frontier in web development. This binary instruction format allows browsers to execute code at near-native speed, enabling developers to build even more powerful and performant web applications. Learning JavaScript: Resources for Aspiring Developers For those looking to embark on a journey with JavaScript, a plethora of resources awaits. Online tutorials, documentation, coding boot camps, and community forums provide ample opportunities for aspiring developers to hone their skills. Embracing the JavaScript Ecosystem JavaScript's versatility is evident in its sprawling ecosystem of libraries, frameworks, and tools. From data visualization with D3.js to server-side rendering with Next.js, developers can leverage these resources to expedite development and enhance functionality. JavaScript's Role in Mobile App Development JavaScript's influence extends beyond the web to mobile app development. Frameworks like React Native enable developers to create cross-platform mobile apps using familiar JavaScript paradigms, reducing development time and effort. The Global Impact of JavaScript: A Socioeconomic Perspective Beyond its technical aspects, JavaScript has had a profound socioeconomic impact. It has democratized web development, enabling individuals from diverse backgrounds to enter the field and contribute to the digital economy. Conclusion In the dynamic landscape of web development, JavaScript stands as an indispensable tool. From crafting captivating user interfaces to building powerful server-side applications, its role is unparalleled. As the digital realm continues to evolve, JavaScript will undoubtedly remain a driving force, empowering developers to shape the future of the online world. FAQs (Frequently Asked Questions) Is JavaScript only used for web development? No, JavaScript is also used in other contexts like server-side programming and mobile app development. What is the difference between front-end and back-end development? Front-end development involves creating the user interface and interactions, while back-end development is front-end and back-end development. Can I use JavaScript and PHP together? Yes, you can use them together in a technology stack to harness their respective strengths. Why is JavaScript so popular among developers? JavaScript's popularity stems from its versatility, active community, and continuous evolution. How can I stay updated with JavaScript's advancements? Following online community blogs and attending tech conferences can help you stay current with JavaScript trends and updates.
React, the popular JavaScript library for building user interfaces, has seen significant changes and improvements over the years. One of the most game-changing additions to React is the introduction of Hooks. React Hooks revolutionized how developers manage state and lifecycle in functional components. In this comprehensive guide, we'll delve deep into the world of React Hooks, exploring their benefits, use cases, and how to leverage them to write cleaner and more maintainable React code. Introduction React, developed by Facebook, has become the go-to library for building modern and interactive web applications. Traditionally, React components have been written as classes with complex state and lifecycle management. However, with the release of React 16.8 in early 2019, the React team introduced Hooks, which enables developers to use state and other React features in functional components. This shift in React's paradigm has had a profound impact on how developers write and structure their code. In this guide, we'll explore the various aspects of React Hooks, from understanding their core concepts to using them effectively in real-world scenarios. Whether you're a React novice or a seasoned developer, this guide aims to provide you with a comprehensive understanding of React Hooks. What Are React Hooks? React Hooks are functions that let you "hook into" React state and lifecycle features from functional components. Prior to Hooks, these features were only available in class components. With Hooks, functional components can now manage state, perform side effects, and access context in a more direct and expressive way. The primary motivation behind React Hooks is to simplify the reuse of stateful logic across components and eliminate the need for class components entirely. Hooks are functions that follow a naming convention: they all start with the word use. React provides several built-in Hooks, and you can create your own custom Hooks to encapsulate reusable logic. Let's explore the key Hooks and their use cases. The Motivation Behind Hooks Before diving into the specifics of React Hooks, it's important to understand the motivations behind their introduction: 1. Reusing Stateful Logic In class components, sharing stateful logic between components often involves complex patterns like higher-order components (HOCs) and render props. This can lead to "wrapper hell" and make code harder to understand. Hooks allow you to reuse stateful logic without changing your component hierarchy. This makes code more modular and easier to maintain. 2. Simplifying Component Logic Class components can become cumbersome as the logic they contain grows. Hooks let you split a component into smaller, more focused functions based on the logic they encapsulate. This makes code easier to read and maintain. 3. Eliminating the Need for Classes Class components have a steeper learning curve and can be less intuitive for developers coming from a functional programming background. Hooks provide a more functional way to work with React, making it easier for developers to understand and use the library. 4. Reducing Boilerplate Code Class components often require writing repetitive code for lifecycle methods and bindings. Hooks eliminate much of this boilerplate, resulting in cleaner and more concise code. Basic Hooks Let's start our journey into React Hooks with the fundamental building blocks: 1. useState The useState Hook allows functional components to manage state. It takes an initial state value and returns an array with the current state and a function to update it. Here's a basic example: JavaScript import React, { useState } from 'react'; function Counter() { const [count, setCount] = useState(0); return ( <div> <p>Count: {count}</p> <button onClick={() => setCount(count + 1)}>Increment</button> </div> ); } export default Counter; In this example, we've initialized a count state variable with an initial value of 0 using useState. We can update the count state using the setCount function when the "Increment" button is clicked. 2. useEffect The useEffect Hook enables you to perform side effects in your functional components. Side effects can include data fetching, DOM manipulation, and more. useEffect takes two arguments: a function that contains the side effect code and an optional array of dependencies. Here's an example that fetches data from an API when the component mounts: JavaScript import React, { useState, useEffect } from 'react'; function DataFetching() { const [data, setData] = useState(null); useEffect(() => { fetch('https://api.example.com/data') .then(response => response.json()) .then(data => setData(data)); }, []); // Empty dependency array means this effect runs once return ( <div> {data ? ( <ul> {data.map(item => <li key={item.id}>{item.name}</li>)} </ul> ) : ( <p>Loading data...</p> )} </div> ); } export default DataFetching; In this example, the useEffect Hook fetches data from an API when the component mounts (thanks to the empty dependency array). The fetched data is stored in the data state variable and the component renders it when available. These basic Hooks provide the foundation for managing state and performing side effects in functional components. However, React provides a variety of additional Hooks to handle more complex scenarios. Additional Hooks React comes with several built-in Hooks that cater to different aspects of component logic. Here are some commonly used additional Hooks: 1. useContext The useContext Hook allows functional components to access the context of a parent component. Context provides a way to share values, such as themes or authentication states, across the component tree without having to pass props manually. Here's an example of using useContext to access a theme in a component: JavaScript import React, { useContext } from 'react'; const ThemeContext = React.createContext('light'); function ThemedButton() { const theme = useContext(ThemeContext); return ( <button className={`btn btn-${theme}`}>Themed Button</button> ); } export default ThemedButton; In this example, useContext retrieves the current theme from the ThemeContext, allowing the ThemedButton component to style itself accordingly. 2. useReducer The useReducer Hook is an alternative to useState that is more suitable for managing complex state logic. It takes a reducer function and an initial state and returns the current state and a dispatch function. Here's an example of a simple counter using useReducer: JavaScript import React, { useReducer } from 'react'; function counterReducer(state, action) { switch (action.type) { case 'INCREMENT': return { count: state.count + 1 }; case 'DECREMENT': return { count: state.count - 1 }; default: return state; } } function Counter() { const [state, dispatch] = useReducer(counterReducer, { count: 0 }); return ( <div> <p>Count: {state.count}</p> <button onClick={() => dispatch({ type: 'INCREMENT' })}>Increment</button> <button onClick={() => dispatch({ type: 'DECREMENT' })}>Decrement</button> </div> ); } export default Counter; In this example, we've defined a reducer function counterReducer to handle state updates. The useReducer Hook initializes the state and provides a dispatch function to dispatch actions. 3. useRef The useRef Hook creates a mutable ref object. Refs are commonly used for accessing the DOM, managing focus, or caching values that don't trigger re-renders. Here's an example of using useRef to focus on an input element: JavaScript import React, { useRef } from 'react'; function InputWithFocus() { const inputRef = useRef(); const focusInput = () => { inputRef.current.focus(); }; return ( <div> <input ref={inputRef} type="text" /> <button onClick={focusInput}>Focus Input</button> </div> ); } export default InputWithFocus; In this example, the inputRef ref is attached to the input element, allowing us to focus it when the "Focus Input" button is clicked. 4. useCallback and useMemo The useCallback and useMemo Hooks are used to optimize performance by memoizing functions or computed values. useCallback memoizes a function while useMemo memoizes a computed value. Here's an example of useMemo to calculate the square of a number only when it changes: JavaScript import React, { useState, useMemo } from 'react'; function SquareCalculator() { const [number, setNumber] = useState(0); const squaredNumber = useMemo(() => { return number * number; }, [number]); return ( <div> <input type="number" value={number} onChange={(e) => setNumber(parseInt(e.target.value))} /> <p>Squared: {squaredNumber}</p> </div> ); } export default SquareCalculator; In this example, the squaredNumber value is memoized using useMemo, so it's only recalculated when the number state changes. These additional Hooks provide flexibility and optimization opportunities for your functional components. You can mix and match these Hooks to suit the specific needs of your application. Custom Hooks While React provides a set of built-in Hooks, you can also create your own custom Hooks to encapsulate reusable logic. Custom Hooks follow the same naming convention and can use existing Hooks internally. Here's an example of a custom Hook that manages a timer: JavaScript import { useState, useEffect } from 'react'; function useTimer(initialTime = 0) { const [time, setTime] = useState(initialTime); useEffect(() => { const intervalId = setInterval(() => { setTime((prevTime) => prevTime + 1); }, 1000); return () => { clearInterval(intervalId); }; }, []); return time; } export default useTimer; In this example, the useTimer custom Hook manages a timer that increments every second. It utilizes the useState and useEffect Hooks internally. You can use this custom Hook in any functional component to manage a timer without duplicating the timer logic. Common Hook Patterns React Hooks open up numerous possibilities for simplifying common patterns in web development. Let's explore a few practical scenarios where Hooks can be particularly beneficial: 1. Data Fetching Fetching data from APIs or other sources is a common task in web applications. You can use the useEffect Hook to fetch data and manage the loading state. Here's a simplified example: JavaScript import React, { useState, useEffect } from 'react'; function DataFetching() { const [data, setData] = useState([]); const [loading, setLoading] = useState(true); useEffect(() => { fetch('https://api.example.com/data') .then((response) => response.json()) .then((data) => { setData(data); setLoading(false); }); }, []); return ( <div> {loading ? ( <p>Loading data...</p> ) : ( <ul> {data.map((item) => ( <li key={item.id}>{item.name}</li> ))} </ul> )} </div> ); } export default DataFetching; In this example, we use useState to manage the data and loading state and useEffect to fetch data when the component mounts. 2. Form Handling Forms are a crucial part of most web applications. React Hooks simplify form handling by allowing you to manage form state and validation logic more cleanly. Here's a basic example: JavaScript import React, { useState } from 'react'; function Form() { const [formData, setFormData] = useState({ username: '', password: '', }); const handleChange = (e) => { const { name, value } = e.target; setFormData({ ...formData, [name]: value, }); }; const handleSubmit = (e) => { e.preventDefault(); // Handle form submission with formData }; return ( <form onSubmit={handleSubmit}> <input type="text" name="username" value={formData.username} onChange={handleChange} /> <input type="password" name="password" value={formData.password} onChange={handleChange} /> <button type="submit">Submit</button> </form> ); } export default Form; In this example, we use the useState Hook to manage form data and the handleChange function to update the form state as the user types. Conclusion In this comprehensive guide, we've journeyed through the world of React Hooks, from their introduction and motivations to practical examples and common patterns. React Hooks have revolutionized how developers write React components, making functional components more powerful and expressive than ever before. React Hooks have become an essential tool in the React developer's toolkit, improving code readability, maintainability, and reusability. Whether you're a beginner looking to get started with React or an experienced developer aiming to refactor your codebase, React Hooks offers a modern and efficient way to build robust web applications.
Anthony Gore
Founder,
Vue.js Developers
John Vester
Staff Engineer,
Marqeta @JohnJVester
Justin Albano
Software Engineer,
IBM
Swizec Teller
CEO,
preona