diff --git a/404.html b/404.html index 2a9260e..9a2fb13 100644 --- a/404.html +++ b/404.html @@ -1,4 +1,4 @@ - Page not found - Hey ๐Ÿ‘‹ + Page not found - ~/ @@ -32,4 +32,4 @@ } setup() -

404

Page not found

\ No newline at end of file +

404

Page not found

\ No newline at end of file diff --git a/_astro/about.DOkJFLFu.css b/_astro/bookshelf.DmiATZhB.css similarity index 75% rename from _astro/about.DOkJFLFu.css rename to _astro/bookshelf.DmiATZhB.css index 97e678e..81f76d6 100644 --- a/_astro/about.DOkJFLFu.css +++ b/_astro/bookshelf.DmiATZhB.css @@ -1 +1 @@ -*,:before,:after{--un-rotate:0;--un-rotate-x:0;--un-rotate-y:0;--un-rotate-z:0;--un-scale-x:1;--un-scale-y:1;--un-scale-z:1;--un-skew-x:0;--un-skew-y:0;--un-translate-x:0;--un-translate-y:0;--un-translate-z:0;--un-ring-offset-shadow:0 0 rgb(0 0 0 / 0);--un-ring-shadow:0 0 rgb(0 0 0 / 0);--un-shadow-inset: ;--un-shadow:0 0 rgb(0 0 0 / 0);--un-ring-inset: ;--un-ring-offset-width:0px;--un-ring-offset-color:#fff;--un-ring-width:0px;--un-ring-color:rgb(147 197 253 / .5)}::backdrop{--un-rotate:0;--un-rotate-x:0;--un-rotate-y:0;--un-rotate-z:0;--un-scale-x:1;--un-scale-y:1;--un-scale-z:1;--un-skew-x:0;--un-skew-y:0;--un-translate-x:0;--un-translate-y:0;--un-translate-z:0;--un-ring-offset-shadow:0 0 rgb(0 0 0 / 0);--un-ring-shadow:0 0 rgb(0 0 0 / 0);--un-shadow-inset: ;--un-shadow:0 0 rgb(0 0 0 / 0);--un-ring-inset: ;--un-ring-offset-width:0px;--un-ring-offset-color:#fff;--un-ring-width:0px;--un-ring-color:rgb(147 197 253 / .5)}.prose :where(h1,h2,h3,h4,h5,h6):not(:where(.not-prose,.not-prose *)){color:var(--un-prose-headings);font-weight:600;line-height:1.25}.prose :where(a):not(:where(.not-prose,.not-prose *)){color:var(--un-prose-links);text-decoration:underline;font-weight:500;textDecoration:none;font-size:.9em;textDecorationThickness:.1em;textDecorationColor:rgb(var(--color-text-link))}.prose :where(a code):not(:where(.not-prose,.not-prose *)){color:var(--un-prose-links)}.prose :where(p,ul,ol,pre):not(:where(.not-prose,.not-prose *)){margin:1em 0;line-height:1.75}.prose :where(blockquote):not(:where(.not-prose,.not-prose *)){margin:1em 0;padding-left:1em;font-style:italic;border-left:.25em solid var(--un-prose-borders)}.prose :where(h1):not(:where(.not-prose,.not-prose *)){margin:1rem 0;font-size:2.25em}.prose :where(h2):not(:where(.not-prose,.not-prose *)){margin:1.75em 0 .5em;font-size:1.75em}.prose :where(h3):not(:where(.not-prose,.not-prose *)){margin:1.5em 0 .5em;font-size:1.375em}.prose :where(h4):not(:where(.not-prose,.not-prose *)){margin:1em 0;font-size:1.125em}.prose :where(img,video):not(:where(.not-prose,.not-prose *)){max-width:100%}.prose :where(figure,picture):not(:where(.not-prose,.not-prose *)){margin:1em 0}.prose :where(figcaption):not(:where(.not-prose,.not-prose *)){color:var(--un-prose-captions);font-size:.875em}.prose :where(code):not(:where(.not-prose,.not-prose *)){color:var(--un-prose-code);font-size:.875em;font-weight:600;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.prose :where(:not(pre)>code):not(:where(.not-prose,.not-prose *)):before,.prose :where(:not(pre)>code):not(:where(.not-prose,.not-prose *)):after{content:"`"}.prose :where(pre):not(:where(.not-prose,.not-prose *)){padding:1.25rem 1.5rem;overflow-x:auto;border-radius:.375rem}.prose :where(pre,code):not(:where(.not-prose,.not-prose *)){white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;hyphens:none;background:transparent}.prose :where(pre code):not(:where(.not-prose,.not-prose *)){font-weight:inherit;margin:.2rem;padding:.15em .3em;border-radius:.2em;background-color:var(--color-code-bg)}.prose :where(ol,ul):not(:where(.not-prose,.not-prose *)){padding-left:1.25em}.prose :where(ol):not(:where(.not-prose,.not-prose *)){list-style-type:decimal}.prose :where(ol[type=A]):not(:where(.not-prose,.not-prose *)){list-style-type:upper-alpha}.prose :where(ol[type=a]):not(:where(.not-prose,.not-prose *)){list-style-type:lower-alpha}.prose :where(ol[type=A s]):not(:where(.not-prose,.not-prose *)){list-style-type:upper-alpha}.prose :where(ol[type=a s]):not(:where(.not-prose,.not-prose *)){list-style-type:lower-alpha}.prose :where(ol[type=I]):not(:where(.not-prose,.not-prose *)){list-style-type:upper-roman}.prose :where(ol[type=i]):not(:where(.not-prose,.not-prose *)){list-style-type:lower-roman}.prose :where(ol[type=I s]):not(:where(.not-prose,.not-prose *)){list-style-type:upper-roman}.prose :where(ol[type=i s]):not(:where(.not-prose,.not-prose *)){list-style-type:lower-roman}.prose :where(ol[type="1"]):not(:where(.not-prose,.not-prose *)){list-style-type:decimal}.prose :where(ul):not(:where(.not-prose,.not-prose *)){list-style-type:disc}.prose :where(ol>li):not(:where(.not-prose,.not-prose *))::marker,.prose :where(ul>li):not(:where(.not-prose,.not-prose *))::marker,.prose :where(summary):not(:where(.not-prose,.not-prose *))::marker{color:var(--un-prose-lists)}.prose :where(hr):not(:where(.not-prose,.not-prose *)){margin:2em 0;border:1px solid var(--un-prose-hr)}.prose :where(table):not(:where(.not-prose,.not-prose *)){display:block;margin:1em 0;border-collapse:collapse;overflow-x:auto}.prose :where(tr):not(:where(.not-prose,.not-prose *)):nth-child(2n){background:var(--un-prose-bg-soft)}.prose :where(td,th):not(:where(.not-prose,.not-prose *)){border:1px solid var(--un-prose-borders);padding:.625em 1em}.prose :where(abbr):not(:where(.not-prose,.not-prose *)){cursor:help}.prose :where(kbd):not(:where(.not-prose,.not-prose *)){color:var(--un-prose-code);border:1px solid;padding:.25rem .5rem;font-size:.875em;border-radius:.25rem}.prose :where(details):not(:where(.not-prose,.not-prose *)){margin:1em 0;padding:1.25rem 1.5rem;background:var(--un-prose-bg-soft)}.prose :where(summary):not(:where(.not-prose,.not-prose *)){cursor:pointer;font-weight:600}.prose :where(li):not(:where(.not-prose,.not-prose *)){word-break:break-word}.prose :where(li code):not(:where(.not-prose,.not-prose *)){white-space:pre-wrap;word-break:break-word;margin:.2rem;padding:.15em .3em;border-radius:.2em;background-color:var(--color-code-bg)}.prose :where(li code):not(:where(.not-prose,.not-prose *)):after{content:none}.prose :where(li code):not(:where(.not-prose,.not-prose *)):before{content:none}.prose :where(a):not(:where(.not-prose,.not-prose *)):hover{color:rgb(var(--color-text-link-hover))}.prose :where(p):not(:where(.not-prose,.not-prose *)){word-break:break-word}.prose :where(p code):not(:where(.not-prose,.not-prose *)):after{content:none}.prose :where(p code):not(:where(.not-prose,.not-prose *)):before{content:none}.prose :where(blockquote p):not(:where(.not-prose,.not-prose *)){word-break:break-word}.prose :where(blockquote code):not(:where(.not-prose,.not-prose *)){white-space:pre-wrap;word-break:break-word;margin:.2rem;padding:.15em .3em;border-radius:.2em;background-color:var(--color-code-bg)}.prose :where(img):not(:where(.not-prose,.not-prose *)){border:0;display:block;height:auto;width:100%}.prose :where(cite):not(:where(.not-prose,.not-prose *)){text-align:center;with:100%;display:block;font-size:.8em}.prose{color:var(--un-prose-body);max-width:65ch}.prose-stone{--un-prose-body:#44403c;--un-prose-headings:#1c1917;--un-prose-links:#1c1917;--un-prose-lists:#a8a29e;--un-prose-hr:#e7e5e4;--un-prose-captions:#78716c;--un-prose-code:#1c1917;--un-prose-borders:#e7e5e4;--un-prose-bg-soft:#f5f5f4;--un-prose-invert-body:#e7e5e4;--un-prose-invert-headings:#f5f5f4;--un-prose-invert-links:#f5f5f4;--un-prose-invert-lists:#78716c;--un-prose-invert-hr:#44403c;--un-prose-invert-captions:#a8a29e;--un-prose-invert-code:#f5f5f4;--un-prose-invert-borders:#44403c;--un-prose-invert-bg-soft:#292524}.dark .dark\:prose-invert{--un-prose-body:var(--un-prose-invert-body);--un-prose-headings:var(--un-prose-invert-headings);--un-prose-links:var(--un-prose-invert-links);--un-prose-lists:var(--un-prose-invert-lists);--un-prose-hr:var(--un-prose-invert-hr);--un-prose-captions:var(--un-prose-invert-captions);--un-prose-code:var(--un-prose-invert-code);--un-prose-borders:var(--un-prose-invert-borders);--un-prose-bg-soft:var(--un-prose-invert-bg-soft)}.p-2{padding:.5rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-4{padding-left:1rem;padding-right:1rem}.mx-1{margin-left:.25rem;margin-right:.25rem}.mx-4{margin-left:1rem;margin-right:1rem}.my{margin-top:1rem;margin-bottom:1rem}.my-1{margin-top:.25rem;margin-bottom:.25rem}.my-3{margin-top:.75rem;margin-bottom:.75rem}.my-6{margin-top:1.5rem;margin-bottom:1.5rem}.mb-1{margin-bottom:.25rem}.mb-6{margin-bottom:1.5rem}.me{margin-inline-end:1rem}.me\!{margin-inline-end:1rem!important}.ml-2{margin-left:.5rem}.ml-auto{margin-left:auto}.mr-1{margin-right:.25rem}.mr-2{margin-right:.5rem}.mr-4{margin-right:1rem}.ms{margin-inline-start:1rem}.mt-1{margin-top:.25rem}.mt-12{margin-top:3rem}.mt-6{margin-top:1.5rem}.mt-8{margin-top:2rem}.mt-9{margin-top:2.25rem}.inline{display:inline}.block{display:block}.contents{display:contents}.hidden{display:none}.opacity-100{opacity:1}.opacity-80,.hover\:opacity-80:hover{opacity:.8}.bg-stone-100{--un-bg-opacity:1;background-color:rgb(245 245 244 / var(--un-bg-opacity))}.bg-transparent{background-color:transparent}.dark .dark\:bg-stone-600{--un-bg-opacity:1;background-color:rgb(87 83 78 / var(--un-bg-opacity))}.dark .dark\:bg-stone-700{--un-bg-opacity:1;background-color:rgb(68 64 60 / var(--un-bg-opacity))}.dark .dark\:fill-transparent,.fill-transparent{fill:transparent}.dark .dark\:fill-white{--un-fill-opacity:1;fill:rgb(255 255 255 / var(--un-fill-opacity))}.fill-black{--un-fill-opacity:1;fill:rgb(0 0 0 / var(--un-fill-opacity))}.border-0{border-width:0px}.rounded-lg{border-radius:.5rem}.text-2xl{font-size:1.5rem;line-height:2rem}.text-4xl{font-size:2.25rem;line-height:2.5rem}.text-sm{font-size:.875rem;line-height:1.25rem}.font-bold{font-weight:700}.font-normal{font-weight:400}.font-semibold{font-weight:600}.hover\:font-bold:hover{font-weight:700}.tab{-moz-tab-size:4;-o-tab-size:4;tab-size:4}.indent{text-indent:1.5rem}.hover\:no-underline:hover{text-decoration:none}.font-italic,.italic{font-style:italic}.shadow{--un-shadow:var(--un-shadow-inset) 0 1px 3px 0 var(--un-shadow-color, rgb(0 0 0 / .1)),var(--un-shadow-inset) 0 1px 2px -1px var(--un-shadow-color, rgb(0 0 0 / .1));box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow)}.flex{display:flex}.grow{flex-grow:1}.flex-col{flex-direction:column}.grid{display:grid}.fixed{position:fixed}.relative{position:relative}.static{position:static}.max-w-prose{max-width:65ch}.w3{width:.75rem}.cursor-pointer,.hover\:cursor-pointer:hover{cursor:pointer}.place-content-center{place-content:center}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-300{transition-duration:.3s}.ease{transition-timing-function:cubic-bezier(.4,0,.2,1)}@media (min-width: 640px){.sm\:flex-row{flex-direction:row}} +*,:before,:after{--un-rotate:0;--un-rotate-x:0;--un-rotate-y:0;--un-rotate-z:0;--un-scale-x:1;--un-scale-y:1;--un-scale-z:1;--un-skew-x:0;--un-skew-y:0;--un-translate-x:0;--un-translate-y:0;--un-translate-z:0;--un-ring-offset-shadow:0 0 rgb(0 0 0 / 0);--un-ring-shadow:0 0 rgb(0 0 0 / 0);--un-shadow-inset: ;--un-shadow:0 0 rgb(0 0 0 / 0);--un-ring-inset: ;--un-ring-offset-width:0px;--un-ring-offset-color:#fff;--un-ring-width:0px;--un-ring-color:rgb(147 197 253 / .5)}::backdrop{--un-rotate:0;--un-rotate-x:0;--un-rotate-y:0;--un-rotate-z:0;--un-scale-x:1;--un-scale-y:1;--un-scale-z:1;--un-skew-x:0;--un-skew-y:0;--un-translate-x:0;--un-translate-y:0;--un-translate-z:0;--un-ring-offset-shadow:0 0 rgb(0 0 0 / 0);--un-ring-shadow:0 0 rgb(0 0 0 / 0);--un-shadow-inset: ;--un-shadow:0 0 rgb(0 0 0 / 0);--un-ring-inset: ;--un-ring-offset-width:0px;--un-ring-offset-color:#fff;--un-ring-width:0px;--un-ring-color:rgb(147 197 253 / .5)}.prose :where(h1,h2,h3,h4,h5,h6):not(:where(.not-prose,.not-prose *)){color:var(--un-prose-headings);font-weight:600;line-height:1.25}.prose :where(a):not(:where(.not-prose,.not-prose *)){color:var(--un-prose-links);text-decoration:underline;font-weight:500;textDecoration:none;font-size:.9em;textDecorationThickness:.1em;textDecorationColor:rgb(var(--color-text-link))}.prose :where(a code):not(:where(.not-prose,.not-prose *)){color:var(--un-prose-links)}.prose :where(p,ul,ol,pre):not(:where(.not-prose,.not-prose *)){margin:1em 0;line-height:1.75}.prose :where(blockquote):not(:where(.not-prose,.not-prose *)){margin:1em 0;padding-left:1em;font-style:italic;border-left:.25em solid var(--un-prose-borders)}.prose :where(h1):not(:where(.not-prose,.not-prose *)){margin:1rem 0;font-size:2.25em}.prose :where(h2):not(:where(.not-prose,.not-prose *)){margin:1.75em 0 .5em;font-size:1.75em}.prose :where(h3):not(:where(.not-prose,.not-prose *)){margin:1.5em 0 .5em;font-size:1.375em}.prose :where(h4):not(:where(.not-prose,.not-prose *)){margin:1em 0;font-size:1.125em}.prose :where(img,video):not(:where(.not-prose,.not-prose *)){max-width:100%}.prose :where(figure,picture):not(:where(.not-prose,.not-prose *)){margin:1em 0}.prose :where(figcaption):not(:where(.not-prose,.not-prose *)){color:var(--un-prose-captions);font-size:.875em}.prose :where(code):not(:where(.not-prose,.not-prose *)){color:var(--un-prose-code);font-size:.875em;font-weight:600;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.prose :where(:not(pre)>code):not(:where(.not-prose,.not-prose *)):before,.prose :where(:not(pre)>code):not(:where(.not-prose,.not-prose *)):after{content:"`"}.prose :where(pre):not(:where(.not-prose,.not-prose *)){padding:1.25rem 1.5rem;overflow-x:auto;border-radius:.375rem}.prose :where(pre,code):not(:where(.not-prose,.not-prose *)){white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;hyphens:none;background:transparent}.prose :where(pre code):not(:where(.not-prose,.not-prose *)){font-weight:inherit;margin:.2rem;padding:.15em .3em;border-radius:.2em;background-color:var(--color-code-bg)}.prose :where(ol,ul):not(:where(.not-prose,.not-prose *)){padding-left:1.25em}.prose :where(ol):not(:where(.not-prose,.not-prose *)){list-style-type:decimal}.prose :where(ol[type=A]):not(:where(.not-prose,.not-prose *)){list-style-type:upper-alpha}.prose :where(ol[type=a]):not(:where(.not-prose,.not-prose *)){list-style-type:lower-alpha}.prose :where(ol[type=A s]):not(:where(.not-prose,.not-prose *)){list-style-type:upper-alpha}.prose :where(ol[type=a s]):not(:where(.not-prose,.not-prose *)){list-style-type:lower-alpha}.prose :where(ol[type=I]):not(:where(.not-prose,.not-prose *)){list-style-type:upper-roman}.prose :where(ol[type=i]):not(:where(.not-prose,.not-prose *)){list-style-type:lower-roman}.prose :where(ol[type=I s]):not(:where(.not-prose,.not-prose *)){list-style-type:upper-roman}.prose :where(ol[type=i s]):not(:where(.not-prose,.not-prose *)){list-style-type:lower-roman}.prose :where(ol[type="1"]):not(:where(.not-prose,.not-prose *)){list-style-type:decimal}.prose :where(ul):not(:where(.not-prose,.not-prose *)){list-style-type:disc}.prose :where(ol>li):not(:where(.not-prose,.not-prose *))::marker,.prose :where(ul>li):not(:where(.not-prose,.not-prose *))::marker,.prose :where(summary):not(:where(.not-prose,.not-prose *))::marker{color:var(--un-prose-lists)}.prose :where(hr):not(:where(.not-prose,.not-prose *)){margin:2em 0;border:1px solid var(--un-prose-hr)}.prose :where(table):not(:where(.not-prose,.not-prose *)){display:block;margin:1em 0;border-collapse:collapse;overflow-x:auto}.prose :where(tr):not(:where(.not-prose,.not-prose *)):nth-child(2n){background:var(--un-prose-bg-soft)}.prose :where(td,th):not(:where(.not-prose,.not-prose *)){border:1px solid var(--un-prose-borders);padding:.625em 1em}.prose :where(abbr):not(:where(.not-prose,.not-prose *)){cursor:help}.prose :where(kbd):not(:where(.not-prose,.not-prose *)){color:var(--un-prose-code);border:1px solid;padding:.25rem .5rem;font-size:.875em;border-radius:.25rem}.prose :where(details):not(:where(.not-prose,.not-prose *)){margin:1em 0;padding:1.25rem 1.5rem;background:var(--un-prose-bg-soft)}.prose :where(summary):not(:where(.not-prose,.not-prose *)){cursor:pointer;font-weight:600}.prose :where(li):not(:where(.not-prose,.not-prose *)){word-break:break-word}.prose :where(li code):not(:where(.not-prose,.not-prose *)){white-space:pre-wrap;word-break:break-word;margin:.2rem;padding:.15em .3em;border-radius:.2em;background-color:var(--color-code-bg)}.prose :where(li code):not(:where(.not-prose,.not-prose *)):after{content:none}.prose :where(li code):not(:where(.not-prose,.not-prose *)):before{content:none}.prose :where(a):not(:where(.not-prose,.not-prose *)):hover{color:rgb(var(--color-text-link-hover))}.prose :where(p):not(:where(.not-prose,.not-prose *)){word-break:break-word}.prose :where(p code):not(:where(.not-prose,.not-prose *)):after{content:none}.prose :where(p code):not(:where(.not-prose,.not-prose *)):before{content:none}.prose :where(blockquote p):not(:where(.not-prose,.not-prose *)){word-break:break-word}.prose :where(blockquote code):not(:where(.not-prose,.not-prose *)){white-space:pre-wrap;word-break:break-word;margin:.2rem;padding:.15em .3em;border-radius:.2em;background-color:var(--color-code-bg)}.prose :where(img):not(:where(.not-prose,.not-prose *)){border:0;display:block;height:auto;width:100%}.prose :where(cite):not(:where(.not-prose,.not-prose *)){text-align:center;with:100%;display:block;font-size:.8em}.prose{color:var(--un-prose-body);max-width:65ch}.prose-stone{--un-prose-body:#44403c;--un-prose-headings:#1c1917;--un-prose-links:#1c1917;--un-prose-lists:#a8a29e;--un-prose-hr:#e7e5e4;--un-prose-captions:#78716c;--un-prose-code:#1c1917;--un-prose-borders:#e7e5e4;--un-prose-bg-soft:#f5f5f4;--un-prose-invert-body:#e7e5e4;--un-prose-invert-headings:#f5f5f4;--un-prose-invert-links:#f5f5f4;--un-prose-invert-lists:#78716c;--un-prose-invert-hr:#44403c;--un-prose-invert-captions:#a8a29e;--un-prose-invert-code:#f5f5f4;--un-prose-invert-borders:#44403c;--un-prose-invert-bg-soft:#292524}.dark .dark\:prose-invert{--un-prose-body:var(--un-prose-invert-body);--un-prose-headings:var(--un-prose-invert-headings);--un-prose-links:var(--un-prose-invert-links);--un-prose-lists:var(--un-prose-invert-lists);--un-prose-hr:var(--un-prose-invert-hr);--un-prose-captions:var(--un-prose-invert-captions);--un-prose-code:var(--un-prose-invert-code);--un-prose-borders:var(--un-prose-invert-borders);--un-prose-bg-soft:var(--un-prose-invert-bg-soft)}.p-2{padding:.5rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-4{padding-left:1rem;padding-right:1rem}.mx-1{margin-left:.25rem;margin-right:.25rem}.mx-4{margin-left:1rem;margin-right:1rem}.my{margin-top:1rem;margin-bottom:1rem}.my-1{margin-top:.25rem;margin-bottom:.25rem}.my-3{margin-top:.75rem;margin-bottom:.75rem}.my-6{margin-top:1.5rem;margin-bottom:1.5rem}.mb-1{margin-bottom:.25rem}.mb-6{margin-bottom:1.5rem}.me{margin-inline-end:1rem}.me\!{margin-inline-end:1rem!important}.ml-2{margin-left:.5rem}.ml-auto{margin-left:auto}.mr-1{margin-right:.25rem}.mr-2{margin-right:.5rem}.mr-4{margin-right:1rem}.ms{margin-inline-start:1rem}.mt-1{margin-top:.25rem}.mt-12{margin-top:3rem}.mt-6{margin-top:1.5rem}.mt-8{margin-top:2rem}.mt-9{margin-top:2.25rem}.inline{display:inline}.block{display:block}.contents{display:contents}.hidden{display:none}.opacity-100{opacity:1}.opacity-80{opacity:.8}.opacity\$\.subscribe\(\(value\){opacity:var(--\.subscribe\(\(value\))}.hover\:opacity-80:hover{opacity:.8}.bg-stone-100{--un-bg-opacity:1;background-color:rgb(245 245 244 / var(--un-bg-opacity))}.bg-transparent{background-color:transparent}.dark .dark\:bg-stone-600{--un-bg-opacity:1;background-color:rgb(87 83 78 / var(--un-bg-opacity))}.dark .dark\:bg-stone-700{--un-bg-opacity:1;background-color:rgb(68 64 60 / var(--un-bg-opacity))}.dark .dark\:fill-transparent,.fill-transparent{fill:transparent}.dark .dark\:fill-white{--un-fill-opacity:1;fill:rgb(255 255 255 / var(--un-fill-opacity))}.fill-black{--un-fill-opacity:1;fill:rgb(0 0 0 / var(--un-fill-opacity))}.b,.border{border-width:1px}.border-0{border-width:0px}.rounded-lg{border-radius:.5rem}.text-2xl{font-size:1.5rem;line-height:2rem}.text-4xl{font-size:2.25rem;line-height:2.5rem}.text-sm{font-size:.875rem;line-height:1.25rem}.font-bold{font-weight:700}.font-normal{font-weight:400}.font-semibold{font-weight:600}.hover\:font-bold:hover{font-weight:700}.tab{-moz-tab-size:4;-o-tab-size:4;tab-size:4}.indent{text-indent:1.5rem}.hover\:no-underline:hover{text-decoration:none}.font-italic,.italic{font-style:italic}.shadow{--un-shadow:var(--un-shadow-inset) 0 1px 3px 0 var(--un-shadow-color, rgb(0 0 0 / .1)),var(--un-shadow-inset) 0 1px 2px -1px var(--un-shadow-color, rgb(0 0 0 / .1));box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow)}.flex{display:flex}.grow{flex-grow:1}.flex-col{flex-direction:column}.grid{display:grid}.fixed{position:fixed}.relative{position:relative}.static{position:static}.max-w-prose{max-width:65ch}.w3{width:.75rem}.cursor-pointer,.hover\:cursor-pointer:hover{cursor:pointer}.visible{visibility:visible}.place-content-center{place-content:center}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-300{transition-duration:.3s}.ease{transition-timing-function:cubic-bezier(.4,0,.2,1)}.transform{transform:translate(var(--un-translate-x)) translateY(var(--un-translate-y)) translateZ(var(--un-translate-z)) rotate(var(--un-rotate)) rotateX(var(--un-rotate-x)) rotateY(var(--un-rotate-y)) rotate(var(--un-rotate-z)) skew(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y)) scaleZ(var(--un-scale-z))}@media (min-width: 640px){.sm\:flex-row{flex-direction:row}} diff --git a/_astro/console-log.C6BYzl66_Z1eTeng.webp b/_astro/console-log.C6BYzl66_Z1eTeng.webp new file mode 100644 index 0000000..1e7a4d7 Binary files /dev/null and b/_astro/console-log.C6BYzl66_Z1eTeng.webp differ diff --git a/_astro/fat-cat-balance.CJVVoCkZ_E4qcv.webp b/_astro/fat-cat-balance.CJVVoCkZ_E4qcv.webp new file mode 100644 index 0000000..186a4dd Binary files /dev/null and b/_astro/fat-cat-balance.CJVVoCkZ_E4qcv.webp differ diff --git a/_astro/fruits-example.CWBnkv61_2ckqVz.webp b/_astro/fruits-example.CWBnkv61_2ckqVz.webp new file mode 100644 index 0000000..7aef554 Binary files /dev/null and b/_astro/fruits-example.CWBnkv61_2ckqVz.webp differ diff --git a/_astro/game-level-1-2.DZyrUwgs_kfx5H.webp b/_astro/game-level-1-2.DZyrUwgs_kfx5H.webp new file mode 100644 index 0000000..13b292a Binary files /dev/null and b/_astro/game-level-1-2.DZyrUwgs_kfx5H.webp differ diff --git a/_astro/game-level-1-opacity-after.BDoVsrhq_Z2qg21I.webp b/_astro/game-level-1-opacity-after.BDoVsrhq_Z2qg21I.webp new file mode 100644 index 0000000..75e8bd7 Binary files /dev/null and b/_astro/game-level-1-opacity-after.BDoVsrhq_Z2qg21I.webp differ diff --git a/_astro/game-level-1-opacity-before.DXDL2nD4_Z1wDtYM.webp b/_astro/game-level-1-opacity-before.DXDL2nD4_Z1wDtYM.webp new file mode 100644 index 0000000..87308e5 Binary files /dev/null and b/_astro/game-level-1-opacity-before.DXDL2nD4_Z1wDtYM.webp differ diff --git a/_astro/game-level-2-no-cat.-GcNc6tc_wuJtD.webp b/_astro/game-level-2-no-cat.-GcNc6tc_wuJtD.webp new file mode 100644 index 0000000..ffc83e9 Binary files /dev/null and b/_astro/game-level-2-no-cat.-GcNc6tc_wuJtD.webp differ diff --git a/_astro/map-filter-stream.C7zYYY3o_gcVwz.webp b/_astro/map-filter-stream.C7zYYY3o_gcVwz.webp new file mode 100644 index 0000000..4823600 Binary files /dev/null and b/_astro/map-filter-stream.C7zYYY3o_gcVwz.webp differ diff --git a/_astro/marbles-level-0-1.CJYwtaCj_Z1CadXJ.webp b/_astro/marbles-level-0-1.CJYwtaCj_Z1CadXJ.webp new file mode 100644 index 0000000..b9d199e Binary files /dev/null and b/_astro/marbles-level-0-1.CJYwtaCj_Z1CadXJ.webp differ diff --git a/_astro/marbles-level-1-1.C0Ag8ejy_13JfLf.webp b/_astro/marbles-level-1-1.C0Ag8ejy_13JfLf.webp new file mode 100644 index 0000000..220d8f5 Binary files /dev/null and b/_astro/marbles-level-1-1.C0Ag8ejy_13JfLf.webp differ diff --git a/_astro/marbles-level-1-2.BaMkloqh_OwbCc.webp b/_astro/marbles-level-1-2.BaMkloqh_OwbCc.webp new file mode 100644 index 0000000..150cd41 Binary files /dev/null and b/_astro/marbles-level-1-2.BaMkloqh_OwbCc.webp differ diff --git a/_astro/marbles-level-3-1.ByipG0jO_wg3Nu.webp b/_astro/marbles-level-3-1.ByipG0jO_wg3Nu.webp new file mode 100644 index 0000000..481f73a Binary files /dev/null and b/_astro/marbles-level-3-1.ByipG0jO_wg3Nu.webp differ diff --git a/_astro/marbles-level-3-2.57meHjkY_Z2myQ3K.webp b/_astro/marbles-level-3-2.57meHjkY_Z2myQ3K.webp new file mode 100644 index 0000000..94119d2 Binary files /dev/null and b/_astro/marbles-level-3-2.57meHjkY_Z2myQ3K.webp differ diff --git a/_astro/marbles-level-3-3.B44tIZIS_ZOvAaU.webp b/_astro/marbles-level-3-3.B44tIZIS_ZOvAaU.webp new file mode 100644 index 0000000..879c375 Binary files /dev/null and b/_astro/marbles-level-3-3.B44tIZIS_ZOvAaU.webp differ diff --git a/_astro/marbles-level-3-4.48bauHCo_ZTFfFS.webp b/_astro/marbles-level-3-4.48bauHCo_ZTFfFS.webp new file mode 100644 index 0000000..c433e10 Binary files /dev/null and b/_astro/marbles-level-3-4.48bauHCo_ZTFfFS.webp differ diff --git a/_astro/marbles-level-4-1.Bq6s3X4K_PgNwa.webp b/_astro/marbles-level-4-1.Bq6s3X4K_PgNwa.webp new file mode 100644 index 0000000..ec9f3a3 Binary files /dev/null and b/_astro/marbles-level-4-1.Bq6s3X4K_PgNwa.webp differ diff --git a/_astro/marbles-level-4-2.C107W-P8_Z20HI5q.webp b/_astro/marbles-level-4-2.C107W-P8_Z20HI5q.webp new file mode 100644 index 0000000..92c072b Binary files /dev/null and b/_astro/marbles-level-4-2.C107W-P8_Z20HI5q.webp differ diff --git a/_astro/marbles-level-4-3-4.CvHFNXRU_1BQ2p2.webp b/_astro/marbles-level-4-3-4.CvHFNXRU_1BQ2p2.webp new file mode 100644 index 0000000..73af9f9 Binary files /dev/null and b/_astro/marbles-level-4-3-4.CvHFNXRU_1BQ2p2.webp differ diff --git a/_astro/please-send-help.BqpbnZm__Z22oG8K.webp b/_astro/please-send-help.BqpbnZm__Z22oG8K.webp new file mode 100644 index 0000000..4804f5e Binary files /dev/null and b/_astro/please-send-help.BqpbnZm__Z22oG8K.webp differ diff --git a/_astro/rxjs-free-cancellation.--Slf4gr_UAI3O.webp b/_astro/rxjs-free-cancellation.--Slf4gr_UAI3O.webp new file mode 100644 index 0000000..be89f47 Binary files /dev/null and b/_astro/rxjs-free-cancellation.--Slf4gr_UAI3O.webp differ diff --git a/_astro/ssh-server-helper-android-sm.DLlLyxQm_206vq8.webp b/_astro/ssh-server-helper-android-sm.DLlLyxQm_206vq8.webp new file mode 100644 index 0000000..cc3cc20 Binary files /dev/null and b/_astro/ssh-server-helper-android-sm.DLlLyxQm_206vq8.webp differ diff --git a/_astro/stream-basic-four-values.B7f0Woit_uytjs.webp b/_astro/stream-basic-four-values.B7f0Woit_uytjs.webp new file mode 100644 index 0000000..4df8276 Binary files /dev/null and b/_astro/stream-basic-four-values.B7f0Woit_uytjs.webp differ diff --git a/_astro/take-cat-to-party-no-cancel.CZZccS4T_m4WPq.webp b/_astro/take-cat-to-party-no-cancel.CZZccS4T_m4WPq.webp new file mode 100644 index 0000000..ee2dc11 Binary files /dev/null and b/_astro/take-cat-to-party-no-cancel.CZZccS4T_m4WPq.webp differ diff --git a/_astro/take-cat-to-party.BZWhi2xp_1ewDcU.webp b/_astro/take-cat-to-party.BZWhi2xp_1ewDcU.webp new file mode 100644 index 0000000..7d568e0 Binary files /dev/null and b/_astro/take-cat-to-party.BZWhi2xp_1ewDcU.webp differ diff --git a/_astro/wrong-kido.D6xBmMGW_2iVX02.webp b/_astro/wrong-kido.D6xBmMGW_2iVX02.webp new file mode 100644 index 0000000..20f2010 Binary files /dev/null and b/_astro/wrong-kido.D6xBmMGW_2iVX02.webp differ diff --git a/android-chrome-192x192.png b/android-chrome-192x192.png new file mode 100644 index 0000000..0143ab7 Binary files /dev/null and b/android-chrome-192x192.png differ diff --git a/android-chrome-512x512.png b/android-chrome-512x512.png new file mode 100644 index 0000000..bf11705 Binary files /dev/null and b/android-chrome-512x512.png differ diff --git a/apple-touch-icon.png b/apple-touch-icon.png new file mode 100644 index 0000000..b97f464 Binary files /dev/null and b/apple-touch-icon.png differ diff --git a/bookshelf/example/index.html b/bookshelf/example/index.html index 907681e..c385799 100644 --- a/bookshelf/example/index.html +++ b/bookshelf/example/index.html @@ -1,4 +1,4 @@ - Book title - Hey ๐Ÿ‘‹ + - - ~/ @@ -88,5 +88,5 @@ border-color: #bee5eb; color: #d1ecf1; } -

Book title

Posted on 02, Mar, 2024

ยปTitle

-

Text

Last modified on 12, Oct, 2024
\ No newline at end of file +

-

Posted on 02, Mar, 2024

ยปTitle

+

This content is a work in progress.

Last modified on 12, Oct, 2024
\ No newline at end of file diff --git a/bookshelf/index.html b/bookshelf/index.html index affe08d..681088e 100644 --- a/bookshelf/index.html +++ b/bookshelf/index.html @@ -1,4 +1,4 @@ - posts - Hey ๐Ÿ‘‹ + posts - ~/ @@ -32,4 +32,4 @@ } setup() -
2024
\ No newline at end of file +
2024
\ No newline at end of file diff --git a/favicon-16x16.png b/favicon-16x16.png new file mode 100644 index 0000000..d68d48d Binary files /dev/null and b/favicon-16x16.png differ diff --git a/favicon-32x32.png b/favicon-32x32.png new file mode 100644 index 0000000..f09b1dd Binary files /dev/null and b/favicon-32x32.png differ diff --git a/favicon.ico b/favicon.ico index 26ff805..af95861 100644 Binary files a/favicon.ico and b/favicon.ico differ diff --git a/index.html b/index.html index 400afb2..2a2d3e2 100644 --- a/index.html +++ b/index.html @@ -1,4 +1,4 @@ - posts - Hey ๐Ÿ‘‹ + posts - ~/ @@ -32,21 +32,20 @@ } setup() -
2024
2022
2021
2020
2019
2018
2022
2021
2020
2019
2018
\ No newline at end of file +#css
\ No newline at end of file diff --git a/posts/about-css-conf-eu-berlin-2018/index.html b/posts/about-css-conf-eu-berlin-2018/index.html index 6232e7a..58aa233 100644 --- a/posts/about-css-conf-eu-berlin-2018/index.html +++ b/posts/about-css-conf-eu-berlin-2018/index.html @@ -1,4 +1,4 @@ - CSSconf EU 2018 - Hey ๐Ÿ‘‹ + CSSconf EU 2018 - ~/ @@ -400,4 +400,4 @@

ยปAnd thenโ€ฆ

-

I will also write a blog post about the JSConf that happened in the following two days where Iโ€™ll also talk about Berlin and the venue to give you a real insider perspective on the conference so that you can properly decide whether or not you will attend it next year.

Last modified on 12, Oct, 2024
\ No newline at end of file +

I will also write a blog post about the JSConf that happened in the following two days where Iโ€™ll also talk about Berlin and the venue to give you a real insider perspective on the conference so that you can properly decide whether or not you will attend it next year.

Last modified on 12, Oct, 2024
\ No newline at end of file diff --git a/posts/about-js-conf-eu-berlin-2018/index.html b/posts/about-js-conf-eu-berlin-2018/index.html index 53c95c5..ee39328 100644 --- a/posts/about-js-conf-eu-berlin-2018/index.html +++ b/posts/about-js-conf-eu-berlin-2018/index.html @@ -1,4 +1,4 @@ - JSConf EU 2018 - Hey ๐Ÿ‘‹ + JSConf EU 2018 - ~/ @@ -561,4 +561,4 @@

