diff --git a/apps/valor-software-site/src/assets/img/valor_img/valor-logo.svg b/apps/valor-software-site/src/assets/img/valor-img/valor-logo.svg similarity index 100% rename from apps/valor-software-site/src/assets/img/valor_img/valor-logo.svg rename to apps/valor-software-site/src/assets/img/valor-img/valor-logo.svg diff --git a/apps/valor-software-site/src/assets/img/valor_img/valor_social.jpg b/apps/valor-software-site/src/assets/img/valor-img/valor_social.jpg similarity index 100% rename from apps/valor-software-site/src/assets/img/valor_img/valor_social.jpg rename to apps/valor-software-site/src/assets/img/valor-img/valor_social.jpg diff --git a/apps/valor-software-site/src/index.html b/apps/valor-software-site/src/index.html index 895a3c08e..131bdcd39 100644 --- a/apps/valor-software-site/src/index.html +++ b/apps/valor-software-site/src/index.html @@ -1,35 +1,42 @@ - - - - Home - Valor Software - - - - - - - - - - - - - - - + + + + - + - - - - - + }; + + + + + + diff --git a/assets/articles/0071-zero-cost-way-on-react-d3/Main_zero-cost-way-on-react-d3.png b/assets/articles/0071-zero-cost-way-on-react-d3/Main_zero-cost-way-on-react-d3.png new file mode 100644 index 000000000..a732071c8 Binary files /dev/null and b/assets/articles/0071-zero-cost-way-on-react-d3/Main_zero-cost-way-on-react-d3.png differ diff --git a/assets/articles/0071-zero-cost-way-on-react-d3/Slava_Chub.jpg b/assets/articles/0071-zero-cost-way-on-react-d3/Slava_Chub.jpg new file mode 100644 index 000000000..a730ee24f Binary files /dev/null and b/assets/articles/0071-zero-cost-way-on-react-d3/Slava_Chub.jpg differ diff --git a/assets/articles/0071-zero-cost-way-on-react-d3/img1.gif b/assets/articles/0071-zero-cost-way-on-react-d3/img1.gif new file mode 100644 index 000000000..82cb7db95 Binary files /dev/null and b/assets/articles/0071-zero-cost-way-on-react-d3/img1.gif differ diff --git a/assets/articles/0071-zero-cost-way-on-react-d3/img2.gif b/assets/articles/0071-zero-cost-way-on-react-d3/img2.gif new file mode 100644 index 000000000..ed1950538 Binary files /dev/null and b/assets/articles/0071-zero-cost-way-on-react-d3/img2.gif differ diff --git a/assets/articles/0071-zero-cost-way-on-react-d3/img3.jpeg b/assets/articles/0071-zero-cost-way-on-react-d3/img3.jpeg new file mode 100644 index 000000000..25322687f Binary files /dev/null and b/assets/articles/0071-zero-cost-way-on-react-d3/img3.jpeg differ diff --git a/assets/articles/0071-zero-cost-way-on-react-d3/img4.png b/assets/articles/0071-zero-cost-way-on-react-d3/img4.png new file mode 100644 index 000000000..096622ec7 Binary files /dev/null and b/assets/articles/0071-zero-cost-way-on-react-d3/img4.png differ diff --git a/assets/articles/0071-zero-cost-way-on-react-d3/img5.gif b/assets/articles/0071-zero-cost-way-on-react-d3/img5.gif new file mode 100644 index 000000000..5f4892107 Binary files /dev/null and b/assets/articles/0071-zero-cost-way-on-react-d3/img5.gif differ diff --git a/assets/articles/0071-zero-cost-way-on-react-d3/zero-cost-way-on-react-d3.adoc b/assets/articles/0071-zero-cost-way-on-react-d3/zero-cost-way-on-react-d3.adoc new file mode 100644 index 000000000..e2704be38 --- /dev/null +++ b/assets/articles/0071-zero-cost-way-on-react-d3/zero-cost-way-on-react-d3.adoc @@ -0,0 +1,552 @@ +== Introduction +It's no secret that the rules on pure React solutions are pretty simple. We just need to properly use all React inventory like useState, useEffect, useMemo, and useCallback. There are many intelligent articles, guides, and examples on the topic. But let's answer the following question. + +_How many "pure" projects did you tackle?_ + +A pure React (Angular, NodeJS, etc.) project could look like nonsense in real life. Customers expect complicated solutions, including different 3-rd party stuff like Payment Systems, Graphical Libraries, CRM integrations, Tracking Tools, etc. Obviously, not all of them are React-friendly, so we should count these libraries' features in most cases and try to perfect React code simultaneously. + +Today I want to tell you some performance specifics across React and D3. + +I wrote several articles on the D3 topics before, and I guess it will also be interesting for you. There are the following related articles. + +* https://valor-software.com/articles/tasty-recipes-for-react-d3-the-ranking-bar[Tasty Recipes for React & D3. The Ranking Bar, window=_blank]. +* https://valor-software.com/articles/a-qwik-view-of-the-ranking-bar[A Qwik View of the Ranking Bar, window=_blank] + +But let's focus on the current topic. + +_https://d3js.org/[D3.js, window=_blank] is a JavaScript library for manipulating documents based on data. D3 helps you bring data to life using HTML, SVG, and CSS. D3’s emphasis on web standards gives you the full capabilities of modern browsers without tying yourself to a proprietary framework, combining powerful visualization components and a data-driven approach to DOM manipulation._ + +D3 is great! I'm fond of this beautiful library. But it lives its own life. That's why we need to remember this fact when we work with D3 outside Vanilla JS, say, in React. + +=== The Objective +The goal is a simple D3 line chart implemented with dynamic guideline movement every second. + +I'm pretty sure the best way to understand what's good is to explain what's wrong. That's why I will start my solutions from the worst example. I'm also going to explain why the example is so wrong, and after that, I'll propose to you the best way of implementation. + +=== The Worst Solution +There is the following component represents a https://stackblitz.com/edit/react-d3-zero-cost?file=src%2FLineChart.jsx[line chart, window=_blank]. + +[, js] +---- +import React, { useEffect, useRef } from "react"; +import * as d3 from "d3"; + +const transform = "translate(50,50)"; + +export default function LineChart({ data, width, height, marker }) { + const svgRef = useRef(); + + const renderSvg = () => { + const chartWidth = width - 200; + const chartHeight = height - 200; + + const svg = d3.select(svgRef.current); + + svg.selectAll("*").remove(); + + const xScale = d3.scaleLinear().domain([0, 100]).range([0, chartWidth]); + const yScale = d3.scaleLinear().domain([0, 200]).range([chartHeight, 0]); + + const g = svg.append("g").attr("transform", transform); + + g.append("g") + .attr("transform", "translate(0," + chartHeight + ")") + .call(d3.axisBottom(xScale)); + + g.append("g").call(d3.axisLeft(yScale)); + + svg + .append("g") + .selectAll("dot") + .data(data) + .enter() + .append("circle") + .attr("cx", function (d) { + return xScale(d[0]); + }) + .attr("cy", function (d) { + return yScale(d[1]); + }) + .attr("r", 3) + .attr("transform", transform) + .style("fill", "#CC0000"); + + const line = d3 + .line() + .x(function (d) { + return xScale(d[0]); + }) + .y(function (d) { + return yScale(d[1]); + }) + .curve(d3.curveMonotoneX); + + svg + .append("path") + .datum(data) + .attr("class", "line") + .attr("transform", transform) + .attr("d", line) + .style("fill", "none") + .style("stroke", "#CC0000") + .style("stroke-width", "2"); + + if (marker) { + svg + .append("svg:line") + .attr("transform", transform) + .attr("stroke", "#00ff00") + .attr("stroke-linejoin", "round") + .attr("stroke-linecap", "round") + .attr("stroke-width", 2) + .attr("x1", xScale(marker)) + .attr("y1", 200) + .attr("x2", xScale(marker)) + .attr("y2", 0); + } + }; + + useEffect(() => { + renderSvg(); + }, [width, height, data, marker]); + + if (!width || !height || !data) { + return <>; + } + + return ; +} +---- + +This component takes the following props. + +* data - chart data as a two-dimensional array of x and y +* #width# - with of the chart +* #height# - height of the chart +* #marker# - X axis of a guideline + +And there is a related https://stackblitz.com/edit/react-d3-zero-cost?file=src%2FApp.js[parent component, window=_blank]. + +[, js] +---- +import React, { useState, useEffect } from "react"; +import LineChart from "./LineChart"; +import "./style.css"; + +const data = [ + [1, 1], + [12, 20], + [24, 36], + [32, 50], + [40, 70], + [50, 100], + [55, 106], + [65, 123], + [73, 130], + [78, 134], + [83, 136], + [89, 138], + [100, 140], +]; + +export default function App() { + const [marker, setMarker] = useState(10); + + useEffect(() => { + const intervalId = setInterval(() => { + setMarker((prevMarker) => (prevMarker + 10 > 100 ? 10 : prevMarker + 10)); + }, 1000); + + return () => { + clearInterval(intervalId); + }; + }, []); + + return ( +
+ +
+ ); +} +---- + +There is an interval refresh a marker value every second and pass it as a chat's prop. + +[.img] +image::img1.gif[] + +You can play with the complete example https://stackblitz.com/edit/react-d3-zero-cost?file=src%2FApp.js[here, window=_blank]. + +It seems it's nothing foreshadowing the issue. I want to modify the code above. The aim is to show the issue eloquently. + +=== The Number of Renders +First, I'll add a global #renders# and #timeStart# variables to https://stackblitz.com/edit/react-d3-zero-cost-hqxk3a?file=public%2Findex.html[public/index.html, window=_blank] + +[, js] +---- + +
+---- + +Second, I increase #renders# every #LineChart# render. + +[, js] +---- +export default function LineChart({ data, width, height, marker }) { + // no changes here ... + + renders++; + + useEffect(() => { + renderSvg(); + }, [width, height, data, marker]); + + if (!width || !height || !data) { + return <>; + } + + return ; +} +---- + +And finally, I changed the parent component the following way. + +[, js] +---- +export default function App() { + const [marker, setMarker] = useState(10); + + useEffect(() => { + const intervalId = setInterval(() => { + setMarker((prevMarker) => (prevMarker + 10 > 100 ? 10 : prevMarker + 10)); + }, 1000); + + return () => { + clearInterval(intervalId); + }; + }, []); + + const currentTime = new Date().toISOString(); + + return ( +
+
+ renders: {renders} +
+ start: {timeStart} +
+ now: {currentTime} +
+ +
+ ); +} +---- + +The main goal is to display three metrics: the number of renderings, start time, and current time. +Let's run the https://stackblitz.com/edit/react-d3-zero-cost-hqxk3a?file=src%2FApp.js[modified example, window=_blank]. + +[.img] +image::img2.gif[] + +As far as we can see, each marker change causes LineChart component to render. If the result above doesn't persuade you, I have prepared the experiment below. I left the working example for a few minutes and drank coffee. + +[.img] +image::img3.jpeg[] + +When I returned, I saw the following. + +[.img] +image::img4.png[] + +*#948# render per cup of coffee! Looks awful...* +Moreover, a bunch of D3 heavyweight operations covers each render! + +=== The Best Solution +It's time to fix the issue above. + +First, let me provide you the final https://stackblitz.com/edit/react-d3-zero-cost-d5kyuq?file=src%2FLineChart.jsx[LineChart version, window=_blank] and explain what's changed there step by step. + +[, js] +---- +import React, { + useEffect, + forwardRef, + useImperativeHandle, + useRef, +} from "react"; +import * as d3 from "d3"; + +const transform = "translate(50,50)"; + +const LineChart = forwardRef(({ data, width, height }, ref) => { + const svgRef = useRef(); + let svg; + let xScale; + + useImperativeHandle(ref, () => ({ + setMarker: (value) => { + if (isNaN(value)) { + return; + } + svg.selectAll(".marker").remove(); + + svg + .append("svg:line") + .attr("transform", transform) + .attr("class", "marker") + .attr("stroke", "#00ff00") + .attr("stroke-linejoin", "round") + .attr("stroke-linecap", "round") + .attr("stroke-width", 2) + .attr("x1", xScale(value)) + .attr("y1", 200) + .attr("x2", xScale(value)) + .attr("y2", 0); + }, + })); + + const renderSvg = () => { + const chartWidth = width - 200; + const chartHeight = height - 200; + + svg = d3.select(svgRef.current); + + svg.selectAll("*").remove(); + + xScale = d3.scaleLinear().domain([0, 100]).range([0, chartWidth]); + const yScale = d3.scaleLinear().domain([0, 200]).range([chartHeight, 0]); + + const g = svg.append("g").attr("transform", transform); + + g.append("g") + .attr("transform", "translate(0," + chartHeight + ")") + .call(d3.axisBottom(xScale)); + + g.append("g").call(d3.axisLeft(yScale)); + + svg + .append("g") + .selectAll("dot") + .data(data) + .enter() + .append("circle") + .attr("cx", function (d) { + return xScale(d[0]); + }) + .attr("cy", function (d) { + return yScale(d[1]); + }) + .attr("r", 3) + .attr("transform", transform) + .style("fill", "#CC0000"); + + const line = d3 + .line() + .x(function (d) { + return xScale(d[0]); + }) + .y(function (d) { + return yScale(d[1]); + }) + .curve(d3.curveMonotoneX); + + svg + .append("path") + .datum(data) + .attr("class", "line") + .attr("transform", transform) + .attr("d", line) + .style("fill", "none") + .style("stroke", "#CC0000") + .style("stroke-width", "2"); + }; + + renders++; + + useEffect(() => { + renderSvg(); + }, [width, height, data]); + + if (!width || !height || !data) { + return <>; + } + + return ; +}); + +export default LineChart; +---- + +*forwardRef* +Now LineChart's parent is able to work with the related component reference. +[, js] +---- +const LineChart = forwardRef(({ data, width, height }, ref) => { +---- + +*useImperativeHandle* +During some interviews, I ask my interviewees this question. I'm surprised because most of them can't answer it. In my opinion, https://react.dev/reference/react/useImperativeHandle[this hook, window=_blank] is as important as the basic like useState and useEffect because it makes your code more flexible and performative. + +Here is the exposed code. +[, js] +---- +useImperativeHandle(ref, () => ({ + setMarker: (value) => { + if (isNaN(value)) { + return; + } + svg.selectAll(".marker").remove(); + + svg + .append("svg:line") + .attr("transform", transform) + .attr("class", "marker") + .attr("stroke", "#00ff00") + .attr("stroke-linejoin", "round") + .attr("stroke-linecap", "round") + .attr("stroke-width", 2) + .attr("x1", xScale(value)) + .attr("y1", 200) + .attr("x2", xScale(value)) + .attr("y2", 0); + }, +})); +---- + +I moved it from the end of #renderSvg# function. See the https://stackblitz.com/edit/react-d3-zero-cost-hqxk3a?file=src%2FLineChart.jsx[previous example, window=_blank]. +Let's focus on the https://stackblitz.com/edit/react-d3-zero-cost-d5kyuq?file=src%2FApp.js[parent component, window=_blank]. Please, read comments there. + +[, js] +---- +import React, { useState, useEffect, useMemo, useRef } from 'react'; +import LineChart from './LineChart'; +import './style.css'; + +const data = [ + // no changes +]; + +export default function App() { + const [marker, setMarker] = useState(10); + // Provide a reference for LineChart + const chartRef = useRef(); + + useEffect(() => { + // If the marker has been changed set it on LineChart directly, see useImperativeHandle + chartRef.current.setMarker(marker); + }, [marker]); + + useEffect(() => { + const intervalId = setInterval(() => { + setMarker((prevMarker) => (prevMarker + 10 > 100 ? 10 : prevMarker + 10)); + }, 1000); + + return () => { + clearInterval(intervalId); + }; + }, []); + + const currentTime = new Date().toISOString(); + + // There is a trick because we don't need to render LineChart after every App state variable change + // As you can see we don't pass the marker here. + const chart = useMemo(() => { + return ; + }, [data]); + + return ( +
+
+ renders: {renders} +
+ start: {timeStart} +
+ now: {currentTime} +
+ {chart} +
+ ); +} +---- + +According to the comments above, there are three points of change. +1. Provide a reference for LineChart +2. Marker direct setting via #useImperativeHandle#. Pay attention to the fact that every useImperativeHandle-based call doesn't cause the component to render. It's super important! +3. Memoise the #LineChart# component. We don't need to refresh it with each #App# state change. + +Finally, the most tricky stuff has remained. + +After attentional looks at the code above, you could ask a question. + +_On the one hand, now the component shouldn't be re-rendered. On the other hand, the guideline moves from point A to point B. Of course, c#hartRef.current.setMarker(marker);# direct call allows us to set the guideline in the new position. But what approach allows us to remove the previous guideline from point A?_ + +At the start of the article, I meant that we need to count D3 library features. In this case, we should know two facts below. + +* D3 objects are stateful, so we can operate them whenever needed. In this context, please look at the following code. + +[, js] +---- + let svg; + + const renderSvg = () => { + // ... + svg = d3.select(svgRef.current); + //All futures results of modifications will be present persistently in SVG object +}; +---- + +* According to the feature above, we can change the D3 object every time without re-rendering. Moreover, we can manipulate different chart parts via fake CSS classes. + +Look at the following code. + +[, js] +---- + setMarker: (value) => { + if (isNaN(value)) { + return; + } + svg.selectAll('.marker').remove(); + + svg + .append('svg:line') + .attr('transform', transform) + .attr('class', 'marker') + .attr('stroke', '#00ff00') + .attr('stroke-linejoin', 'round') + .attr('stroke-linecap', 'round') + .attr('stroke-width', 2) + .attr('x1', xScale(value)) + .attr('y1', 200) + .attr('x2', xScale(value)) + .attr('y2', 0); + }, + })); +---- + +When we add a guideline, we add a special fake class into it: +[, js] +---- +.attr('class', 'marker') +---- +But before we remove the previous guideline via +[, js] +---- +svg.selectAll('.marker').remove(); +---- +That's all for today about the secrets of D3. + +It's time to run the final example! You can play with the complete final example https://stackblitz.com/edit/react-d3-zero-cost-d5kyuq?file=src%2FApp.js[here, window=_blank]. + +[.img] +image::img5.gif[] + +Only #two renders# per all time. Looks cool! +That’s like music to the ears of React developer! + +=== Happy coding! + +PS: If you are wondering why two renders, please read about https://legacy.reactjs.org/docs/strict-mode.html[React Strict Mode, window=_blank]. \ No newline at end of file diff --git a/assets/articles/0071-zero-cost-way-on-react-d3/zero-cost-way-on-react-d3.json b/assets/articles/0071-zero-cost-way-on-react-d3/zero-cost-way-on-react-d3.json new file mode 100644 index 000000000..4d2a08705 --- /dev/null +++ b/assets/articles/0071-zero-cost-way-on-react-d3/zero-cost-way-on-react-d3.json @@ -0,0 +1,12 @@ +{ +"title": "Zero-cost Way on React & D3.", +"order": 71, +"domains": ["dev_quality_assurance"], +"authorImg": "assets/articles/zero-cost-way-on-react-d3/Slava_Chub.jpg", +"language": "en", +"bgImg": "assets/articles/zero-cost-way-on-react-d3/Main_zero-cost-way-on-react-d3.png", +"author": "Vyacheslav Chub", +"position": "Full Stack Software Engineer", +"date": "Thu Apr 20 2023 10:45:55 GMT+0000 (Coordinated Universal Time)", +"seoDescription": "Performance specifics across React and D3" +} diff --git a/assets/articles/0072-the-evolution-of-web-application-development/Main_Evolution_Web_Development.png b/assets/articles/0072-the-evolution-of-web-application-development/Main_Evolution_Web_Development.png new file mode 100644 index 000000000..9f300d63b Binary files /dev/null and b/assets/articles/0072-the-evolution-of-web-application-development/Main_Evolution_Web_Development.png differ diff --git a/assets/articles/0072-the-evolution-of-web-application-development/img1.png b/assets/articles/0072-the-evolution-of-web-application-development/img1.png new file mode 100644 index 000000000..3eb918aa1 Binary files /dev/null and b/assets/articles/0072-the-evolution-of-web-application-development/img1.png differ diff --git a/assets/articles/0072-the-evolution-of-web-application-development/img2.png b/assets/articles/0072-the-evolution-of-web-application-development/img2.png new file mode 100644 index 000000000..b7604dc9a Binary files /dev/null and b/assets/articles/0072-the-evolution-of-web-application-development/img2.png differ diff --git a/assets/articles/0072-the-evolution-of-web-application-development/img3.png b/assets/articles/0072-the-evolution-of-web-application-development/img3.png new file mode 100644 index 000000000..136637775 Binary files /dev/null and b/assets/articles/0072-the-evolution-of-web-application-development/img3.png differ diff --git a/assets/articles/0072-the-evolution-of-web-application-development/the-evolution-of-web-application-development.adoc b/assets/articles/0072-the-evolution-of-web-application-development/the-evolution-of-web-application-development.adoc new file mode 100644 index 000000000..bdd200360 --- /dev/null +++ b/assets/articles/0072-the-evolution-of-web-application-development/the-evolution-of-web-application-development.adoc @@ -0,0 +1,25 @@ +== Introduction +Web application development has undergone significant changes over the years, as developers and businesses seek to create more scalable, maintainable and user-friendly applications. In this short snippet, we will explore the evolution of web application development, from the traditional way of building monolithic applications to the more modern trends of using micro-frontends and Module Federation. + +=== Conventional Ways of Developing Web Applications + +Monolithic web applications are those where everything is part of the same front-end application and the request goes to only one huge back-end. This is the simplest and easiest way to build a web application, but it has many drawbacks. For example, a monolithic web application is hard to scale, maintain, and update. It also creates a tight coupling between the front-end and the back-end, which limits the flexibility and reusability of the code. A monolithic web application is like a single executable file or directory that makes deployment easier, but also makes changes more difficult. + +[.img] +image::img1.png[] + +Direct access with one front-end to multiple microservices is an improvement over the monolithic approach. In this case, everything is still part of the same front-end application, but the requests are made to different microservices. Microservices are small, independent, and loosely coupled units of functionality that communicate with each other through APIs. This way, a web application can achieve better scalability, modularity, and performance. However, this approach still has some challenges. For instance, it can increase the complexity and overhead of managing multiple microservices. It also does not solve the problem of code duplication and inconsistency across the front-end. + +[.img] +image::img2.png[] + +=== Future of Web Application Development Aimed at Large Companies, Applications, and Teams + +Micro-frontend and module federation are the future of web application development aimed at large companies, applications, and teams. In this approach, multiple containers are being consumed in one shell app. Each container represents a micro-frontend that encapsulates a specific feature or domain of the web application. The shell app acts as a host that dynamically loads and renders the micro-frontends based on the user's needs and preferences. Module federation is a technique that enables the shell app and the micro-frontends to share code and dependencies without bundling them together. This way, each micro-frontend can be developed, deployed, and updated independently of each other and from the shell app. + +Micro-frontend and module federation offer many benefits for web application development. They enable faster delivery, better quality, and more innovation of web features. They also promote team autonomy, code reusability, and consistency across the web application. They also reduce the risk of breaking changes and conflicts among different parts of the web application. + +Web application development has evolved from monolithic to micro-frontend and module federation approaches to deal with the increasing complexity and variety of web applications. These approaches provide more flexibility, scalability, and efficiency for web developers and users alike. + +[.img] +image::img3.png[] \ No newline at end of file diff --git a/assets/articles/0072-the-evolution-of-web-application-development/the-evolution-of-web-application-development.json b/assets/articles/0072-the-evolution-of-web-application-development/the-evolution-of-web-application-development.json new file mode 100644 index 000000000..2c64e21b8 --- /dev/null +++ b/assets/articles/0072-the-evolution-of-web-application-development/the-evolution-of-web-application-development.json @@ -0,0 +1,11 @@ +{ +"title": "The Evolution of Web Application Development", +"order": 72, +"domains": ["dev_quality_assurance"], +"language": "en", +"bgImg": "assets/articles/the-evolution-of-web-application-development/Main_Evolution_Web_Development.png", +"author": "Nikita Demchenko and Bruno Silva", +"position": "Technical Writer, Next.js developer", +"date": "Thu Apr 24 2023 10:45:55 GMT+0000 (Coordinated Universal Time)", +"seoDescription": "Exploring the evolution of web application development" +} diff --git a/libs/common-docs/src/components/app-footer/app-footer.component.html b/libs/common-docs/src/components/app-footer/app-footer.component.html index cec318a6f..cf5c56d89 100644 --- a/libs/common-docs/src/components/app-footer/app-footer.component.html +++ b/libs/common-docs/src/components/app-footer/app-footer.component.html @@ -4,7 +4,7 @@ class="w-full flex flex-col-reverse md:flex-row items-center justify-between border-t-2 border-placeholder_col pt-6">
- Workflow + Workflow