-

See you soon.

Last modified on 12, Oct, 2024
\ No newline at end of file +

See you soon.

Last modified on 12, Oct, 2024
\ No newline at end of file diff --git a/posts/awesome-reactive/index.html b/posts/awesome-reactive/index.html index 545138c..327bc05 100644 --- a/posts/awesome-reactive/index.html +++ b/posts/awesome-reactive/index.html @@ -1,4 +1,4 @@ - Reactive Series (pt. 5) - Awesome Resources for Reactive Programming - Hey ๐Ÿ‘‹ + Reactive Series (pt. 5) - Awesome Resources for Reactive Programming - ~/ @@ -161,4 +161,4 @@

Part 3 - Hands-on Reactive Programming with RxJS
  • Part 4 - Reactive Programming: The Good and the Bad
  • Part 5 - Awesome RxJS and Reactive Programming Resources
  • -
    Last modified on 12, Oct, 2024
    \ No newline at end of file +
    Last modified on 12, Oct, 2024
    \ No newline at end of file diff --git a/posts/best-http-request-mock-tool/index.html b/posts/best-http-request-mock-tool/index.html index b8f672f..00f51ce 100644 --- a/posts/best-http-request-mock-tool/index.html +++ b/posts/best-http-request-mock-tool/index.html @@ -1,4 +1,4 @@ - How to Mock API Calls - Hey ๐Ÿ‘‹ + How to Mock API Calls - ~/ @@ -90,5 +90,5 @@ }

    How to Mock API Calls

    See https://tweak-extension.com/blog/how-to-mock-api-calls.

    Last modified on 12, Oct, 2024
    \ No newline at end of file +#productivity +#software-testing Posted on 14, Jun, 2020

    See https://tweak-extension.com/blog/how-to-mock-api-calls.

    Last modified on 12, Oct, 2024
    \ No newline at end of file diff --git a/posts/better-imports-webpack-alias/index.html b/posts/better-imports-webpack-alias/index.html index 645e3b1..d08dd3f 100644 --- a/posts/better-imports-webpack-alias/index.html +++ b/posts/better-imports-webpack-alias/index.html @@ -1,4 +1,4 @@ - Better imports with webpack resolve.alias - Hey ๐Ÿ‘‹ + Better imports with webpack resolve.alias - ~/ @@ -100,4 +100,4 @@
    const webpack = require('webpack')
    // ...
    const options = {
    entry: {
    // ...
    },
    output: {
    // ...
    },
    module: {
    // ...
    },
    resolve: {
    alias: {
    // Note: 'src/common/utils' is the path to the module, in this case "utils"
    '@project-x-utils': path.resolve(__dirname, 'src/common/utils'),
    // ...
    },
    },
    plugins: [
    // ...
    ],
    }
    module.exports = options
    -

    Now you can go ahead and remove all those 100 characters long relative imports at the beginning of your JavaScript files.

    Last modified on 12, Oct, 2024
    \ No newline at end of file +

    Now you can go ahead and remove all those 100 characters long relative imports at the beginning of your JavaScript files.

    Last modified on 12, Oct, 2024
    \ No newline at end of file diff --git a/posts/browser-polyfill-madness-mozilla-internet-explorer/index.html b/posts/browser-polyfill-madness-mozilla-internet-explorer/index.html index 606736a..5e2d2a6 100644 --- a/posts/browser-polyfill-madness-mozilla-internet-explorer/index.html +++ b/posts/browser-polyfill-madness-mozilla-internet-explorer/index.html @@ -1,4 +1,4 @@ - Browser polyfill madness, Mozilla and IE - Hey ๐Ÿ‘‹ + Browser polyfill madness, Mozilla and IE - ~/ @@ -119,4 +119,4 @@

    Last modified on 12, Oct, 2024 \ No newline at end of file +
    Last modified on 12, Oct, 2024
    \ No newline at end of file diff --git a/posts/call-react-hooks-inside-conditions/index.html b/posts/call-react-hooks-inside-conditions/index.html index 830ede9..ee39fce 100644 --- a/posts/call-react-hooks-inside-conditions/index.html +++ b/posts/call-react-hooks-inside-conditions/index.html @@ -1,4 +1,4 @@ - You Can't Call Hooks Inside Conditions? Yes you can - Hey ๐Ÿ‘‹ + You Can't Call Hooks Inside Conditions? Yes you can - ~/ @@ -180,4 +180,4 @@

    useEffect(() => {
    setState(number)
    }, [setState, number])
    return null
    }
    export default function App() {
    const [isHookActive, setIsHookActive] = useState(false)
    const [number, setNumber] = useState(0)
    return (
    <div className="App">
    <h1>Bending the Rules of Hooks</h1>
    <button onClick={() => setIsHookActive(!isHookActive)}>Click to toggle custom hook usage</button>
    <h4>{isHookActive ? `hook output is: ${number}` : 'hook is not active'}</h4>
    {isHookActive && <RandomNumberWrapper setState={setNumber} />}
    </div>
    )
    }
    -
    Last modified on 12, Oct, 2024
    \ No newline at end of file +
    Last modified on 12, Oct, 2024
    \ No newline at end of file diff --git a/posts/debugging-javascript-with-vscode/index.html b/posts/debugging-javascript-with-vscode/index.html index 951e310..dccab6b 100644 --- a/posts/debugging-javascript-with-vscode/index.html +++ b/posts/debugging-javascript-with-vscode/index.html @@ -1,4 +1,4 @@ - Debugging Javascript with VS Code - Hey ๐Ÿ‘‹ + Debugging Javascript with VS Code - ~/ @@ -104,7 +104,7 @@

    {
    "version": "0.2.0",
    "configurations": [
    {
    "type": "chrome",
    "request": "launch",
    "name": "JS Debugger",
    "userDataDir": true,
    "url": "http://localhost:3002/",
    "webRoot": "${workspaceFolder}",
    "sourceMapPathOverrides": {
    "webpack:///*": "${webRoot}/*"
    }
    }
    ]
    }
    +
    .vscode/launch.json
    {
    "version": "0.2.0",
    "configurations": [
    {
    "type": "chrome",
    "request": "launch",
    "name": "JS Debugger",
    "userDataDir": true,
    "url": "http://localhost:3002/",
    "webRoot": "${workspaceFolder}",
    "sourceMapPathOverrides": {
    "webpack:///*": "${webRoot}/*"
    }
    }
    ]
    }
    You can read more about sourceMapPathOverrides property in the official{" "}
    @@ -171,4 +171,4 @@

    ยปConclusions

    vscode setup redux final

    -

    I might have spent one or two days trying to figure out how to bring all these pieces together, but having done it, believe that it increased my productivity in ways that by far compensate the invested time. I hope you find this article useful especially if it saves you one day of trouble trying to figure out the right configs.

    Last modified on 12, Oct, 2024
    \ No newline at end of file +

    I might have spent one or two days trying to figure out how to bring all these pieces together, but having done it, believe that it increased my productivity in ways that by far compensate the invested time. I hope you find this article useful especially if it saves you one day of trouble trying to figure out the right configs.

    Last modified on 12, Oct, 2024
    \ No newline at end of file diff --git a/posts/destructuring-the-not-so-good-parts/index.html b/posts/destructuring-the-not-so-good-parts/index.html new file mode 100644 index 0000000..7678f91 --- /dev/null +++ b/posts/destructuring-the-not-so-good-parts/index.html @@ -0,0 +1,154 @@ + Destructuring in JavaScript: the not so good parts - ~/ + + +

    ~/

    Back

    Destructuring in JavaScript: the not so good parts

    Posted on 20, Jun, 2019

    Generally speaking, I would say everybody loves the destructuring assignment, and for good reasons. Next, I list some of the amazing things one can achieve with destructuring in JavaScript.

    +

    ยปObject destructuring

    +
    const user = { username: 'captain', email: 'captain@email.com', age: 30 }
    const { username, age } = user
    +
    +

    ยปProvide default values

    +
    const user = { email: 'captain@email.com', age: 30 }
    const { username = 'unknown', age } = user
    // you can now use username with value 'unknown'
    +
    +

    ยปRenaming variables on creation

    +
    const user = { username: 'captain', email: 'captain@email.com', age: 30 }
    const { username, age: userAge } = user
    // you know have age value 30 in the variable userAge
    +
    +

    ยปNested object destructuring โš ๏ธ

    +
    const user = { username: 'captain', age: { value: 30, label: '30 years old' } }
    const {
    username,
    age: { label },
    } = user
    // you can now use label directly
    +
    +

    ยปArray destructuring

    +
    const rgb = [200, 255, 100]
    const [red, green, blue] = rgb
    // the variables red, green and blue match the elements order in rgb array
    +
    +

    ยปWorks with any iterable on the right-side (e.g Set)

    +
    let [a, b, c] = 'abc' // ["a", "b", "c"]
    let [one, two, three] = new Set([1, 2, 3])
    +
    +

    ยปSwapping Variables

    +
    let a = 1
    let b = 2
    ;[b, a] = [a, b]
    +
    +

    ยปSwapping array positions

    +
    const arr = [1, 3, 2, 4, 5]
    ;[arr[1], arr[2]] = [arr[2], arr[1]]
    // arr is now [1, 2, 3, 4, 5]
    +
    +

    ยปSkipping items โš ๏ธ

    +
    const rgb = [200, 255, 100]
    // skip the first two items
    // assign the only third item to the blue variable
    const [, , blue] = rgb
    +
    +

    ยปNested array destructuring โš ๏ธ

    +
    const color = ['#FF00FF', [255, 0, 255], 'rgb(255, 0, 255)']
    // use nested destructuring to assign red, green and blue
    const [hex, [red, green, blue]] = color
    console.log(hex, red, green, blue) // #FF00FF 255 0 255
    +
    +

    ยปFunction parameter destructuring โš ๏ธ

    +
    function getUserInfo({ username, email }) {
    return `Username: ${username}; Email: ${email}`
    }
    const user = { username: 'captain', email: 'captain@email.com' }
    const userInfo = getUserInfo(user)
    +
    +

    ยปWhatโ€™s not so good here

    +

    The most significant dilemma of destructuring itโ€™s related with the fact that relies on properties of nested structures that can only be evaluated at runtime, let me give you an example.

    +
    function addEmailToList(list, { email }) {
    if (email) {
    list.push(email)
    }
    }
    const list = ['a@mail.com', 'b@mail.com']
    const user = undefined
    +
    addEmailToList(list, user)
    // Uncaught TypeError: Cannot destructure property `email` of 'undefined' or 'null'
    +

    On the other hand, if you write this without destructuring, you wonโ€™t make assumptions on the user, you can double check whether the given user is valid.

    +
    function addEmailToList(list, user) {
    if (user && user.email) {
    list.push(user.email)
    }
    }
    const list = ['a@mail.com', 'b@mail.com']
    const user = undefined
    +
    addEmailToList(list, user)
    +
    +

    ยปnull - a catastrophe waiting to happen

    +

    Just a note, for you to be conscious that as in default assignment default parameters the null value itโ€™s not considered false, thus you canโ€™t fall back to some default value in destructuring a property whose value is null.

    +
    function formatAddress(info) {
    const { country = '-', city = '-' } = info
    return `${country}, ${city}`
    }
    +
    const info = { country: 'Portugal', city: null }
    const result = formatAddress(info)
    // "Portugal, null" it's what you get instead of "Portugal, -"
    +

    Every feature above mentioned exists for you to use, but we should find a balance (as in everything else in life). Below I list a few guidelines/rules that will help you find the balance when using destructuring.

    +

    cat kong fu balance

    +

    source: https://imgflip.com/tag/fat+cat+balance

    +

    ยปThe 3 Golden Rules

    +

    You might have noticed some previous warning sings (โš ๏ธ) in some of the listed usages of destructuring. I left those signs there because I consider those use cases potentially harmful.

    +
      +
    1. Donโ€™t use nested destructuring on data that comes from any external data sources (such as REST APIs, GraphQL endpoints or files). You never know when these APIs change their data contracts. Hopefully all of this itโ€™s synced between teams, but sometimes we get lost on the tiny details such as that one small property in some nested object that got renamed and instead of using snake_case it uses now camelCase just because it made more sense from an aesthetic point of view. Now your marvelous single page application is burning and falling into pieces because you were unsafely accessing this property when you could have protected yourself against it.
    2. +
    3. Donโ€™t use nested destructuring on function arguments that have long or complicated signatures. It makes it super hard to understand what the actually API of the function is, and you might get breaking behavior because someone decided to use your function, but in some edge case they do not provide you the valid input for you to destruct upon.
    4. +
    5. Donโ€™t use destructuring to retrieve a value if you rely on order. I listed the possibility of skipping elements with destructuring, it might sound tempting, but you might end up again breaking your website because your simple routine relies on the access of the Nth element of some inputted array. Also, you can still retrieve data out of the array, but it might be that in that position you donโ€™t get what you were expecting. If you choose this path, at this point, it should also be arguable that thereโ€™s something very wrong either with your code or with the data model from where youโ€™re your reading data, use this one with care.
    6. +
    +

    ยปReferences

    +
    Last modified on 12, Oct, 2024
    \ No newline at end of file diff --git a/posts/developers-bad-at-testing-own-code/index.html b/posts/developers-bad-at-testing-own-code/index.html index cdfda2c..ac09f50 100644 --- a/posts/developers-bad-at-testing-own-code/index.html +++ b/posts/developers-bad-at-testing-own-code/index.html @@ -1,4 +1,4 @@ - Why are developers bad at (manually) testing their code? - Hey ๐Ÿ‘‹ + Why are developers bad at (manually) testing their code? - ~/ @@ -88,8 +88,8 @@ border-color: #bee5eb; color: #d1ecf1; } -

    Why are developers bad at (manually) testing their code?

    -#softwaretesting
    Posted on 06, Apr, 2021

    Manual testing of any software itโ€™s a vital activity to ensure its quality and long-term stability. Together with their coding activities, developers test their code changes to understand if they produce the desired outcome without breaking any existing software functionality. So far, so good.

    +

    Why are developers bad at (manually) testing their code?

    Posted on 06, Apr, 2021

    Manual testing of any software itโ€™s a vital activity to ensure its quality and long-term stability. Together with their coding activities, developers test their code changes to understand if they produce the desired outcome without breaking any existing software functionality. So far, so good.

    Today Iโ€™m writing a few points on why developers are bad at manual testing their own code changes, especially at detecting unwanted side-effects in areas of the software often unrelated to the modified parts.

    Iโ€™m writing because, at this point in time, I have experienced two very different setups towards software testing:

      @@ -145,4 +145,4 @@

      Last modified on 12, Oct, 2024 \ No newline at end of file +

      See ya ๐Ÿ‘‹

    Last modified on 12, Oct, 2024
    \ No newline at end of file diff --git a/posts/functional-bits-tips/index.html b/posts/functional-bits-tips/index.html index f4c00ce..262c275 100644 --- a/posts/functional-bits-tips/index.html +++ b/posts/functional-bits-tips/index.html @@ -1,4 +1,4 @@ - Functional bits - Hey ๐Ÿ‘‹ + Functional bits - ~/ @@ -186,4 +186,4 @@

    const users = [
    {
    email: 'melany.wijngaard@example.com',
    gender: 'female',
    phone_number: '(727)-033-9347',
    birthdate: 608022796,
    location: {
    street: '2431 predikherenkerkhof',
    city: 'staphorst',
    state: 'gelderland',
    postcode: 64265,
    },
    username: 'bigpeacock217',
    password: 'eagle',
    first_name: 'melany',
    last_name: 'wijngaard',
    title: 'miss',
    },
    {
    email: 'nanna.pedersen@example.com',
    gender: 'female',
    phone_number: '43672992',
    birthdate: 591428535,
    location: {
    street: '2177 fรฅborgvej',
    city: 'aarhus',
    state: 'syddanmark',
    postcode: 87547,
    },
    username: 'purpleduck599',
    password: 'davids',
    first_name: 'nanna',
    last_name: 'pedersen',
    title: 'ms',
    },
    {
    email: 'amelia.mercier@example.com',
    gender: 'female',
    phone_number: '(168)-747-5950',
    birthdate: 1132298571,
    location: {
    street: '7454 rue duquesne',
    city: 'echandens-denges',
    state: 'vaud',
    postcode: 3811,
    },
    username: 'whitefrog218',
    password: 'forest',
    first_name: 'amelia',
    last_name: 'mercier',
    title: 'madame',
    },
    ]
    -

    Last modified on 12, Oct, 2024
    \ No newline at end of file +
    Last modified on 12, Oct, 2024
    \ No newline at end of file diff --git a/posts/guide-to-custom-react-hooks-with-mutationobserver/index.html b/posts/guide-to-custom-react-hooks-with-mutationobserver/index.html new file mode 100644 index 0000000..d66f470 --- /dev/null +++ b/posts/guide-to-custom-react-hooks-with-mutationobserver/index.html @@ -0,0 +1,162 @@ + Guide to custom React Hooks (wrap the MutationObserver) - ~/ + + +

    ~/

    Back

    Guide to custom React Hooks (wrap the MutationObserver)

    Posted on 21, Jul, 2021
    +

    This article was originally published by LogRocket. You can checkout the original post here.

    +

    With the introduction of React Hooks, the amount of shareable code within React codebases has exploded. Because Hooks are thin APIs on top of React, developers can collaborate by attaching reusable behavior to components and segregating these behaviors into smaller modules.

    +

    While this is similar to how JavaScript developers abstract business logic away in vanilla JavaScript modules, Hooks provide more than pure JavaScript functions. Instead of taking data in and out, developers can stretch the spectrum of possibilities of what can happen inside a Hook.

    +

    For instance, developers can:

    +
      +
    • Mutate and manage a piece of state for a specific component or an entire application
    • +
    • Trigger side effects on a page, like changing the title of a browser tab
    • +
    • Rectify external APIs by tapping into React componentsโ€™ lifecycle with Hooks
    • +
    +

    In this post weโ€™ll explore the latter possibility. As a case study, weโ€™ll abstract the MutationObserver API in a custom React Hook, demonstrating how we can build robust, shareable pieces of logic in a React codebase.

    +

    Weโ€™ll create a dynamic label that updates itself to indicate how many items we have in a list. Instead of using the provided React state array of elements, weโ€™ll use the MutationObserver API to detect added elements and update the label accordingly.

    +

    &#x27;Update the dynamic label to count the number of fruits in the list&#x27;

    +

    source: https://blog.logrocket.com/guide-to-custom-react-hooks-with-mutationobserver/

    +

    ยปImplementation overview

    +

    The following code is a simple component that renders our list. It also updates a counter value that represents the number of fruits currently in the list:

    +
    export default function App() {
    const listRef = useRef()
    const [count, setCount] = useState(2)
    const [fruits, setFruits] = useState(['apple', 'peach'])
    const onListMutation = useCallback(
    (mutationList) => {
    setCount(mutationList[0].target.children.length)
    },
    [setCount],
    )
    +
    useMutationObservable(listRef.current, onListMutation)
    +
    return (
    <div>
    <span>{`Added ${count} fruits`}</span>
    <br />
    <button onClick={() => setFruits([...fruits, `random fruit ${fruits.length}`])}>Add random fruit</button>
    <ul ref={listRef}>
    {fruits.map((f) => (
    <li key={f}>{f}</li>
    ))}
    </ul>
    </div>
    )
    }
    +

    We want to trigger a callback function whenever our list element is mutated. Within the callback we refer to, the elementโ€™s children give us the number of elements in the list.

    +

    ยปImplementing the useMutationObservable custom Hook

    +

    Letโ€™s look at the integration point:

    +
    useMutationObservable(listRef.current, onListMutation)
    +

    The above useMutationObservable custom Hook abstracts the necessary operations to observe changes on the element passed as the first parameter. It then runs the callback passed as the second parameter whenever the target element changes.

    +

    Now, letโ€™s implement our useMutationObservable custom Hook.

    +

    In the Hook, there are a number of boilerplate operations to understand. First, we must provide a set of options that comply with the MutationObserver API.

    +

    Once a MutationObserver instance is created, we must call observe to listen for changes in the targeted DOM element.

    +

    When we no longer need to listen to the changes, we must call disconnect on the observer to clean up our subscription. This must happen when the App component unmounts:

    +
    const DEFAULT_OPTIONS = {
    config: { attributes: true, childList: true, subtree: true },
    }
    function useMutationObservable(targetEl, cb, options = DEFAULT_OPTIONS) {
    const [observer, setObserver] = useState(null)
    +
    useEffect(() => {
    const obs = new MutationObserver(cb)
    setObserver(obs)
    }, [cb, options, setObserver])
    +
    useEffect(() => {
    if (!observer) return
    const { config } = options
    observer.observe(targetEl, config)
    return () => {
    if (observer) {
    observer.disconnect()
    }
    }
    }, [observer, targetEl, options])
    }
    +

    All the above work, including initializing the MutationObserver with the right parameters, observing changes with the call to observer.observe, and cleaning up with observer.disconnect, are abstracted away from the client.

    +

    Not only do we export functionality, but we also clean up by hooking into the React componentsโ€™ lifecycle and by leveraging cleanup callbacks on effect Hooks to tear down the MutationObserver instance.

    +

    Now that we have a functional and basic version of our Hook, we can think about improving its quality by iterating on its API and enhancing the developer experience around this shareable piece of code.

    +

    ยปInput validation and development

    +

    One important aspect when designing custom React Hooks is input validation. We must be able to communicate to developers when things are not running smoothly or a certain use case is hitting an edge case.

    +

    Usually, development logs help developers understand unfamiliar code to adjust their implementation. Likewise, we can enhance the above implementation by adding runtime checks and comprehensive warning logs to validate and communicate issues to other developers:

    +
    function useMutationObservable(targetEl, cb, options = DEFAULT_OPTIONS) {
    const [observer, setObserver] = useState(null)
    +
    useEffect(() => {
    // A)
    if (!cb || typeof cb !== 'function') {
    console.warn(`You must provide a valid callback function, instead you've provided ${cb}`)
    return
    }
    const { debounceTime } = options
    const obs = new MutationObserver(cb)
    setObserver(obs)
    }, [cb, options, setObserver])
    useEffect(() => {
    if (!observer) return
    if (!targetEl) {
    // B)
    console.warn(`You must provide a valid DOM element to observe, instead you've provided ${targetEl}`)
    }
    const { config } = options
    try {
    observer.observe(targetEl, config)
    } catch (e) {
    // C)
    console.error(e)
    }
    return () => {
    if (observer) {
    observer.disconnect()
    }
    }
    }, [observer, targetEl, options])
    }
    +

    In this example, weโ€™re checking that a callback is passed as a second argument. This API check at runtime can easily alert the developer that something is wrong on the caller side.

    +

    We can also see whether the provided DOM element is invalid with an erroneous value provided to the Hook at runtime or not. These are logged together to inform us to quickly resolve the issue.

    +

    And, if observe throws an error, we can catch and report it. We must avoid breaking the JavaScript runtime flow as much as possible, so by catching the error, we can choose to either log it or report it depending on the environment.

    +

    ยปExtensibility via configuration

    +

    If we want to add more capabilities to our Hook, we should do this in a retro-compatible fashion, such as an opt-in capability that has little or no friction towards its adoption.

    +

    Letโ€™s look at how we can optionally debounce the provided callback function so callers can specify an interval of time when no other changes in the target element trigger. This runs the callback once rather than running the same amount of times the element or its children mutated:

    +
    import debounce from "lodash.debounce";
    +
    const DEFAULT_OPTIONS = {
    config: { attributes: true, childList: true, subtree: true },
    debounceTime: 0
    };
    function useMutationObservable(targetEl, cb, options = DEFAULT_OPTIONS) {
    const [observer, setObserver] = useState(null);
    useEffect(() => {
    if (!cb || typeof cb !== "function") {
    console.warn(
    `You must provide a valida callback function, instead you've provided ${cb}`
    );
    return;
    }
    const { debounceTime } = options;
    const obs = new MutationObserver(
    debounceTime > 0 ? debounce(cb, debounceTime) : cb
    );
    setObserver(obs);
    }, [cb, options, setObserver]);
    // ...
    +

    This is handy if we must run a heavy operation, such as triggering a web request, ensuring it runs the minimum number of times possible.

    +

    Our debounceTime option can now pass into our custom Hook. If a value bigger than 0 passes to MutationObservable, the callback delays accordingly.

    +

    With a simple configuration exposed in our Hook API, we allow other developers to debounce their callbacks which can result in a more performant implementation given that we might drastically reduce the number of times the callback code gets executed.

    +

    Of course, we can always debounce the callback on the client side, but this way we enrich our API and make the caller-side implementation smaller and declarative.

    +

    ยปTesting

    +

    Testing is an essential part of developing any kind of shared capability. It helps us ensure a certain level of quality for generic APIs when they are heavily contributed to and shared.

    +

    The guide to testing React Hooks has expansive detail around testing that can be implemented into this tutorial.

    +

    ยปDocumentation

    +

    Documentation can level up the quality of custom Hooks and make it developer-friendly.

    +

    But even when writing plain JavaScript, JSDoc documentation can be written for custom hook APIs to ensure the Hook passes the right message to developers.

    +

    Letโ€™s focus on the useMutationObservable function declaration and how to add formatted JSDoc documentation to it:

    +
    /**
    * This custom hooks abstracts the usage of the Mutation Observer with React components.
    * Watch for changes being made to the DOM tree and trigger a custom callback.
    * @param {Element} targetEl DOM element to be observed
    * @param {Function} cb callback that will run when there's a change in targetEl or any
    * child element (depending on the provided options)
    * @param {Object} options
    * @param {Object} options.config check \[options\](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver/observe)
    * @param {number} [options.debounceTime=0] a number that represents the amount of time in ms
    * that you which to debounce the call to the provided callback function
    */
    function useMutationObservable(targetEl, cb, options = DEFAULT_OPTIONS) {
    +

    Writing this is not only useful for documentation, but it also leverages IntelliSense capabilities that autocomplete Hook usage and provide spot information for the Hookโ€™s parameters. This saves developers a few seconds per usage, potentially adding up to hours wasted on reading through the code and trying to understand it.

    +

    ยปConclusion

    +

    With different kinds of custom Hooks we can implement, we see how they integrate extrinsic APIs into the React world. Itโ€™s easy to integrate state management within Hooks and run effects based on inputs from components using the Hook.

    +

    Remember that to build quality Hooks, itโ€™s important to:

    +
      +
    • Design easy-to-use, declarative APIs
    • +
    • Enhance the development experience by checking for proper usage and logging warnings and errors
    • +
    • Expose features through configurations, such as the debounceTime example
    • +
    • Ease Hook usage by writing JSDoc documentation
    • +
    +

    You can check the full implementation of the custom React hook here.

    Last modified on 12, Oct, 2024
    \ No newline at end of file diff --git a/posts/hands-on-reactive-programming-rxjs/index.html b/posts/hands-on-reactive-programming-rxjs/index.html new file mode 100644 index 0000000..ceef6ca --- /dev/null +++ b/posts/hands-on-reactive-programming-rxjs/index.html @@ -0,0 +1,392 @@ + Reactive Series (pt. 3) - Hands-on Reactive Programming with RxJS - ~/ + + +

    ~/

    Back

    Reactive Series (pt. 3) - Hands-on Reactive Programming with RxJS

    +

    This part of the series itโ€™s all about getting your hands dirty! Part 3 is a tutorial on core techniques to build an application with reactive streams using RxJS.

    + +

    Weโ€™re going to start with a small refresher from the previous article, implementing a click event handler with RxJS. Next, weโ€™ll have a brief overview of RxJS, followed by the main challenge of building a small animated game the reactive way.

    +

    ยปWarmup - Handle a click event with RxJS

    +

    Hereโ€™s how a usual JavaScript event handler function looks like.

    +
    <button id="btn">+1</button>
    +
    const btn = document.getElementById('btn')
    let counter = 0
    btn.addEventListener('click', (event) => {
    counter++
    console.log(counter)
    })
    +

    In the above code, weโ€™re incrementing counter that is global and logging its value each time we update it.

    +

    Usually, we donโ€™t increment globals or console.log directly in real-life apps, but we do have other side-effects in place (like updating something else in the UI, triggering a web request, firing an animation, etc.). You can always organize your code to split concerns, but it seems the event handler is already doing two very distinct things, and weโ€™re barely getting started.

    +

    Letโ€™s take a look at how this looks like in the reactive world, with RxJS. Hereโ€™s how you should visualize and implement a stream of click events.

    +

    marbles diagram of a click event stream

    +

    Note: e stands for event and itโ€™s the value pushed to the stream at each click in the button element, just like you would have an event argument in the typical onClick callback (event handler) at each click.

    +
    let counter = 0
    const click$ = fromEvent(btn, 'click')
    click$.subscribe((event) => {
    counter++
    console.log(counter)
    })
    +

    A few things to notice here:

    +
      +
    • + + fromEvent + is the tool in RxJS that will allow you to transform any DOM event into an Observable, a stream of events of a given +type (like click, drag, scroll etc.). +
    • +
    • + +subscribe is a must! Without subscribing to our stream, nothing would ever happen.
    • +
    • The $ is just a common (non-standard) notation to identify streams in your code - totally optionally, no need to follow.
    • +
    +

    Opposite to the click event handler, you can subscribe to the stream of clicks as many times as you want and split all kinds of different tasks into separate subscriptions that share the same event listener! Attaching event handlers to the DOM is costly especially due to the event attaching phase that happens at page load (onload or DOMContentReady events) a busy period for modern web applications - paraphrased from โ€œHigh Performance JavaScriptโ€, book by Nicholas C. Zakas.

    +

    In a way, things are opening up, you get a more flexible model to organize your code, compared to the classic event handler callback.

    +
    +

    Anything can be a stream, remember that, let it be the cornerstone of your reactive thinking.

    +
    +

    This should get you warmed up for the real challenge later in the article. Before that, thereโ€™s something I want to mention about the RxJS library.

    +

    ยปRxJS Overview

    +

    I would highly recommend you later to go through the RxJS official docs overview; it complements a few concepts Iโ€™ve covered in the previous article giving you a more mature view into the fundamentals. But for now, I want to focus on two things only: creating streams and manipulating them. Looking at RxJS, youโ€™ll see that you can identify two big groups of โ€œthingsโ€:

    +
      +
    • Stream creators/producers are tools that will allow you to take anything and make it a stream. In the previous section, for example, we used fromEvent to transform DOM click events into an Observable, a stream that you can subscribe to.
    • +
    • Operators are instruments that will allow you to not only hook into the stream to perform operations with the data flowing through it but also combine them.
    • +
    +

    ยปLetโ€™s build something!

    +

    Now weโ€™ve had our refresher, we can jump into building a small animated game with RxJS, something more complex.

    +

    The game weโ€™re building is called โ€œTake the cat to the partyโ€, hereโ€™s how it goes down.

    +

    &#x27;take the cat to the party game demonstration&#x27;

    +
      +
    • Thereโ€™s a cat in a motorbike in your top left corner (draggable HTML element).
    • +
    • Thereโ€™s a decoy at the bottom left corner (draggable HTML element).
    • +
    • Thereโ€™s a party at the right bottom corner of the page (our drop zone).
    • +
    • We need to drop the cat at the party in less than 5 seconds to win. Nothing should happen if the decoy is the dragged element.
    • +
    • While dragging, thereโ€™s an animation of the cat driving to the party and a counter appearing at the top right corner.
    • +
    • Per each attempt, if the cat is dragged for longer than 5 seconds you lose.
    • +
    +

    Weโ€™re going to split this into 4 levels. Per each level, Iโ€™ll explain the goal weโ€™re trying to achieve, show you how the implementation looks like, and what it does with plenty of detail. Feel free to try and code the levels yourself before checking the suggested solution. You can check out this repo if youโ€™re willing to give it a try. The file cat-party-empty.html contains the necessary boilerplate in a single HTML file. You can just open it in the browser and start coding.

    +

    Before we start, letโ€™s take a look at some boilerplate code already in place.

    +

    There are a bunch of RxJS taken from the global window at the top (Iโ€™m just injecting RxJS via <script> at the top of the HTML file). Then we have our markup appended to the elementโ€™s app innerHTML. Just some boilerplate, donโ€™t bother with the detail.

    +
    <head>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.5/rxjs.umd.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.5/rxjs.umd.js.map"></script>
    </head>
    <body>
    <div id="app"></div>
    <script>
    const {
    map,
    // ...
    } = window.rxjs.operators
    const {
    fromEvent,
    // ...
    } = window.rxjs
    // ...
    document.getElementById('app').innerHTML = `
    <h2>Take the ๐Ÿˆ to the ๐ŸŽ‰</h2>
    <div id="loader" style="display:none;position:absolute;right:0;z-index:-1;">
    <img src="https://media2.giphy.com/media/cfuL5gqFDreXxkWQ4o/giphy.webp?cid=ecf05e479338392950f1cf8bade564aaec071e3aae3b68b0&rid=giphy.webp" width="248" height="248" style="opacity:0.5;"/>
    <span id="countdown" style="top:0;right:0;position:absolute;z-index:9999;font-weight:bold;font-size:46px;">-</span>
    </div>
    <div id="drop-zone" style="position:absolute;right:5px;bottom:5px;width:180px;height:180px;border:2px dashed black;z-index:2;"></div>
    <img src="https://i.ebayimg.com/images/g/qHEAAOSw01pdVAaZ/s-l300.jpg" width="150" height="150" style="position:absolute;right:20px;bottom:20px;"/>
    </div>
    <img id="cat" draggable="true" src="https://image.shutterstock.com/image-vector/vintage-scooter-cat-cool-stuff-260nw-429646657.jpg" width="100" height="100" style="position:absolute;z-index:1;margin:18px 16px;text-align:center;cursor:grab;"/>
    <div id="decoy" draggable="true" style="font-size:28px;position:absolute;left:20px;bottom:10px;text-align:center;cursor:grab;font-weight:bold;">DECOY</div>
    `
    </script>
    </body>
    +

    The next block contains a few of the DOM elements weโ€™ll often be referring to.

    +
    const cat = document.getElementById('cat')
    const party = document.getElementById('drop-zone')
    const loading = document.getElementById('loader')
    const countdown = document.getElementById('countdown')
    +

    Finally, we introduce three streams that weโ€™ll use quite often while going through the challenge. These are the most basic building blocks of our solutions.

    +
    const dragstart$ = fromEvent(document, 'dragstart')
    const dragend$ = fromEvent(document, 'dragend')
    const drop$ = fromEvent(document, 'drop')
    +
    +
    +

    ยปLevel 1 - Combining streams of DOM events

    +
      +
    • We should be able to take the cat to the party by dragging it. +
        +
      • Place the cat inside the party together with other cats.
      • +
      • We also want to hide the original cat element while dragging it for a better user experience.
      • +
      +
    • +
    +

    Letโ€™s follow a bottom-up approach. Weโ€™ll start by building some specific streams that will allow us to assemble more complex behaviors as we move along.

    +

    Letโ€™s start by answering the following: how do we know that an HTML element has been dropped at the party? Weโ€™ll need to detect any drop event that occurs in the party element.

    +
    const partyDrop$ = drop$.pipe(filter((event) => event.target === party))
    +

    marbles diagram filter event target on drop event

    + ( +

    + e stands for event and it's the value pushed to the stream at each drop event in the whole document!{" "} + e1, e2 and e3 have different event.target (target DOM elements). +

    + )} +/> +

    Cool, we now have a stream that emits a drop event every time something is dropped in the party, we ensure that using the filter operator.

    +

    I would like to pause and add further detail on this marble diagram. Notice that we read from top to bottom, meaning the last timeline is the only one that really matters, itโ€™s the fine grained stream that gives us more useful values. In this case since we wonโ€™t subscribe to drop$ directly, think of it as an auxiliary conceptual timeline which is a step towards shaping the ideal stream to tackle our task: partyDrop$.

    +

    Another relevant detail on the marbles diagram: e1 and e2 marbles donโ€™t make it to partyDrop$ (bottom stream). For those events, +event.target itโ€™s not party, this means the user did not drop the HTML element in the party.

    +

    At this point we can already notice the exceptional compositional capabilities of streams and how easy it is to plug & play the smaller blocks (such as drop$) to build higher level streams.

    +

    Next, letโ€™s see how we can detect that the cat is dropped at the party.

    +
    const catAtParty$ = partyDrop$.pipe(
    withLatestFrom(dragstart$),
    filter(([dropEvent, dragStartEvent]) => dragStartEvent.target === cat),
    )
    +

    Alright, a few things are going on here. Weโ€™re familiar with withLatestFrom, but this time, itโ€™s a bit more complex to perceive. Weโ€™re looking into a stream of dragstart events dragstart$ and asking it for the last value emitted in that stream; this is the same as saying that we want the last draggable element in the page that emitted a dragstart event! Next, we have filter again. Weโ€™re going to use filter to narrow down our stream to allow dragstart events to pass through only if their target element is cat! And thatโ€™s it! We have a stream that will emit an event every time we drop the cat at the party.

    +

    marbles diagram filter event target on drop event

    + +

    In the above diagram, e1, e2 and e3 are dragstart events (dragstart$ stream) that might come from anywhere in the page. de represents a drop event at the party. Notice how withLatestFrom operates by bringing together the last element on dragstart$ which is e3 and with the party element which is published to the dragend$ stream. Now because e3 event target is actually the cat youโ€™ll get e3 pushed to our final stream catAtParty$!

    +

    But weโ€™re not finished just yet; we need to leverage the work weโ€™ve constructed so far to actually render the cat in the party. For that, we just need to subscribe to our catAtParty$ stream and do some DOM manipulations.

    +
    catAtParty$.subscribe(([partyDropEvent, dragStartEvent]) => {
    // some console.logs to check what we really get subscribing to catAtParty$
    // console.log({ partyDropEvent, dragStartEvent });
    // console.log({ partyDropEvent: event.type, dragStartEvent: event.type });
    cat.remove()
    party.appendChild(cat)
    })
    +

    result of subscribing to cat at party stream

    + ( +

    + The console.log reveals the drop and dragstart events as payload for our subscription. We + also log the event.type of each individual event. +

    + )} +/> +

    If youโ€™re following this point, youโ€™ll notice that although we can drag the cat perfectly, we still see the original cat element while carrying it. We see the original cat element while dragging it.

    +

    &#x27;dragging the cat without manipulating the opacity value&#x27;

    +
    +

    Setting the catโ€™s element opacity to 0 yields a more natural user experience.

    +

    &#x27;dragging the cat with opacity manipulation&#x27;

    +

    To achieve the same behavior we have in the above GIF, we want to toggle the cat elementโ€™s opacity while dragging. Yes, another side effect, we can actually simply create another subscription to take care of this.

    +
    const catDragStart$ = dragstart$.pipe(filter((event) => event.target === cat))
    const catDragEnd$ = dragend$.pipe(filter((event) => event.target === cat))
    catDragStart$.subscribe((event) => {
    cat.style.opacity = 0
    })
    catDragEnd$.subscribe((event) => {
    cat.style.opacity = 1
    })
    +

    And done with Level 1!

    +

    I want to leave a challenge for the reader. Looking at how weโ€™re setting the opacity value of the cat element, could we somehow handle everything in one single subscription instead of two? I would suggest you to read the whole article and come back to this one later, you should be then able to nail it then.

    + + Check the operator merge. + +
    + +
    const catDragStart$ = dragstart$.pipe(filter((event) => event.target === cat))
    const catDragEnd$ = dragend$.pipe(filter((event) => event.target === cat))
    const opacity$ = merge(catDragStart$.pipe(mapTo(0)), catDragEnd$.pipe(mapTo(1)))
    +
    opacity$.subscribe((value) => {
    cat.style.opacity = value
    })
    +
    +
    +

    ยปLevel 2 - Hacking race conditions with artificial delays

    +
      +
    • We should announce the catsโ€™ arrival with a window.alert message. +
        +
      • Before triggering the window.alert weโ€™ll want to make sure the cat is actually rendered at the party.
      • +
      +
    • +
    +

    So we already have a stream that emits an event each time we drag the cat to the party, itโ€™s called catAtParty$. Our work here is mostly done. We just need to subscribe to it.

    +
    catAtParty$.subscribe(() => {
    window.alert('Cat is at the party! You win!')
    window.location.reload()
    })
    +
    +
    +

    window.alert is triggered when we drop the cat at the party, but the cat is not rendered!

    +

    &#x27;demonstration asynchronous rendering cat DOM element&#x27;

    +

    Hmm.. strange, we did everything right but the window.alert triggers before we get to actually render the cat at the party, what a bummer.

    +

    It seems that the DOM changes donโ€™t kick in before we trigger the alert; thatโ€™s because the rendering is asynchronous, and the page only gets updated later on after weโ€™re done with executing this function and dismissing the alert. I donโ€™t want to spend much time fixing this since I donโ€™t have any mechanism to tell me whether the DOM node has been rendered successfully, Iโ€™m going to code a hack and trigger the alert inside a setTimeout that should buy us some time to correctly render the cat element.

    +
    catAtParty$.subscribe(() => {
    setTimeout(() => {
    window.alert('Cat is at the party! You win!')
    window.location.reload()
    }, 100)
    })
    +
    +

    Great! The window.alert is triggered after the cat element is rendered at the party!

    +

    &#x27;take the cat to the party game demonstration after applying delay on render&#x27;

    +

    It works. We can achieve a similar hack with RxJS without having a solution based on callbacks keeping our subscription handler clean (but still a hack!).

    +
    catAtParty$.pipe(delay(100)).subscribe(() => {
    window.alert('Cat is at the party! You win!')
    window.location.reload()
    })
    +

    See that delay there? Thatโ€™s another operator. It allows you to hold the value in the stream for a determined amount of time before pushing it to the subscribers. So whatโ€™s happening here is that instead of the Observer (subscription) being aware that we have to code a hack to trigger the alert after the cat enters the party, we shift that responsibility to the Observable, the stream is now the hacky code that is delaying the whole thing. This is super powerful, the way you can shift responsibilities and broadcast your changes to all the subscribers. Yes, all the subscribers will receive a delayed update! This can be simultaneously a gift and a wrecking footgun!

    +

    ยปLevel 3 - Merging streams into one stream

    +
      +
    • We want to display a GIF while the cat is moving.
    • +
    +

    In our markup, thereโ€™s a GIF positioned at the top right corner. We want to show it while dragging the cat and hide it again once we drop the cat.

    +

    We already have two streams: catDragStart$ and catDragEnd$ . We need to combine them to form a new stream. Letโ€™s try and draw a small diagram of what that stream should look like.

    +

    streams of events for dragging and dropping the cat respectively

    + ( +

    + In the image, ds symbolizes a dragstart event, and de represents a dragend event. +

    + )} +/> +

    Our GIF is wrapped in an HTML element whose display attribute is set to "none" by default. Weโ€™ll want to set it to "" (empty string) to make it visible and back to "none" once itโ€™s time to hide it again. Our side effect should look something like this:

    +
    // shouldHideGIF would be true in case we need to hide the GIF
    const v = shouldHideGIF ? 'none' : ''
    loading.style.setProperty('display', v)
    +

    Letโ€™s try to build a stream to feed to this function a value for display that reflects whether the cat is being dragged or not. Our stream should remove the necessity of the imperative logic dictated by the ternary operator, weโ€™ll create values that react to changes to replace the imperative code.

    +

    First, letโ€™s look back at our streams catDragStart$ and catDragEnd$. When catDragStart$ emits a value, we want to set the display to "". So this means we need to somehow transform our stream of events into a stream of empty strings. Each time a string is emitted, we shall pass that value onto the function that will set the display property on the loading element.

    +

    stream mapping dragstart events to an empty string

    + +

    After you apply an operator, you transform your stream into a more refined one, ideally better suited to the task youโ€™re trying to tackle! Youโ€™re making every pushed value to the topmost timeline flow through a function provided in the map operator. That function will map each value into the bottom timeline, your โ€œfinalโ€ stream. If you subscribe to it now, you will receive empty strings instead of DOM events!

    +

    The same thing should happen to catDragEnd$.

    +

    stream mapping dragend events to none

    + +

    Now we have two streams, and we could just subscribe to each one of them and set the value of the "display" property for the loading element!

    +
    const catDragStartDisplay$ = catDragStart$.pipe(map((event) => ''))
    const catDragEndDisplay$ = catDragEnd$.pipe(map((event) => 'none'))
    +
    catDragStartDisplay$.subscribe((value) => loading.style.setProperty('display', value))
    catDragEndDisplay$.subscribe((value) => loading.style.setProperty('display', value))
    +

    Although this works, we could further refine our streams and merge them into a single one.

    +

    two streams merged into a single

    + ( +

    + A single stream becomes the source of truth for the display property value of the loading{" "} + element throughout time. +

    + )} +/> +

    Awesome! The merge operator gives us the power to take to streams and merge them into one so that the values of the "display" flow through a single stream (a single source of truth). Hereโ€™s how the code looks like.

    +
    const catDragStartDisplay$ = catDragStart$.pipe(map((event) => ''))
    const catDragEndDisplay$ = catDragEnd$.pipe(map((event) => 'none'))
    const display$ = merge(catDragStartDisplay$, catDragEndDisplay$)
    +
    display$.subscribe((value) => loading.style.setProperty('display', value))
    +

    One subscription to handle the toggling of showing/hiding the GIF.

    +
    ยปBonus for Level 3
    +

    I feel overwhelmed by the tremendous amount of operators offered by RxJS, its API is considerably large. We could further simplify the above implementation with the operator mapTo.

    +
    const catDragStartDisplay$ = catDragStart$.pipe(mapTo(''))
    const catDragEndDisplay$ = catDragEnd$.pipe(mapTo('none'))
    const display$ = merge(catDragStartDisplay$, catDragEndDisplay$)
    +
    display$.subscribe((value) => loading.style.setProperty('display', value))
    +

    You get the same result as applying map but without needing to provide a function, because weโ€™re really not doing anything else rather than returning a hardcoded value on that inline function.

    +
    ยปNote about the coding style
    +

    Youโ€™ve might have noticed that Iโ€™m creating a new variable per each stream, this allows me to better explain the building blocks of each solution and map them to the diagrams. Although this helps keeping the code readable, this is not mandatory, it can be overkill per times!. Streams can be inlined. Letโ€™s look again to a small refactor of the above solution to understand what Iโ€™m referring to.

    +
    const display$ = merge(catDragStart$.pipe(mapTo('')), catDragEnd$.pipe(mapTo('none')))
    +
    display$.subscribe((value) => loading.style.setProperty('display', value))
    +

    We could drop catDragStartDisplay$ and catDragEndDisplay$ by inlining them in the merge operator.

    +

    ยปLevel 4 - Flatten observables, timers and cancellation

    +
      +
    • The cat needs to make it to the party in less than 5 seconds! He only has 5 seconds of gas in his bike.
    • +
    +

    We need to code a new path in our game, the one where the user loses the cat isnโ€™t dragged to the party within 5 seconds. This looks like a countdown, with a few actions tied to it:

    +
      +
    1. The countdown must be interrupted if the player manages to win within 5 seconds.
    2. +
    3. In case the countdown ends, and the cat was not dropped anywhere, we need to display a โ€œgame overโ€ message.
    4. +
    +

    As in the previous levels, letโ€™s build this incrementally. First, letโ€™s build a countdown. To emphasize how RxJS shines here, letโ€™s first see how we would make a countdown with native JavaScript APIs.

    +
    function countdown(seconds) {
    let secondsLeft = seconds
    let intervalId
    +
    intervalId = setInterval(() => {
    if (secondsLeft === 0) {
    console.log("time's up!")
    clearInterval(intervalId)
    } else {
    console.log(secondsLeft)
    secondsLeft--
    }
    }, 1000)
    }
    countdown(5)
    +

    Thatโ€™s a rough implementation of a countdown. We need to keep track of the timer identifier in a variable to ensure we clean up after running the setInterval provided callback the expected amount of times.

    +

    Now hereโ€™s how you can do the same with RxJS.

    +
    const countdown$ = timer(0, 1000).pipe(
    map((t) => 5 - t),
    takeWhile((t) => t > 0),
    )
    countdown$.subscribe({
    next: (secondsLeft) => console.log(secondsLeft),
    complete: () => console.log("time's up!"),
    })
    +

    Letโ€™s examine the above code ๐Ÿคฏ. This time weโ€™re not creating a stream from an event or anything like that. Instead, we use timer from RxJS to generate a timer stream that emits a new numeric value every second.

    +

    Now a timer emits values in ascending order (0, 1, 3, etc.). Weโ€™ll want to reverse that to display an actual countdown. To reverse the numerical value on the stream, we simply use map. We should only give the user 5 seconds to drag the cat to the party. This translates to stop emitting values when we hit the mark of the 0 seconds, this is controlled by the takeWhile operator. A few evident advantages compared with the classic implementation:

    +
      +
    1. No global variables secondsLeft and intervalId.
    2. +
    3. Less error-prone code by relying on takeWhile and other built-in operators to be the rail guards of our business logic.
    4. +
    5. Once the stream emits the 6th value, it will complete automatically and stop emitting values! We donโ€™t need to worry about cleanup logic - with setInterval we need to worry about cleaning the interval with clearInterval so that it does not run indefinitely!
    6. +
    +

    diagram depicting the countdown stream

    + +

    In the above image, the bottom timeline represents the stream; notice the pipe (|) at the end of the timeline indicates that the stream completes after emitting the value 1.

    +

    A crucial aspect to notice is that youโ€™ll trigger a new timer as soon as you subscribe. This is a Cold Observable. Our countdown$ will start emitting values right away upon subscription, so we canโ€™t just .subscribe() Weโ€™ll have to subscribe to the countdown when the user starts dragging the cat element.

    +

    Now remember we had a stream catDragStart$; weโ€™ll use that to trigger our countdown. But how can we trigger a subscription to a stream from within another stream? In my opinion, this is a hard concept to comprehend.

    +

    Presenting the switchMap operator. switchMap allows you to trigger a subscription from within a stream to a second stream. Not clear yet? Think of it this way switchMap allows you to switch to a new observable. Ok, letโ€™s look at our countdown implementation, now that we have this information.

    +
    const countdown$ = timer(0, 1000).pipe(
    map((t) => 5 - t),
    takeWhile((t) => t >= 0),
    )
    const countdownRunning$ = catDragStart$.pipe(switchMap((event) => countdown$))
    +
    countdownRunning$.subscribe((secondsLeft) => {
    countdown.innerText = `${secondsLeft}`
    })
    +

    Note: You might have noticed that Iโ€™m using t => t >= 0 โ€œgreater or equal toโ€ in our predicate. This is just for convenience, so that we get 0 pushed onto the stream to easily rendered it, finishing the countdown at 0.

    +

    diagram to demonstrate the switchMap operator

    +

    We're switching from one stream to another and subscribing to it immediately.

    } /> +

    Notice the return value of switchMap is countdown$, this means that when a dragstart event is emitted in catDragStart$ the following things will happen:

    +
      +
    1. Since we subscribed, the DOM event will flow through the pipe and hit our switchMap operator.
    2. +
    3. Weโ€™ll automatically subscribe to countdown$ and start emitting its values leaving the dragstart event behind (because we donโ€™t really need it for this task).
    4. +
    5. In our subscription handler, we simply do a DOM mutation to the element that displays our countdown value so that the user gets the time left displayed on the screen.
    6. +
    +

    Once more, as soon as a ds (dragstart event in the diagram) is pushed to our catDragStart$ stream, switchMap will kick in and subscribe to our countdown$ stream, โ€œpassing the ballโ€ (moving the flow of control) to the countdown$. Weโ€™ll start to push to our subscribers the seconds left in the countdown like if we had subscribed to the countdown$ in the first place, but in this case we programmatically triggered the subscription.

    +

    Although weโ€™re going more deep into flattening Observables in this level, weโ€™ve already seen this before in this article. If youโ€™re thinking about withLatestFrom from Level 1, youโ€™re right! That operator also triggers a inner subscription.

    +

    We can now leverage countdownRunning$ to display an alert.

    +
    countdownRunning$.pipe(filter((secondsLeft) => secondsLeft === 0)).subscribe(() => {
    window.alert("Cat didn't make it! You lose!")
    window.location.reload()
    })
    +

    We have a filter to check for the second 0; this way, weโ€™re basically ensuring that we just emit a value when the countdown runs out. Similarly to Level 2, we have a delay with random time to safeguard our countdown value gets rendered with 0 on the screen before we trigger the alert.

    +

    Only one thing left! If youโ€™re following up until this point, youโ€™re probably wondering why the counter is not reset once you stop dragging the cat. See the following GIF to understand what Iโ€™m referring to.

    +

    &#x27;counter does not stop demonstration&#x27;

    +

    Hmm.. Strange the counter does not reset once we drop the cat! It should, since the 5 seconds didnโ€™t ran out while we were dragging the cat. Letโ€™s look at our countdown$ implementation once more.

    +
    const countdown$ = timer(0, 1000).pipe(
    map((t) => 5 - t),
    takeWhile((t) => t >= 0)
    );
    const countdownRunning$ = catDragStart$.pipe(switchMap((event) => countdown$);
    +

    Now, itโ€™s pretty clear that weโ€™re not doing anything to โ€œstopโ€ the counter. Once we subscribe to countdown$ with the switchMap operator, thereโ€™s no stopping it, it will go on until it reaches the end, and when this happens, weโ€™ll display the alert to the user. Ideally, we would stop the counter upon any of the two following events:

    +
      +
    1. When the cat arrives at the party. Also partially done, we have a stream catAtParty$, once a value is pushed into this stream, you know the cat was dropped at the party.
    2. +
    3. We stop dragging the cat. We already have a stream catDragEnd$, each emitted value means we just dropped the cat element. You might be thinking the first point would be enough, but remember the cat can be dropped literally anywhere on the screen. We have no guarantees that it will get dropped at the party.
    4. +
    +

    Meet takeUntil, this operator allows you to listen to a stream until something happens! Letโ€™s look at the implementation of the countdown$.

    +
    const countdown$ = timer(0, 1000).pipe(
    map((t) => 5 - t),
    takeWhile((t) => t >= 0),
    takeUntil(/*something we'll figure out later*/),
    )
    +

    Now, what is the argument of the takeUntil operator? takeUntil receives a stream, once that stream emits a value, our countdown$ will stop emitting, thus canceling the timer and giving our users another chance to dragging the cat to the party.

    +

    But we have two streams, catDragEnd$ and catAtParty$, can we make it one? Yes, we can! Weโ€™ve already met merge! This operator can take multiple streams and combine into a single one, think of it as a funnel.

    +

    Letโ€™s call this last stream cancelCountdown$. After we declare it, we can feed it as an input to our takeUntil operator. Hereโ€™s the final implementation of Level 4 with cancellation.

    +
    const cancelCountdown$ = merge(catDragEnd$, catAtParty$);
    const countdown$ = timer(0, 1000).pipe(
    map((t) => 5 - t),
    takeWhile((t) => t >= 0),
    takeUntil(cancelCountdown$)
    );
    const countdownRunning$ = catDragStart$.pipe(switchMap((event) => countdown$);
    +
    countdownRunning$.subscribe((secondsLeft) => {
    countdown.innerText = `${secondsLeft}`;
    });
    countdownRunning$
    .pipe(
    filter((secondsLeft) => secondsLeft === 0),
    delay(100)
    )
    .subscribe(() => {
    window.alert("Cat didn't make it! You lose!");
    window.location.reload();
    });
    +

    stream representation of the countdown with cancellation

    + ( +

    + In this diagram, our stream gets cancelled after the value 3 is published. The first dragend event + causes the countdownRunning$ to stop emitting values. +

    + )} +/> +

    You can check out the complete implementation of this challenge here.

    +
    +

    ยปClosing Notes

    +

    Here are the concepts youโ€™ve learned by building this game:

    +
      +
    1. Creating streams from DOM events.
    2. +
    3. Manipulating streams with basic operators such as map, filter.
    4. +
    5. Combining streams with operators such as: withLatestFrom, switchMap, and merge.
    6. +
    7. Cancellation, being able to stop listening to a specific stream based on another. In our last level, we used takeUntil to achieve this.
    8. +
    +

    Now go out there and try to apply these concepts to build your own applications. I would be pleased to hear from you if youโ€™ve learned something here today.

    +

    In the upcoming articles, Iโ€™ll share with you the good and bad parts of adopting reactive programming and start using RxJS in your codebase, and at last, Iโ€™ll share with you a bunch of fantastic resources to learn RxjS and reactive programming.

    +
    +
    Last modified on 12, Oct, 2024
    \ No newline at end of file diff --git a/posts/hidden-potential-webpack-define-plugin/index.html b/posts/hidden-potential-webpack-define-plugin/index.html new file mode 100644 index 0000000..27304f2 --- /dev/null +++ b/posts/hidden-potential-webpack-define-plugin/index.html @@ -0,0 +1,133 @@ + The hidden potential of webpack DefinePlugin - ~/ + + +

    ~/

    Back

    The hidden potential of webpack DefinePlugin

    Posted on 30, May, 2019

    The odds are high that youโ€™re using webpack to bundle your assets. If youโ€™re looking into an excellent way to feed environment variables to your applications, DefinePlugin might well be the answer.

    +

    ยปtl;dr

    +

    If you want to pass environment variables and use it within your JavaScript, you need to:

    +
      +
    1. Create the environment variable.
    2. +
    +
    Terminal window
    MY_ENV_VAR="sweet env var"
    +
      +
    1. In your webpack config, use the webpack.DefinePlugin.
    2. +
    +
    new webpack.DefinePlugin({
    // it won't work without JSON.stringify!!!
    myEnvVar: JSON.stringify(process.env.MY_ENV_VAR),
    })
    +
      +
    1. In your JavaScript.
    2. +
    +
    console.log(`Accessing my env var like a ninja: ${myEnvVar}`)
    +
    +
    +

    ยปSome Background

    +

    One of these days, I had this exciting challenge, I was integrating some third party that required from me to provide the current commit hash of the code to a JavaScript SDK that performs some magic in bug tracking and versioning.

    +

    Well seemed pretty simple for me at the time. I need to take the commit hash at build time and somehow feed it to our JavaScript, whatever that means.

    +

    In this blog post, Iโ€™ll explain step by step how to implement the following:

    +
    +

    We want to log in the console the link to the GitHub history of the application +that points to the commit of the current version of the application.

    +
    +

    ยปThe step by step

    +

    Hereโ€™s a step by step implementation of the previously announced challenge. I will use one of my open source pet projects on GitHub, el-conversor.

    +
    ยปStep 1 - Grab the commit hash
    +

    Iโ€™m going to do this within a npm script in the package.json. In the dev npm script you can find the following:

    +
    "dev": "COMMIT_HASH=\"$(git rev-parse HEAD)\" npm-run-all --parallel *:dev",
    +

    So in COMMIT_HASH=\"$(git rev-parse HEAD)\" we are basically assigning the output of the command git rev-parse HEAD to an env variable COMMIT_HASH.

    +
    ยปStep 2 - Configure webpack.DefinePlugin
    +

    Now, letโ€™s dive into the webpack.config.js. Well itโ€™s not that complicated, the only nasty detail is that even if you want to pass in a simple string, you need to wrap it with a JSON.stringify otherwise, webpack dumps whatever text comes out of your environment variable and dump it in your JavaScript, and you get a very nasty syntax error. So letโ€™s use the DefinePlugin:

    +
    new webpack.DefinePlugin({
    __commitHash__: JSON.stringify(process.env.COMMIT_HASH),
    })
    +

    Pro Tip: You might be wondering why the strange naming __commitHash__, well the prefix and suffix underscores are just a way to prevent naming collisions. I hope that you donโ€™t often use __var__ as a style to name variables in JavaScript, well I donโ€™t, thatโ€™s why I found this extremely unlikely to collide.

    +
    ยปStep 3 - Use the environment variables
    +

    In the end, we use the environment variable. In this case to console.log a helpful GitHub link:

    +
    console.log(
    `You are running on this commit of the application https://github.com/danielcaldas/el-conversor/commit/${__commitHash__}`,
    )
    +

    You can see the final result in the image below, it logs:

    +

    final result in console of el-conversor

    +
    Last modified on 12, Oct, 2024
    \ No newline at end of file diff --git a/tags/softwaretesting/index.html b/posts/how-to-copy-file-android/index.html similarity index 56% rename from tags/softwaretesting/index.html rename to posts/how-to-copy-file-android/index.html index 59839d4..60cb329 100644 --- a/tags/softwaretesting/index.html +++ b/posts/how-to-copy-file-android/index.html @@ -1,4 +1,4 @@ - posts - Hey ๐Ÿ‘‹ + How to overengineer copying files from any Android device - ~/ @@ -32,7 +32,77 @@ } setup() -
    2021
    2020
    \ No newline at end of file +
    Back

    How to overengineer copying files from any Android device

    Posted on 01, Jul, 2021

    tl;dr SSH into your android and download the files with scp. This one works for me.

    +

    So this afternoon, I had decided to take some time to clear my smartphone, archive some old pictures and stuff. I usually park them on an external drive. Anyways, it happens that my OnePlus 6 does not connect to my new shiny MacBook Pro.

    +
    +(โ•ฏยฐโ–กยฐ)โ•ฏ๏ธต โ”ปโ”โ”ป +
    +
    +
    +

    ยปUSB/USB-C Cables

    +

    I tried, but Apple doesnโ€™t make it easy to recognize other devices.

    +

    ยปSSH to the rescue!

    +

    I start browsing the Google Play store for some apps that allow me to run an SSH server on my smartphone. Quickly Iโ€™ve stumbled into this.

    +

    Straightforward setup, as you can see in the screenshot below.

    +

    ssh server app running on android device

    +

    Since my laptop and my smartphone are on the same network, they can communicate.

    +
    Terminal window
    ssh admin@<my_phone_ip> -p <ssh_server_port_from_app>
    # enter password...
    +
    # then just copy stuff to my MacBook
    scp -r Images <my_macbook_user>@<my_macbook_ip>:~/Desktop
    +

    Hope this tip helps you out in case you get stuck due to the usual apple mumbo jumbo.

    Last modified on 12, Oct, 2024
    \ No newline at end of file diff --git a/posts/how-to-fix-github-password-authentication/index.html b/posts/how-to-fix-github-password-authentication/index.html index d6237da..1138b93 100644 --- a/posts/how-to-fix-github-password-authentication/index.html +++ b/posts/how-to-fix-github-password-authentication/index.html @@ -1,4 +1,4 @@ - How to Fix GitHub Actions: Support for password authentication was removed - Hey ๐Ÿ‘‹ + How to Fix GitHub Actions: Support for password authentication was removed - ~/ @@ -118,4 +118,4 @@

    Last modified on 12, Oct, 2024 \ No newline at end of file +

    Hope this workaround helps you as it helped me!

    Last modified on 12, Oct, 2024
    \ No newline at end of file diff --git a/posts/how-to-svelte-rxjs/index.html b/posts/how-to-svelte-rxjs/index.html index 7b87b56..5a681a1 100644 --- a/posts/how-to-svelte-rxjs/index.html +++ b/posts/how-to-svelte-rxjs/index.html @@ -1,4 +1,4 @@ - How to use RxJS with Svelte - Hey ๐Ÿ‘‹ + How to use RxJS with Svelte - ~/ @@ -99,4 +99,4 @@

    โ€œAs usual, React and Vue lead the pack, but Svelte is quickly establishing itself as a very serious contender for the front-end crown.โ€œ, State of JS 2020: Front-end Frameworks

    -

    RxJS is known to be the go to library when it comes to programming with streams. Reactive programming is famous for making complex things easier by offering an extensive API of operators that give developers extreme power and flexibility when writing complex event-driven user interfaces.

    Last modified on 12, Oct, 2024
    \ No newline at end of file +

    RxJS is known to be the go to library when it comes to programming with streams. Reactive programming is famous for making complex things easier by offering an extensive API of operators that give developers extreme power and flexibility when writing complex event-driven user interfaces.

    Last modified on 12, Oct, 2024
    \ No newline at end of file diff --git a/posts/my-two-cents-on-tech-job-interviews/index.html b/posts/my-two-cents-on-tech-job-interviews/index.html index 9bb1762..5fff216 100644 --- a/posts/my-two-cents-on-tech-job-interviews/index.html +++ b/posts/my-two-cents-on-tech-job-interviews/index.html @@ -1,4 +1,4 @@ - My 2 cents on (tech) job interviews - Hey ๐Ÿ‘‹ + My 2 cents on (tech) job interviews - ~/ @@ -227,4 +227,4 @@

    ยปFinal note

    -

    Again much of the written above is just my opinion. I hope you find useful some of the sections of this post even if you donโ€™t work on the tech industry.

    Last modified on 12, Oct, 2024
    \ No newline at end of file +

    Again much of the written above is just my opinion. I hope you find useful some of the sections of this post even if you donโ€™t work on the tech industry.

    Last modified on 12, Oct, 2024
    \ No newline at end of file diff --git a/posts/open-source-dilemma/index.html b/posts/open-source-dilemma/index.html index ec05f5b..771f9fb 100644 --- a/posts/open-source-dilemma/index.html +++ b/posts/open-source-dilemma/index.html @@ -1,4 +1,4 @@ - There's something off about Open Source - Hey ๐Ÿ‘‹ + There's something off about Open Source - ~/ @@ -90,7 +90,7 @@ }

    There's something off about Open Source

    #open-source -#opinion
    Posted on 02, Mar, 2024
    +#opinion Posted on 16, Dec, 2020

    Update Oct 12th 2024

    I didnโ€™t modify this article. I donโ€™t fully agree with my views here anymore. The problem is not on FOSS, but rather on peoplesโ€™ expectations and approach to FOSS. Iโ€™m still of the opinion that FOSS is not at a fully sustainable stage. Major projects today are typically funded and itโ€™s no longer rare to see full-time employed developers work on maintaining FOSS these days.

    @@ -121,4 +121,4 @@

    Last modified on 12, Oct, 2024 \ No newline at end of file +

    This is quite a dilemma for me. Iโ€™m interested to see how all this is going pan out. My bet is on the money. Money wins, most of the time.

    Last modified on 12, Oct, 2024
    \ No newline at end of file diff --git a/posts/please-help-deleted-my-git-stash/index.html b/posts/please-help-deleted-my-git-stash/index.html new file mode 100644 index 0000000..fca86c7 --- /dev/null +++ b/posts/please-help-deleted-my-git-stash/index.html @@ -0,0 +1,112 @@ + Deleted my git stash! Please send help! - ~/ + + +

    ~/

    Back

    Deleted my git stash! Please send help!

    Posted on 09, Dec, 2018

    I have this poor habit you knowโ€ฆ Whenever I accumulate a lot of git stashes in some repository I just get a little maniac and start cleaning everything.

    +

    please send help

    + +

    I found it sometimes even funny to open the terminal and play around with some bash to delete the stashes, look:

    +
    Terminal window
    # if you're just flashing by don't copy and past this line into your terminal
    for i in {1..10}; do git stash drop; done
    +

    Well, it happens that today (a few hours ago actually) I just did this and regretted the moment I did because there was this huge feature that for some reason I had locally stashed and not committed
    ยฏ\_(ใƒ„)_/ยฏ.

    +

    Good news!, the commits are not actually gone until git runs garbage collection, so to check the list of commits that might still be rescued just go ahead and type:

    +
    Terminal window
    git fsck --unreachable
    +

    or maybe

    +
    Terminal window
    git fsck --unreachable | grep commit
    +

    because you actually need to check whether your hash is somewhere referenced as a unreachable commit.

    +

    Then all you need to do once you find your stash (if you donโ€™t know the hash Iโ€™m afraid you will have to apply stashes until you find it) is just:

    +
    Terminal window
    git stash apply <hash>
    +

    Here, some cool references on git stash:

    + +

    Notice that the references above are from Atlassian, which I highly recommend when it comes to quick search into a specific Git topic.

    +

    And thatโ€™s it, hope this saves you just like it saved me.

    +

    Have a nice day.

    Last modified on 12, Oct, 2024
    \ No newline at end of file diff --git a/categories/tech/index.html b/posts/presenting-babel-plugin-cloudinary/index.html similarity index 64% rename from categories/tech/index.html rename to posts/presenting-babel-plugin-cloudinary/index.html index 38d5daa..ad7f56b 100644 --- a/categories/tech/index.html +++ b/posts/presenting-babel-plugin-cloudinary/index.html @@ -1,4 +1,4 @@ - posts - Hey ๐Ÿ‘‹ + Presenting babel-plugin-cloudinary - ~/ @@ -32,6 +32,65 @@ } setup() -
    2024
    \ No newline at end of file +
    Back

    Presenting babel-plugin-cloudinary

    Posted on 04, Apr, 2019

    During the past two months, Iโ€™ve been developing a side project at work, this started with me doing a simple refactor task. At the time I looked at the solution we had in place, and it was dirty, but it was necessary to skip some considerable performance penalty. Maybe it was something that we just had to learn to live withโ€ฆ

    +

    wrong kiddo meme

    +

    source: https://imgur.com/gallery/J3WYR

    +

    Hell no! At the time the alternatives werenโ€™t that promising, but soon I realized the untapped potential of doing work at build time with babeljs. Iโ€™m very proud to present babel-plugin-cloudinary. In this trivago tech blog article, you can find all the details and motivation behind the plugin. +And by the way, if you want to know what the heck am I talking about, just take a look at the official repository of babel-plugin-cloudinary.

    Last modified on 12, Oct, 2024
    \ No newline at end of file diff --git a/posts/reactive-programming-fundamentals/index.html b/posts/reactive-programming-fundamentals/index.html new file mode 100644 index 0000000..7979842 --- /dev/null +++ b/posts/reactive-programming-fundamentals/index.html @@ -0,0 +1,277 @@ + Reactive Series (pt. 2) - Fundamentals of Reactive Programming - ~/ + + +

    ~/

    Back

    Reactive Series (pt. 2) - Fundamentals of Reactive Programming

    +
    + +

    Letโ€™s continue our journey on Reactive Programming. This part will highlight some of this paradigmโ€™s key concepts for quick learning.

    +

    ยปThe Observer Pattern

    +

    You might have encountered several diagrams and explanations of the Observer pattern. For those of you that might not yet be familiar +with it, allow me to explain it with an example-based approach, a very different one than what I had at school.

    +
    +

    โ€Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.โ€ by sourcemaking.com

    +
    +

    I didnโ€™t get it for the first time with this definition and set me away instead of fascinating me. I thought this would be +a super tricky thing to get. But not if youโ€™ve had explained it to me with something we all know like youtube.

    +
    ยปObserver Pattern: Real-Life Analogy
    +

    Letโ€™s look at youtube.com for a second. Iโ€™ll use it as an example to explain this patternโ€™s two core participants: Observer and Observable.

    +

    Your browsing on youtube and you find a new channel. You want to receive notifications anytime some new +content is published into that youtube channel, but for that, youโ€™ll need to hit the subscribe button first. You are the Observer, consuming the content posted (published) by that youtube channel. This makes the youtube channel the Observable. Another vital aspect to see is the multiplicity of this relationship. Thereโ€™s one youtube channel to many youtube users (subscribers).

    +
    ยปNaming the things we already know
    +

    Keeping this analogy in mind, letโ€™s clarify a couple more concepts that complement the relationship between an Observable and its Observables.

    +
      +
    • Producer or creator - something that generates/publishes content. Back to our analogy, the content creator is the youtube +channel owner. An Observable produces data.
    • +
    • Consumer or client - something that subscribes to new content (data) published by the producers; our user is the consumer in our analogy. He or she gets notified when new content is out.
    • +
    • Subscription - think of it as an object that holds the information about you subscribing to that youtube channel. One crucial thing that the subscription must own is a way for you to unsubscribe from that youtube channel when you no longer want to be notified of new content.
    • +
    • Pull - we talk about a pull protocol when a consumer/client explicitly decides when to get data from the producer/creator. For instance, when we create a simple function and call it to do some work for us.
    • +
    • Push - in these systems, the consumer/client doesnโ€™t know exactly when new content is published. Our youtube analogy describes such a system where the channel subscribers have no clue when a new video will be posted.
    • +
    +

    These concepts are more comfortable to follow when backing them up with some real-life example, but most importantly, it is good that you possess a grasp of the jargon. Itโ€™s crucial to clearly express yourself with your peers. Last but not least, this becomes super relevant when youโ€™re trying to Google your away around some issue.

    +
    ยปA naive use case for the Observer pattern in JavaScript
    +

    Now letโ€™s get our hands on and implement the Observer pattern for our youtube case study. The idea is to use the Observer pattern to notify many youtube users when a new video is published into the channel. Weโ€™ll keep the example slim and straightforward. The main idea is to have an end-to-end perception of how the data flows in our application when using Observables. We wonโ€™t have a UI here. We will just use console.log to signal some events in our small program.

    +
    class Video {
    constructor(title) {
    this.title = title
    }
    }
    +
    class Channel {
    constructor(name, subscribers = []) {
    this.name = name
    this.subscribers = subscribers
    this.videos = []
    }
    +
    // subscribes a user to an instance of channel
    // returns the unsubscription function
    subscribe(user) {
    this.subscribers.push(user)
    +
    const userIndex = this.subscribers.length - 1
    +
    // remember we need to allow our users
    // to unsubscribe anytime
    const unsubscribe = () => {
    this.subscribers.splice(userIndex, 1)
    }
    +
    return unsubscribe
    }
    +
    // publishes a new video in this channel notifying
    // all its subscribers
    publish(video) {
    this.videos.push(video)
    this.subscribers.forEach((user) => {
    user.notify(`
    Hey there ${user.email}!
    There's a new video from ${this.name}
    ${video.title}
    `)
    })
    }
    }
    +
    class User {
    constructor(email) {
    this.email = email
    }
    +
    // just logs a notification update
    notify(update) {
    console.log(update)
    }
    }
    +
    function main() {
    // our platform has 3 users: elisa, hans and mark
    const elisa = new User('elisa@gmail.com')
    const hans = new User('hans@gmail.com')
    const mark = new User('mark@gmail.com')
    +
    // our platform has 1 channel "#FunnyVide0s"
    const funnyVideosChannel = new Channel('#FunnyVide0s')
    +
    // Our scenario
    +
    // elisa, hans and mark hit the subscribe button on "#FunnyVide0s"
    const subscriptions = [elisa, hans, mark].map((user) => {
    return funnyVideosChannel.subscribe(user)
    })
    +
    // oh! it seems a new video is about to come out on "#FunnyVide0s"
    const newVideo = new Video('funny cats (part 1)')
    funnyVideosChannel.publish(newVideo)
    +
    // no! is seems like elisa didn't like the video "funny cats (part 1)"
    // she unsubscribes from the channel
    const unsubscribeElisa = subscriptions[0]
    +
    unsubscribeElisa()
    +
    // but "#FunnyVide0s" it's not going to stop!
    // we have a new cats video, of course this time
    // only Hans and Mark are notified
    const yetAnotherNewVideo = new Video('funny cats (part 2)')
    funnyVideosChannel.publish(yetAnotherNewVideo)
    }
    +
    main()
    // The output is:
    //
    // Hey there elisa@gmail.com!
    // There's a new video from #FunnyVide0s
    // funny cats (part 1)
    //
    // Hey there hans@gmail.com!
    // There's a new video from #FunnyVide0s
    // funny cats (part 1)
    //
    // Hey there mark@gmail.com!
    // There's a new video from #FunnyVide0s
    // funny cats (part 1)
    //
    // Hey there hans@gmail.com!
    // There's a new video from #FunnyVide0s
    // funny cats (part 2)
    //
    // Hey there mark@gmail.com!
    // There's a new video from #FunnyVide0s
    // funny cats (part 2)
    +

    Letโ€™s map the entities of the above example:

    +
      +
    • The User is our Observer.
    • +
    • The Channel is our Observable.
    • +
    • The Video is just really the contract for the payload we sent out to subscribers, it can really be anything.
    • +
    +

    I hope this example clarifies that you can leverage the Observer pattern with no additional libraries in any programming language. I found this knowledge of paramount importance to understand more complex use cases in reactive programming, specially when using libraries, we sit as a client, we go one layer of abstraction above what youโ€™ve seen in the previous example. With reactive programming libraries, you can juggle with Observables in fantastic ways effortlessly.

    +

    As a side note, if youโ€™re keen on learning more about the Observer pattern (or any other design pattern), I highly recommend the following resources:

    + +
    ยปObservable as a first-class citizen
    +
    +

    โ€Observables never had a chance to shine on their own. The real win, IMO, to RxJS is the Observable type itself. Not the operators.โ€ from โ€œObservables, Reactive Programming and Regretโ€

    +
    +

    Observables are everywhere these days. Libraries such as RxJS has an internal implementation of an Observable type, an refers to it as a first-class citizen.

    +

    I would like to highlight the tc39 Observable proposal. Observables are a very natural fit to handle many programming challenges. I think this has become more noticeable given that people are even considering bringing it into the language (in this case JavaScript)! I believe we are blurring the line between language specification and library, but Iโ€™m happy to see the proliferation of Observablesโ€™ discussions.

    +

    ยปReactive Programming

    +

    Reactive Programming is programming with streams of data. Streams are vessels of values pushed over time. Streams can be transformed into and combined with other streams.

    +

    To make the above clearer, letโ€™s go over a few essential concepts, those youโ€™ll hear all the time.

    +
    ยปCore Properties of Observables
    +

    There are 3 very important aspects about Observables in reactive programming. Maybe some of them donโ€™t make sense now, but they will, once you have some hands-on experience with some reactive library.

    +

    Observables areโ€ฆ

    +
      +
    • a primitive type with 0 to many values, pushed over any amount of time (yes, mentioned quite a few times already).
    • +
    • cancellable, you can cancel a subscription by sending stuff to these observables: โ€œI donโ€™tโ€™ want you to send me those values anymore.โ€
    • +
    • lazy. Observables donโ€™t do anything until you subscribe. This is the complete opposite of Promises. Promises are eager, thatโ€™s why we often wrap them in a function, making them lazy, so they only execute when we call them.
    • +
    +
    ยปStream
    +

    An overused term these days, but Iโ€™ll try to make it simple. +First of all, a stream is an Observable; youโ€™ll find that these two concepts are often interchangeable. Simply put, a stream is a collection of values pushed over time. Letโ€™s think of โ€œlive streamingโ€ in the video industry for a moment; on one end, thereโ€™s your laptop observing the stream; on the other end, something is pushing several bits of data over the wire. Simple right? But how to think about streams in the conceptual world? Thatโ€™s where diagrams (more precisely marble diagrams) come in handy. At the most basic level, a stream is simply represented by a horizontal line, representative of time, with geometric figures. Each figure represents an emitted value on the stream. Whatโ€™s an emitted value? If a stream emits a value, it means that everyone looking into the stream will get notified of that value. Hopefully, the following figure makes it crystal clear.

    +
    +

    The following represents a stream that emits 4 values (1 value emitted per second).

    +

    &#x27;simple stream animation of four values pushed over time&#x27;

    +

    source: https://rxviz.com/

    +
    +

    From now on, whenever you hear โ€œstreamโ€, try to visualize the above in your head.

    +
    ยปOperators
    +

    Streams alone are useful as they allow multiple Observers to subscribe to it for updates. Things start to get more enjoyable when you want to manipulate a stream. As mentioned, streams can be transformed and even combined. The power itโ€™s all yours. Letโ€™s look at two essential operations, mapping, and filtering. Take a look at the following animation.

    +
    +
    +

    &#x27;visualization of map and filter when applied to streams&#x27;

    +

    source: https://reactive.how/filter

    +
    +

    As you can see on the left, map applied over the left-most stream generates a new stream of values where each value in the original stream goes through the function isEven provided to the mapping operation. Subscribing to this stream, you would get a boolean (true vs. false) or number (0 vs. 1) that tells you whether a given value is even or odd. Now letโ€™s look at filter. With filter, instead of transforming each value, youโ€™re actually creating a new stream that only emits the original value when the same itโ€™s even. So from an Observer perspective, you would be notified half of the times, while with map your Observer will be notified for every single value.

    +
    ยปHot Observable VS Cold Observable
    +

    This is a concept that Iโ€™ve experienced before understanding it, and it can be daunting.

    +

    A Hot Observable emits values before the subscription happens. Think of it as a live music concert in a stadium. You will only get to see the full performance if you enter the stadium (subscribe) before the show starts (stream emits values). Of course, you can arrive at the venue to see the concert at any future time. Everyone at the stadium shares the same experience (meaning the values are shared among subscribers). +In a real use case, a Hot Observable can be a stream of click events on the page (there might be clicks happening before you subscribe to it) in a user interface.

    +

    A Cold Observable only starts emitting values upon subscription. Think of it as a movie on Netflix. The film only starts playing once you press play on your laptop. You wonโ€™t miss pieces of the movie since you can start playing from the very start. If someone else watches the same movie on Netflix, they wonโ€™t share your timeline; each user has its own individual timeline (values are not shared among subscribers). +In a real use case, a cold Observable can be a countdown clock where each subscriber has its own clock.

    +
    ยปBrief History of Reactive Programming
    +

    According to my research in the paper Functional Reactive Animation (1997), we see some first mentions to things such as: reactivity. Some kind of temporal representation animations resembles the concept of a stream.

    +

    Haskell mentions in its WiKi page :

    +
    +

    โ€œFunctional Reactive Programming (FRP) integrates time flow and compositional events into functional programming.โ€

    +
    +

    The phrasing here is bit complicated. Thereโ€™s this concept of a primitive type called Behavior, which is defined as a time-varying value. This resembles a stream, right? A stream is somehow a time-varying value since you can get different values pushed to the stream over time, not that the reference to the stream changes, but its contents instead.

    +

    Today reactive is not only used to manage animated user interfaces. Itโ€™s on every platform, from the web to mobile, from UIs to servers. Hereโ€™s a couple of the most famous reactive programming libraries today:

    + +
    ยปQuotes
    +

    I hope this section offers some other views (and their sources) on reactive programming, expressed in a different way that might complement or even wholly build your understanding of things so far.

    +
    +

    โ€Reactive Programming raises the level of abstraction of your code so you can focus on the interdependence of events that define the business logic, rather than having to constantly fiddle with a large amount of implementation details"

    +
    + + + "The introduction to Reactive Programming you've been missing" + + +
    +
    +

    "When you fully embrace RP, the core of your app ends up being a declaration of a graph through which your data flows."

    +
    + + + "Streams for reactive programming" + + +
    +
    +

    "Never store a mutable state on your types. Instead, when you generate a new value in response to a change, send it in to a channel.โ€

    +
    + + + "What is reactive programming and why should I use it?" + + +
    +

    Itโ€™s good that you discovered these concepts before you jump into practice. In my opinion, these canโ€™t be absorbed only by reading. Go over to the next blog post, where weโ€™ll build a small game together so that you can materialize the things Iโ€™ve covered in this blog post.

    +
    +
    Last modified on 12, Oct, 2024
    \ No newline at end of file diff --git a/posts/reactive-rxjs-pros-cons/index.html b/posts/reactive-rxjs-pros-cons/index.html new file mode 100644 index 0000000..c87fdbb --- /dev/null +++ b/posts/reactive-rxjs-pros-cons/index.html @@ -0,0 +1,221 @@ + Reactive Series (pt. 4) - Reactive Programming: The Good and the Bad - ~/ + + +

    ~/

    Back

    Reactive Series (pt. 4) - Reactive Programming: The Good and the Bad

    +
    +

    After seeing reactive streams in action with RxJS, I would now like to expand โ€œPart 1 - Why you should consider Reactive Programmingโ€ by debating some of the gains & pains of reactive streams and RxJS. +As of now, weโ€™ve covered some fundamental aspects of reactive programming, and weโ€™ve seen some code as well! +In this part of the series, Iโ€™ll be compacting some of the benefits I felt while using reactive programming and some of the major pain points of adopting it. Since weโ€™ve used RxJS, Iโ€™ll make occasional mentions of this reactive libraryโ€™s ups and downs.

    + +

    ยปThe good

    +

    Letโ€™s explore some beneficial aspects of adopting reactive programming and RxJS now that we have looked at how to approach a problem and model it with streams.

    +
    ยปYour code becomes inheritably lazy
    +

    Working with streams means nothing happens until you subscribe to them, which is terrific! Because any code path now has a โ€œfree of chargeโ€ stop & play capability! Without any effort, your computations become lazy by default executing when needed - Promises are eager. They start running as soon as you construct them. Itโ€™s common to wrap a Promise with a function to make it lazy.

    +
    ยปYour code is likely to be more concise
    +

    As weโ€™ve seen in the hands-on part of this series, youโ€™ll likely have to model events and think about what parties are interested in consuming such events. Youโ€™ll spend less time focusing on the implementation details. Youโ€™ll rather spend your time drawing a mental map of the eventsโ€™ interdependency that define your business logic. In my experience, this often results in more compact implementations, making it more accessible to capture the big picture of the internal flows in your application.

    +
    ยปYouโ€™re likely to write less code
    +

    It only makes sense that if you have an ecosystem, such as RxJS, in your arsenal, youโ€™ll spend less time implementing behaviors that are already built-in in some operator (e.g., debouncing, throttling, etc.). Just like youโ€™ll spend less time mutating the DOM directly when you use a UI framework such as React or Vue, youโ€™ll think more about how your UI looks like and what data binds to what parts of your UI.

    +
    ยปEffortless cancellation
    +

    In my opinion, an overlooked easy win of coding with streams and RxJS is how easy it becomes to implement cancellation. What kind of behavior does cancellation concern, you ask? Letโ€™s see an example.

    +

    &#x27;cancellation of http request on hover items in a list&#x27;

    +

    In the above GIF, we have a list of items. Hovering on each item in the list triggers an ajax request to fetch some data from the server. The problem of eagerly (compared with clicking the item, for instance) triggering the requests upon mouse hovering is that you can potentially fetch the data for all the items but ending up not displaying any of the data to the end-user. To avoid that, we cancel an itemโ€™s request when the userโ€™s mouse leaves it.

    +

    (Note: oh, btw on the above GIF, Iโ€™m not calling a real API, Iโ€™m just using the tweak browser extension to mock the HTTP requests seamlessly)

    +
    +

    Hereโ€™s the snippet of the above pattern, implemented with switchMap and takeUntil.

    +
    const listEl = document.getElementById('list')
    const resEl = document.getElementById('res')
    const fetchById = (id) => ajax(`https://some-service.com/api/items/${id}`).pipe(map((r) => r.response))
    const mouseOutItem$ = fromEvent(listEl, 'mouseout')
    const mouseOverItem$ = fromEvent(listEl, 'mouseover')
    .pipe(
    switchMap((event) => {
    const id = event.target.id
    resEl.innerHTML = `loading item ${id}...`
    console.log(`Fetching data for item ${id}...`)
    return fetchById(id)
    }),
    map((response) => {
    resEl.innerHTML = JSON.stringify(response, null, 2)
    console.log(`Done fetching data for item ${response.item.id}!`)
    }),
    takeUntil(
    mouseOutItem$.pipe(
    tap(() => {
    console.log(`โŒ Cancelled fetch data for item ${event.target.id}!`)
    resEl.innerHTML = '...'
    }),
    ),
    ),
    repeat(),
    )
    .subscribe()
    +
    + +
    <!doctype html>
    +
    <head>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.5/rxjs.umd.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.5/rxjs.umd.js.map"></script>
    <style>
    li {
    padding: 4px;
    border: 1px dashed green;
    color: black;
    }
    +
    li:hover {
    background-color: lightblue;
    color: blueviolet;
    cursor: pointer;
    font-weight: bold;
    }
    </style>
    </head>
    +
    <body>
    <div id="app"></div>
    <script>
    const { delay, filter, map, mapTo, switchMap, takeUntil, takeWhile, tap, withLatestFrom, repeat } =
    window.rxjs.operators
    const { fromEvent, merge, timer } = window.rxjs
    const ajax = window.rxjs.ajax.ajax
    +
    // preventing the default event on dragover
    // so that we're allowed to drop an element
    fromEvent(document, 'dragover')
    .pipe(tap((event) => event.preventDefault()))
    .subscribe()
    +
    // all the game markup goes here
    document.getElementById('app').innerHTML = `
    <h2>Hover on the items to fetch some async data</h2>
    <ul id="list">
    <li id="item-1">item 1 (mouse over to fetch data)</li>
    <li id="item-2">item 2 (mouse over to fetch data)</li>
    <li id="item-3">item 3 (mouse over to fetch data)</li>
    <li id="item-4">item 4 (mouse over to fetch data)</li>
    </ul>
    <h3>Item detail</h3>
    <pre id="res">...</pre>
    `
    +
    const listEl = document.getElementById('list')
    const resEl = document.getElementById('res')
    const fetchById = (id) => ajax(`https://some-service.com/api/items/${id}`).pipe(map((r) => r.response))
    const mouseOutItem$ = fromEvent(listEl, 'mouseout')
    const mouseOverItem$ = fromEvent(listEl, 'mouseover')
    .pipe(
    switchMap((event) => {
    const id = event.target.id
    resEl.innerHTML = `loading item ${id}...`
    console.log(`Fetching data for item ${id}...`)
    return fetchById(id)
    }),
    map((response) => {
    resEl.innerHTML = JSON.stringify(response, null, 2)
    console.log(`Done fetching data for item ${response.item.id}!`)
    }),
    takeUntil(
    mouseOutItem$.pipe(
    tap(() => {
    console.log(`โŒ Cancelled fetch data for item ${event.target.id}!`)
    resEl.innerHTML = '...'
    }),
    ),
    ),
    repeat(),
    )
    .subscribe()
    </script>
    </body>
    +
    +
    +
    +
    ยปEasier to deal with backpressure
    +
    +

    โ€Backpressure is when the progress of turning that input to output is resisted in some way. In most cases that resistance is computational speedโ€, from โ€œBackpressure explained โ€” the resisted flow of data through softwareโ€

    +
    +

    Did it ever happen that your addEventListener handle is just executing too many times? For instance, while running an event handler for the scroll event? Itโ€™s common to use a debounce strategy to run your event handler only after X time has passed since the last scroll event. Again, with built-in operators such as debounce in RxJS, you can quickly relieve the load in your consumer functions (event handlers). The operator single-handedly takes care of debouncing emissions for you!

    +
    ยปConverging towards a single style of coding
    +

    Iโ€™ve covered this in greater detail during โ€œPart 1 - Why you should consider Reactive Programmingโ€ the gist is that working with streams when done right, might make callbacks, Promises, and async/await nearly obsolete. I say nearly because youโ€™ might still use Promises under the hood, but your code now can look the same whether youโ€™re tackling synchronous or asynchronous tasks.

    +
    ยปComplicated things made easy
    +

    I want to reinforce that you can leverage reactive libraries such as RxJS to solve complex problems with little effort. If you havenโ€™t watched the talk โ€œComplex features made easy with RxJSโ€ by Ben Lesh, I would highly recommend it. In the first 10 minutes, youโ€™ll see how easy it becomes to tackle some of the following problems with the help of RxJS:

    +
      +
    • Basic drag & drop implementation [5:54]
    • +
    • Avoid double submission through a button that involves an asynchronous Ajax call [7:04]
    • +
    • Throttle autosuggestion field that involves fetching data with a given search term [7:24]
    • +
    +

    If you aim to solve one of the problems mentioned in the above list, you can avoid reinventing the wheel by following the reactive patterns demonstrated in the video.

    +
    ยปMaintainability
    +

    There are two aspects of maintainability that I would point out. First, youโ€™ll write less code; thereโ€™s a lot of heavy lifting that RxJS can do for you; if you leverage that, youโ€™ll undoubtedly ending writing less code. The second is that a combination of streams with powerful operators solve complex problems in a very declarative manner. Code will look concise and straightforward; instead of verbose branching and imperative logic, youโ€™ll have a few combinations of operators that will do all the magic for you. +However, with RxJS and streams, maintainability is a double-edged sword. Weโ€™re going to cover that in the next part of this article.

    +

    ยปThe bad

    +

    Letโ€™s look at the not so bright side.

    +
    ยปLearning curve
    +

    Any modern JavaScript codebase uses a cocktail of libraries. RxJS (or whatever library you would adopt) would be just one more thing youโ€™ll have to teach newcomers. But donโ€™t take this as a light decision. Youโ€™re not only introducing a new library in the codebase, but youโ€™re also introducing a new paradigm. I feel thereโ€™s quite a learning curve towards mastering reactive programming and RxJS. Not only youโ€™re exposed to an entirely new ecosystem with new APIs, but you also need the time to process this different paradigm of programming with streams. As weโ€™ve experienced in previous articles, it can be quite different from a traditional writing code style (compared with imperative programming, for example).

    +

    Depending on how broadly youโ€™ll embrace this paradigm, you might need to ship new tooling for unit testing and master additional concepts such as marble diagrams - which helps you properly model and assert data streams. However, it might require you and your team to learn another tricky DSL (domain-specific language) to deal with these diagrams.

    +

    Some might argue that the โ€œlearning curveโ€ is a โ€œone-time costโ€. As it turns out, in the software industry, software engineers tend to move a lot (due to the high demand for the skill these days), and they might be sticking around in the same company for an average of 2 to 3 years before embracing a new challenge. The bustling job market makes me believe that it is not wise to think of the โ€œlearning curveโ€ as a one time cost because soon, your freshly trained engineer with RxJS skills might say goodbye.

    +
    ยปDebugging does not get any easier
    +

    Just the same way, you can split your code into several functions, and those functions call other functions which, without some structure, might end up in spaghetti code. Likewise, you can end up entangled in a spaghetti of streams and not know which way to turn. I think simple functions are usually more straightforward to debug, given that they are composed sequentially. Youโ€™re reading a function; that function might call other N functions and so forth. Well, with streams, it might not be that candid because thereโ€™s no such thing as a stream invoking another stream. Instead, youโ€™ll have a stream plugging with other streams in mixed ways depending on the operators that join them. It might feel overweighing at some point to find your way around some particular flow (hopefully, you wonโ€™tโ€™ reach that point because your code is clear and concise).

    +

    Another aspect that you might run into while debugging streams is that such an amount of abstractions and compact implementation will let no space for you to plug into a stream and debug it or inspect it the same way you debug a function. Things tend to be on a higher level of abstraction - which is beneficial because it will free up your mind on some implementation details. Hence when it comes to the point you need to dig further down, understanding whatโ€™s going on at the very core of your flow, you might need to tap into streams here and there to figure out whatโ€™s the issue. Although old this article, it might be one of the best walkthroughs of the problem Iโ€™m trying to surface here. It will give you a solid strategy to debug streams (even if youโ€™re looking at that particular code for the first time) - tip for reading it: mentally replace do per tap.

    +
    ยปHuman creativity
    +

    Kiss: Keep it Simple, Stupid, not easy with reactive streams and RxJS tough. Iโ€™ll tell from personal experience that it will come the day youโ€™ll look at your code, and although itโ€™s just fine, youโ€™ll feel this voice inside your head: โ€œAre you sure thereโ€™s nothing else much fancier in the RxJS API that would allow you to write less two lines of code?โ€ - fight that voice! Creative solutions with RxJS might often result in dreadful consequences for your product and team. But yet, RxJS has such a unique and evolving ecosystem that will feel tempting with time to start to chime in some new operators just for the sake of adding more operators. Fight that in code reviews - this is the place you can understand why your colleague is shipping that new exotic operator and challenge simpler alternatives already in use. I guess this is general advice. I wouldnโ€™t apply it to reactive programming only. If you donโ€™t have a code review process, ยฏ\_(ใƒ„)_/ยฏ.

    +
    ยปWith great power
    +

    As usual, with powerful tools, there is increased responsibility towards their use. If you pick RxJS as your weapon of choice, there are things youโ€™ll need to be extra careful. I want to highlight one: shareReplay. We didnโ€™t look into this operator in particular during this series, but we did learn the difference between Hot and Cold observables, a quick refresher:

    +
      +
    • Hot Observables - multicast; all subscribers get data from the same producer (e.g., a live music concert in a stadium).
    • +
    • Cold Observables - unicast; each subscriber gets data from different producers (e.g., a show on Netflix).
    • +
    +

    <a +href=โ€œhttps://www.learnrxjs.io/learn-rxjs/operators/multicasting/sharereplayโ€ +target=โ€œ_blankโ€ +title=โ€œshareReplay - Learn RxJSโ€

    +
    +

    shareReplay + allows you to take a cold observable and make it hot in the sense that you can now multicast the underlying computation +to multiple subscribers ๐Ÿคฏ - thatโ€™s share. With this operator, new subscribers will be able to โ€œcatch upโ€ with +previously emitted values at any point in time - thatโ€™s replay.

    +
    +

    Donโ€™t want to get into extensive detail of what the operator pertains to; be mindful of your implementation gaps that might trigger massive memory leaks in your application by using this operator. The gist is that using shareReply without refCount may origin a memory leak because the operator doesnโ€™t automatically close the stream after all its consumersโ€™ have unsubscribed.

    +

    Also, historically, there have been some issues around its implementation [1] [2].

    +
    ยปError handling
    +

    Finally, yet notably, error handling. Something I did not cover in the previous articles. Error handling is yet something else that changes considerably. Letโ€™s look at a simple example comparing reactive vs. non-reactive.

    +
    try...catch
    switchMap(event => {
    try {
    const id = event.target.id;
    resEl.innerHTML = `loading item ${id}...`;
    console.log(`Fetching data for item ${id}...`);
    return fetchById(id);
    } catch (error) {
    // that's not how this works with streams...
    }
    }),
    map(response => {
    resEl.innerHTML = JSON.stringify(response, null, 2);
    console.log(`Done fetching data for item ${response.item.id}!`);
    }),
    +
    catchError
    switchMap(event => {
    const id = event.target.id;
    resEl.innerHTML = `loading item ${id}...`;
    console.log(`Fetching data for item ${id}...`);
    return fetchById(id);
    }),
    catchError(error => {
    if (error) {
    console.error(error);
    }
    // fallback to an empty item and a message
    // we need to return an observable!
    return of({
    item: {},
    message: 'something went wrong',
    });
    }),
    map(response => {
    resEl.innerHTML = JSON.stringify(response, null, 2);
    console.log(`Done fetching data for item ${response.item.id}!`);
    }),
    +

    For a more natural integration, youโ€™ll have to stick with the catchError operator. As a beginner, I would tend to wrap stuff around with try/catch, but things work slightly differently with streams. Observable is our primitive here, remember? +Something mentioned as a โ€œgotchaโ€ of RxJS is the fact that an RxJS Observable does not โ€œtrapโ€ errors; when an error bubbles to the end of the observer chain, when unhandled, the error, it will be re-thrown.

    +

    ยปClosing notes

    +

    Would I use reactive programming and RxJS in my next project? Dependsโ€ฆ Thereโ€™s a great deal of learning involved in using these technologies, so even though I might not always use them, Iโ€™m confident that these skills will (and are!) playing an essential role in my evolution as a software engineer. Just know that I would do it again (all the learning and writing).

    +

    I do think Reactive is powerful and useful, but itโ€™s not a holy grail that will solve all your problems overnight.

    +

    Please focus on the problem first, scrutinize the use cases, and Reactive shall reveal itself a solution.

    +

    I hope youโ€™ve enjoyed this series is now approaching its end. This series is far from the perfect learning resource, but I hope that my perspective and way of explaining things fit some of you, complementing your learnings in a way. To close, Iโ€™ll leave you with a list of fantastic learning resources for reactive programming and RxJS.

    +
    +
    Last modified on 12, Oct, 2024
    \ No newline at end of file diff --git a/posts/reactive-series-preface/index.html b/posts/reactive-series-preface/index.html index 1dfb09a..997034f 100644 --- a/posts/reactive-series-preface/index.html +++ b/posts/reactive-series-preface/index.html @@ -1,4 +1,4 @@ - Reactive Series - Hey ๐Ÿ‘‹ + Reactive Series - ~/ @@ -106,4 +106,4 @@

    Part 3 - Hands-on Reactive Programming with RxJS
  • Part 4 - Reactive Programming: The Good and the Bad
  • Part 5 - Awesome RxJS and Reactive Programming Resources
  • -

    Last modified on 12, Oct, 2024
    \ No newline at end of file +
    Last modified on 12, Oct, 2024
    \ No newline at end of file diff --git a/posts/sli-slo-sla/index.html b/posts/sli-slo-sla/index.html index 7aaa7af..4c856ca 100644 --- a/posts/sli-slo-sla/index.html +++ b/posts/sli-slo-sla/index.html @@ -1,4 +1,4 @@ - SLIs, SLOs and SLAs in 2 minutes - Hey ๐Ÿ‘‹ + SLIs, SLOs and SLAs in 2 minutes - ~/ @@ -127,4 +127,4 @@

    Last modified on 12, Oct, 2024 \ No newline at end of file +

    It might be useful to think of the SLA as your answer: โ€œWhat happens if one fails to comply with the agreed SLOs?โ€

    Last modified on 12, Oct, 2024
    \ No newline at end of file diff --git a/posts/tips-end-to-end-testing-puppeteer/index.html b/posts/tips-end-to-end-testing-puppeteer/index.html index 1c7f994..f0c7943 100644 --- a/posts/tips-end-to-end-testing-puppeteer/index.html +++ b/posts/tips-end-to-end-testing-puppeteer/index.html @@ -1,4 +1,4 @@ - Tips for End to End Testing with Puppeteer - Hey ๐Ÿ‘‹ + Tips for End to End Testing with Puppeteer - ~/ @@ -246,4 +246,4 @@

    According to the official documentation, you can use Puppeteer with Firefox, with the caveat that you might encounter some issues since this capability is experimental at the time of this writing. You can specify which browser to run via puppeteer.launch options API that Iโ€™ve covered in this section.



    -

    What are your favorite bits of Puppeteer? What would you recommend me to learn next?

    Last modified on 12, Oct, 2024
    \ No newline at end of file +

    What are your favorite bits of Puppeteer? What would you recommend me to learn next?

    Last modified on 12, Oct, 2024
    \ No newline at end of file diff --git a/posts/tips-jest-unit-testing/index.html b/posts/tips-jest-unit-testing/index.html index 1b9a3ec..4de41ff 100644 --- a/posts/tips-jest-unit-testing/index.html +++ b/posts/tips-jest-unit-testing/index.html @@ -1,4 +1,4 @@ - Unrevealed tips for unit testing with Jest - Hey ๐Ÿ‘‹ + Unrevealed tips for unit testing with Jest - ~/ @@ -149,4 +149,4 @@

    Last modified on 12, Oct, 2024 \ No newline at end of file +

    Cheers!

    Last modified on 12, Oct, 2024
    \ No newline at end of file diff --git a/posts/unit-testing-with-fixtures-unleashed/index.html b/posts/unit-testing-with-fixtures-unleashed/index.html index 20cb70b..9c5c8a9 100644 --- a/posts/unit-testing-with-fixtures-unleashed/index.html +++ b/posts/unit-testing-with-fixtures-unleashed/index.html @@ -1,4 +1,4 @@ - Unit testing with fixtures unleashed - Hey ๐Ÿ‘‹ + Unit testing with fixtures unleashed - ~/ @@ -206,7 +206,7 @@

    ยป๐ŸŽฐ Bonus section: development watch mode for fixtures

    Another thing that comes in handy when setting up our testing workflow, is to have a way to update the fixtures by tweaking existing test case inputs or adding new ones and automatically re-running the run-fixtures.js script and the unit tests with Jest. How would that go?

    If you never tried nodemon, now itโ€™s a good time to check it out. Itโ€™s a mighty tool to restart some job based on specific changes in your project. Letโ€™s use nodemon to set up a npm script that seamlessly re-runs our fixtures. The idea is that we achieve the same workflow that we would normally have with jest โ€”watchAll, on our fixtures folder. After installing nodemon, we just need to use the โ€”watch option to check for changes in our fixtures.

    -
    "fixtures:run": "node run-fixtures && jest ./fixtures/tests/fixtures.spec.js",
    "fixtures:clean": "...",
    "fixtures:watch": "nodemon --watch ./fixtures --ignore ./fixtures/tests --exec \"npm run fixtures:run\""
    +
    package.json
    "fixtures:run": "node run-fixtures && jest ./fixtures/tests/fixtures.spec.js",
    "fixtures:clean": "...",
    "fixtures:watch": "nodemon --watch ./fixtures --ignore ./fixtures/tests --exec \"npm run fixtures:run\""
    [package.json] @@ -246,4 +246,4 @@

    ยปConclusions

    As already mentioned, all the code examples in this blog post are in a public GitHub repository.
    I hope that if you went through the article, you have now one more testing software pattern on your toolbelt that will (for the right use cases) allow you scale the tests in your codebase effortlessly and a in self-documented/self-organized fashion.

    -

    What do you think about having a fixture based testing architecture? Do you have any project in mind where you see that this could be the right fit?

    Last modified on 12, Oct, 2024
    \ No newline at end of file +

    What do you think about having a fixture based testing architecture? Do you have any project in mind where you see that this could be the right fit?

    Last modified on 12, Oct, 2024
    \ No newline at end of file diff --git a/posts/web-compatible-developers/index.html b/posts/web-compatible-developers/index.html index 3674cab..1a7993f 100644 --- a/posts/web-compatible-developers/index.html +++ b/posts/web-compatible-developers/index.html @@ -1,4 +1,4 @@ - Keeping the web compatible for developers - Hey ๐Ÿ‘‹ + Keeping the web compatible for developers - ~/ @@ -105,4 +105,4 @@

    I did so, with the attributeFilter issue on the MutationObserverInit.attributeFilter API. Hereโ€™s the final result where caniuse is displaying the information for users about the compatibility issue on the attributeFilter API.

    GitHub pull request for project browser-compat-data

    source: https://caniuse.com/#search=attributeFilter

    -

    I hope this short episode makes you aware that you can help to make the web compatible and help the community get relieved of all of these annoying nuances of the different JavaScript APIs in the various browsers, especially when working with Internet Explorer and Edge.

    Last modified on 12, Oct, 2024
    \ No newline at end of file +

    I hope this short episode makes you aware that you can help to make the web compatible and help the community get relieved of all of these annoying nuances of the different JavaScript APIs in the various browsers, especially when working with Internet Explorer and Edge.

    Last modified on 12, Oct, 2024
    \ No newline at end of file diff --git a/posts/why-reactive-programming/index.html b/posts/why-reactive-programming/index.html index 7af2342..1e3bff0 100644 --- a/posts/why-reactive-programming/index.html +++ b/posts/why-reactive-programming/index.html @@ -1,4 +1,4 @@ - Reactive Series (pt. 1) - Why You Should Consider Reactive Programming - Hey ๐Ÿ‘‹ + Reactive Series (pt. 1) - Why You Should Consider Reactive Programming - ~/ @@ -209,43 +209,10 @@

    https://danielcaldas.github.io/posts/reactive-programming-fundamentalsโ€ -target=โ€œ_blankโ€ -title=โ€œFundamentals of Reactive Programming | danielcaldas.github.ioโ€ -
    -

    Part 2 - Fundamentals of Reactive Programming -

    -
    - -
  • <a -href=โ€œhttps://danielcaldas.github.io/posts/hands-on-reactive-programming-rxjsโ€ -target=โ€œ_blankโ€ -title=โ€œHands-on Reactive Programming with RxJS | danielcaldas.github.ioโ€ -
    -

    Part 3 - Hands-on Reactive Programming with RxJS -

    -
    -
  • -
  • <a -href=โ€œhttps://danielcaldas.github.io/posts/reactive-rxjs-pros-consโ€ -target=โ€œ_blankโ€ -title=โ€œReactive Programming: The Good and the Bad | danielcaldas.github.ioโ€ -
    -

    Part 4 - Reactive Programming: The Good and the Bad -

    -
    -
  • -
  • <a -href=โ€œhttps://danielcaldas.github.io/posts/awesome-reactiveโ€ -target=โ€œ_blankโ€ -title=โ€œAwesome RxJS and Reactive Programming Resources | danielcaldas.github.ioโ€ -
    -

    Part 5 - Awesome RxJS and Reactive Programming Resources -

    -
    -
  • -
    Last modified on 12, Oct, 2024
    \ No newline at end of file +
  • Part 2 - Fundamentals of Reactive Programming
  • +
  • Part 3 - Hands-on Reactive Programming with RxJS
  • +
  • Part 4 - Reactive Programming: The Good and the Bad
  • +
  • Part 5 - Awesome RxJS and Reactive Programming Resources
  • +
    Last modified on 12, Oct, 2024
    \ No newline at end of file diff --git a/about/index.html b/projects/index.html similarity index 84% rename from about/index.html rename to projects/index.html index 64d1a36..9dc3a33 100644 --- a/about/index.html +++ b/projects/index.html @@ -1,4 +1,4 @@ - recent - Hey ๐Ÿ‘‹ + projects - ~/ @@ -32,10 +32,7 @@ } setup() -

    ยปA privacy-first, no-nonsense, super-fast astro blogging theme

    -

    No trackers, a few javascript, few stylesheets. and your words.

    -

    Looks great on any device

    -

    Tiny (~30kb), optimized, and awesome pages

    -

    No trackers, ads, little scripts

    -

    RSS feeds

    -

    Publish something awesome with your panda hands

    \ No newline at end of file +

    clipflow โ˜ ๏ธ

    clipflow.ai

    The content clipper that highlights and syncs content, with opt-in AI enhancements.

    tweak

    tweak-extension.com

    +Mock or modify your HTTP requests to test, develop and demo your web application. tweak allows you to mock and + modify HTTP requests without leaving the browser. +

    react-d3-graph

    github.com/danielcaldas/react-d3-graph

    Interactive and configurable graphs with react and d3 effortlessly.

    \ No newline at end of file diff --git a/rss.xml b/rss.xml index 73a6d27..800fd4d 100644 --- a/rss.xml +++ b/rss.xml @@ -1,4 +1,4 @@ -Hey ๐Ÿ‘‹Blogpost, note taking and other thingshttps://danielcaldas.github.io/CSSconf EU 2018https://danielcaldas.github.io/posts/about-css-conf-eu-berlin-2018/https://danielcaldas.github.io/posts/about-css-conf-eu-berlin-2018/My notes on the CSSconfg EU 2018Sun, 17 Jun 2018 00:00:00 GMT<p>Hello all!! I decided to write this blog post to share with you the new things I learned attending this year's edition of &lt;a href=&quot;https://2018. cssconf.eu&quot; target=&quot;_blank&quot; title=&quot;cssconf.eu official web page&quot;&gt;CSSconf EU&lt;/a&gt; in Berlin (Friday, June 1st). In this post I will focus more on the talks, I will cover other venue details in &lt;a href=&quot;https://danielcaldas.github.io/posts/about-js-conf-eu-berlin-2018/&quot; target=&quot;_blank&quot; title=&quot;JSconf EU 2018 | Blog&quot;&gt;another blog post&lt;/a&gt; on &lt;a href=&quot;https://danielcaldas.github.io/posts/about-js-conf-eu-berlin-2018/&quot; target=&quot;_blank&quot; title=&quot;JSconf EU 2018 | Blog&quot;&gt;JSConf EU&lt;/a&gt; that had a similar organization.</p> +~/Blogpost, note taking and other thingshttps://danielcaldas.github.io/CSSconf EU 2018https://danielcaldas.github.io/posts/about-css-conf-eu-berlin-2018/https://danielcaldas.github.io/posts/about-css-conf-eu-berlin-2018/My notes on the CSSconfg EU 2018Sun, 17 Jun 2018 00:00:00 GMT<p>Hello all!! I decided to write this blog post to share with you the new things I learned attending this year's edition of &lt;a href=&quot;https://2018. cssconf.eu&quot; target=&quot;_blank&quot; title=&quot;cssconf.eu official web page&quot;&gt;CSSconf EU&lt;/a&gt; in Berlin (Friday, June 1st). In this post I will focus more on the talks, I will cover other venue details in &lt;a href=&quot;https://danielcaldas.github.io/posts/about-js-conf-eu-berlin-2018/&quot; target=&quot;_blank&quot; title=&quot;JSconf EU 2018 | Blog&quot;&gt;another blog post&lt;/a&gt; on &lt;a href=&quot;https://danielcaldas.github.io/posts/about-js-conf-eu-berlin-2018/&quot; target=&quot;_blank&quot; title=&quot;JSconf EU 2018 | Blog&quot;&gt;JSConf EU&lt;/a&gt; that had a similar organization.</p> <h3>For starters</h3> <p>If you were there as I was, you were probably wondering whether you were at the right conference, since at the beginning you would ask yourself <strong>where is the CSS</strong>?</p> <p><img src="https://media1.giphy.com/media/v1.Y2lkPTc5MGI3NjExM3liNnh6dTM4ZmlwNnZiemxhMmR3aWNlYWh0ZndiZWhydGsxdWFzaiZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/hEc4k5pN17GZq/giphy.webp" alt="where is the css, john travolta"> @@ -1267,7 +1267,7 @@ export default function App() { <li>On your root folder create a <code>.vscode</code> folder (it may already exist, in that case, jump this step).</li> <li>Create a <code>launch.json</code> file inside the <code>.vscode</code> directory with the following configuration or click on the gear icon in the debug tab on vscode:</li> </ol> -<pre><code class="language-json:title=.vscode/launch.json">{ +<pre><code class="language-json">{ &quot;version&quot;: &quot;0.2.0&quot;, &quot;configurations&quot;: [ { @@ -1375,6 +1375,131 @@ export default createStore(reducer, {}, composeEnhancers(middleware)) <h3>Conclusions</h3> <p><img src="./assets/debugging-javascript-with-vscode/debug-animation-2.gif" alt="vscode setup redux final" title="vscode setup redux final"></p> <p>I might have spent one or two days trying to figure out how to bring all these pieces together, but having done it, believe that it increased my productivity in ways that by far compensate the invested time. I hope you find this article useful especially if it saves you one day of trouble trying to figure out the right configs.</p> +Destructuring in JavaScript: the not so good partshttps://danielcaldas.github.io/posts/destructuring-the-not-so-good-parts/https://danielcaldas.github.io/posts/destructuring-the-not-so-good-parts/Destructuring in JavaScript: the not so good partsThu, 20 Jun 2019 00:00:00 GMT<p>Generally speaking, I would say everybody loves the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment&quot; target=&quot;_blank&quot; title=&quot;the destructuring assignment syntax MDN web docs&quot;&gt;destructuring assignment&lt;/a&gt;, and for good reasons. Next, I list some of the amazing things one can achieve with destructuring in JavaScript.</p> +<h4>Object destructuring</h4> +<pre><code class="language-javascript">const user = { username: 'captain', email: 'captain@email.com', age: 30 } +const { username, age } = user +</code></pre> +<p>&lt;br /&gt;</p> +<h4>Provide default values</h4> +<pre><code class="language-javascript">const user = { email: 'captain@email.com', age: 30 } +const { username = 'unknown', age } = user +// you can now use username with value 'unknown' +</code></pre> +<p>&lt;br /&gt;</p> +<h4>Renaming variables on creation</h4> +<pre><code class="language-javascript">const user = { username: 'captain', email: 'captain@email.com', age: 30 } +const { username, age: userAge } = user +// you know have age value 30 in the variable userAge +</code></pre> +<p>&lt;br /&gt;</p> +<h4>Nested object destructuring โš ๏ธ</h4> +<pre><code class="language-javascript">const user = { username: 'captain', age: { value: 30, label: '30 years old' } } +const { + username, + age: { label }, +} = user +// you can now use label directly +</code></pre> +<p>&lt;br /&gt;</p> +<h4>Array destructuring</h4> +<pre><code class="language-javascript">const rgb = [200, 255, 100] +const [red, green, blue] = rgb +// the variables red, green and blue match the elements order in rgb array +</code></pre> +<p>&lt;br /&gt;</p> +<h4>Works with any iterable on the right-side (e.g <code>Set</code>)</h4> +<pre><code class="language-javascript">let [a, b, c] = 'abc' // [&quot;a&quot;, &quot;b&quot;, &quot;c&quot;] +let [one, two, three] = new Set([1, 2, 3]) +</code></pre> +<p>&lt;br /&gt;</p> +<h4>Swapping Variables</h4> +<pre><code class="language-javascript">let a = 1 +let b = 2 +;[b, a] = [a, b] +</code></pre> +<p>&lt;br /&gt;</p> +<h4>Swapping array positions</h4> +<pre><code class="language-javascript">const arr = [1, 3, 2, 4, 5] +;[arr[1], arr[2]] = [arr[2], arr[1]] +// arr is now [1, 2, 3, 4, 5] +</code></pre> +<p>&lt;br /&gt;</p> +<h4>Skipping items โš ๏ธ</h4> +<pre><code class="language-javascript">const rgb = [200, 255, 100] +// skip the first two items +// assign the only third item to the blue variable +const [, , blue] = rgb +</code></pre> +<p>&lt;br /&gt;</p> +<h4>Nested array destructuring โš ๏ธ</h4> +<pre><code class="language-javascript">const color = ['#FF00FF', [255, 0, 255], 'rgb(255, 0, 255)'] +// use nested destructuring to assign red, green and blue +const [hex, [red, green, blue]] = color +console.log(hex, red, green, blue) // #FF00FF 255 0 255 +</code></pre> +<p>&lt;br /&gt;</p> +<h4>Function parameter destructuring โš ๏ธ</h4> +<pre><code class="language-javascript">function getUserInfo({ username, email }) { + return `Username: ${username}; Email: ${email}` +} +const user = { username: 'captain', email: 'captain@email.com' } +const userInfo = getUserInfo(user) +</code></pre> +<p>&lt;br /&gt;</p> +<h3>What's not so good here</h3> +<p>The most significant dilemma of destructuring it's related with the fact that relies on properties of nested structures that can only be evaluated at runtime, let me give you an example.</p> +<pre><code class="language-javascript">function addEmailToList(list, { email }) { + if (email) { + list.push(email) + } +} +const list = ['a@mail.com', 'b@mail.com'] +const user = undefined + +addEmailToList(list, user) +// Uncaught TypeError: Cannot destructure property `email` of 'undefined' or 'null' +</code></pre> +<p>On the other hand, if you write this without destructuring, you won't make assumptions on the user, you can double check whether the given user is valid.</p> +<pre><code class="language-javascript">function addEmailToList(list, user) { + if (user &amp;&amp; user.email) { + list.push(user.email) + } +} +const list = ['a@mail.com', 'b@mail.com'] +const user = undefined + +addEmailToList(list, user) +</code></pre> +<p>&lt;br /&gt;</p> +<h4>null - a catastrophe waiting to happen</h4> +<p>Just a note, for you to be conscious that as in default assignment &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters&quot; target=&quot;_blank&quot; title=&quot;default function parameters allow named parameters to be initialized with default values&quot;&gt;default parameters&lt;/a&gt; the <code>null</code> value it's not considered false, thus you can't fall back to some default value in destructuring a property whose value is <code>null</code>.</p> +<pre><code class="language-javascript">function formatAddress(info) { + const { country = '-', city = '-' } = info + return `${country}, ${city}` +} + +const info = { country: 'Portugal', city: null } +const result = formatAddress(info) +// &quot;Portugal, null&quot; it's what you get instead of &quot;Portugal, -&quot; +</code></pre> +<p>Every feature above mentioned exists for you to use, but we should find a balance (as in everything else in life). Below I list a few guidelines/rules that will help you find the balance when using destructuring.</p> +<p><img src="./assets/destructuring-the-not-so-good-parts/fat-cat-balance.jpg" alt="cat kong fu balance" title="cat kong fu balance"></p> +<p>&lt;cite&gt;source: https://imgflip.com/tag/fat+cat+balance&lt;/cite&gt;</p> +<h3>The 3 Golden Rules</h3> +<p>You might have noticed some previous warning sings (โš ๏ธ) in some of the listed usages of destructuring. I left those signs there because I consider those use cases potentially harmful.</p> +<ol> +<li><strong>Don't use nested destructuring on data that comes from any external data sources (such as REST APIs, GraphQL endpoints or files)</strong>. You never know when these APIs change their data contracts. Hopefully all of this it's synced between teams, but sometimes we get lost on the tiny details such as that one small property in some nested object that got renamed and instead of using <code>snake_case</code> it uses now <code>camelCase</code> just because it made more sense from an aesthetic point of view. Now your marvelous single page application is burning and falling into pieces because you were unsafely accessing this property when you could have protected yourself against it.</li> +<li><strong>Don't use nested destructuring on function arguments that have long or complicated signatures</strong>. It makes it super hard to understand what the actually API of the function is, and you might get breaking behavior because someone decided to use your function, but in some edge case they do not provide you the valid input for you to destruct upon.</li> +<li><strong>Don't use destructuring to retrieve a value if you rely on order.</strong> I listed the possibility of skipping elements with destructuring, it might sound tempting, but you might end up again breaking your website because your simple routine relies on the access of the <em>Nth</em> element of some inputted array. Also, you can still retrieve data out of the array, but it might be that in that position you don't get what you were expecting. If you choose this path, at this point, it should also be arguable that there's something very wrong either with your code or with the data model from where you're your reading data, use this one with care.</li> +</ol> +<h4>References</h4> +<ul> +<li>&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment&quot; target=&quot;_blank&quot; title=&quot;the destructuring assignment syntax MDN web docs&quot;&gt;MDN web docs: Destructuring assignment&lt;/a&gt;</li> +<li>&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment&quot; target=&quot;_blank&quot; title=&quot;medium es6 destructuring the complete guide&quot;&gt;ES6 Destructuring: The Complete Guide&lt;/a&gt;</li> +<li>&lt;a href=&quot;https://javascript.info/destructuring-assignment&quot; target=&quot;_blank&quot; title=&quot;javascript info destructuring assignment&quot;&gt;JAVASCRIPT.INFO: Destructuring assignment&lt;/a&gt;</li> +<li>&lt;a href=&quot;https://github.com/getify/You-Dont-Know-JS&quot; target=&quot;_blank&quot; title=&quot;A book series on JavaScript&quot;&gt;You Don't Know JS: ES6 &amp; Beyond&lt;/a&gt;</li> +</ul> Why are developers bad at (manually) testing their code?https://danielcaldas.github.io/posts/developers-bad-at-testing-own-code/https://danielcaldas.github.io/posts/developers-bad-at-testing-own-code/Why are developers bad at (manually) testing their code?Tue, 06 Apr 2021 00:00:00 GMT<p>Manual testing of any software it's a vital activity to ensure its quality and long-term stability. Together with their coding activities, developers test their code changes to understand if they produce the desired outcome without breaking any existing software functionality. So far, so good.</p> <p>Today I'm writing a few points on <strong>why developers are bad at manual testing</strong> their own code changes, especially at detecting unwanted side-effects in areas of the software often unrelated to the modified parts.</p> <p>I'm writing because, at this point in time, I have experienced two very different setups towards software testing:</p> @@ -1711,6 +1836,732 @@ mutated). ] </code></pre> <p>&lt;/Accordion&gt;</p> +Guide to custom React Hooks (wrap the MutationObserver)https://danielcaldas.github.io/posts/guide-to-custom-react-hooks-with-mutationobserver/https://danielcaldas.github.io/posts/guide-to-custom-react-hooks-with-mutationobserver/Guide to custom React Hooks with MutationObserverWed, 21 Jul 2021 00:00:00 GMT<p>&lt;!-- canonicalUrl: &quot;https://blog.logrocket.com/guide-to-custom-react-hooks-with-mutationobserver/&quot; --&gt;</p> +<p>This article was originally published by &lt;a href=&quot;https://logrocket.com/&quot; target=&quot;_blank&quot; title=&quot;LogRocket Modern Frontend Monitoring and Product Analytics&quot;&gt;LogRocket&lt;/a&gt;. You can checkout the original post &lt;a href=&quot;https://blog.logrocket.com/guide-to-custom-react-hooks-with-mutationobserver/&quot; target=&quot;_blank&quot; title=&quot;Guide to custom React Hooks with MutationObserver LogRocket Blog&quot;&gt;here&lt;/a&gt;.</p> +<p>With the introduction of React Hooks, the amount of shareable code within React codebases has exploded. Because Hooks are thin APIs on top of React, developers can collaborate by attaching reusable behavior to components and segregating these behaviors into smaller modules.</p> +<p>While this is similar to how JavaScript developers abstract business logic away in vanilla JavaScript modules, Hooks provide more than pure JavaScript functions. Instead of taking data in and out, developers can stretch the spectrum of possibilities of what can happen inside a Hook.</p> +<p>For instance, developers can:</p> +<ul> +<li>Mutate and manage a piece of state for a specific component or an entire application</li> +<li>Trigger side effects on a page, like changing the title of a browser tab</li> +<li>Rectify external APIs by tapping into React componentsโ€™ lifecycle with Hooks</li> +</ul> +<p>In this post weโ€™ll explore the latter possibility. As a case study, weโ€™ll abstract the <code>MutationObserver</code> API in a custom React Hook, demonstrating how we can build robust, shareable pieces of logic in a React codebase.</p> +<p>Weโ€™ll create a dynamic label that updates itself to indicate how many items we have in a list. Instead of using the provided React state array of elements, weโ€™ll use the <a href="https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver">MutationObserver API</a> to detect added elements and update the label accordingly.</p> +<p><img src="./assets/guide-to-custom-react-hooks-with-mutationobserver/fruits-example.gif" alt="'Update the dynamic label to count the number of fruits in the list'" title="Update the dynamic label to count the number of fruits in the list"></p> +<p>&lt;cite&gt;source: https://blog.logrocket.com/guide-to-custom-react-hooks-with-mutationobserver/&lt;/cite&gt;</p> +<h2>Implementation overview</h2> +<p>The following code is a simple component that renders our list. It also updates a counter value that represents the number of fruits currently in the list:</p> +<pre><code class="language-javascript">export default function App() { + const listRef = useRef() + const [count, setCount] = useState(2) + const [fruits, setFruits] = useState(['apple', 'peach']) + const onListMutation = useCallback( + (mutationList) =&gt; { + setCount(mutationList[0].target.children.length) + }, + [setCount], + ) + + useMutationObservable(listRef.current, onListMutation) + + return ( + &lt;div&gt; + &lt;span&gt;{`Added ${count} fruits`}&lt;/span&gt; + &lt;br /&gt; + &lt;button onClick={() =&gt; setFruits([...fruits, `random fruit ${fruits.length}`])}&gt;Add random fruit&lt;/button&gt; + &lt;ul ref={listRef}&gt; + {fruits.map((f) =&gt; ( + &lt;li key={f}&gt;{f}&lt;/li&gt; + ))} + &lt;/ul&gt; + &lt;/div&gt; + ) +} +</code></pre> +<p>We want to trigger a callback function whenever our <code>list</code> element is mutated. Within the callback we refer to, the elementโ€™s children give us the number of elements in the list.</p> +<h2>Implementing the <code>useMutationObservable</code> custom Hook</h2> +<p>Letโ€™s look at the integration point:</p> +<pre><code class="language-javascript">useMutationObservable(listRef.current, onListMutation) +</code></pre> +<p>The above <code>useMutationObservable</code> custom Hook abstracts the necessary operations to observe changes on the element passed as the first parameter. It then runs the callback passed as the second parameter whenever the target element changes.</p> +<p>Now, letโ€™s implement our <code>useMutationObservable</code> custom Hook.</p> +<p>In the Hook, there are a number of boilerplate operations to understand. First, we must provide a set of options that comply with the <code>MutationObserver</code> API.</p> +<p>Once a <code>MutationObserver</code> instance is created, we must call <code>observe</code> to listen for changes in the targeted DOM element.</p> +<p>When we no longer need to listen to the changes, we must call <code>disconnect</code> on the observer to clean up our subscription. This must happen when the <code>App</code> component unmounts:</p> +<pre><code class="language-javascript">const DEFAULT_OPTIONS = { + config: { attributes: true, childList: true, subtree: true }, +} +function useMutationObservable(targetEl, cb, options = DEFAULT_OPTIONS) { + const [observer, setObserver] = useState(null) + + useEffect(() =&gt; { + const obs = new MutationObserver(cb) + setObserver(obs) + }, [cb, options, setObserver]) + + useEffect(() =&gt; { + if (!observer) return + const { config } = options + observer.observe(targetEl, config) + return () =&gt; { + if (observer) { + observer.disconnect() + } + } + }, [observer, targetEl, options]) +} +</code></pre> +<p>All the above work, including initializing the <code>MutationObserver</code> with the right parameters, observing changes with the call to <code>observer.observe</code>, and cleaning up with <code>observer.disconnect</code>, are abstracted away from the client.</p> +<p>Not only do we export functionality, but we also clean up by hooking into the React componentsโ€™ lifecycle and by leveraging cleanup callbacks on effect Hooks to tear down the <code>MutationObserver</code> instance.</p> +<p>Now that we have a functional and basic version of our Hook, we can think about improving its quality by iterating on its API and enhancing the developer experience around this shareable piece of code.</p> +<h3>Input validation and development</h3> +<p>One important aspect when designing custom React Hooks is input validation. We must be able to communicate to developers when things are not running smoothly or a certain use case is hitting an edge case.</p> +<p>Usually, development logs help developers understand unfamiliar code to adjust their implementation. Likewise, we can enhance the above implementation by adding runtime checks and comprehensive warning logs to validate and communicate issues to other developers:</p> +<pre><code class="language-javascript">function useMutationObservable(targetEl, cb, options = DEFAULT_OPTIONS) { + const [observer, setObserver] = useState(null) + + useEffect(() =&gt; { + // A) + if (!cb || typeof cb !== 'function') { + console.warn(`You must provide a valid callback function, instead you've provided ${cb}`) + return + } + const { debounceTime } = options + const obs = new MutationObserver(cb) + setObserver(obs) + }, [cb, options, setObserver]) + useEffect(() =&gt; { + if (!observer) return + if (!targetEl) { + // B) + console.warn(`You must provide a valid DOM element to observe, instead you've provided ${targetEl}`) + } + const { config } = options + try { + observer.observe(targetEl, config) + } catch (e) { + // C) + console.error(e) + } + return () =&gt; { + if (observer) { + observer.disconnect() + } + } + }, [observer, targetEl, options]) +} +</code></pre> +<p>In this example, weโ€™re checking that a callback is passed as a second argument. This API check at runtime can easily alert the developer that something is wrong on the caller side.</p> +<p>We can also see whether the provided DOM element is invalid with an erroneous value provided to the Hook at runtime or not. These are logged together to inform us to quickly resolve the issue.</p> +<p>And, if <code>observe</code> throws an error, we can catch and report it. We must avoid breaking the JavaScript runtime flow as much as possible, so by catching the error, we can choose to either log it or report it depending on the environment.</p> +<h3>Extensibility via configuration</h3> +<p>If we want to add more capabilities to our Hook, we should do this in a retro-compatible fashion, such as an opt-in capability that has little or no friction towards its adoption.</p> +<p>Letโ€™s look at how we can optionally debounce the provided callback function so callers can specify an interval of time when no other changes in the target element trigger. This runs the callback once rather than running the same amount of times the element or its children mutated:</p> +<pre><code class="language-javascript">import debounce from &quot;lodash.debounce&quot;; + +const DEFAULT_OPTIONS = { + config: { attributes: true, childList: true, subtree: true }, + debounceTime: 0 +}; +function useMutationObservable(targetEl, cb, options = DEFAULT_OPTIONS) { + const [observer, setObserver] = useState(null); + useEffect(() =&gt; { + if (!cb || typeof cb !== &quot;function&quot;) { + console.warn( + `You must provide a valida callback function, instead you've provided ${cb}` + ); + return; + } + const { debounceTime } = options; + const obs = new MutationObserver( + debounceTime &gt; 0 ? debounce(cb, debounceTime) : cb + ); + setObserver(obs); + }, [cb, options, setObserver]); + // ... +</code></pre> +<p>This is handy if we must run a heavy operation, such as triggering a web request, ensuring it runs the minimum number of times possible.</p> +<p>Our <code>debounceTime</code> option can now pass into our custom Hook. If a value bigger than <code>0</code> passes to <code>MutationObservable</code>, the callback delays accordingly.</p> +<p>With a simple configuration exposed in our Hook API, we allow other developers to debounce their callbacks which can result in a more performant implementation given that we might drastically reduce the number of times the callback code gets executed.</p> +<p>Of course, we can always debounce the callback on the client side, but this way we enrich our API and make the caller-side implementation smaller and declarative.</p> +<h3>Testing</h3> +<p>Testing is an essential part of developing any kind of shared capability. It helps us ensure a certain level of quality for generic APIs when they are heavily contributed to and shared.</p> +<p>The <a href="https://blog.logrocket.com/a-quick-guide-to-testing-react-hooks-fa584c415407/">guide to testing React Hooks</a> has expansive detail around testing that can be implemented into this tutorial.</p> +<h3>Documentation</h3> +<p>Documentation can level up the quality of custom Hooks and make it developer-friendly.</p> +<p>But even when writing plain JavaScript, <a href="https://jsdoc.app/">JSDoc documentation</a> can be written for custom hook APIs to ensure the Hook passes the right message to developers.</p> +<p>Letโ€™s focus on the <code>useMutationObservable</code> function declaration and how to add formatted JSDoc documentation to it:</p> +<pre><code class="language-javascript">/** + * This custom hooks abstracts the usage of the Mutation Observer with React components. + * Watch for changes being made to the DOM tree and trigger a custom callback. + * @param {Element} targetEl DOM element to be observed + * @param {Function} cb callback that will run when there's a change in targetEl or any + * child element (depending on the provided options) + * @param {Object} options + * @param {Object} options.config check \[options\](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver/observe) + * @param {number} [options.debounceTime=0] a number that represents the amount of time in ms + * that you which to debounce the call to the provided callback function + */ +function useMutationObservable(targetEl, cb, options = DEFAULT_OPTIONS) { +</code></pre> +<p>Writing this is not only useful for documentation, but it also leverages IntelliSense capabilities that autocomplete Hook usage and provide spot information for the Hookโ€™s parameters. This saves developers a few seconds per usage, potentially adding up to hours wasted on reading through the code and trying to understand it.</p> +<h2>Conclusion</h2> +<p>With different kinds of custom Hooks we can implement, we see how they integrate extrinsic APIs into the React world. Itโ€™s easy to integrate state management within Hooks and run effects based on inputs from components using the Hook.</p> +<p>Remember that to build quality Hooks, itโ€™s important to:</p> +<ul> +<li>Design easy-to-use, declarative APIs</li> +<li>Enhance the development experience by checking for proper usage and logging warnings and errors</li> +<li>Expose features through configurations, such as the <code>debounceTime</code> example</li> +<li>Ease Hook usage by writing JSDoc documentation</li> +</ul> +<p>You can check the <a href="https://codepen.io/danielcaldas/pen/BaWPWEw">full implementation of the custom React hook here</a>.</p> +Reactive Series (pt. 3) - Hands-on Reactive Programming with RxJShttps://danielcaldas.github.io/posts/hands-on-reactive-programming-rxjs/https://danielcaldas.github.io/posts/hands-on-reactive-programming-rxjs/Reactive Series (pt. 3) - Hands-on Reactive Programming with RxJSWed, 16 Sep 2020 00:00:00 GMT<ul> +<li>&lt;a href=&quot;https://danielcaldas.github.io/posts/why-reactive-programming&quot; target=&quot;_blank&quot; title=&quot;Why You Should Consider Reactive Programming | danielcaldas.github.io&quot;&gt;Part 1 - Why You Should Consider Reactive Programming&lt;/a&gt;</li> +<li>&lt;a href=&quot;https://danielcaldas.github.io/posts/reactive-programming-fundamentals&quot; target=&quot;_blank&quot; title=&quot;Fundamentals of Reactive Programming | danielcaldas.github.io&quot;&gt;Part 2 - Fundamentals of Reactive Programming&lt;/a&gt;</li> +<li><strong>Part 3 - Hands-on Reactive Programming with RxJS</strong></li> +<li>&lt;a href=&quot;https://danielcaldas.github.io/posts/reactive-rxjs-pros-cons&quot; target=&quot;_blank&quot; title=&quot;Reactive Programming: The Good and the Bad | danielcaldas.github.io&quot;&gt;Part 4 - Reactive Programming: The Good and the Bad&lt;/a&gt;</li> +<li>&lt;a href=&quot;https://danielcaldas.github.io/posts/awesome-reactive&quot; target=&quot;_blank&quot; title=&quot;Awesome RxJS and Reactive Programming Resources | danielcaldas.github.io&quot;&gt;Part 5 - Awesome RxJS and Reactive Programming Resources&lt;/a&gt;</li> +</ul> +<p>This part of the series it's all about getting your hands dirty! Part 3 is a tutorial on core techniques to build an application with reactive streams using &lt;a href=&quot;https://rxjs-dev.firebaseapp.com/&quot; target=&quot;_blank&quot; title=&quot;A reactive programming library for JavaScript&quot;&gt;RxJS&lt;/a&gt;.</p> +<ul> +<li><a href="#warmup---handle-a-click-event-with-rxjs">Warmup - Handle a click event with RxJS</a></li> +<li><a href="#rxjs-overview">RxJS Overview</a></li> +<li><a href="#lets-build-something">Let's build something!</a></li> +<li><a href="#level-1---combining-streams-of-dom-events">Level 1 - Combining streams of DOM events</a></li> +<li><a href="#level-2---hacking-race-conditions-with-artificial-delays">Level 2 - Hacking race conditions with artificial delays</a></li> +<li><a href="#level-3---merging-streams-into-one-stream">Level 3 - Merging streams into one stream</a> +<ul> +<li><a href="#bonus-for-level-3">Bonus for Level 3</a></li> +<li><a href="#note-about-the-coding-style">Note about the coding style</a></li> +</ul> +</li> +<li><a href="#level-4---flatten-observables-timers-and-cancellation">Level 4 - Flatten observables, timers and cancellation</a></li> +<li><a href="#closing-notes">Closing Notes</a></li> +</ul> +<p>We're going to start with a small refresher from &lt;a href=&quot;https://danielcaldas.github.io/posts/reactive-programming-fundamentals&quot; target=&quot;_blank&quot; title=&quot;Fundamentals of Reactive Programming | danielcaldas.github.io&quot;&gt;the previous article&lt;/a&gt;, implementing a click event handler with &lt;a href=&quot;https://rxjs-dev.firebaseapp.com/&quot; target=&quot;_blank&quot; title=&quot;A reactive programming library for JavaScript&quot;&gt;RxJS&lt;/a&gt;. Next, we'll have a brief overview of RxJS, followed by the main challenge of building a small animated game the reactive way.</p> +<h4>Warmup - Handle a click event with RxJS</h4> +<p>Here's how a usual JavaScript event handler function looks like.</p> +<pre><code class="language-html">&lt;button id=&quot;btn&quot;&gt;+1&lt;/button&gt; +</code></pre> +<pre><code class="language-javascript">const btn = document.getElementById('btn') +let counter = 0 +btn.addEventListener('click', (event) =&gt; { + counter++ + console.log(counter) +}) +</code></pre> +<p>In the above code, we're incrementing <code>counter</code> that is global and logging its value each time we update it.</p> +<p>Usually, we don't increment globals or <code>console.log</code> directly in real-life apps, but we do have other side-effects in place (like updating something else in the UI, triggering a web request, firing an animation, etc.). You can always organize your code to split concerns, but it seems the event handler is already doing two very distinct things, and we're barely getting started.</p> +<p>Let's take a look at how this looks like in the reactive world, with RxJS. Here's how you should visualize and implement a stream of click events.</p> +<p><img src="./assets/hands-on-reactive-programming-rxjs/marbles-level-0-1.png" alt="marbles diagram of a click event stream" title="marbles diagram of a click event stream"></p> +<p>Note: &lt;b&gt;e&lt;/b&gt; stands for &lt;b&gt;event&lt;/b&gt; and it's the value pushed to the stream at each click in the button element, just like you would have an &lt;b&gt;event&lt;/b&gt; argument in the typical &lt;b&gt;onClick&lt;/b&gt; callback (event handler) at each click.</p> +<pre><code class="language-javascript">let counter = 0 +const click$ = fromEvent(btn, 'click') +click$.subscribe((event) =&gt; { + counter++ + console.log(counter) +}) +</code></pre> +<p>A few things to notice here:</p> +<ul> +<li>&lt;a href=&quot;https://rxjs-dev.firebaseapp.com/api/index/function/fromEvent&quot; target=&quot;_blank&quot; title=&quot;RxJS - fromEvent&quot;&gt; +fromEvent +&lt;/a&gt; is the tool in RxJS that will allow you to transform any DOM event into an Observable, a stream of events of a given +type (like click, drag, scroll etc.).</li> +<li><code>subscribe</code> is a must! Without subscribing to our stream, nothing would ever happen.</li> +<li>The <code>$</code> is just a common (non-standard) notation to identify streams in your code - totally optionally, no need to follow.</li> +</ul> +<p>Opposite to the click event handler, you can subscribe to the stream of clicks as many times as you want and split all kinds of different tasks into separate subscriptions that <strong>share the same event listener</strong>! <strong>Attaching event handlers to the DOM is costly</strong> especially due to the event attaching phase that happens at page load (<code>onload</code> or <code>DOMContentReady</code> events) a busy period for modern web applications - &lt;i&gt;paraphrased from &lt;a href=&quot;https://www.oreilly.com/library/view/high-performance-javascript/9781449382308/&quot; target=&quot;_blank&quot; rel=&quot;nofollow&quot; title=&quot;High Performance JavaScript, book by Nicholas C. Zakas&quot;&gt;&quot;High Performance JavaScript&quot;, book by Nicholas C. Zakas&lt;/a&gt;&lt;/i&gt;.</p> +<p>In a way, things are opening up, you get a more flexible model to organize your code, compared to the classic event handler callback.</p> +<blockquote> +<p>Anything can be a stream, remember that, let it be the cornerstone of your reactive thinking.</p> +</blockquote> +<p>This should get you warmed up for the real challenge later in the article. Before that, there's something I want to mention about the RxJS library.</p> +<h4>RxJS Overview</h4> +<p>I would highly recommend you later to go through the &lt;a href=&quot;https://rxjs-dev.firebaseapp.com/guide/overviewhttps://rxjs-dev.firebaseapp.com/guide/overview&quot; target=&quot;_blank&quot; title=&quot;RxJS - Introduction&quot;&gt;RxJS official docs overview&lt;/a&gt;; it complements a few concepts I've covered in &lt;a href=&quot;https://danielcaldas.github.io/posts/reactive-programming-fundamentals&quot; target=&quot;_blank&quot; title=&quot;Fundamentals of Reactive Programming | danielcaldas.github.io&quot;&gt;the previous article&lt;/a&gt; giving you a more mature view into the fundamentals. But for now, I want to focus on two things only: <strong>creating streams and manipulating them</strong>. Looking at RxJS, you'll see that you can identify two big groups of &quot;things&quot;:</p> +<ul> +<li><strong>Stream creators/producers</strong> are tools that will allow you to take anything and make it a stream. In the previous section, for example, we used <code>fromEvent</code> to transform DOM click events into an Observable, <strong>a stream that you can subscribe to</strong>.</li> +<li><strong>Operators</strong> are instruments that will allow you to not only hook into the stream to perform operations with the data flowing through it but also combine them.</li> +</ul> +<h4>Let's build something!</h4> +<p>Now we've had our refresher, we can jump into building a small animated game with RxJS, something more complex.</p> +<p>The game we're building is called <strong>&quot;Take the cat to the party&quot;</strong>, here's how it goes down.</p> +<p><img src="./assets/hands-on-reactive-programming-rxjs/take-cat-to-party.gif" alt="'take the cat to the party game demonstration'" title="take the cat to the party game demonstration"></p> +<ul> +<li>There's a cat in a motorbike in your top left corner (draggable HTML element).</li> +<li>There's a decoy at the bottom left corner (draggable HTML element).</li> +<li>There's a party at the right bottom corner of the page (our drop zone).</li> +<li><strong>We need to drop the cat at the party in less than 5 seconds to win</strong>. Nothing should happen if the decoy is the dragged element.</li> +<li>While dragging, there's an animation of the cat driving to the party and a counter appearing at the top right corner.</li> +<li>Per each attempt, if the cat is dragged for longer than 5 seconds you lose.</li> +</ul> +<p>We're going to split this into <strong>4 levels</strong>. Per each level, I'll explain the goal we're trying to achieve, show you how the implementation looks like, and what it does with plenty of detail. Feel free to try and code the levels yourself before checking the suggested solution. You can &lt;a href=&quot;https://github.com/danielcaldas/take-the-cat-to-the-party&quot; target=&quot;_blank&quot; title=&quot;danielcaldas/take-the-cat-to-the-party: Learn reactive programming with RxJS the fun way&quot;&gt;check out this repo&lt;/a&gt; if you're willing to give it a try. The file &lt;a href=&quot;https://github.com/danielcaldas/take-the-cat-to-the-party/blob/master/cat-party-empty.html&quot; target=&quot;_blank&quot; title=&quot;danielcaldas/take-the-cat-to-the-party: Learn reactive programming with RxJS the fun way template file&quot;&gt;cat-party-empty.html&lt;/a&gt; contains the necessary boilerplate in a single HTML file. You can just open it in the browser and start coding.</p> +<p>Before we start, let's take a look at some boilerplate code already in place.</p> +<p>There are a bunch of RxJS taken from the global <code>window</code> at the top (I'm just injecting RxJS via <code>&lt;script&gt;</code> at the top of the HTML file). Then we have our markup appended to the element's <code>app</code> <code>innerHTML</code>. Just some boilerplate, don't bother with the detail.</p> +<pre><code class="language-html">&lt;head&gt; + &lt;script src=&quot;https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.5/rxjs.umd.js&quot;&gt;&lt;/script&gt; + &lt;script src=&quot;https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.5/rxjs.umd.js.map&quot;&gt;&lt;/script&gt; +&lt;/head&gt; +&lt;body&gt; + &lt;div id=&quot;app&quot;&gt;&lt;/div&gt; + &lt;script&gt; + const { + map, + // ... + } = window.rxjs.operators + const { + fromEvent, + // ... + } = window.rxjs + // ... + document.getElementById('app').innerHTML = ` + &lt;h2&gt;Take the ๐Ÿˆ to the ๐ŸŽ‰&lt;/h2&gt; + &lt;div id=&quot;loader&quot; style=&quot;display:none;position:absolute;right:0;z-index:-1;&quot;&gt; + &lt;img src=&quot;https://media2.giphy.com/media/cfuL5gqFDreXxkWQ4o/giphy.webp?cid=ecf05e479338392950f1cf8bade564aaec071e3aae3b68b0&amp;rid=giphy.webp&quot; width=&quot;248&quot; height=&quot;248&quot; style=&quot;opacity:0.5;&quot;/&gt; + &lt;span id=&quot;countdown&quot; style=&quot;top:0;right:0;position:absolute;z-index:9999;font-weight:bold;font-size:46px;&quot;&gt;-&lt;/span&gt; + &lt;/div&gt; + &lt;div id=&quot;drop-zone&quot; style=&quot;position:absolute;right:5px;bottom:5px;width:180px;height:180px;border:2px dashed black;z-index:2;&quot;&gt;&lt;/div&gt; + &lt;img src=&quot;https://i.ebayimg.com/images/g/qHEAAOSw01pdVAaZ/s-l300.jpg&quot; width=&quot;150&quot; height=&quot;150&quot; style=&quot;position:absolute;right:20px;bottom:20px;&quot;/&gt; + &lt;/div&gt; + &lt;img id=&quot;cat&quot; draggable=&quot;true&quot; src=&quot;https://image.shutterstock.com/image-vector/vintage-scooter-cat-cool-stuff-260nw-429646657.jpg&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;position:absolute;z-index:1;margin:18px 16px;text-align:center;cursor:grab;&quot;/&gt; + &lt;div id=&quot;decoy&quot; draggable=&quot;true&quot; style=&quot;font-size:28px;position:absolute;left:20px;bottom:10px;text-align:center;cursor:grab;font-weight:bold;&quot;&gt;DECOY&lt;/div&gt; + ` + &lt;/script&gt; +&lt;/body&gt; +</code></pre> +<p>The next block contains a few of the DOM elements we'll often be referring to.</p> +<pre><code class="language-javascript">const cat = document.getElementById('cat') +const party = document.getElementById('drop-zone') +const loading = document.getElementById('loader') +const countdown = document.getElementById('countdown') +</code></pre> +<p>Finally, <strong>we introduce three streams that we'll use quite often while going through the challenge. These are the most basic building blocks of our solutions.</strong></p> +<pre><code class="language-javascript">const dragstart$ = fromEvent(document, 'dragstart') +const dragend$ = fromEvent(document, 'dragend') +const drop$ = fromEvent(document, 'drop') +</code></pre> +<p>&lt;br /&gt; +&lt;br /&gt;</p> +<h4>Level 1 - Combining streams of DOM events</h4> +<ul> +<li>We should be able to take the cat to the party by dragging it. +<ul> +<li>Place the cat inside the party together with other cats.</li> +<li>We also want to hide the original cat element while dragging it for a better user experience.</li> +</ul> +</li> +</ul> +<p>Let's follow a bottom-up approach. We'll start by building some specific streams that will allow us to assemble more complex behaviors as we move along.</p> +<p>Let's start by answering the following: how do we know that an HTML element has been dropped at the <code>party</code>? We'll need to detect any <code>drop</code> event that occurs in the <code>party</code> element.</p> +<pre><code class="language-javascript">const partyDrop$ = drop$.pipe(filter((event) =&gt; event.target === party)) +</code></pre> +<p><img src="./assets/hands-on-reactive-programming-rxjs/marbles-level-1-1.png" alt="marbles diagram filter event target on drop event" title="marbles diagram filter event target on drop event"></p> +<p>&lt;Caption +text={() =&gt; ( +&lt;p&gt; +&lt;b&gt;e&lt;/b&gt; stands for &lt;b&gt;event&lt;/b&gt; and it's the value pushed to the stream at each drop event in the whole document!{&quot; &quot;} +&lt;b&gt;e1&lt;/b&gt;, &lt;b&gt;e2&lt;/b&gt; and &lt;b&gt;e3&lt;/b&gt; have different &lt;b&gt;event.target&lt;/b&gt; (target DOM elements). +&lt;/p&gt; +)} +/&gt;</p> +<p>Cool, we now have a stream that emits a <code>drop</code> event every time something is dropped in the <code>party</code>, we ensure that using the &lt;a href=&quot;https://rxjs-dev.firebaseapp.com/api/operators/filter&quot; target=&quot;_blank&quot; title=&quot;RxJS - filter&quot;&gt;filter&lt;/a&gt; operator.</p> +<p>I would like to pause and add further detail on this marble diagram. Notice that we read from top to bottom, meaning the last timeline is the only one that really matters, it's the fine grained stream that gives us more useful values. In this case since we won't subscribe to <code>drop$</code> directly, think of it as an auxiliary conceptual timeline which is a step towards shaping the ideal stream to tackle our task: <code>partyDrop$</code>.</p> +<p>Another relevant detail on the marbles diagram: <code>e1</code> and <code>e2</code> marbles don't make it to <code>partyDrop$</code> (bottom stream). For those events, +<code>event.target</code> it's not <code>party</code>, this means the user did not drop the HTML element in the <code>party</code>.</p> +<p><strong>At this point we can already notice the exceptional compositional capabilities of streams and how easy it is to plug &amp; play the smaller blocks (such as <code>drop$</code>) to build higher level streams.</strong></p> +<p>Next, let's see how we can detect that the <code>cat</code> is dropped at the <code>party</code>.</p> +<pre><code class="language-javascript">const catAtParty$ = partyDrop$.pipe( + withLatestFrom(dragstart$), + filter(([dropEvent, dragStartEvent]) =&gt; dragStartEvent.target === cat), +) +</code></pre> +<p>Alright, a few things are going on here. We're familiar with &lt;a href=&quot;https://rxjs-dev.firebaseapp.com/api/operators/withLatestFrom&quot; target=&quot;_blank&quot; title=&quot;RxJS - withLatestFrom&quot;&gt;withLatestFrom&lt;/a&gt;, but this time, it's a bit more complex to perceive. We're looking into a stream of <code>dragstart</code> events <code>dragstart$</code> and asking it for the last value emitted in that stream; this is the same as saying that we want the last draggable element in the page that emitted a <code>dragstart</code> event! Next, we have <code>filter</code> again. We're going to use <code>filter</code> to narrow down our stream to allow <code>dragstart</code> events to pass through only if their target element is <code>cat</code>! And that's it! We have a stream that will emit an event every time we drop the cat at the party.</p> +<p><img src="./assets/hands-on-reactive-programming-rxjs/marbles-level-1-2.png" alt="marbles diagram filter event target on drop event" title="marbles diagram filter event target on drop event"></p> +<p>&lt;Caption /&gt;</p> +<p>In the above diagram, <code>e1</code>, <code>e2</code> and <code>e3</code> are <code>dragstart</code> events (<code>dragstart$</code> stream) that might come from anywhere in the page. <code>de</code> represents a <code>drop</code> event at the <code>party</code>. Notice how <code>withLatestFrom</code> operates by bringing together the last element on <code>dragstart$</code> which is <code>e3</code> and with the <code>party</code> element which is published to the <code>dragend$</code> stream. Now because <code>e3</code> event target is actually the <code>cat</code> you'll get <code>e3</code> pushed to our final stream <code>catAtParty$</code>!</p> +<p>But we're not finished just yet; we need to leverage the work we've constructed so far to actually render the <code>cat</code> in the <code>party</code>. For that, we just need to subscribe to our <code>catAtParty$</code> stream and do some DOM manipulations.</p> +<pre><code class="language-javascript">catAtParty$.subscribe(([partyDropEvent, dragStartEvent]) =&gt; { + // some console.logs to check what we really get subscribing to catAtParty$ + // console.log({ partyDropEvent, dragStartEvent }); + // console.log({ partyDropEvent: event.type, dragStartEvent: event.type }); + cat.remove() + party.appendChild(cat) +}) +</code></pre> +<p><img src="./assets/hands-on-reactive-programming-rxjs/game-level-1-2.png" alt="result of subscribing to cat at party stream" title="result of subscribing to cat at party stream"></p> +<p>&lt;Caption +text={() =&gt; ( +&lt;p&gt; +The &lt;b&gt;console.log&lt;/b&gt; reveals the &lt;b&gt;drop&lt;/b&gt; and &lt;b&gt;dragstart&lt;/b&gt; events as payload for our subscription. We +also log the &lt;b&gt;event.type&lt;/b&gt; of each individual event. +&lt;/p&gt; +)} +/&gt;</p> +<p>If you're following this point, you'll notice that although we can drag the cat perfectly, we still see the original <code>cat</code> element while carrying it. We see the original cat element while dragging it.</p> +<p><img src="./assets/hands-on-reactive-programming-rxjs/game-level-1-opacity-before.gif" alt="'dragging the cat without manipulating the opacity value'" title="dragging the cat without manipulating the opacity value"></p> +<p>&lt;br /&gt;</p> +<p>Setting the cat's element opacity to &lt;b&gt;0&lt;/b&gt; yields a more natural user experience.</p> +<p><img src="./assets/hands-on-reactive-programming-rxjs/game-level-1-opacity-after.gif" alt="'dragging the cat with opacity manipulation'" title="dragging the cat with opacity manipulation"></p> +<p>To achieve the same behavior we have in the above GIF, we want to toggle the <code>cat</code> element's opacity while dragging. Yes, another side effect, we can actually simply create another subscription to take care of this.</p> +<pre><code class="language-javascript">const catDragStart$ = dragstart$.pipe(filter((event) =&gt; event.target === cat)) +const catDragEnd$ = dragend$.pipe(filter((event) =&gt; event.target === cat)) +catDragStart$.subscribe((event) =&gt; { + cat.style.opacity = 0 +}) +catDragEnd$.subscribe((event) =&gt; { + cat.style.opacity = 1 +}) +</code></pre> +<p>And done with Level 1!</p> +<p>I want to leave a challenge for the reader. Looking at how we're setting the opacity value of the <code>cat</code> element, could we somehow handle everything in one single subscription instead of two? I would suggest you to read the whole article and come back to this one later, you should be then able to nail it then.</p> +<p>&lt;Accordion summary=&quot;Hint&quot;&gt; +Check the operator &lt;b&gt;merge&lt;/b&gt;. +&lt;/Accordion&gt;</p> +<p>&lt;br /&gt;</p> +<p>&lt;Accordion summary='Solution'&gt;</p> +<pre><code class="language-javascript">const catDragStart$ = dragstart$.pipe(filter((event) =&gt; event.target === cat)) +const catDragEnd$ = dragend$.pipe(filter((event) =&gt; event.target === cat)) +const opacity$ = merge(catDragStart$.pipe(mapTo(0)), catDragEnd$.pipe(mapTo(1))) + +opacity$.subscribe((value) =&gt; { + cat.style.opacity = value +}) +</code></pre> +<p>&lt;/Accordion&gt;</p> +<p>&lt;br /&gt;</p> +<h4>Level 2 - Hacking race conditions with artificial delays</h4> +<ul> +<li>We should announce the cats' arrival with a <code>window.alert</code> message. +<ul> +<li>Before triggering the <code>window.alert</code> we'll want to make sure the <code>cat</code> is actually rendered at the <code>party</code>.</li> +</ul> +</li> +</ul> +<p>So we already have a stream that emits an event each time we drag the cat to the party, it's called <code>catAtParty$</code>. Our work here is mostly done. We just need to subscribe to it.</p> +<pre><code class="language-javascript">catAtParty$.subscribe(() =&gt; { + window.alert('Cat is at the party! You win!') + window.location.reload() +}) +</code></pre> +<p>&lt;br /&gt; +&lt;br /&gt;</p> +<p>&lt;b&gt;window.alert&lt;/b&gt; is triggered when we drop the cat at the &lt;b&gt;party&lt;/b&gt;, but the &lt;b&gt;cat&lt;/b&gt; is not rendered!</p> +<p><img src="./assets/hands-on-reactive-programming-rxjs/game-level-2-no-cat.gif" alt="'demonstration asynchronous rendering cat DOM element'" title="demonstration asynchronous rendering cat DOM element"></p> +<p>Hmm.. strange, we did everything right but the <code>window.alert</code> triggers before we get to actually render the <code>cat</code> at the <code>party</code>, what a bummer.</p> +<p>It seems that the DOM changes don't kick in before we trigger the alert; that's because the rendering is asynchronous, and the page only gets updated later on after we're done with executing this function and dismissing the alert. I don't want to spend much time fixing this since I don't have any mechanism to tell me whether the DOM node has been rendered successfully, I'm going to code a hack and trigger the alert inside a <code>setTimeout</code> that should buy us some time to correctly render the <code>cat</code> element.</p> +<pre><code class="language-javascript">catAtParty$.subscribe(() =&gt; { + setTimeout(() =&gt; { + window.alert('Cat is at the party! You win!') + window.location.reload() + }, 100) +}) +</code></pre> +<p>&lt;br /&gt;</p> +<p>Great! The <code>window.alert</code> is triggered after the <strong>cat</strong> element is rendered at the <strong>party</strong>!</p> +<p><img src="./assets/hands-on-reactive-programming-rxjs/take-cat-to-party.gif" alt="'take the cat to the party game demonstration after applying delay on render'" title="take the cat to the party game demonstration after applying delay on render"></p> +<p>It works. We can achieve a similar <strong>hack</strong> with RxJS without having a solution based on callbacks keeping our subscription handler clean (<strong>but still a hack!</strong>).</p> +<pre><code class="language-javascript">catAtParty$.pipe(delay(100)).subscribe(() =&gt; { + window.alert('Cat is at the party! You win!') + window.location.reload() +}) +</code></pre> +<p>See that &lt;a href=&quot;https://rxjs-dev.firebaseapp.com/api/operators/delay&quot; target=&quot;_blank&quot; title=&quot;RxJS - withLatestFrom&quot;&gt;delay&lt;/a&gt; there? That's another operator. It <strong>allows you to hold the value in the stream for a determined amount of time before pushing it to the subscribers</strong>. So what's happening here is that instead of the Observer (subscription) being aware that we have to code a hack to trigger the alert after the cat enters the party, we shift that responsibility to the Observable, the stream is now the <em>hacky</em> code that is delaying the whole thing. This is super powerful, the way you can shift responsibilities and broadcast your changes to all the subscribers. Yes, all the subscribers will receive a delayed update! <strong>This can be simultaneously a gift and a wrecking footgun!</strong></p> +<h4>Level 3 - Merging streams into one stream</h4> +<ul> +<li>We want to display a GIF while the cat is moving.</li> +</ul> +<p>In our markup, there's a GIF positioned at the top right corner. We want to show it while dragging the cat and hide it again once we drop the cat.</p> +<p>We already have two streams: <code>catDragStart$</code> and <code>catDragEnd$</code> . We need to combine them to form a new stream. Let's try and draw a small diagram of what that stream should look like.</p> +<p><img src="./assets/hands-on-reactive-programming-rxjs/marbles-level-3-1.png" alt="streams of events for dragging and dropping the cat respectively" title="streams of events for dragging and dropping the cat respectively"></p> +<p>&lt;Caption +text={() =&gt; ( +&lt;p&gt; +In the image, &lt;b&gt;ds&lt;/b&gt; symbolizes a &lt;b&gt;dragstart&lt;/b&gt; event, and &lt;b&gt;de&lt;/b&gt; represents a &lt;b&gt;dragend&lt;/b&gt; event. +&lt;/p&gt; +)} +/&gt;</p> +<p>Our GIF is wrapped in an HTML element whose <code>display</code> attribute is set to <code>&quot;none&quot;</code> by default. We'll want to set it to <code>&quot;&quot;</code> (empty string) to make it visible and back to <code>&quot;none&quot;</code> once it's time to hide it again. Our side effect should look something like this:</p> +<pre><code class="language-javascript">// shouldHideGIF would be true in case we need to hide the GIF +const v = shouldHideGIF ? 'none' : '' +loading.style.setProperty('display', v) +</code></pre> +<p>Let's try to build a stream to feed to this function a value for <code>display</code> that reflects whether the cat is being dragged or not. Our stream should remove the necessity of the imperative logic dictated by the ternary operator, <strong>we'll create values that react to changes to replace the imperative code</strong>.</p> +<p>First, let's look back at our streams <code>catDragStart$</code> and <code>catDragEnd$</code>. When <code>catDragStart$</code> emits a value, we want to set the <code>display</code> to <code>&quot;&quot;</code>. So this means we need to somehow transform our stream of events into a stream of empty strings. Each time a string is emitted, we shall pass that value onto the function that will set the <code>display</code> property on the <code>loading</code> element.</p> +<p><img src="./assets/hands-on-reactive-programming-rxjs/marbles-level-3-2.png" alt="stream mapping dragstart events to an empty string" title="stream mapping dragstart events to an empty string"></p> +<p>&lt;Caption text={&quot;Mapping each event to an empty string.&quot;} /&gt;</p> +<p>After you apply an operator, you transform your stream into a more refined one, ideally better suited to the task you're trying to tackle! You're making every pushed value to the topmost timeline flow through a function provided in the <code>map</code> operator. That function will map each value into the bottom timeline, your <em>&quot;final&quot;</em> stream. If you subscribe to it now, you will receive empty strings instead of DOM events!</p> +<p>The same thing should happen to <code>catDragEnd$</code>.</p> +<p><img src="./assets/hands-on-reactive-programming-rxjs/marbles-level-3-3.png" alt="stream mapping dragend events to none" title="stream mapping dragend events to none"></p> +<p>&lt;Caption text={&quot;Mapping each event to 'none'.&quot;} /&gt;</p> +<p>Now we have two streams, and we could just subscribe to each one of them and set the value of the <code>&quot;display&quot;</code> property for the <code>loading</code> element!</p> +<pre><code class="language-javascript">const catDragStartDisplay$ = catDragStart$.pipe(map((event) =&gt; '')) +const catDragEndDisplay$ = catDragEnd$.pipe(map((event) =&gt; 'none')) + +catDragStartDisplay$.subscribe((value) =&gt; loading.style.setProperty('display', value)) +catDragEndDisplay$.subscribe((value) =&gt; loading.style.setProperty('display', value)) +</code></pre> +<p>Although this works, we could further refine our streams and <strong>merge</strong> them into a single one.</p> +<p><img src="./assets/hands-on-reactive-programming-rxjs/marbles-level-3-4.png" alt="two streams merged into a single" title="two streams merged into a single one"></p> +<p>&lt;Caption +text={() =&gt; ( +&lt;p&gt; +&lt;b&gt;A single stream becomes the source of truth&lt;/b&gt; for the &lt;i&gt;display&lt;/i&gt; property value of the &lt;b&gt;loading&lt;/b&gt;{&quot; &quot;} +element throughout time. +&lt;/p&gt; +)} +/&gt;</p> +<p>Awesome! The &lt;a href=&quot;https://rxjs-dev.firebaseapp.com/api/operators/merge&quot; target=&quot;_blank&quot; title=&quot;RxJS - merge&quot;&gt;merge&lt;/a&gt; operator gives us the power to take to streams and merge them into one so that the values of the <code>&quot;display&quot;</code> flow through a single stream (a single source of truth). Here's how the code looks like.</p> +<pre><code class="language-javascript">const catDragStartDisplay$ = catDragStart$.pipe(map((event) =&gt; '')) +const catDragEndDisplay$ = catDragEnd$.pipe(map((event) =&gt; 'none')) +const display$ = merge(catDragStartDisplay$, catDragEndDisplay$) + +display$.subscribe((value) =&gt; loading.style.setProperty('display', value)) +</code></pre> +<p>One subscription to handle the toggling of showing/hiding the GIF.</p> +<h6>Bonus for Level 3</h6> +<p>I feel overwhelmed by the tremendous amount of operators offered by RxJS, its API is considerably large. We could further simplify the above implementation with the operator &lt;a href=&quot;https://rxjs-dev.firebaseapp.com/api/operators/mapTo&quot; target=&quot;_blank&quot; title=&quot;RxJS - mapTo&quot;&gt;mapTo&lt;/a&gt;.</p> +<pre><code class="language-javascript">const catDragStartDisplay$ = catDragStart$.pipe(mapTo('')) +const catDragEndDisplay$ = catDragEnd$.pipe(mapTo('none')) +const display$ = merge(catDragStartDisplay$, catDragEndDisplay$) + +display$.subscribe((value) =&gt; loading.style.setProperty('display', value)) +</code></pre> +<p>You get the same result as applying <code>map</code> but without needing to provide a function, because we're really not doing anything else rather than returning a hardcoded value on that inline function.</p> +<h6>Note about the coding style</h6> +<p>You've might have noticed that I'm creating a new variable per each stream, this allows me to better explain the building blocks of each solution and map them to the diagrams. <strong>Although this helps keeping the code readable, this is not mandatory, it can be overkill per times!</strong>. Streams can be inlined. Let's look again to a small refactor of the above solution to understand what I'm referring to.</p> +<pre><code class="language-javascript">const display$ = merge(catDragStart$.pipe(mapTo('')), catDragEnd$.pipe(mapTo('none'))) + +display$.subscribe((value) =&gt; loading.style.setProperty('display', value)) +</code></pre> +<p>We could drop <code>catDragStartDisplay$</code> and <code>catDragEndDisplay$</code> by inlining them in the <code>merge</code> operator.</p> +<h4>Level 4 - Flatten observables, timers and cancellation</h4> +<ul> +<li>The cat needs to make it to the party in less than 5 seconds! He only has 5 seconds of gas in his bike.</li> +</ul> +<p>We need to code a new path in our game, the one where the user loses the cat isn't dragged to the party within 5 seconds. This looks like a countdown, with a few actions tied to it:</p> +<ol> +<li>The countdown must be <strong>interrupted</strong> if the player manages to win within 5 seconds.</li> +<li>In case the countdown ends, and the cat was not dropped anywhere, we need to display a &quot;<em>game over</em>&quot; message.</li> +</ol> +<p>As in the previous levels, let's build this incrementally. First, let's build a countdown. To emphasize how RxJS shines here, let's first see how we would make a countdown with native JavaScript APIs.</p> +<pre><code class="language-javascript">function countdown(seconds) { + let secondsLeft = seconds + let intervalId + + intervalId = setInterval(() =&gt; { + if (secondsLeft === 0) { + console.log(&quot;time's up!&quot;) + clearInterval(intervalId) + } else { + console.log(secondsLeft) + secondsLeft-- + } + }, 1000) +} +countdown(5) +</code></pre> +<p>That's a rough implementation of a countdown. We need to keep track of the timer identifier in a variable to ensure we clean up after running the <code>setInterval</code> provided callback the expected amount of times.</p> +<p>Now here's how you can do the same with RxJS.</p> +<pre><code class="language-javascript">const countdown$ = timer(0, 1000).pipe( + map((t) =&gt; 5 - t), + takeWhile((t) =&gt; t &gt; 0), +) +countdown$.subscribe({ + next: (secondsLeft) =&gt; console.log(secondsLeft), + complete: () =&gt; console.log(&quot;time's up!&quot;), +}) +</code></pre> +<p>Let's examine the above code ๐Ÿคฏ. This time we're not creating a stream from an event or anything like that. Instead, we use &lt;a href=&quot;https://rxjs-dev.firebaseapp.com/api/index/function/timer&quot; target=&quot;_blank&quot; title=&quot;RxJS - mapTo&quot;&gt;timer&lt;/a&gt; from RxJS to generate a timer stream that emits a new numeric value every second.</p> +<p>Now a timer emits values in ascending order (0, 1, 3, etc.). We'll want to reverse that to display an actual countdown. To reverse the numerical value on the stream, we simply use <code>map</code>. We should only give the user 5 seconds to drag the <code>cat</code> to the <code>party</code>. This translates to stop emitting values when we hit the mark of the <code>0</code> seconds, this is controlled by the &lt;a href=&quot;https://rxjs-dev.firebaseapp.com/api/operators/takeWhile&quot; target=&quot;_blank&quot; title=&quot;RxJS - takeWhile&quot;&gt;takeWhile&lt;/a&gt; operator. A few evident advantages compared with the classic implementation:</p> +<ol> +<li><strong>No global variables</strong> <code>secondsLeft</code> and <code>intervalId</code>.</li> +<li><strong>Less error-prone code</strong> by relying on <code>takeWhile</code> and other built-in operators to be the rail guards of our business logic.</li> +<li>Once the stream emits the 6th value, <strong>it will complete automatically</strong> and stop emitting values! <strong>We don't need to worry about cleanup logic</strong> - with <code>setInterval</code> we need to worry about cleaning the interval with <code>clearInterval</code> so that it does not run indefinitely!</li> +</ol> +<p><img src="./assets/hands-on-reactive-programming-rxjs/marbles-level-4-1.png" alt="diagram depicting the countdown stream" title="diagram depicting the countdown stream"></p> +<p>&lt;Caption /&gt;</p> +<p>In the above image, the bottom timeline represents the stream; notice the pipe (<strong>|</strong>) at the end of the timeline indicates that the stream completes after emitting the value <strong>1</strong>.</p> +<p>A crucial aspect to notice is that you'll trigger a new timer as soon as you subscribe. This is a &lt;a href=&quot;https://danielcaldas.github.io/posts/reactive-programming-fundamentals/#hot-observable-vs-cold-observable&quot; target=&quot;_blank&quot; title=&quot;Fundamentals of Reactive Programming, Cold Observable&quot;&gt;Cold Observable&lt;/a&gt;. Our <code>countdown$</code> will start emitting values right away upon subscription, so we can't just <code>.subscribe()</code> We'll have to subscribe to the countdown when the user starts dragging the <code>cat</code> element.</p> +<p>Now remember we had a stream <code>catDragStart$</code>; we'll use that to trigger our countdown. <strong>But how can we trigger a subscription to a stream from within another stream?</strong> In my opinion, this is a hard concept to comprehend.</p> +<p>Presenting the &lt;a href=&quot;https://rxjs-dev.firebaseapp.com/api/operators/switchMap&quot; target=&quot;_blank&quot; title=&quot;RxJS - switchMap&quot;&gt;switchMap&lt;/a&gt; operator. <code>switchMap</code> allows you to trigger a subscription from within a stream to a second stream. Not clear yet? Think of it this way <code>switchMap</code> <strong>allows you to switch to a new observable</strong>. Ok, let's look at our countdown implementation, now that we have this information.</p> +<pre><code class="language-javascript">const countdown$ = timer(0, 1000).pipe( + map((t) =&gt; 5 - t), + takeWhile((t) =&gt; t &gt;= 0), +) +const countdownRunning$ = catDragStart$.pipe(switchMap((event) =&gt; countdown$)) + +countdownRunning$.subscribe((secondsLeft) =&gt; { + countdown.innerText = `${secondsLeft}` +}) +</code></pre> +<p><strong>Note</strong>: You might have noticed that I'm using <code>t =&gt; t &gt;= 0</code> &quot;<em>greater or equal to</em>&quot; in our predicate. This is just for convenience, so that we get <code>0</code> pushed onto the stream to easily rendered it, finishing the countdown at <code>0</code>.</p> +<p><img src="./assets/hands-on-reactive-programming-rxjs/marbles-level-4-2.png" alt="diagram to demonstrate the switchMap operator" title="diagram to demonstrate the switchMap operator"></p> +<p>&lt;Caption text={() =&gt; &lt;p&gt;We're switching from one stream to another and subscribing to it immediately.&lt;/p&gt;} /&gt;</p> +<p>Notice the return value of <code>switchMap</code> is <code>countdown$</code>, this means that when a <code>dragstart</code> event is emitted in <code>catDragStart$</code> the following things will happen:</p> +<ol> +<li>Since we subscribed, the DOM event will flow through the pipe and hit our <code>switchMap</code> operator.</li> +<li>We'll automatically subscribe to <code>countdown$</code> and start emitting its values leaving the <code>dragstart</code> event behind (because we don't really need it for this task).</li> +<li>In our subscription handler, we simply do a DOM mutation to the element that displays our countdown value so that the user gets the time left displayed on the screen.</li> +</ol> +<p>Once more, as soon as a <code>ds</code> (<code>dragstart</code> event in the diagram) is pushed to our <code>catDragStart$</code> stream, <code>switchMap</code> will kick in and subscribe to our <code>countdown$</code> stream, <em>&quot;passing the ball&quot;</em> (moving the flow of control) to the <code>countdown$</code>. We'll start to push to our subscribers the seconds left in the countdown like if we had subscribed to the <code>countdown$</code> in the first place, but in this case <strong>we programmatically triggered the subscription</strong>.</p> +<p>Although we're going more deep into <em>flattening</em> Observables in this level, we've already seen this before in this article. If you're thinking about <code>withLatestFrom</code> from <strong>Level 1</strong>, you're right! That operator also triggers a inner subscription.</p> +<p>We can now leverage <code>countdownRunning$</code> to display an alert.</p> +<pre><code class="language-javascript">countdownRunning$.pipe(filter((secondsLeft) =&gt; secondsLeft === 0)).subscribe(() =&gt; { + window.alert(&quot;Cat didn't make it! You lose!&quot;) + window.location.reload() +}) +</code></pre> +<p>We have a filter to check for the second <code>0</code>; this way, we're basically ensuring that we just emit a value when the countdown runs out. Similarly to <strong>Level 2</strong>, we have a <code>delay</code> with random time to safeguard our countdown value gets rendered with <code>0</code> on the screen before we trigger the alert.</p> +<p>Only one thing left! If you're following up until this point, you're probably wondering why the counter is not reset once you stop dragging the cat. See the following GIF to understand what I'm referring to.</p> +<p><img src="./assets/hands-on-reactive-programming-rxjs/take-cat-to-party-no-cancel.gif" alt="'counter does not stop demonstration'" title="counter does not stop demonstration"></p> +<p>Hmm.. Strange the counter does not reset once we drop the cat! It should, since the 5 seconds didn't ran out while we were dragging the cat. Let's look at our <code>countdown$</code> implementation once more.</p> +<pre><code class="language-javascript">const countdown$ = timer(0, 1000).pipe( + map((t) =&gt; 5 - t), + takeWhile((t) =&gt; t &gt;= 0) +); +const countdownRunning$ = catDragStart$.pipe(switchMap((event) =&gt; countdown$); +</code></pre> +<p>Now, it's pretty clear that we're not doing anything to &quot;stop&quot; the counter. Once we subscribe to <code>countdown$</code> with the <code>switchMap</code> operator, there's no stopping it, it will go on until it reaches the end, and when this happens, we'll display the alert to the user. Ideally, we would stop the counter upon any of the two following events:</p> +<ol> +<li><strong>When the cat arrives at the party</strong>. Also partially done, we have a stream <code>catAtParty$</code>, once a value is pushed into this stream, you know the <code>cat</code> was dropped at the <code>party</code>.</li> +<li><strong>We stop dragging the cat</strong>. We already have a stream <code>catDragEnd$</code>, each emitted value means we just dropped the <code>cat</code> element. You might be thinking the first point would be enough, but remember the cat can be dropped literally anywhere on the screen. We have no guarantees that it will get dropped at the party.</li> +</ol> +<p>Meet &lt;a href=&quot;https://rxjs-dev.firebaseapp.com/api/operators/takeUntil&quot; target=&quot;_blank&quot; title=&quot;RxJS - takeUntil&quot;&gt;takeUntil&lt;/a&gt;, this operator allows you to listen to a stream until something happens! Let's look at the implementation of the <code>countdown$</code>.</p> +<pre><code class="language-javascript">const countdown$ = timer(0, 1000).pipe( + map((t) =&gt; 5 - t), + takeWhile((t) =&gt; t &gt;= 0), + takeUntil(/*something we'll figure out later*/), +) +</code></pre> +<p>Now, what is the <strong>argument</strong> of the <code>takeUntil</code> operator? <code>takeUntil</code> receives a stream, once that <strong>stream</strong> emits a value, our <code>countdown$</code> will stop emitting, thus canceling the timer and giving our users another chance to dragging the <code>cat</code> to the <code>party</code>.</p> +<p>But we have two streams, <code>catDragEnd$</code> and <code>catAtParty$</code>, can we make it one? Yes, we can! We've already met <code>merge</code>! This operator can take multiple streams and combine into a single one, think of it as a funnel.</p> +<p>Let's call this last stream <code>cancelCountdown$</code>. After we declare it, we can feed it as an input to our <code>takeUntil</code> operator. Here's the final implementation of Level 4 with cancellation.</p> +<pre><code class="language-javascript">const cancelCountdown$ = merge(catDragEnd$, catAtParty$); +const countdown$ = timer(0, 1000).pipe( + map((t) =&gt; 5 - t), + takeWhile((t) =&gt; t &gt;= 0), + takeUntil(cancelCountdown$) +); +const countdownRunning$ = catDragStart$.pipe(switchMap((event) =&gt; countdown$); + +countdownRunning$.subscribe((secondsLeft) =&gt; { + countdown.innerText = `${secondsLeft}`; +}); +countdownRunning$ + .pipe( + filter((secondsLeft) =&gt; secondsLeft === 0), + delay(100) + ) + .subscribe(() =&gt; { + window.alert(&quot;Cat didn't make it! You lose!&quot;); + window.location.reload(); + }); +</code></pre> +<p><img src="./assets/hands-on-reactive-programming-rxjs/marbles-level-4-3-4.png" alt="stream representation of the countdown with cancellation" title="stream representation of the countdown with cancellation"></p> +<p>&lt;Caption +text={(a) =&gt; ( +&lt;p&gt; +In this diagram, our stream gets cancelled after the value &lt;b&gt;3&lt;/b&gt; is published. The first &lt;b&gt;dragend&lt;/b&gt; event +causes the &lt;b&gt;countdownRunning$&lt;/b&gt; to stop emitting values. +&lt;/p&gt; +)} +/&gt;</p> +<p>You can check out &lt;a href=&quot;https://github.com/danielcaldas/take-the-cat-to-the-party&quot; target=&quot;_blank&quot; title=&quot;danielcaldas/take-the-cat-to-the-party: Learn reactive programming with RxJS the fun way&quot;&gt;the complete implementation of this challenge here&lt;/a&gt;.</p> +<p>&lt;br /&gt;</p> +<h4>Closing Notes</h4> +<p>Here are the concepts you've learned by building this game:</p> +<ol> +<li><strong>Creating streams</strong> from DOM events.</li> +<li><strong>Manipulating streams</strong> with basic operators such as <code>map</code>, <code>filter</code>.</li> +<li><strong>Combining streams</strong> with operators such as: <code>withLatestFrom</code>, <code>switchMap</code>, and <code>merge</code>.</li> +<li><strong>Cancellation</strong>, being able to stop listening to a specific stream based on another. In our last level, we used <code>takeUntil</code> to achieve this.</li> +</ol> +<p>Now go out there and try to apply these concepts to build your own applications. I would be pleased to hear from you if you've learned something here today.</p> +<p>In the upcoming articles, I'll share with you the good and bad parts of adopting reactive programming and start using RxJS in your codebase, and at last, I'll share with you a bunch of fantastic resources to learn RxjS and reactive programming.</p> +<p>&lt;br /&gt;</p> +<ul> +<li>&lt;a href=&quot;https://danielcaldas.github.io/posts/why-reactive-programming&quot; target=&quot;_blank&quot; title=&quot;Why You Should Consider Reactive Programming | danielcaldas.github.io&quot;&gt;Part 1 - Why You Should Consider Reactive Programming&lt;/a&gt;</li> +<li>&lt;a href=&quot;https://danielcaldas.github.io/posts/reactive-programming-fundamentals&quot; target=&quot;_blank&quot; title=&quot;Fundamentals of Reactive Programming | danielcaldas.github.io&quot;&gt;Part 2 - Fundamentals of Reactive Programming&lt;/a&gt;</li> +<li><strong>Part 3 - Hands-on Reactive Programming with RxJS</strong></li> +<li>&lt;a href=&quot;https://danielcaldas.github.io/posts/reactive-rxjs-pros-cons&quot; target=&quot;_blank&quot; title=&quot;Reactive Programming: The Good and the Bad | danielcaldas.github.io&quot;&gt;Part 4 - Reactive Programming: The Good and the Bad&lt;/a&gt;</li> +<li>&lt;a href=&quot;https://danielcaldas.github.io/posts/awesome-reactive&quot; target=&quot;_blank&quot; title=&quot;Awesome RxJS and Reactive Programming Resources | danielcaldas.github.io&quot;&gt;Part 5 - Awesome RxJS and Reactive Programming Resources&lt;/a&gt;</li> +</ul> +The hidden potential of webpack DefinePluginhttps://danielcaldas.github.io/posts/hidden-potential-webpack-define-plugin/https://danielcaldas.github.io/posts/hidden-potential-webpack-define-plugin/You can author content using the familiar markdown syntax you already know. All basic markdown syntax is supported.Thu, 30 May 2019 00:00:00 GMT<p>The odds are high that you're using &lt;a href=&quot;https://webpack.js.org/&quot; target=&quot;_blank&quot; title=&quot;a bundler for javascript and friends&quot;&gt;webpack&lt;/a&gt; to bundle your assets. If you're looking into an excellent way to feed environment variables to your applications, &lt;a href=&quot;https://webpack.js.org/plugins/define-plugin/#usage&quot; target=&quot;_blank&quot; title=&quot;the defineplugin allows you to create global constants which can be configured at compile time&quot;&gt;DefinePlugin&lt;/a&gt; might well be the answer.</p> +<h3>tl;dr</h3> +<p>If you want to pass environment variables and use it within your JavaScript, you need to:</p> +<ol> +<li>Create the environment variable.</li> +</ol> +<pre><code class="language-bash">MY_ENV_VAR=&quot;sweet env var&quot; +</code></pre> +<ol start="2"> +<li>In your webpack config, use the <code>webpack.DefinePlugin</code>.</li> +</ol> +<pre><code class="language-javascript">new webpack.DefinePlugin({ + // it won't work without JSON.stringify!!! + myEnvVar: JSON.stringify(process.env.MY_ENV_VAR), +}) +</code></pre> +<ol start="3"> +<li>In your JavaScript.</li> +</ol> +<pre><code class="language-javascript">console.log(`Accessing my env var like a ninja: ${myEnvVar}`) +</code></pre> +<p>&lt;br /&gt; +&lt;br /&gt;</p> +<h3>Some Background</h3> +<p>One of these days, I had this exciting challenge, I was integrating some third party that required from me to provide the current commit hash of the code to a JavaScript SDK that performs some magic in bug tracking and versioning.</p> +<p>Well seemed pretty simple for me at the time. I need to take the commit hash at build time and somehow feed it to our JavaScript, whatever that means.</p> +<p>In this blog post, I'll explain step by step how to implement the following:</p> +<blockquote> +<p>We want to log in the console the link to the GitHub history of the application +that points to the commit of the current version of the application.</p> +</blockquote> +<h4>The step by step</h4> +<p>Here's a step by step implementation of the previously announced challenge. I will use one of my open source pet projects on GitHub, &lt;a href=&quot;https://github.com/danielcaldas/el-conversor&quot; target=&quot;_blank&quot; title=&quot;a number to word list converter as a node backend and react/redux fronted&quot;&gt;el-conversor&lt;/a&gt;.</p> +<h5>Step 1 - Grab the commit hash</h5> +<p>I'm going to do this within a npm script in the &lt;a href=&quot;https://github.com/danielcaldas/el-conversor/blob/master/package.json#L17&quot; target=&quot;_blank&quot; title=&quot;a number to word list converter as a node backend and react/redux fronted package.json&quot;&gt;package.json&lt;/a&gt;. In the <code>dev</code> npm script you can find the following:</p> +<pre><code class="language-javascript">&quot;dev&quot;: &quot;COMMIT_HASH=\&quot;$(git rev-parse HEAD)\&quot; npm-run-all --parallel *:dev&quot;, +</code></pre> +<p>So in <code>COMMIT_HASH=\&quot;$(git rev-parse HEAD)\&quot;</code> we are basically assigning the output of the command <code>git rev-parse HEAD</code> to an env variable <code>COMMIT_HASH</code>.</p> +<h5>Step 2 - Configure webpack.DefinePlugin</h5> +<p>Now, let's dive into the &lt;a href=&quot;https://github.com/danielcaldas/el-conversor/blob/master/package.json#L17&quot; target=&quot;_blank&quot; title=&quot;a number to word list converter as a node backend and react/redux fronted package.json&quot;&gt;webpack.config.js&lt;/a&gt;. Well it's not that complicated, the only nasty detail is that even if you want to pass in a simple string, you need to wrap it with a <code>JSON.stringify</code> otherwise, webpack dumps whatever text comes out of your environment variable and dump it in your JavaScript, and you get a very nasty syntax error. So let's use the &lt;a href=&quot;https://webpack.js.org/plugins/define-plugin/#usage&quot; target=&quot;_blank&quot; title=&quot;the defineplugin allows you to create global constants which can be configured at compile time&quot;&gt;DefinePlugin&lt;/a&gt;:</p> +<pre><code class="language-javascript">new webpack.DefinePlugin({ + __commitHash__: JSON.stringify(process.env.COMMIT_HASH), +}) +</code></pre> +<p><strong>Pro Tip</strong>: You might be wondering why the strange naming <code>__commitHash__,</code> well the prefix and suffix underscores are just a way to prevent naming collisions. I hope that you don't often use <code>__var__</code> as a style to name variables in JavaScript, well I don't, that's why I found this extremely unlikely to collide.</p> +<h5>Step 3 - Use the environment variables</h5> +<p>In the end, we use the environment variable. In this case to <code>console.log</code> a helpful GitHub link:</p> +<pre><code class="language-javascript">console.log( + `You are running on this commit of the application https://github.com/danielcaldas/el-conversor/commit/${__commitHash__}`, +) +</code></pre> +<p>You can see the final result in the image below, it logs:</p> +<p><img src="./assets/hidden-potential-webpack-define-plugin/console-log.png" alt="final result in console of el-conversor" title="final result in console of el-conversor"></p> +<p>&lt;Caption /&gt;</p> +How to overengineer copying files from any Android devicehttps://danielcaldas.github.io/posts/how-to-copy-file-android/https://danielcaldas.github.io/posts/how-to-copy-file-android/(especially if you have an Apple computer)Thu, 01 Jul 2021 00:00:00 GMT<p><strong>tl;dr</strong> SSH into your android and download the files with &lt;a href=&quot;https://linux.die.net/man/1/scp&quot; target=&quot;_blank&quot; title=&quot;scp(1): secure copy - Linux man page&quot;&gt;scp&lt;/a&gt;. &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.arachnoid.sshelper&quot; target=&quot;_blank&quot; title=&quot;SSHelper - Apps on Google Play&quot;&gt;This one&lt;/a&gt; works for me.</p> +<p>So this afternoon, I had decided to take some time to clear my smartphone, archive some old pictures and stuff. I usually park them on an external drive. Anyways, it happens that my &lt;a href=&quot;https://www.oneplus.com&quot; target=&quot;_blank&quot; title=&quot;OnePlus 6 - OnePlus (Nederland)&quot;&gt;OnePlus 6&lt;/a&gt; does not connect to my new shiny &lt;a href=&quot;https://www.apple.com/macbook-pro/&quot; target=&quot;_blank&quot; title=&quot;MacBook Pro - Apple&quot;&gt;MacBook Pro&lt;/a&gt;.</p> +<p>&lt;br /&gt; +(โ•ฏยฐโ–กยฐ)โ•ฏ๏ธต โ”ปโ”โ”ป +&lt;br /&gt; +&lt;br /&gt; +&lt;br /&gt;</p> +<h4>USB/USB-C Cables</h4> +<p>I tried, but Apple doesn't make it easy to recognize other devices.</p> +<h4>SSH to the rescue!</h4> +<p>I start browsing the Google Play store for some apps that allow me to run an SSH server on my smartphone. Quickly I've stumbled into &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.arachnoid.sshelper&quot; target=&quot;_blank&quot; title=&quot;SSHelper - Apps on Google Play&quot;&gt;this&lt;/a&gt;.</p> +<p>Straightforward setup, as you can see in the screenshot below.</p> +<p><img src="./assets/how-to-copy-file-android/ssh-server-helper-android-sm.png" alt="ssh server app running on android device" title="ssh server app running on android device"></p> +<p>Since my laptop and my smartphone are on the same network, they can communicate.</p> +<pre><code class="language-bash">ssh admin@&lt;my_phone_ip&gt; -p &lt;ssh_server_port_from_app&gt; +# enter password... + +# then just copy stuff to my MacBook +scp -r Images &lt;my_macbook_user&gt;@&lt;my_macbook_ip&gt;:~/Desktop +</code></pre> +<p>Hope this tip helps you out in case you get stuck due to the usual apple <em>mumbo jumbo</em>.</p> How to Fix GitHub Actions: Support for password authentication was removedhttps://danielcaldas.github.io/posts/how-to-fix-github-password-authentication/https://danielcaldas.github.io/posts/how-to-fix-github-password-authentication/Linking private repos as npm dependencies the proper wayMon, 01 Aug 2022 00:00:00 GMT<p>Today the GitHub PAT (<em>personal access token</em>) expired in <a href="https://tweak-extension.com">one of my projects</a>, I literally waked up to this:</p> <pre><code>npm ERR! code 128 npm ERR! An unknown git error occurred @@ -1935,7 +2786,7 @@ hand remember that you getting each time more experiment and more comfortable ar </ul> <h3>Final note</h3> <p>Again much of the written above is just my opinion. I hope you find useful some of the sections of this post even if you don't work on the tech industry.</p> -There's something off about Open Sourcehttps://danielcaldas.github.io/posts/open-source-dilemma/https://danielcaldas.github.io/posts/open-source-dilemma/OSS dilemmaSat, 02 Mar 2024 00:00:00 GMT<blockquote> +There's something off about Open Sourcehttps://danielcaldas.github.io/posts/open-source-dilemma/https://danielcaldas.github.io/posts/open-source-dilemma/OSS dilemmaWed, 16 Dec 2020 00:00:00 GMT<blockquote> <p><strong>Update Oct 12th 2024</strong></p> <p>I didn't modify this article. I don't fully agree with my views here anymore. The problem is not on FOSS, but rather on peoples' expectations and approach to FOSS. I'm still of the opinion that FOSS is not at a fully sustainable stage. Major projects today are typically funded and it's no longer rare to see full-time employed developers work on maintaining FOSS these days.</p> </blockquote> @@ -1967,7 +2818,593 @@ hand remember that you getting each time more experiment and more comfortable ar </ol> <p>I'm mostly worried about the last point. Would this mean that restrictions on who can contribute to the source would damage its quality? Simultaneously, would software companies that are just starting out be completely crashed because they now have to pay for every software they use?</p> <p>This is quite a dilemma for me. I'm interested to see how all this is going pan out. My bet is on the money. Money wins, most of the time.</p> -techReactive Serieshttps://danielcaldas.github.io/posts/reactive-series-preface/https://danielcaldas.github.io/posts/reactive-series-preface/A series of articles on Reactive Programming and RxJSWed, 22 Jul 2020 00:00:00 GMT<p>Over the past two years, I invested some time learning &lt;a href=&quot;https://en.wikipedia.org/wiki/Reactive_programming&quot; target=&quot;_blank&quot; title=&quot;Reactive programming - Wikipedia&quot;&gt;Reactive Programming&lt;/a&gt; (with &lt;a href=&quot;https://rxjs-dev.firebaseapp.com/&quot; target=&quot;_blank&quot; title=&quot;RxJS&quot;&gt;RxJS&lt;/a&gt;) and +Deleted my git stash! Please send help!https://danielcaldas.github.io/posts/please-help-deleted-my-git-stash/https://danielcaldas.github.io/posts/please-help-deleted-my-git-stash/You can author content using the familiar markdown syntax you already know. All basic markdown syntax is supported.Sun, 09 Dec 2018 00:00:00 GMT<p>I have this poor habit you know... Whenever I accumulate a lot of git stashes in some repository I just get a little maniac and start cleaning everything.</p> +<p><img src="./assets/please-help-deleted-my-git-stash/please-send-help.jpg" alt="please send help" title="please send help doggo meme"></p> +<p>&lt;Caption source=&quot;https://me.me/i/please-send-help-none-c0081e69a4f4419a824e82a234ac09e6&quot; /&gt;</p> +<p>I found it sometimes even funny to open the terminal and play around with some bash to delete the stashes, look:</p> +<pre><code class="language-bash"># if you're just flashing by don't copy and past this line into your terminal +for i in {1..10}; do git stash drop; done +</code></pre> +<p>Well, it happens that today (a few hours ago actually) I just did this and regretted the moment I did because there was this huge feature that for some reason I had locally stashed and not committed &lt;br/&gt; ยฏ\_(ใƒ„)_/ยฏ.</p> +<p><strong>Good news!</strong>, the commits are not actually gone until git runs &lt;a href=&quot;https://en.wikipedia.org/wiki/Garbage_collection_(computer_science)#Reachability_of_an_object&quot; target=&quot;_blank&quot; title=&quot;wiki page garbage collection&quot;&gt;<em>garbage collection</em>&lt;/a&gt;, so to check the list of commits that might still be rescued just go ahead and type:</p> +<pre><code class="language-bash">git fsck --unreachable +</code></pre> +<p>or maybe</p> +<pre><code class="language-bash">git fsck --unreachable | grep commit +</code></pre> +<p>because you actually need to check whether your hash is somewhere referenced as a <strong>unreachable commit</strong>.</p> +<p>Then all you need to do once you find your <em>stash</em> (if you don't know the hash I'm afraid you will have to apply stashes until you find it) is just:</p> +<pre><code class="language-bash">git stash apply &lt;hash&gt; +</code></pre> +<p>Here, some cool references on git stash:</p> +<ul> +<li>&lt;a href=&quot;https://www.atlassian.com/git/tutorials/saving-changes/git-stash&quot; target=&quot;_blank&quot; title=&quot;atlassian tutorials git stash&quot;&gt;Atlassian - Git stash&lt;/a&gt;</li> +<li>&lt;a href=&quot;https://community.atlassian.com/t5/Sourcetree-questions/Retrieve-a-deleted-stash/qaq-p/162673&quot; target=&quot;_blank&quot; title=&quot;atlassian community recover deleted stash&quot;&gt;Atlassian community - Retrieve a deleted stash&lt;/a&gt;</li> +</ul> +<p>Notice that the references above are from Atlassian, which I highly recommend when it comes to quick search into a specific Git topic.</p> +<p>And that's it, hope this saves you just like it saved me.</p> +<p>Have a nice day.</p> +Presenting babel-plugin-cloudinaryhttps://danielcaldas.github.io/posts/presenting-babel-plugin-cloudinary/https://danielcaldas.github.io/posts/presenting-babel-plugin-cloudinary/Presenting babel-plugin-cloudinaryThu, 04 Apr 2019 00:00:00 GMT<p>During the past two months, I've been developing a side project at work, this started with me doing a simple refactor task. At the time I looked at the solution we had in place, and it was dirty, but it was necessary to skip some considerable performance penalty. Maybe it was something that we just had to learn to live with...</p> +<p><img src="./assets/presenting-babel-plugin-cloudinary/wrong-kido.jpg" alt="wrong kiddo meme" title="wrong kiddo meme"></p> +<p>&lt;cite&gt;source: https://imgur.com/gallery/J3WYR&lt;/cite&gt;</p> +<p>Hell no! At the time the alternatives weren't that promising, but soon I realized the untapped potential of doing work at build time with &lt;a href=&quot;https://babeljs.io/&quot; target=&quot;_blank&quot; title=&quot;babel or babel.js is a free and open-source javascript compiler and configurable transpiler used in web development&quot;&gt;babeljs&lt;/a&gt;. I'm very proud to present &lt;a href=&quot;https://github.com/trivago/babel-plugin-cloudinary&quot; target=&quot;_blank&quot; title=&quot;official repository for babel-plugin-cloudinary compile cloudinary urls at build time.&quot;&gt;babel-plugin-cloudinary&lt;/a&gt;. In &lt;a href=&quot;https://tech.trivago.com/2019/04/02/presenting-babel-plugin-cloudinary/&quot; target=&quot;_blank&quot; title=&quot;trivago tech blog official tech blog for babel-plugin-cloudinary&quot;&gt;this trivago tech blog article&lt;/a&gt;, you can find all the details and motivation behind the plugin. +And by the way, if you want to know what the heck am I talking about, just take a look at the official repository of &lt;a href=&quot;https://github.com/trivago/babel-plugin-cloudinary&quot; target=&quot;_blank&quot; title=&quot;official repository for babel-plugin-cloudinary compile cloudinary urls at build time.&quot;&gt;babel-plugin-cloudinary&lt;/a&gt;.</p> +Reactive Series (pt. 2) - Fundamentals of Reactive Programminghttps://danielcaldas.github.io/posts/reactive-programming-fundamentals/https://danielcaldas.github.io/posts/reactive-programming-fundamentals/Reactive Series (pt. 2) - Fundamentals of Reactive ProgrammingThu, 03 Sep 2020 00:00:00 GMT<ul> +<li>&lt;a href=&quot;https://danielcaldas.github.io/posts/why-reactive-programming&quot; target=&quot;_blank&quot; title=&quot;Why You Should Consider Reactive Programming | danielcaldas.github.io&quot;&gt;Part 1 - Why You Should Consider Reactive Programming&lt;/a&gt;</li> +<li><strong>Part 2 - Fundamentals of Reactive Programming</strong></li> +<li>&lt;a href=&quot;https://danielcaldas.github.io/posts/hands-on-reactive-programming-rxjs&quot; target=&quot;_blank&quot; title=&quot;Hands-on Reactive Programming with RxJS | danielcaldas.github.io&quot;&gt;Part 3 - Hands-on Reactive Programming with RxJS&lt;/a&gt;</li> +<li>&lt;a href=&quot;https://danielcaldas.github.io/posts/reactive-rxjs-pros-cons&quot; target=&quot;_blank&quot; title=&quot;Reactive Programming: The Good and the Bad | danielcaldas.github.io&quot;&gt;Part 4 - Reactive Programming: The Good and the Bad&lt;/a&gt;</li> +<li>&lt;a href=&quot;https://danielcaldas.github.io/posts/awesome-reactive&quot; target=&quot;_blank&quot; title=&quot;Awesome RxJS and Reactive Programming Resources | danielcaldas.github.io&quot;&gt;Part 5 - Awesome RxJS and Reactive Programming Resources&lt;/a&gt;</li> +</ul> +<p>&lt;br&gt;</p> +<ul> +<li><a href="#the-observer-pattern">The Observer Pattern</a> +<ul> +<li><a href="#observer-pattern-real-life-analogy">Observer Pattern: Real-Life Analogy</a></li> +<li><a href="#naming-the-things-we-already-know">Naming the things we already know</a></li> +<li><a href="#a-naive-use-case-for-the-observer-pattern-in-javascript">A naive use case for the Observer pattern in JavaScript</a></li> +<li><a href="#observable-as-a-first-class-citizen">Observable as a first-class citizen</a></li> +</ul> +</li> +<li><a href="#reactive-programming">Reactive Programming</a> +<ul> +<li><a href="#core-properties-of-observables">Core Properties of Observables</a></li> +<li><a href="#stream">Stream</a></li> +<li><a href="#operators">Operators</a></li> +<li><a href="#hot-observable-vs-cold-observable">Hot Observable VS Cold Observable</a></li> +<li><a href="#brief-history-of-reactive-programming">Brief History of Reactive Programming</a></li> +<li><a href="#quotes">Quotes</a></li> +</ul> +</li> +</ul> +<p>Let's continue our journey on Reactive Programming. This part will highlight some of this paradigm's key concepts for quick learning.</p> +<h4>The Observer Pattern</h4> +<p>You might have encountered several diagrams and explanations of the Observer pattern. For those of you that might not yet be familiar +with it, allow me to explain it with an example-based approach, a very different one than what I had at school.</p> +<blockquote> +<p>&quot;Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.&quot; by &lt;a href=&quot;https://sourcemaking.com/design_patterns/observer&quot; target=&quot;_blank&quot; title=&quot;Observer Design Pattern&quot;&gt;sourcemaking.com&lt;/a&gt;</p> +</blockquote> +<p>I didn't get it for the first time with this definition and set me away instead of fascinating me. I thought this would be +a super tricky thing to get. But not if you've had <strong>explained it to me with something we all know</strong> like youtube.</p> +<h5>Observer Pattern: Real-Life Analogy</h5> +<p>Let's look at <em>youtube.com</em> for a second. I'll use it as an example to explain this pattern's <strong>two core participants</strong>: <strong>Observer</strong> and <strong>Observable</strong>.</p> +<p>Your browsing on youtube and you find a new <strong>channel</strong>. You want to <strong>receive notifications</strong> anytime some new +<strong>content is published into that youtube channel</strong>, but for that, you'll need to <strong>hit the subscribe button first</strong>. You are the <strong>Observer</strong>, consuming the content posted (published) by that youtube channel. This makes the youtube channel the <strong>Observable</strong>. Another vital aspect to see is the multiplicity of this relationship. There's <strong>one youtube channel to many youtube users</strong> (subscribers).</p> +<h5>Naming the things we already know</h5> +<p>Keeping this analogy in mind, let's clarify a couple more concepts that complement the relationship between an Observable and its Observables.</p> +<ul> +<li><strong>Producer or creator</strong> - something that generates/publishes content. Back to our analogy, the content creator is the youtube +channel owner. An Observable produces data.</li> +<li><strong>Consumer or client</strong> - something that subscribes to new content (data) published by the producers; our user is the consumer in our analogy. He or she gets notified when new content is out.</li> +<li><strong>Subscription</strong> - think of it as an object that holds the information about you subscribing to that youtube channel. One crucial thing that the subscription must own is a way for you to unsubscribe from that youtube channel when you no longer want to be notified of new content.</li> +<li><strong>Pull</strong> - we talk about a pull protocol when a consumer/client explicitly decides when to get data from the producer/creator. For instance, when we create a simple function and call it to do some work for us.</li> +<li><strong>Push</strong> - in these systems, the consumer/client doesn't know exactly when new content is published. Our youtube analogy describes such a system where the channel subscribers have no clue when a new video will be posted.</li> +</ul> +<p>These concepts are more comfortable to follow when backing them up with some real-life example, but most importantly, it is good that you possess a <strong>grasp of the jargon</strong>. <strong>It's crucial to clearly express yourself</strong> with your peers. Last but not least, this <strong>becomes super relevant when you're trying to Google your away around some issue</strong>.</p> +<h5>A naive use case for the Observer pattern in JavaScript</h5> +<p>Now let's get our hands on and implement the Observer pattern for our youtube case study. The idea is to use the Observer pattern to notify many youtube users when a new video is published into the channel. We'll keep the example slim and straightforward. The main idea is to have an end-to-end perception of how the data flows in our application when using Observables. We won't have a UI here. We will just use <code>console.log</code> to signal some events in our small program.</p> +<pre><code class="language-javascript">class Video { + constructor(title) { + this.title = title + } +} + +class Channel { + constructor(name, subscribers = []) { + this.name = name + this.subscribers = subscribers + this.videos = [] + } + + // subscribes a user to an instance of channel + // returns the unsubscription function + subscribe(user) { + this.subscribers.push(user) + + const userIndex = this.subscribers.length - 1 + + // remember we need to allow our users + // to unsubscribe anytime + const unsubscribe = () =&gt; { + this.subscribers.splice(userIndex, 1) + } + + return unsubscribe + } + + // publishes a new video in this channel notifying + // all its subscribers + publish(video) { + this.videos.push(video) + this.subscribers.forEach((user) =&gt; { + user.notify(` + Hey there ${user.email}! + There's a new video from ${this.name} + ${video.title} + `) + }) + } +} + +class User { + constructor(email) { + this.email = email + } + + // just logs a notification update + notify(update) { + console.log(update) + } +} + +function main() { + // our platform has 3 users: elisa, hans and mark + const elisa = new User('elisa@gmail.com') + const hans = new User('hans@gmail.com') + const mark = new User('mark@gmail.com') + + // our platform has 1 channel &quot;#FunnyVide0s&quot; + const funnyVideosChannel = new Channel('#FunnyVide0s') + + // Our scenario + + // elisa, hans and mark hit the subscribe button on &quot;#FunnyVide0s&quot; + const subscriptions = [elisa, hans, mark].map((user) =&gt; { + return funnyVideosChannel.subscribe(user) + }) + + // oh! it seems a new video is about to come out on &quot;#FunnyVide0s&quot; + const newVideo = new Video('funny cats (part 1)') + funnyVideosChannel.publish(newVideo) + + // no! is seems like elisa didn't like the video &quot;funny cats (part 1)&quot; + // she unsubscribes from the channel + const unsubscribeElisa = subscriptions[0] + + unsubscribeElisa() + + // but &quot;#FunnyVide0s&quot; it's not going to stop! + // we have a new cats video, of course this time + // only Hans and Mark are notified + const yetAnotherNewVideo = new Video('funny cats (part 2)') + funnyVideosChannel.publish(yetAnotherNewVideo) +} + +main() +// The output is: +// +// Hey there elisa@gmail.com! +// There's a new video from #FunnyVide0s +// funny cats (part 1) +// +// Hey there hans@gmail.com! +// There's a new video from #FunnyVide0s +// funny cats (part 1) +// +// Hey there mark@gmail.com! +// There's a new video from #FunnyVide0s +// funny cats (part 1) +// +// Hey there hans@gmail.com! +// There's a new video from #FunnyVide0s +// funny cats (part 2) +// +// Hey there mark@gmail.com! +// There's a new video from #FunnyVide0s +// funny cats (part 2) +</code></pre> +<p>Let's map the entities of the above example:</p> +<ul> +<li>The <code>User</code> is our <code>Observer</code>.</li> +<li>The <code>Channel</code> is our <code>Observable</code>.</li> +<li>The <code>Video</code> is just really the contract for the payload we sent out to subscribers, it <strong>can really be anything</strong>.</li> +</ul> +<p>I hope this example clarifies that you can leverage the Observer pattern with no additional libraries in any programming language. I found this knowledge of paramount importance to understand more complex use cases in reactive programming, specially when using libraries, we sit as a client, we go one layer of abstraction above what you've seen in the previous example. With reactive programming libraries, you can juggle with Observables in fantastic ways effortlessly.</p> +<p>As a side note, if you're keen on learning more about the Observer pattern (or any other design pattern), I highly recommend the following resources:</p> +<ul> +<li>&lt;a href=&quot;https://sourcemaking.com/design_patterns/observer&quot; target=&quot;_blank&quot; title=&quot;Observer Design Pattern&quot;&gt;sourcemaking.com&lt;/a&gt;</li> +<li>&lt;a href=&quot;https://en.wikipedia.org/wiki/Design_Patterns&quot; target=&quot;_blank&quot; rel=&quot;nofollow&quot; title=&quot;Design Patterns: Elements of Reusable Object-Oriented Software&quot;&gt;Design Patterns: Elements of Reusable Object-Oriented Software&lt;/a&gt;</li> +<li>&lt;a href=&quot;https://addyosmani.com/resources/essentialjsdesignpatterns/book/&quot; target=&quot;_blank&quot; rel=&quot;nofollow&quot; title=&quot;Learning JavaScript Design Patterns&quot;&gt;Learning JavaScript Design Patterns&lt;/a&gt; - a better resource if you want to learn these concepts with JavaScript.</li> +</ul> +<h5>Observable as a first-class citizen</h5> +<blockquote> +<p>&quot;Observables never had a chance to shine on their own. The real win, IMO, to RxJS is the Observable type itself. Not the operators.&quot; from &lt;a href=&quot;https://dev.to/benlesh/observables-reactive-programming-and-regret-4jm6&quot; target=&quot;_blank&quot; title=&quot;Observables, Reactive Programming, and Regret - DEV&quot;&gt;&quot;Observables, Reactive Programming and Regret&quot;&lt;/a&gt;</p> +</blockquote> +<p>Observables are everywhere these days. Libraries such as RxJS has an internal implementation of an Observable type, an refers to it as a &lt;a href=&quot;https://en.wikipedia.org/wiki/First-class_citizen&quot; target=&quot;_blank&quot; title=&quot;First-class citizen - Wikipedia&quot;&gt;first-class citizen&lt;/a&gt;.</p> +<p>I would like to highlight the &lt;a href=&quot;https://tc39.es/proposal-observable/&quot; target=&quot;_blank&quot; title=&quot;TC39 โ€“ Specifying JavaScript, Observable proposal&quot;&gt;tc39 Observable proposal&lt;/a&gt;. Observables are a very natural fit to handle many programming challenges. I think this has become more noticeable given that people are even considering bringing it into the language (in this case JavaScript)! I believe we are blurring the line between language specification and library, but I'm happy to see the proliferation of Observables' discussions.</p> +<h4>Reactive Programming</h4> +<p><strong>Reactive Programming is programming with streams of data. Streams are vessels of values pushed over time. Streams can be transformed into and combined with other streams</strong>.</p> +<p><strong>To make the above clearer</strong>, let's go over a few essential concepts, those you'll hear all the time.</p> +<h5>Core Properties of Observables</h5> +<p>There are <strong>3 very important aspects about Observables</strong> in reactive programming. Maybe some of them don't make sense now, but they will, once you have some hands-on experience with some reactive library.</p> +<p><strong>Observables are</strong>...</p> +<ul> +<li><strong>a primitive type</strong> with 0 to many values, pushed over any amount of time (yes, mentioned quite a few times already).</li> +<li><strong>cancellable</strong>, you can cancel a subscription by sending stuff to these observables: &quot;I don't' want you to send me those values anymore.&quot;</li> +<li><strong>lazy. Observables don't do anything until you subscribe.</strong> This is the complete opposite of &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise&quot; target=&quot;_blank&quot; title=&quot;Promise - JavaScript | MDN&quot;&gt;Promises&lt;/a&gt;. Promises are eager, that's why we often wrap them in a function, making them lazy, so they only execute when we call them.</li> +</ul> +<h5>Stream</h5> +<p>An overused term these days, but I'll try to make it simple. +First of all, <strong>a stream is an Observable</strong>; you'll find that these two concepts are often interchangeable. Simply put, a stream is a collection of values pushed over time. Let's think of <em>&quot;live streaming&quot;</em> in the video industry for a moment; on one end, there's your laptop observing the stream; on the other end, something is pushing several bits of data over the wire. Simple right? But <strong>how to think about streams in the conceptual world</strong>? That's where diagrams (more precisely marble diagrams) come in handy. At the most basic level, a stream is simply represented by a horizontal line, representative of time, with geometric figures. Each figure represents an emitted value on the stream. What's an emitted value? If a stream emits a value, it means that everyone looking into the stream will get notified of that value. Hopefully, the following figure makes it crystal clear.</p> +<p>&lt;br /&gt;</p> +<p>The following represents a stream that emits 4 values (1 value emitted per second).</p> +<p><img src="./assets/reactive-programming-fundamentals/stream-basic-four-values.gif" alt="'simple stream animation of four values pushed over time'" title="stream animation"></p> +<p>&lt;cite&gt;source: https://rxviz.com/&lt;/cite&gt;</p> +<p>&lt;br /&gt;</p> +<p>From now on, whenever you hear <strong>&quot;stream&quot;</strong>, try to visualize the above in your head.</p> +<h5>Operators</h5> +<p>Streams alone are useful as they allow multiple Observers to subscribe to it for updates. Things start to get more enjoyable when you want to <strong>manipulate a stream</strong>. As mentioned, streams can be transformed and even combined. The power it's all yours. Let's look at two essential operations, <strong>mapping, and filtering</strong>. Take a look at the following animation.</p> +<p>&lt;br /&gt; +&lt;br /&gt;</p> +<p><img src="./assets//reactive-programming-fundamentals/map-filter-stream.gif" alt="'visualization of map and filter when applied to streams'" title="map and filter stream"></p> +<p>&lt;cite&gt;source: https://reactive.how/filter&lt;/cite&gt;</p> +<p>&lt;br /&gt;</p> +<p>As you can see on the left, <code>map</code> applied over the left-most stream generates a new stream of values where each value in the original stream goes through the function <code>isEven</code> provided to the mapping operation. Subscribing to this stream, you would get a boolean (true vs. false) or number (0 vs. 1) that tells you whether a given value is even or odd. Now let's look at filter. With <code>filter</code>, instead of transforming each value, you're actually creating a new stream that only emits the original value when the same it's even. So from an Observer perspective, you would be notified half of the times, while with map your Observer will be notified for every single value.</p> +<h5>Hot Observable VS Cold Observable</h5> +<p>This is a concept that I've experienced before understanding it, and it can be daunting.</p> +<p>A <strong>Hot Observable</strong> emits values before the subscription happens. <strong>Think of it as a live music concert in a stadium</strong>. You will only get to see the full performance if you enter the stadium (subscribe) before the show starts (stream emits values). Of course, you can arrive at the venue to see the concert at any future time. Everyone at the stadium shares the same experience (meaning the values are shared among subscribers). +In a real use case, a Hot Observable can be a stream of click events on the page (there might be clicks happening before you subscribe to it) in a user interface.</p> +<p>A <strong>Cold Observable</strong> only starts emitting values upon subscription. <strong>Think of it as a movie on Netflix</strong>. The film only starts playing once you press play on your laptop. You won't miss pieces of the movie since you can start playing from the very start. If someone else watches the same movie on Netflix, they won't share your timeline; each user has its own individual timeline (values are not shared among subscribers). +In a real use case, a cold Observable can be a countdown clock where each subscriber has its own clock.</p> +<h5>Brief History of Reactive Programming</h5> +<p>According to my research in the paper &lt;a href=&quot;http://conal.net/papers/icfp97/&quot; target=&quot;_blank&quot; title=&quot;Functional Reactive Animation paper&quot;&gt;Functional Reactive Animation (1997)&lt;/a&gt;, we see some first mentions to things such as: <strong>reactivity</strong>. Some kind of temporal representation animations resembles the concept of a stream.</p> +<p>Haskell mentions in its WiKi page &lt;a href=&quot;https://wiki.haskell.org/Functional_Reactive_Programming&quot; target=&quot;_blank&quot; title=&quot;Functional Reactive Programming - HaskellWiki&quot;&gt;&lt;/a&gt;:</p> +<blockquote> +<p>&quot;Functional Reactive Programming (FRP) integrates time flow and compositional events into functional programming.&quot;</p> +</blockquote> +<p>The phrasing here is bit complicated. There's this concept of a primitive type called <code>Behavior</code>, which is defined as a time-varying value. This resembles a stream, right? A stream is somehow a time-varying value since you can get different values pushed to the stream over time, not that the reference to the stream changes, but its contents instead.</p> +<p>Today reactive is not only used to manage animated user interfaces. <strong>It's on every platform, from the web to mobile, from UIs to servers.</strong> Here's a couple of the most famous reactive programming libraries today:</p> +<ul> +<li>&lt;a href=&quot;http://baconjs.github.io/&quot; target=&quot;_blank&quot; title=&quot;Bacon.js - Functional Reactive Programming library for JavaScript&quot;&gt;bacon.js&lt;/a&gt;</li> +<li>&lt;a href=&quot;https://vertx.io/&quot; target=&quot;_blank&quot; title=&quot;Eclipse Vert.x&quot;&gt; +vertex.io +&lt;/a&gt;</li> +<li>&lt;a href=&quot;http://reactivex.io/&quot; target=&quot;_blank&quot; title=&quot;ReactiveX&quot;&gt; +ReactiveX +&lt;/a&gt;(&lt;a href=&quot;https://github.com/ReactiveX/RxJava&quot; target=&quot;_blank&quot; title=&quot;ReactiveX/RxJava: RxJava โ€“ Reactive Extensions for the JVM โ€“ a library for composing asynchronous and event-based programs using observable sequences for the Java VM&quot; +<blockquote> +<p>RxJava&lt;/a&gt;, &lt;a href=&quot;https://github.com/ReactiveX/RxSwift&quot; target=&quot;_blank&quot; title=&quot;ReactiveX/RxSwift: Reactive Programming in Swift&quot;&gt;RxSwift&lt;/a&gt;, &lt;a href=&quot;https://github.com/ReactiveX/rxjs&quot; target=&quot;_blank&quot; title=&quot;ReactiveX/rxjs: A reactive programming library for JavaScript&quot; +RxJS&lt;/a&gt; and others)</p> +</blockquote> +</li> +<li>&lt;a href=&quot;https://kefirjs.github.io/kefir/&quot; target=&quot;_blank&quot; title=&quot;Kefir.js โ€” fast and light Reactive Programming library for JavaScript inspired by Bacon.js and RxJS&quot;&gt;kefir&lt;/a&gt;</li> +</ul> +<h5>Quotes</h5> +<p>I hope this section offers some other views (and their sources) on reactive programming, expressed in a different way that might complement or even wholly build your understanding of things so far.</p> +<blockquote> +<p>&quot;Reactive Programming raises the level of abstraction of your code so you can focus on the interdependence of events that define the business logic, rather than having to constantly fiddle with a large amount of implementation details&quot;</p> +</blockquote> +<p>&lt;small&gt; +&lt;a +href=&quot;https://gist.github.com/staltz/868e7e9bc2a7b8c1f754&quot; +target=&quot;_blank&quot; +title=&quot;The introduction to Reactive Programming you've been missing&quot;</p> +<blockquote></blockquote> +<pre><code>&quot;The introduction to Reactive Programming you've been missing&quot; +</code></pre> +<p>&lt;/a&gt; +&lt;/small&gt;</p> +<p>&lt;br /&gt;</p> +<blockquote> +<p>&quot;When you fully embrace RP, the core of your app ends up being a declaration of a graph through which your data flows.&quot;</p> +</blockquote> +<p>&lt;small&gt; +&lt;a +href=&quot;https://dassur.ma/things/streams-for-reactive-programming/&quot; +target=&quot;_blank&quot; +title=&quot;Streams for reactive programming&quot;</p> +<blockquote></blockquote> +<pre><code>&quot;Streams for reactive programming&quot; +</code></pre> +<p>&lt;/a&gt; +&lt;/small&gt;</p> +<p>&lt;br /&gt;</p> +<blockquote> +<p>&quot;Never store a mutable state on your types. Instead, when you generate a new value in response to a change, send it in to a channel.&quot;</p> +</blockquote> +<p>&lt;small&gt; +&lt;a +href=&quot;https://www.cocoawithlove.com/blog/reactive-programming-what-and-why.html#a-description-of-reactive-programming&quot; +target=&quot;_blank&quot; +title=&quot;What is reactive programming and why should I use it?&quot;</p> +<blockquote></blockquote> +<pre><code>&quot;What is reactive programming and why should I use it?&quot; +</code></pre> +<p>&lt;/a&gt; +&lt;/small&gt;</p> +<p>&lt;br /&gt;</p> +<p>It's good that you discovered these concepts before you jump into practice. In my opinion, these can't be absorbed only by reading. Go over to the next blog post, where we'll build a small game together so that you can materialize the things I've covered in this blog post.</p> +<p>&lt;br&gt;</p> +<ul> +<li>&lt;a href=&quot;https://danielcaldas.github.io/posts/why-reactive-programming&quot; target=&quot;_blank&quot; title=&quot;Why You Should Consider Reactive Programming | danielcaldas.github.io&quot;&gt;Part 1 - Why You Should Consider Reactive Programming&lt;/a&gt;</li> +<li><strong>Part 2 - Fundamentals of Reactive Programming</strong></li> +<li>&lt;a href=&quot;https://danielcaldas.github.io/posts/hands-on-reactive-programming-rxjs&quot; target=&quot;_blank&quot; title=&quot;Hands-on Reactive Programming with RxJS | danielcaldas.github.io&quot;&gt;Part 3 - Hands-on Reactive Programming with RxJS&lt;/a&gt;</li> +<li>&lt;a href=&quot;https://danielcaldas.github.io/posts/reactive-rxjs-pros-cons&quot; target=&quot;_blank&quot; title=&quot;Reactive Programming: The Good and the Bad | danielcaldas.github.io&quot;&gt;Part 4 - Reactive Programming: The Good and the Bad&lt;/a&gt;</li> +<li>&lt;a href=&quot;https://danielcaldas.github.io/posts/awesome-reactive&quot; target=&quot;_blank&quot; title=&quot;Awesome RxJS and Reactive Programming Resources | danielcaldas.github.io&quot;&gt;Part 5 - Awesome RxJS and Reactive Programming Resources&lt;/a&gt;</li> +</ul> +Reactive Series (pt. 4) - Reactive Programming: The Good and the Badhttps://danielcaldas.github.io/posts/reactive-rxjs-pros-cons/https://danielcaldas.github.io/posts/reactive-rxjs-pros-cons/Reactive Series - Part 4Thu, 03 Dec 2020 00:00:00 GMT<ul> +<li>&lt;a href=&quot;https://danielcaldas.github.io/posts/why-reactive-programming&quot; target=&quot;_blank&quot; title=&quot;Why You Should Consider Reactive Programming | danielcaldas.github.io&quot;&gt;Part 1 - Why You Should Consider Reactive Programming&lt;/a&gt;</li> +<li>&lt;a href=&quot;https://danielcaldas.github.io/posts/reactive-programming-fundamentals&quot; target=&quot;_blank&quot; title=&quot;Fundamentals of Reactive Programming | danielcaldas.github.io&quot;&gt;Part 2 - Fundamentals of Reactive Programming&lt;/a&gt;</li> +<li>&lt;a href=&quot;https://danielcaldas.github.io/posts/hands-on-reactive-programming-rxjs&quot; target=&quot;_blank&quot; title=&quot;Hands-on Reactive Programming with RxJS | danielcaldas.github.io&quot;&gt;Part 3 - Hands-on Reactive Programming with RxJS&lt;/a&gt;</li> +<li><strong>Part 4 - Reactive Programming: The Good and the Bad</strong></li> +<li>&lt;a href=&quot;https://danielcaldas.github.io/posts/awesome-reactive&quot; target=&quot;_blank&quot; title=&quot;Awesome RxJS and Reactive Programming Resources | danielcaldas.github.io&quot;&gt;Part 5 - Awesome RxJS and Reactive Programming Resources&lt;/a&gt;</li> +</ul> +<p>&lt;br&gt;</p> +<p>After seeing reactive streams in action with RxJS, I would now like to expand &lt;a href=&quot;https://danielcaldas.github.io/posts/why-reactive-programming&quot; target=&quot;_blank&quot; title=&quot;Why You Should Consider Reactive Programming | danielcaldas.github.io&quot;&gt;&quot;Part 1 - Why you should consider Reactive Programming&quot;&lt;/a&gt; by debating some of the gains &amp; pains of reactive streams and RxJS. +As of now, we've covered some fundamental aspects of reactive programming, and we've seen some code as well! +In this part of the series, I'll be compacting some of the benefits I felt while using reactive programming and some of the major pain points of adopting it. Since we've used RxJS, I'll make occasional mentions of this reactive library's ups and downs.</p> +<ul> +<li><a href="#the-good">The good</a> +<ul> +<li><a href="#your-code-becomes-inheritably-lazy">Your code becomes inheritably lazy</a></li> +<li><a href="#your-code-is-likely-to-be-more-concise">Your code is likely to be more concise</a></li> +<li><a href="#youre-likely-to-write-less-code">You're likely to write less code</a></li> +<li><a href="#effortless-cancellation">Effortless cancellation</a></li> +<li><a href="#easier-to-deal-with-backpressure">Easier to deal with backpressure</a></li> +<li><a href="#converging-towards-a-single-style-of-coding">Converging towards a single style of coding</a></li> +<li><a href="#complicated-things-made-easy">Complicated things made easy</a></li> +<li><a href="#maintainability">Maintainability</a></li> +</ul> +</li> +<li><a href="#the-bad">The bad</a> +<ul> +<li><a href="#learning-curve">Learning curve</a></li> +<li><a href="#debugging-does-not-get-any-easier">Debugging does not get any easier</a></li> +<li><a href="#human-creativity">Human creativity</a></li> +<li><a href="#with-great-power">With great power</a></li> +<li><a href="#error-handling">Error handling</a></li> +</ul> +</li> +<li><a href="#closing-notes">Closing notes</a></li> +</ul> +<h4>The good</h4> +<p>Let's explore some beneficial aspects of adopting reactive programming and RxJS now that we have looked at &lt;a href=&quot;https://danielcaldas.github.io/posts/hands-on-reactive-programming-rxjs&quot; target=&quot;_blank&quot; title=&quot;Hands-on Reactive Programming with RxJS | danielcaldas.github.io&quot;&gt;how to approach a problem and model it with streams&lt;/a&gt;.</p> +<h5>Your code becomes inheritably lazy</h5> +<p>Working with streams means nothing happens until you subscribe to them, which is terrific! Because any code path now has a &quot;free of charge&quot; stop &amp; play capability! Without any effort, your computations become lazy by default executing when needed - &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise&quot; target=&quot;_blank&quot; title=&quot;Promise - JavaScript | MDN&quot;&gt;Promises&lt;/a&gt; are eager. They start running as soon as you construct them. It's common to wrap a Promise with a function to make it lazy.</p> +<h5>Your code is likely to be more concise</h5> +<p>As we've seen in the &lt;a href=&quot;https://danielcaldas.github.io/posts/hands-on-reactive-programming-rxjs&quot; target=&quot;_blank&quot; title=&quot;Hands-on Reactive Programming with RxJS | danielcaldas.github.io&quot;&gt;hands-on&lt;/a&gt; part of this series, you'll likely have to model events and think about what parties are interested in consuming such events. You'll spend less time focusing on the implementation details. You'll rather spend your time drawing a mental map of the events' interdependency that define your business logic. In my experience, this often results in more compact implementations, making it more accessible to capture the big picture of the internal flows in your application.</p> +<h5>You're likely to write less code</h5> +<p>It only makes sense that if you have an ecosystem, such as RxJS, in your arsenal, you'll spend less time implementing behaviors that are already built-in in some operator (e.g., &lt;a href=&quot;https://www.learnrxjs.io/learn-rxjs/operators/filtering/debounce&quot; target=&quot;_blank&quot; title=&quot;debounce - Learn RxJS&quot;&gt;debouncing&lt;/a&gt;, &lt;a href=&quot;https://www.learnrxjs.io/learn-rxjs/operators/filtering/throttle&quot; target=&quot;_blank&quot; title=&quot;throttle - Learn RxJS&quot;&gt;throttling&lt;/a&gt;, etc.). Just like you'll spend less time mutating the DOM directly when you use a UI framework such as React or Vue, you'll think more about how your UI looks like and what data binds to what parts of your UI.</p> +<h5>Effortless cancellation</h5> +<p>In my opinion, an overlooked easy win of coding with streams and RxJS is how easy it becomes to implement cancellation. What kind of behavior does cancellation concern, you ask? Let's see an example.</p> +<p><img src="./assets/reactive-rxjs-pros-cons/rxjs-free-cancellation.gif" alt="'cancellation of http request on hover items in a list'" title="rxjs free cancellation"></p> +<p>In the above GIF, we have a list of items. Hovering on each item in the list triggers an ajax request to fetch some data from the server. The problem of eagerly (compared with clicking the item, for instance) triggering the requests upon mouse hovering is that you can potentially fetch the data for all the items but ending up not displaying any of the data to the end-user. To avoid that, we cancel an item's request when the user's mouse leaves it.</p> +<p>&lt;small&gt;(&lt;b&gt;Note&lt;/b&gt;: oh, btw on the above GIF, I'm not calling a real API, I'm just using the &lt;a href=&quot;https://tweak-extension.com&quot; target=&quot;_blank&quot; title=&quot;tweak browser extension mock API simulator&quot;&gt;tweak browser extension&lt;/a&gt; to mock the HTTP requests seamlessly)&lt;/small&gt;</p> +<p>&lt;br /&gt;</p> +<p>Here's the snippet of the above pattern, implemented with &lt;a href=&quot;https://rxjs-dev.firebaseapp.com/api/operators/switchMap&quot; target=&quot;_blank&quot; title=&quot;RxJS - switchMap&quot;&gt;switchMap&lt;/a&gt; and &lt;a href=&quot;https://rxjs-dev.firebaseapp.com/api/operators/takeUntil&quot; target=&quot;_blank&quot; title=&quot;RxJS - takeUntil&quot;&gt;takeUntil&lt;/a&gt;.</p> +<pre><code class="language-javascript">const listEl = document.getElementById('list') +const resEl = document.getElementById('res') +const fetchById = (id) =&gt; ajax(`https://some-service.com/api/items/${id}`).pipe(map((r) =&gt; r.response)) +const mouseOutItem$ = fromEvent(listEl, 'mouseout') +const mouseOverItem$ = fromEvent(listEl, 'mouseover') + .pipe( + switchMap((event) =&gt; { + const id = event.target.id + resEl.innerHTML = `loading item ${id}...` + console.log(`Fetching data for item ${id}...`) + return fetchById(id) + }), + map((response) =&gt; { + resEl.innerHTML = JSON.stringify(response, null, 2) + console.log(`Done fetching data for item ${response.item.id}!`) + }), + takeUntil( + mouseOutItem$.pipe( + tap(() =&gt; { + console.log(`โŒ Cancelled fetch data for item ${event.target.id}!`) + resEl.innerHTML = '...' + }), + ), + ), + repeat(), + ) + .subscribe() +</code></pre> +<p>&lt;br /&gt;</p> +<p>&lt;Accordion summary='Expand to see example full code'&gt;</p> +<pre><code class="language-html">&lt;!doctype html&gt; + +&lt;head&gt; + &lt;script src=&quot;https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.5/rxjs.umd.js&quot;&gt;&lt;/script&gt; + &lt;script src=&quot;https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.5/rxjs.umd.js.map&quot;&gt;&lt;/script&gt; + &lt;style&gt; + li { + padding: 4px; + border: 1px dashed green; + color: black; + } + + li:hover { + background-color: lightblue; + color: blueviolet; + cursor: pointer; + font-weight: bold; + } + &lt;/style&gt; +&lt;/head&gt; + +&lt;body&gt; + &lt;div id=&quot;app&quot;&gt;&lt;/div&gt; + &lt;script&gt; + const { delay, filter, map, mapTo, switchMap, takeUntil, takeWhile, tap, withLatestFrom, repeat } = + window.rxjs.operators + const { fromEvent, merge, timer } = window.rxjs + const ajax = window.rxjs.ajax.ajax + + // preventing the default event on dragover + // so that we're allowed to drop an element + fromEvent(document, 'dragover') + .pipe(tap((event) =&gt; event.preventDefault())) + .subscribe() + + // all the game markup goes here + document.getElementById('app').innerHTML = ` + &lt;h2&gt;Hover on the items to fetch some async data&lt;/h2&gt; + &lt;ul id=&quot;list&quot;&gt; + &lt;li id=&quot;item-1&quot;&gt;item 1 (mouse over to fetch data)&lt;/li&gt; + &lt;li id=&quot;item-2&quot;&gt;item 2 (mouse over to fetch data)&lt;/li&gt; + &lt;li id=&quot;item-3&quot;&gt;item 3 (mouse over to fetch data)&lt;/li&gt; + &lt;li id=&quot;item-4&quot;&gt;item 4 (mouse over to fetch data)&lt;/li&gt; + &lt;/ul&gt; + &lt;h3&gt;Item detail&lt;/h3&gt; + &lt;pre id=&quot;res&quot;&gt;...&lt;/pre&gt; + ` + + const listEl = document.getElementById('list') + const resEl = document.getElementById('res') + const fetchById = (id) =&gt; ajax(`https://some-service.com/api/items/${id}`).pipe(map((r) =&gt; r.response)) + const mouseOutItem$ = fromEvent(listEl, 'mouseout') + const mouseOverItem$ = fromEvent(listEl, 'mouseover') + .pipe( + switchMap((event) =&gt; { + const id = event.target.id + resEl.innerHTML = `loading item ${id}...` + console.log(`Fetching data for item ${id}...`) + return fetchById(id) + }), + map((response) =&gt; { + resEl.innerHTML = JSON.stringify(response, null, 2) + console.log(`Done fetching data for item ${response.item.id}!`) + }), + takeUntil( + mouseOutItem$.pipe( + tap(() =&gt; { + console.log(`โŒ Cancelled fetch data for item ${event.target.id}!`) + resEl.innerHTML = '...' + }), + ), + ), + repeat(), + ) + .subscribe() + &lt;/script&gt; +&lt;/body&gt; +</code></pre> +<p>&lt;/Accordion&gt;</p> +<p>&lt;br /&gt; +&lt;br /&gt;</p> +<h5>Easier to deal with backpressure</h5> +<blockquote> +<p>&quot;Backpressure is when the progress of turning that input to output is resisted in some way. In most cases that resistance is computational speed&quot;, from &lt;a href=&quot;https://medium.com/@jayphelps/backpressure-explained-the-flow-of-data-through-software-2350b3e77ce7&quot; target=&quot;_blank&quot; title=&quot;Backpressure explained โ€” the resisted flow of data through software | by Jay Phelps | Medium&quot;&gt;&quot;Backpressure explained โ€” the resisted flow of data through software&quot;&lt;/a&gt;</p> +</blockquote> +<p>Did it ever happen that your <code>addEventListener</code> handle is just executing too many times? For instance, while running an event handler for the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Document/scroll_event&quot; target=&quot;_blank&quot; title=&quot;Document: scroll event - Web APIs | MDN&quot;&gt;scroll event&lt;/a&gt;? It's common to use a debounce strategy to run your event handler only after X time has passed since the last scroll event. Again, with built-in operators such as debounce in RxJS, you can quickly relieve the load in your consumer functions (event handlers). The operator single-handedly takes care of debouncing emissions for you!</p> +<h5>Converging towards a single style of coding</h5> +<p>I've covered this in greater detail during &lt;a href=&quot;https://danielcaldas.github.io/posts/why-reactive-programming&quot; target=&quot;_blank&quot; title=&quot;Why You Should Consider Reactive Programming | danielcaldas.github.io&quot;&gt;&quot;Part 1 - Why you should consider Reactive Programming&quot;&lt;/a&gt; the gist is that working with streams when done right, might make callbacks, Promises, and async/await nearly obsolete. I say nearly because you' might still use Promises under the hood, but your code now can look the same whether you're tackling synchronous or asynchronous tasks.</p> +<h5>Complicated things made easy</h5> +<p>I want to reinforce that you can leverage reactive libraries such as RxJS to solve complex problems with little effort. If you haven't watched the talk &lt;a href=&quot;https://www.youtube.com/watch?v=B-nFj2o03i8&quot; target=&quot;_blank&quot; title=&quot;Complex features made easy with RxJS - YouTube&quot;&gt;&quot;Complex features made easy with RxJS&quot;&lt;/a&gt; by &lt;a href=&quot;https://twitter.com/BenLesh&quot; target=&quot;_blank&quot; title=&quot;Ben Lesh (@BenLesh) / Twitter&quot;&gt;Ben Lesh&lt;/a&gt;, I would highly recommend it. In the first 10 minutes, you'll see how easy it becomes to tackle some of the following problems with the help of RxJS:</p> +<ul> +<li>Basic drag &amp; drop implementation &lt;a href=&quot;https://youtu.be/B-nFj2o03i8?t=351&quot; target=&quot;_blank&quot; title=&quot;Complex features made easy with RxJS - YouTube&quot;&gt;[5:54]&lt;/a&gt;</li> +<li>Avoid double submission through a button that involves an asynchronous Ajax call &lt;a href=&quot;https://youtu.be/B-nFj2o03i8?t=424&quot; target=&quot;_blank&quot; title=&quot;Complex features made easy with RxJS - YouTube&quot;&gt;[7:04]&lt;/a&gt;</li> +<li>Throttle autosuggestion field that involves fetching data with a given search term &lt;a href=&quot;https://youtu.be/B-nFj2o03i8?t=444&quot; target=&quot;_blank&quot; title=&quot;Complex features made easy with RxJS - YouTube&quot;&gt;[7:24]&lt;/a&gt;</li> +</ul> +<p>If you aim to solve one of the problems mentioned in the above list, you can avoid reinventing the wheel by following the reactive patterns demonstrated in the video.</p> +<h5>Maintainability</h5> +<p>There are two aspects of maintainability that I would point out. First, you'll write less code; there's a lot of heavy lifting that RxJS can do for you; if you leverage that, you'll undoubtedly ending writing less code. The second is that a combination of streams with powerful operators solve complex problems in a very declarative manner. Code will look concise and straightforward; instead of verbose branching and imperative logic, you'll have a few combinations of operators that will do all the magic for you. +However, with RxJS and streams, maintainability is a double-edged sword. We're going to cover that in the next part of this article.</p> +<h4>The bad</h4> +<p>Let's look at the not so bright side.</p> +<h5>Learning curve</h5> +<p>Any modern JavaScript codebase uses a cocktail of libraries. RxJS (or whatever library you would adopt) would be just one more thing you'll have to teach newcomers. But don't take this as a light decision. You're not only introducing a new library in the codebase, but you're also introducing a new paradigm. I feel there's quite a learning curve towards mastering reactive programming and RxJS. Not only you're exposed to an entirely new ecosystem with new APIs, but you also need the time to process this different paradigm of programming with streams. As we've experienced in previous articles, it can be quite different from a traditional writing code style (compared with imperative programming, for example).</p> +<p>Depending on how broadly you'll embrace this paradigm, you might need to ship new tooling for unit testing and master additional concepts such as &lt;a href=&quot;https://rxmarbles.com/&quot; target=&quot;_blank&quot; title=&quot;RxMarbles: Interactive diagrams of Rx Observables&quot;&gt;marble diagrams&lt;/a&gt; - which helps you properly model and assert data streams. However, it might require you and your team to learn another tricky <strong>DSL</strong> (<strong>d</strong>omain-<strong>s</strong>pecific <strong>l</strong>anguage) to deal with these diagrams.</p> +<p>Some might argue that the &quot;learning curve&quot; is a &quot;one-time cost&quot;. As it turns out, in the software industry, software engineers tend to move a lot (due to the high demand for the skill these days), and they might be sticking around in the same company for an average of 2 to 3 years before embracing a new challenge. The bustling job market makes me believe that it is not wise to think of the &quot;learning curve&quot; as a one time cost because soon, your freshly trained engineer with RxJS skills might say goodbye.</p> +<h5>Debugging does not get any easier</h5> +<p>Just the same way, you can split your code into several functions, and those functions call other functions which, without some structure, might end up in spaghetti code. Likewise, you can end up entangled in a spaghetti of streams and not know which way to turn. I think simple functions are usually more straightforward to debug, given that they are composed sequentially. You're reading a function; that function might call other N functions and so forth. Well, with streams, it might not be that candid because there's no such thing as a stream invoking another stream. Instead, you'll have a stream plugging with other streams in mixed ways depending on the operators that join them. It might feel overweighing at some point to find your way around some particular flow (hopefully, you won't' reach that point because your code is clear and concise).</p> +<p>Another aspect that you might run into while debugging streams is that such an amount of abstractions and compact implementation will let no space for you to plug into a stream and debug it or inspect it the same way you debug a function. Things tend to be on a higher level of abstraction - which is beneficial because it will free up your mind on some implementation details. Hence when it comes to the point you need to dig further down, understanding what's going on at the very core of your flow, you might need to &lt;a href=&quot;https://www.learnrxjs.io/learn-rxjs/operators/utility/do&quot; target=&quot;_blank&quot; title=&quot;tap / do - Learn RxJS&quot;&gt;tap&lt;/a&gt; into streams here and there to figure out what's the issue. Although old this article, it might be one of the best walkthroughs of the problem I'm trying to surface here. It will give you a solid strategy to debug streams (even if you're looking at that particular code for the first time) - <em>tip for reading it: mentally replace <code>do</code> per <code>tap</code></em>.</p> +<h5>Human creativity</h5> +<p><strong>Kiss</strong>: <strong>K</strong>eep <strong>i</strong>t <strong>S</strong>imple, <strong>S</strong>tupid, not easy with reactive streams and RxJS tough. I'll tell from personal experience that it will come the day you'll look at your code, and although it's just fine, you'll feel this <strong>voice inside your head: &quot;Are you sure there's nothing else much fancier in the RxJS API that would allow you to write less two lines of code?&quot; - fight that voice!</strong> Creative solutions with RxJS might often result in dreadful consequences for your product and team. But yet, RxJS has such a unique and evolving ecosystem that will feel tempting with time to start to chime in some new operators just for the sake of adding more operators. Fight that in code reviews - this is the place you can understand why your colleague is shipping that new exotic operator and challenge simpler alternatives already in use. I guess this is general advice. I wouldn't apply it to reactive programming only. If you don't have a code review process, ยฏ\_(ใƒ„)_/ยฏ.</p> +<h5>With great power</h5> +<p>As usual, with powerful tools, there is increased responsibility towards their use. If you pick RxJS as your weapon of choice, there are things you'll need to be extra careful. I want to highlight one: shareReplay. We didn't look into this operator in particular during this series, but we did learn the difference between &lt;a href=&quot;https://danielcaldas.github.io/posts/reactive-programming-fundamentals#hot-observable-vs-cold-observable&quot; target=&quot;_blank&quot; title=&quot;Fundamentals of Reactive Programming | danielcaldas.github.io&quot;&gt;Hot and Cold observables&lt;/a&gt;, a quick refresher:</p> +<ul> +<li><strong>Hot Observables</strong> - multicast; all subscribers get data from the same producer (e.g., a live music concert in a stadium).</li> +<li><strong>Cold Observables</strong> - unicast; each subscriber gets data from different producers (e.g., a show on Netflix).</li> +</ul> +<p>&lt;a +href=&quot;https://www.learnrxjs.io/learn-rxjs/operators/multicasting/sharereplay&quot; +target=&quot;_blank&quot; +title=&quot;shareReplay - Learn RxJS&quot;</p> +<blockquote> +<p>shareReplay +&lt;/a&gt; allows you to take a cold observable and make it hot in the sense that you can now multicast the underlying computation +to multiple subscribers ๐Ÿคฏ - that's &lt;b&gt;share&lt;/b&gt;. With this operator, new subscribers will be able to &quot;catch up&quot; with +previously emitted values at any point in time - that's &lt;b&gt;replay&lt;/b&gt;.</p> +</blockquote> +<p>Don't want to get into extensive detail of what the operator pertains to; be mindful of your implementation gaps that might trigger massive memory leaks in your application by using this operator. The gist is that <strong>using shareReply without refCount may origin a memory leak because the operator doesn't automatically close the stream</strong> after all its consumers' have unsubscribed.</p> +<p>Also, historically, there have been some issues around its implementation <a href="https://github.com/ReactiveX/rxjs/issues/3336">[1]</a> <a href="https://github.com/ReactiveX/rxjs/issues/5034">[2]</a>.</p> +<h5>Error handling</h5> +<p>Finally, yet notably, error handling. Something I did not cover in the previous articles. Error handling is yet something else that changes considerably. Let's look at a simple example comparing reactive vs. non-reactive.</p> +<pre><code class="language-javascript">switchMap(event =&gt; { + try { + const id = event.target.id; + resEl.innerHTML = `loading item ${id}...`; + console.log(`Fetching data for item ${id}...`); + return fetchById(id); + } catch (error) { + // that's not how this works with streams... + } +}), +map(response =&gt; { + resEl.innerHTML = JSON.stringify(response, null, 2); + console.log(`Done fetching data for item ${response.item.id}!`); +}), +</code></pre> +<pre><code class="language-javascript">switchMap(event =&gt; { + const id = event.target.id; + resEl.innerHTML = `loading item ${id}...`; + console.log(`Fetching data for item ${id}...`); + return fetchById(id); +}), +catchError(error =&gt; { + if (error) { + console.error(error); + } + // fallback to an empty item and a message + // we need to return an observable! + return of({ + item: {}, + message: 'something went wrong', + }); +}), +map(response =&gt; { + resEl.innerHTML = JSON.stringify(response, null, 2); + console.log(`Done fetching data for item ${response.item.id}!`); +}), +</code></pre> +<p>For a more natural integration, you'll have to stick with the &lt;a href=&quot;https://www.learnrxjs.io/learn-rxjs/operators/error_handling/catch&quot; target=&quot;_blank&quot; title=&quot;catch / catchError - Learn RxJS&quot;&gt;catchError operator&lt;/a&gt;. As a beginner, I would tend to wrap stuff around with try/catch, but things work slightly differently with streams. Observable is our primitive here, remember? +Something mentioned as a &lt;a href=&quot;https://medium.com/@benlesh/on-the-subject-of-subjects-in-rxjs-2b08b7198b93#.pcmlgg1mx&quot; target=&quot;_blank&quot; title=&quot;On The Subject Of Subjects (in RxJS) | by Ben Lesh | Medium&quot;&gt;&lt;i&gt;&quot;gotcha&quot;&lt;/i&gt; of RxJS&lt;/a&gt; <strong>is the fact that an RxJS Observable does not &quot;trap&quot; errors</strong>; when an error bubbles to the end of the observer chain, when unhandled, the error, it will be re-thrown.</p> +<h4>Closing notes</h4> +<p>Would I use reactive programming and RxJS in my next project? <strong>Depends...</strong> There's a great deal of learning involved in using these technologies, so even though I might not always use them, I'm confident that these skills will (and are!) playing an essential role in my evolution as a software engineer. Just know that <strong>I would do it again</strong> (all the learning and writing).</p> +<p>I do think Reactive is powerful and useful, but it's not a holy grail that will solve all your problems overnight.</p> +<p><strong>Please focus on the problem first, scrutinize the use cases, and Reactive shall reveal itself a solution.</strong></p> +<p>I hope you've enjoyed this series is now approaching its end. This series is far from the perfect learning resource, but I hope that my perspective and way of explaining things fit some of you, complementing your learnings in a way. To close, I'll leave you with a &lt;a href=&quot;https://danielcaldas.github.io/posts/awesome-reactive&quot; target=&quot;_blank&quot; title=&quot;Awesome RxJS and Reactive Programming Resources | danielcaldas.github.io&quot;&gt;list of fantastic learning resources for reactive programming and RxJS&lt;/a&gt;.</p> +<p>&lt;br /&gt;</p> +<ul> +<li>&lt;a href=&quot;https://danielcaldas.github.io/posts/why-reactive-programming&quot; target=&quot;_blank&quot; title=&quot;Why You Should Consider Reactive Programming | danielcaldas.github.io&quot;&gt;Part 1 - Why You Should Consider Reactive Programming&lt;/a&gt;</li> +<li>&lt;a href=&quot;https://danielcaldas.github.io/posts/reactive-programming-fundamentals&quot; target=&quot;_blank&quot; title=&quot;Fundamentals of Reactive Programming | danielcaldas.github.io&quot;&gt;Part 2 - Fundamentals of Reactive Programming&lt;/a&gt;</li> +<li>&lt;a href=&quot;https://danielcaldas.github.io/posts/hands-on-reactive-programming-rxjs&quot; target=&quot;_blank&quot; title=&quot;Hands-on Reactive Programming with RxJS | danielcaldas.github.io&quot;&gt;Part 3 - Hands-on Reactive Programming with RxJS&lt;/a&gt;</li> +<li>&lt;a href=&quot;https://danielcaldas.github.io/posts/reactive-rxjs-pros-cons&quot; target=&quot;_blank&quot; title=&quot;Reactive Programming: The Good and the Bad | danielcaldas.github.io&quot;&gt;Part 4 - Reactive Programming: The Good and the Bad&lt;/a&gt;</li> +<li>&lt;a href=&quot;https://danielcaldas.github.io/posts/awesome-reactive&quot; target=&quot;_blank&quot; title=&quot;Awesome RxJS and Reactive Programming Resources | danielcaldas.github.io&quot;&gt;Part 5 - Awesome RxJS and Reactive Programming Resources&lt;/a&gt;</li> +</ul> +Reactive Serieshttps://danielcaldas.github.io/posts/reactive-series-preface/https://danielcaldas.github.io/posts/reactive-series-preface/A series of articles on Reactive Programming and RxJSWed, 22 Jul 2020 00:00:00 GMT<p>Over the past two years, I invested some time learning &lt;a href=&quot;https://en.wikipedia.org/wiki/Reactive_programming&quot; target=&quot;_blank&quot; title=&quot;Reactive programming - Wikipedia&quot;&gt;Reactive Programming&lt;/a&gt; (with &lt;a href=&quot;https://rxjs-dev.firebaseapp.com/&quot; target=&quot;_blank&quot; title=&quot;RxJS&quot;&gt;RxJS&lt;/a&gt;) and working with Observables and Streams to build interactive UIs. The opportunity to learn RxJS never popped up until the company I was working with started investing heavily in RxJS. I had to learn at the job.</p> <p><strong>I'm announcing a series of articles</strong> that I'm writing on Reactive Programming. I want to demonstrate to you <strong>why you should consider @@ -2798,7 +4235,7 @@ title=&quot;GitHub, Daniel Caldas, test-fixtures-pattern case study project, <h4>๐ŸŽฐ Bonus section: development watch mode for fixtures</h4> <p>Another thing that comes in handy when setting up our testing workflow, is to have a way to update the fixtures by tweaking existing test case inputs or adding new ones and automatically re-running the <code>run-fixtures.js</code> script and the unit tests with Jest. How would that go?</p> <p>If you never tried &lt;a href=&quot;https://github.com/remy/nodemon&quot; target=&quot;_blank&quot; title=&quot;Monitor for any changes in your node.js application and automatically restart the server - perfect for development&quot;&gt;nodemon&lt;/a&gt;, now it's a good time to check it out. It's a mighty tool to restart some job based on specific changes in your project. Let's use nodemon to set up a &lt;a href=&quot;https://docs.npmjs.com/misc/scripts&quot; target=&quot;_blank&quot; title=&quot;npm cli documentation about scripts field&quot;&gt;npm script&lt;/a&gt; that seamlessly re-runs our fixtures. The idea is that we achieve the same workflow that we would normally have with &lt;a href=&quot;https://jestjs.io/docs/en/cli#watchall&quot; target=&quot;_blank&quot; title=&quot;Jest cli, watch all option&quot;&gt;jest --watchAll&lt;/a&gt;, on our fixtures folder. After installing nodemon, we just need to use the &lt;a href=&quot;https://github.com/remy/nodemon#monitoring-multiple-directories&quot; target=&quot;_blank&quot; title=&quot;Monitor for any changes in your node.js application and automatically restart the server - perfect for development, watch mode&quot;&gt;--watch&lt;/a&gt; option to check for changes in our fixtures.</p> -<pre><code class="language-json:package.json">&quot;fixtures:run&quot;: &quot;node run-fixtures &amp;&amp; jest ./fixtures/tests/fixtures.spec.js&quot;, +<pre><code class="language-json">&quot;fixtures:run&quot;: &quot;node run-fixtures &amp;&amp; jest ./fixtures/tests/fixtures.spec.js&quot;, &quot;fixtures:clean&quot;: &quot;...&quot;, &quot;fixtures:watch&quot;: &quot;nodemon --watch ./fixtures --ignore ./fixtures/tests --exec \&quot;npm run fixtures:run\&quot;&quot; </code></pre> @@ -3027,44 +4464,11 @@ complexity seems to be one of the main barriers to its adoption. Reactive <st things that in other times you would think of impossible or had to implement become so much easier.</p> <p>If you feel like taking a shot at Reactive Programming, I'll gladly guide you through it!</p> <p>&lt;br /&gt;</p> -<hr> <ul> <li><strong>Part 1 - Why You Should Consider Reactive Programming</strong></li> -<li>&lt;a -href=&quot;https://danielcaldas.github.io/posts/reactive-programming-fundamentals&quot; -target=&quot;_blank&quot; -title=&quot;Fundamentals of Reactive Programming | danielcaldas.github.io&quot; -<blockquote> -<p>Part 2 - Fundamentals of Reactive Programming -&lt;/a&gt;</p> -</blockquote> -</li> -<li>&lt;a -href=&quot;https://danielcaldas.github.io/posts/hands-on-reactive-programming-rxjs&quot; -target=&quot;_blank&quot; -title=&quot;Hands-on Reactive Programming with RxJS | danielcaldas.github.io&quot; -<blockquote> -<p>Part 3 - Hands-on Reactive Programming with RxJS -&lt;/a&gt;</p> -</blockquote> -</li> -<li>&lt;a -href=&quot;https://danielcaldas.github.io/posts/reactive-rxjs-pros-cons&quot; -target=&quot;_blank&quot; -title=&quot;Reactive Programming: The Good and the Bad | danielcaldas.github.io&quot; -<blockquote> -<p>Part 4 - Reactive Programming: The Good and the Bad -&lt;/a&gt;</p> -</blockquote> -</li> -<li>&lt;a -href=&quot;https://danielcaldas.github.io/posts/awesome-reactive&quot; -target=&quot;_blank&quot; -title=&quot;Awesome RxJS and Reactive Programming Resources | danielcaldas.github.io&quot; -<blockquote> -<p>Part 5 - Awesome RxJS and Reactive Programming Resources -&lt;/a&gt;</p> -</blockquote> -</li> +<li>&lt;a href=&quot;https://danielcaldas.github.io/posts/reactive-programming-fundamentals&quot; target=&quot;_blank&quot; title=&quot;Fundamentals of Reactive Programming | danielcaldas.github.io&quot;&gt;Part 2 - Fundamentals of Reactive Programming&lt;/a&gt;</li> +<li>&lt;a href=&quot;https://danielcaldas.github.io/posts/hands-on-reactive-programming-rxjs&quot; target=&quot;_blank&quot; title=&quot;Hands-on Reactive Programming with RxJS | danielcaldas.github.io&quot;&gt;Part 3 - Hands-on Reactive Programming with RxJS&lt;/a&gt;</li> +<li>&lt;a href=&quot;https://danielcaldas.github.io/posts/reactive-rxjs-pros-cons&quot; target=&quot;_blank&quot; title=&quot;Reactive Programming: The Good and the Bad | danielcaldas.github.io&quot;&gt;Part 4 - Reactive Programming: The Good and the Bad&lt;/a&gt;</li> +<li>&lt;a href=&quot;https://danielcaldas.github.io/posts/awesome-reactive&quot; target=&quot;_blank&quot; title=&quot;Awesome RxJS and Reactive Programming Resources | danielcaldas.github.io&quot;&gt;Part 5 - Awesome RxJS and Reactive Programming Resources&lt;/a&gt;</li> </ul> \ No newline at end of file diff --git a/site.webmanifest b/site.webmanifest new file mode 100644 index 0000000..45dc8a2 --- /dev/null +++ b/site.webmanifest @@ -0,0 +1 @@ +{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} \ No newline at end of file diff --git a/sitemap-0.xml b/sitemap-0.xml index 9305d23..3966b17 100644 --- a/sitemap-0.xml +++ b/sitemap-0.xml @@ -1 +1 @@ -https://danielcaldas.github.io/https://danielcaldas.github.io/about/https://danielcaldas.github.io/bookshelf/https://danielcaldas.github.io/bookshelf/example/https://danielcaldas.github.io/categories/tech/https://danielcaldas.github.io/posts/about-css-conf-eu-berlin-2018/https://danielcaldas.github.io/posts/about-js-conf-eu-berlin-2018/https://danielcaldas.github.io/posts/awesome-reactive/https://danielcaldas.github.io/posts/best-http-request-mock-tool/https://danielcaldas.github.io/posts/better-imports-webpack-alias/https://danielcaldas.github.io/posts/browser-polyfill-madness-mozilla-internet-explorer/https://danielcaldas.github.io/posts/call-react-hooks-inside-conditions/https://danielcaldas.github.io/posts/debugging-javascript-with-vscode/https://danielcaldas.github.io/posts/developers-bad-at-testing-own-code/https://danielcaldas.github.io/posts/functional-bits-tips/https://danielcaldas.github.io/posts/how-to-fix-github-password-authentication/https://danielcaldas.github.io/posts/how-to-svelte-rxjs/https://danielcaldas.github.io/posts/my-two-cents-on-tech-job-interviews/https://danielcaldas.github.io/posts/open-source-dilemma/https://danielcaldas.github.io/posts/reactive-series-preface/https://danielcaldas.github.io/posts/sli-slo-sla/https://danielcaldas.github.io/posts/tips-end-to-end-testing-puppeteer/https://danielcaldas.github.io/posts/tips-jest-unit-testing/https://danielcaldas.github.io/posts/unit-testing-with-fixtures-unleashed/https://danielcaldas.github.io/posts/web-compatible-developers/https://danielcaldas.github.io/posts/why-reactive-programming/https://danielcaldas.github.io/tags/berlin/https://danielcaldas.github.io/tags/conference/https://danielcaldas.github.io/tags/css/https://danielcaldas.github.io/tags/functional-programming/https://danielcaldas.github.io/tags/javascript/https://danielcaldas.github.io/tags/job-market/https://danielcaldas.github.io/tags/open-source/https://danielcaldas.github.io/tags/opinion/https://danielcaldas.github.io/tags/ops/https://danielcaldas.github.io/tags/productivity/https://danielcaldas.github.io/tags/react/https://danielcaldas.github.io/tags/reactive-programming/https://danielcaldas.github.io/tags/software-testing/https://danielcaldas.github.io/tags/softwaretesting/https://danielcaldas.github.io/tags/svelte/https://danielcaldas.github.io/tags/vscode/https://danielcaldas.github.io/tags/webpack/ \ No newline at end of file +https://danielcaldas.github.io/https://danielcaldas.github.io/bookshelf/https://danielcaldas.github.io/bookshelf/example/https://danielcaldas.github.io/posts/about-css-conf-eu-berlin-2018/https://danielcaldas.github.io/posts/about-js-conf-eu-berlin-2018/https://danielcaldas.github.io/posts/awesome-reactive/https://danielcaldas.github.io/posts/best-http-request-mock-tool/https://danielcaldas.github.io/posts/better-imports-webpack-alias/https://danielcaldas.github.io/posts/browser-polyfill-madness-mozilla-internet-explorer/https://danielcaldas.github.io/posts/call-react-hooks-inside-conditions/https://danielcaldas.github.io/posts/debugging-javascript-with-vscode/https://danielcaldas.github.io/posts/destructuring-the-not-so-good-parts/https://danielcaldas.github.io/posts/developers-bad-at-testing-own-code/https://danielcaldas.github.io/posts/functional-bits-tips/https://danielcaldas.github.io/posts/guide-to-custom-react-hooks-with-mutationobserver/https://danielcaldas.github.io/posts/hands-on-reactive-programming-rxjs/https://danielcaldas.github.io/posts/hidden-potential-webpack-define-plugin/https://danielcaldas.github.io/posts/how-to-copy-file-android/https://danielcaldas.github.io/posts/how-to-fix-github-password-authentication/https://danielcaldas.github.io/posts/how-to-svelte-rxjs/https://danielcaldas.github.io/posts/my-two-cents-on-tech-job-interviews/https://danielcaldas.github.io/posts/open-source-dilemma/https://danielcaldas.github.io/posts/please-help-deleted-my-git-stash/https://danielcaldas.github.io/posts/presenting-babel-plugin-cloudinary/https://danielcaldas.github.io/posts/reactive-programming-fundamentals/https://danielcaldas.github.io/posts/reactive-rxjs-pros-cons/https://danielcaldas.github.io/posts/reactive-series-preface/https://danielcaldas.github.io/posts/sli-slo-sla/https://danielcaldas.github.io/posts/tips-end-to-end-testing-puppeteer/https://danielcaldas.github.io/posts/tips-jest-unit-testing/https://danielcaldas.github.io/posts/unit-testing-with-fixtures-unleashed/https://danielcaldas.github.io/posts/web-compatible-developers/https://danielcaldas.github.io/posts/why-reactive-programming/https://danielcaldas.github.io/projects/https://danielcaldas.github.io/tags/berlin/https://danielcaldas.github.io/tags/conference/https://danielcaldas.github.io/tags/css/https://danielcaldas.github.io/tags/functional-programming/https://danielcaldas.github.io/tags/javascript/https://danielcaldas.github.io/tags/job-market/https://danielcaldas.github.io/tags/open-source/https://danielcaldas.github.io/tags/opinion/https://danielcaldas.github.io/tags/ops/https://danielcaldas.github.io/tags/productivity/https://danielcaldas.github.io/tags/react/https://danielcaldas.github.io/tags/reactive-programming/https://danielcaldas.github.io/tags/software-testing/https://danielcaldas.github.io/tags/svelte/https://danielcaldas.github.io/tags/vscode/https://danielcaldas.github.io/tags/webpack/ \ No newline at end of file diff --git a/tags/berlin/index.html b/tags/berlin/index.html index 16ce686..fcfa751 100644 --- a/tags/berlin/index.html +++ b/tags/berlin/index.html @@ -1,4 +1,4 @@ - posts - Hey ๐Ÿ‘‹ + posts - ~/ @@ -32,8 +32,8 @@ } setup() -
    2018
    2018
    \ No newline at end of file +#css
    \ No newline at end of file diff --git a/tags/conference/index.html b/tags/conference/index.html index 16ce686..fcfa751 100644 --- a/tags/conference/index.html +++ b/tags/conference/index.html @@ -1,4 +1,4 @@ - posts - Hey ๐Ÿ‘‹ + posts - ~/ @@ -32,8 +32,8 @@ } setup() -
    2018
    2018
    \ No newline at end of file +#css
    \ No newline at end of file diff --git a/tags/css/index.html b/tags/css/index.html index 6e515b1..bd93d60 100644 --- a/tags/css/index.html +++ b/tags/css/index.html @@ -1,4 +1,4 @@ - posts - Hey ๐Ÿ‘‹ + posts - ~/ @@ -32,7 +32,7 @@ } setup() -
    2018
    2018
    \ No newline at end of file +#berlin
    \ No newline at end of file diff --git a/tags/functional-programming/index.html b/tags/functional-programming/index.html index 5e928f2..d3b4fa8 100644 --- a/tags/functional-programming/index.html +++ b/tags/functional-programming/index.html @@ -1,4 +1,4 @@ - posts - Hey ๐Ÿ‘‹ + posts - ~/ @@ -32,6 +32,6 @@ } setup() -
    2020
    2020
    \ No newline at end of file +#javascript
    \ No newline at end of file diff --git a/tags/javascript/index.html b/tags/javascript/index.html index 6585efb..f28dded 100644 --- a/tags/javascript/index.html +++ b/tags/javascript/index.html @@ -1,4 +1,4 @@ - posts - Hey ๐Ÿ‘‹ + posts - ~/ @@ -32,16 +32,15 @@ } setup() -
    2021
    2020
    2019
    2018
    2021
    2020
    2019
    2018
    \ No newline at end of file +#berlin
    \ No newline at end of file diff --git a/tags/job-market/index.html b/tags/job-market/index.html index c85c03a..cb42802 100644 --- a/tags/job-market/index.html +++ b/tags/job-market/index.html @@ -1,4 +1,4 @@ - posts - Hey ๐Ÿ‘‹ + posts - ~/ @@ -32,5 +32,5 @@ } setup() -
    2018
    \ No newline at end of file +
    2018
    \ No newline at end of file diff --git a/tags/open-source/index.html b/tags/open-source/index.html index 38d5daa..53dff5d 100644 --- a/tags/open-source/index.html +++ b/tags/open-source/index.html @@ -1,4 +1,4 @@ - posts - Hey ๐Ÿ‘‹ + posts - ~/ @@ -32,6 +32,6 @@ } setup() -
    2024
    2020
    \ No newline at end of file +#opinion
    \ No newline at end of file diff --git a/tags/opinion/index.html b/tags/opinion/index.html index 38d5daa..53dff5d 100644 --- a/tags/opinion/index.html +++ b/tags/opinion/index.html @@ -1,4 +1,4 @@ - posts - Hey ๐Ÿ‘‹ + posts - ~/ @@ -32,6 +32,6 @@ } setup() -
    2024
    2020
    \ No newline at end of file +#opinion
    \ No newline at end of file diff --git a/tags/ops/index.html b/tags/ops/index.html index ebd70e4..fdbdc91 100644 --- a/tags/ops/index.html +++ b/tags/ops/index.html @@ -1,4 +1,4 @@ - posts - Hey ๐Ÿ‘‹ + posts - ~/ @@ -32,5 +32,5 @@ } setup() -
    2022
    2020
    \ No newline at end of file +
    2022
    2020
    2018
    \ No newline at end of file diff --git a/tags/productivity/index.html b/tags/productivity/index.html index ae2887e..12f7db6 100644 --- a/tags/productivity/index.html +++ b/tags/productivity/index.html @@ -1,4 +1,4 @@ - posts - Hey ๐Ÿ‘‹ + posts - ~/ @@ -32,9 +32,8 @@ } setup() -
    2020
    2019
    2021
    2020
    2019
    \ No newline at end of file +#vscode
    \ No newline at end of file diff --git a/tags/react/index.html b/tags/react/index.html index 66c02bb..b3653f7 100644 --- a/tags/react/index.html +++ b/tags/react/index.html @@ -1,4 +1,4 @@ - posts - Hey ๐Ÿ‘‹ + posts - ~/ @@ -32,6 +32,6 @@ } setup() -
    2020
    2021
    2020
    \ No newline at end of file +#react
    \ No newline at end of file diff --git a/tags/reactive-programming/index.html b/tags/reactive-programming/index.html index c842bbe..353bbf1 100644 --- a/tags/reactive-programming/index.html +++ b/tags/reactive-programming/index.html @@ -1,4 +1,4 @@ - posts - Hey ๐Ÿ‘‹ + posts - ~/ @@ -32,7 +32,7 @@ } setup() -
    2021
    2020
    2021
    2020
    \ No newline at end of file +#reactive-programming
    \ No newline at end of file diff --git a/tags/software-testing/index.html b/tags/software-testing/index.html index 76e5963..3d7f9c3 100644 --- a/tags/software-testing/index.html +++ b/tags/software-testing/index.html @@ -1,4 +1,4 @@ - posts - Hey ๐Ÿ‘‹ + posts - ~/ @@ -32,7 +32,7 @@ } setup() -
    2020
    2019
    \ No newline at end of file +
    2021
    2020
    2019
    \ No newline at end of file diff --git a/tags/svelte/index.html b/tags/svelte/index.html index 4bc6af2..4d918c6 100644 --- a/tags/svelte/index.html +++ b/tags/svelte/index.html @@ -1,4 +1,4 @@ - posts - Hey ๐Ÿ‘‹ + posts - ~/ @@ -32,7 +32,7 @@ } setup() -
    2021
    2021
    \ No newline at end of file +#reactive-programming
    \ No newline at end of file diff --git a/tags/vscode/index.html b/tags/vscode/index.html index aacd699..b84c399 100644 --- a/tags/vscode/index.html +++ b/tags/vscode/index.html @@ -1,4 +1,4 @@ - posts - Hey ๐Ÿ‘‹ + posts - ~/ @@ -32,7 +32,7 @@ } setup() -
    2019
    2019
    \ No newline at end of file +#vscode
    \ No newline at end of file diff --git a/tags/webpack/index.html b/tags/webpack/index.html index 15f39d6..cb72611 100644 --- a/tags/webpack/index.html +++ b/tags/webpack/index.html @@ -1,4 +1,4 @@ - posts - Hey ๐Ÿ‘‹ + posts - ~/ @@ -32,6 +32,6 @@ } setup() -
    2020
    2020
    2019
    \ No newline at end of file +#webpack
    \ No newline at end of file