<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Webdev with Bengin]]></title><description><![CDATA[Webdev with Bengin]]></description><link>https://blog.cetindere.de/</link><image><url>https://blog.cetindere.de/favicon.png</url><title>Webdev with Bengin</title><link>https://blog.cetindere.de/</link></image><generator>Ghost 4.35</generator><lastBuildDate>Sun, 01 Mar 2026 04:48:57 GMT</lastBuildDate><atom:link href="https://blog.cetindere.de/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[How to Fix Flashing FontAwesome Icons on Page Load]]></title><description><![CDATA[<p>If you ever used FontAwesome with a server side rendered framework like nextjs, remix or static site generator like GatsbyJS, you probably noticed it. On each page load, the FontAwesome icons are huge for a split second. After some research, I found out why that happens and how to fix</p>]]></description><link>https://blog.cetindere.de/fix-huge-flashing-icons-fontawesome/</link><guid isPermaLink="false">62c18c5fe5989b0001be063b</guid><category><![CDATA[webdev]]></category><dc:creator><![CDATA[Bengin Cetindere]]></dc:creator><pubDate>Sun, 17 Jul 2022 09:08:51 GMT</pubDate><media:content url="https://blog.cetindere.de/content/images/2022/07/ezgif.com-gif-maker-2--1.gif" medium="image"/><content:encoded><![CDATA[<img src="https://blog.cetindere.de/content/images/2022/07/ezgif.com-gif-maker-2--1.gif" alt="How to Fix Flashing FontAwesome Icons on Page Load"><p>If you ever used FontAwesome with a server side rendered framework like nextjs, remix or static site generator like GatsbyJS, you probably noticed it. On each page load, the FontAwesome icons are huge for a split second. After some research, I found out why that happens and how to fix it:</p><p><strong>FontAwesome icons flicker on page load because their CSS is loaded only after they are displayed. Even though scaling them via inline CSS makes the flashing less noticeable, including the FontAwesome styles via an import into the root route effectively eliminates the flashing.</strong></p><p>Let&apos;s look at how to apply those two methods to your problem:</p><h2 id="scaling-fontawesome-icons-inline">Scaling FontAwesome Icons Inline</h2><p>If the problem is rather local and not in your whole app / website, maybe this fix will be more feasible:</p><pre><code class="language-jsx">&lt;FontAwesomeIcon icon={faEnvelope} width=&quot;16&quot; /&gt;</code></pre><p>By giving the element a width inline, even without external CSS, the icon is not rendered hugely. Instead, you may notice a small change in size on page load, depending on what the icon&apos;s actual size is.</p><p>If you use icons only e.g. in the menu, you can consider playing with the width until the desired width is reached and the icon is not changing in size after the stylesheet is loaded. Take care of the fact that on different screen sizes different icon sizes might be needed.</p><p>If you display icons at several places within your site, consider trying the next solution that eliminates the flashing completely:</p><h2 id="loading-fontawesome-stylesheet-in-root-route">Loading FontAwesome Stylesheet in Root Route</h2><p>Apart inlining a specific size, you can import the FontAwesome stylesheet to be bundled within the rest of your deliverable code.</p><p>This works a bit different depending on what framework you are using, but you need to find a place where if you put code, it will be executed / sent for each site.</p><p>For vanilla react, this might be inside <code>index.html</code> or <code>index.js</code> while in remix, it&apos;s <code>root.tsx</code> and for next it&apos;s the <code><code>pages/_app.js</code></code> layout. This is what you want to do:</p><pre><code class="language-jsx">import &apos;@fortawesome/fontawesome-svg-core/styles.css&apos;;
import { config } from &quot;@fortawesome/fontawesome-svg-core&quot;;
// Prevent fontawesome from dynamically adding its css since we are going to include it manually
config.autoAddCss = false;</code></pre><p>This will make FontAwesome not add their CSS automatically. Instead, we got the &#xA0;<code>faStyleSheetUrl</code> and will use it manually now. Again, depending on what framework you are using, you have to add the stylesheet differently. Next.js or React will automatically import the CSS and apply it without the client needing to wait for an external stylesheet.</p><p>If you are using a framework that doesn&apos;t automatically add / apply imported CSS, you have to do it manually. Here is an example on how to accomplish that in remix.</p><pre><code>import faStylesheetUrl from&apos;@fortawesome/fontawesome-svg-core/styles.css&apos;;
import { config } from &quot;@fortawesome/fontawesome-svg-core&quot;;
// Prevent fontawesome from dynamically adding its css since we did it manually above
config.autoAddCss = false;

export const links: LinksFunction = () =&gt; {
  return [{ rel: &quot;stylesheet&quot;, href: tailwindStylesheetUrl },
    { rel: &quot;stylesheet&quot;, href: customStylesheetUrl },
    { rel: &quot;stylesheet&quot;, href: faStylesheetUrl }
  ];
};</code></pre><p>If you are using another framework, you should try to import the contents of the stylesheet and put it into the header (like shown for remix above).</p><p>This will make the CSS for styling the icons available in the same file where the icons are rendered, which means no flickering.</p>]]></content:encoded></item><item><title><![CDATA[Summaries and Recommendations: The 4-Hour Work Week by Tim Ferriss]]></title><description><![CDATA[<p>If you have enough of working for your boss in your corporate cubicle, this book is for you. I just finished reading <a href="https://amzn.to/3u5Qzo9">Tim Ferriss&apos; 4-Hour Work Week</a> and I am going to share a high level summary, so you can decide if it&apos;s for you or not.</p>]]></description><link>https://blog.cetindere.de/4-hour-work-week-recommendation-summary-tim-ferriss/</link><guid isPermaLink="false">623ef7db02eb230001355cd9</guid><category><![CDATA[indie-business]]></category><dc:creator><![CDATA[Bengin Cetindere]]></dc:creator><pubDate>Sun, 03 Apr 2022 17:03:39 GMT</pubDate><media:content url="https://blog.cetindere.de/content/images/2022/04/prateek-katyal-_YzGQvASeMk-unsplash-1-.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://blog.cetindere.de/content/images/2022/04/prateek-katyal-_YzGQvASeMk-unsplash-1-.jpg" alt="Summaries and Recommendations: The 4-Hour Work Week by Tim Ferriss"><p>If you have enough of working for your boss in your corporate cubicle, this book is for you. I just finished reading <a href="https://amzn.to/3u5Qzo9">Tim Ferriss&apos; 4-Hour Work Week</a> and I am going to share a high level summary, so you can decide if it&apos;s for you or not.</p><p>Just skim through the sections and see if you find something that piques your interest.</p><h1 id="general-structure">General Structure</h1><p>This book does not contain &quot;a little trick&quot; to only work 4 hours per week. It literally introduces you to a whole new style of working. This is part of what Tim calls &quot;Lifestyle Design&quot; and it&apos;s a sustainable system that does require effort from your side. </p><p>He divides his book into 4 sections, which are Definition, Elimination, Automation and Liberation. All the sections are for optimizing your life on a different dimension. </p><h1 id="definition-of-your-new-life">Definition (of Your New Life)</h1><p>In this chapter, you are taught the concept of New Riches. It&apos;s basically a term Tim uses for people who are not mega rich, but generate the money they need to fulfill their travel-, accommodation- and free time dreams without actively working more than a few hours a week.</p><p>This chapter effectively introduces you into the world of New Riches and sets the mood for the rest of the book. Here you also learn Dreamlining. It&apos;ll help you be more reasonable and see that you actually don&apos;t need that much revenue to live happily, whatever that means to you.</p><p>One of the hidden gems inside this chapter is the Fear Setting technique. Most people, if forced to decide, will choose unhappiness over uncertainty. Fear Settings is a technique you can use to control and correctly assess fears, especially related to things like quitting your job or risking other things.</p><h1 id="eliminate-hours-of-your-workday">Eliminate (Hours of Your Workday)</h1><p>This chapter is probably the most information-dense, that&apos;s why I split it into many subtopics. It sets the baseline for many following techniques or methods.</p><p>Feel free to just skim the headlines if they suffice to decide if you want to read the book or not:</p><h2 id="eliminating-hours-via-paretos-principle">Eliminating Hours via Pareto&apos;s Principle</h2><p>Tim uses the Pareto principle to explain how to work just a few hours in your corporate job each day while still getting results that can even surpass eight hours of an average employee. </p><p>He also applies this to good/bad customers (as a business owner). As a business owned by a New Rich, you don&apos;t want each and all customers you can get. You&apos;d rather want less revenue, but with only the good customers. The ones that are not using up much of your time (for support, e.g.) or cheap.</p><p>This will help you get profitable without working more than a few hours each week. You <em>could </em>make more money, but you&apos;d need significantly more time each week, which is a tradeoff that is not scalable.</p><h2 id="eliminating-physical-presence-and-commute">Eliminating Physical Presence and Commute</h2><p>There is also a very useful part for employees that may not want to take the leap into self-employment or founding: In this part, Tim teaches a very effective way to get your employer to allow remote work. </p><p>I can&apos;t guarantee that every boomer-conservative boss will allow it, but the odds are pretty high with his method. Especially if you use the previous chapters to outperform other people in the company.</p><h2 id="eliminating-hours-via-awareness-of-parkinsons-law">Eliminating Hours via Awareness of Parkinson&apos;s Law</h2><p>He also introduces Parkinson&apos;s Law, a law postulating that we fill 8 hours per day because there are 8 hours to fill. If there would be more, we would fill more. That doesn&apos;t mean we are working productively and on important tasks in those hours. </p><p>Parkinson&apos;s Law: &quot;Work expands so as to fill the time available for its completion.&quot;. This is the reason why it&apos;s very important to set deadlines, even if they are only for yourself. Timebox tasks to work on them more efficiently.</p><p>The reason why it seems off to only work a few hours per day is because we usually <em>do </em>fell all 8 hours. Just not with important stuff.</p><h2 id="eliminate-hours-by-checking-emails-less-frequent">Eliminate Hours by Checking Emails Less Frequent</h2><p>Tim doesn&apos;t like emails. I don&apos;t like emails either, and I think you share our hate for emails too.</p><p>He provides templates for auto-responders and techniques to reduce email checking to a bare minimum. It&apos;s a bold move for an employee you can - at least from my point of view - only pull off after you have already shown your above-average productivity.</p><p>But if you manage to apply it, it&apos;s a game changer for many people that get pulled out of focus from emails every few minutes / hours. You will also waste less time by batching responses to emails and therefore eliminating content-switches.</p><h2 id="exercises-and-tasks-for-working-fewer-hours">Exercises and Tasks for Working Fewer Hours</h2><p>I don&apos;t want to spoiler too much, so I&apos;ll only say that in the book, there are several exercises included to help you apply the above principles, together with a set of rules that will effectively disallow active but unproductive work.</p><p>Most of them I was able to apply pretty well, they do make a difference. He also compiled a set of questions you can ask yourself via an alarm clock to get focused during the day as often as you like (he recommends at least 3 times).</p><p> Other topics I left out but are worth mentioning:</p><ul><li><strong>Low-Information Diet</strong> - carefully decide what information you consume</li><li><strong>Empowerment Failure </strong>- prevent people having to call you because they are not empowered (permission-wise, knowledge-wise, etc.)</li><li><strong>Eliminating-Paper-Tooling</strong> - List of Tools for managing stuff without paper</li></ul><h1 id="automation-and-outsourcing-of-tasks-in-your-business-and-life">Automation and Outsourcing (of Tasks in Your Business and Life)</h1><p>This chapter deals with how to reduce the few tasks that are left after elimination even further by outsourcing and automating. This is my favorite chapter, and I was really not aware outsourcing can work that well.</p><h2 id="outsourcing-via-virtual-assistants">Outsourcing via Virtual Assistants</h2><p>Tim lets a friend tell a story about how outsourcing changed his life forever. The story is largely to show off what tasks a virtual assistant is capable of doing for you. </p><p>I am not going to go into detail here because a big chunk of the story is made up of examples, but I really enjoyed reading it and was instantly inspired to outsource work (which I did and it worked quite well, but that&apos;s for another time).</p><p>It does not exactly apply much to traditional employees, but if you want to take the leap to self-employment or founding, you can use your employee-money to buy time of a virtual assistant who does tasks for you while you still have the safety of an employment.</p><p>He also has a bunch of tips included on where to find good VAs and how to choose the right one.</p><h2 id="automating-income-generation-mostly">Automating Income Generation (mostly)</h2><p>As you probably know, passive income is very rare and should not be the goal of most people. Instead, you should strive for a healthy business and then </p><ol><li>Eliminate everything that&apos;s not directly beneficial for your business</li><li>Automate everything that can be automated</li><li>Outsource everything of the remaining tasks that you don&apos;t like and can outsource</li></ol><p>In this section, the book at hand deals with what types of businesses can be a muse. A muse is a business that does not rely on you spending time to make money (like it would be the case for e.g. a consultancy).</p><p>The goal is to create &quot;an automated vehicle for generating cash without consuming time&quot;. He does explain in detail how to choose, validate and build this, but it&apos;s one of the chapters you have to put a lot of your own personality and interests in. </p><p>In this part, he also touches base on basics of economy and marketing, but if you are serious about either of these two topics, I&apos;d rather recommend you read <a href="https://amzn.to/3tVDcGY">&quot;100M Offers: How To Make Offers So Good People Feel Stupid Saying No&quot; by Alex Hormozi</a>. It&apos;s deceivingly low-priced at like one dollar, Alex will also explain why in his book. He&apos;s a genius, I definitely have to write about that book next. </p><p>Back to the 4-hour work week, Tim pays attention to always remind you that the goal is neither a 80h week CEO position, nor a million dollar exit. Accordingly, he will teach you &quot;Management by Absence&quot;. This includes automating Ads, traffic, conversion to sales, payment processing and even fulfillment if you have physical products. </p><p>This will effectively remove you from the equation and you will be only required to guide the business into the right direction or make changes in the process.</p><h1 id="liberate-yourself">Liberate (Yourself)</h1><p>By liberation, Tim means being able to choose where you work from. He is talking about traveling the world, already having learned how to spend less time doing your job correctly and now being able to choose to stay near the beach/mountains/whatever you like.</p><p>He is very big on geo-arbitrage, which takes advantage of prices being different in different regions. If you believe it or not, your current income most likely will be enough to life an almost luxurious life in another country.</p><p>One part of this chapter is about how to achieve this liberation of location. The other part is really about how to spend your newly gained freetime since many people seem to get bored quickly. How to plan bigger &quot;mini-retirements&quot;, as he calls them, and what to pay attention to.</p><p>It&apos;s inspiring. Even if you don&apos;t get much direct value out of it because you are not there yet, the possibilities are very effective for motivating yourself to master everything before and achieve liberation. </p><h1 id="personal-opinion">Personal Opinion</h1><p>I am a full stack developer and consultant who just quit his job to work on my own stuff. This book was one of the foundations I built on, combined with 2 projects I already run and combined with some other books I&apos;ll soon recommend too.</p><p>The 4-hour work week will help get you further in life, if</p><ul><li>are already running a business<br>as you will learn a lot about elimination, automation and outsourcing</li><li>are thinking about switching from employment to running your own thing<br>as you will learn a lot about how to make the transition smoother and with more safety before quitting (that&apos;s what I did)</li><li>are an employee and want to stay employed.<br>This category will not benefit from this book as much as the above two. But still, you&apos;ll at least learn to use your time in a way more effecient manner and save many hours of work that you can spend with friends and family.</li></ul><p>As you can see, this book is at least moderately useful to almost anyone. Especially well suited if you are searching for a life-changing gift without knowing too much about a person.</p><p>Even though it&apos;s a pretty old book, it still did something great for me. I hope this article shed some light onto the question whether this book is worth a read or not. If you want to support me, <a href="https://amzn.to/3u5Qzo9">consider buying it via my ref-link</a>.</p>]]></content:encoded></item><item><title><![CDATA[Learning React: Custom Notification-System with Context]]></title><description><![CDATA[<p>Displaying a notification to the user is typical for web applications. Let me show you how we can leverage React Context to build a global, customizable Notification-System.</p><p>To give you a high-level overview of this article, we will:</p><ol><li>Create a <code>Notification</code> component that can hold and display one single notification</li></ol>]]></description><link>https://blog.cetindere.de/react-notification-contexts/</link><guid isPermaLink="false">618eb0a91864ed0001825386</guid><category><![CDATA[webdev]]></category><category><![CDATA[ux]]></category><dc:creator><![CDATA[Bengin Cetindere]]></dc:creator><pubDate>Sat, 27 Nov 2021 11:17:00 GMT</pubDate><media:content url="https://blog.cetindere.de/content/images/2021/11/Screenshot-2021-11-18-080713.png" medium="image"/><content:encoded><![CDATA[<img src="https://blog.cetindere.de/content/images/2021/11/Screenshot-2021-11-18-080713.png" alt="Learning React: Custom Notification-System with Context"><p>Displaying a notification to the user is typical for web applications. Let me show you how we can leverage React Context to build a global, customizable Notification-System.</p><p>To give you a high-level overview of this article, we will:</p><ol><li>Create a <code>Notification</code> component that can hold and display one single notification</li><li>Create a <code>NotificationManager</code> component that can display several notifications and remove them again</li><li>Make the <code>NotificationManager</code> available from all / most components (by using Context)</li></ol><p>In contrast to many other tutorials, I am not giving you a huge project on GitHub. The focus here is on learning and not on copying. I&apos;ll also assume you have a React app that is able to run. &#xA0;</p><p>I am using <a href="https://create-react-app.dev/docs/adding-typescript/">react with TypeScript</a>. I recommend everyone to give it a shot, but this is not a debate about TypeScript vs JavaScript. For those of you who don&apos;t use / know TypeScript, you should be able to understand the code and construct a JavaScript version fairly easily.</p><p>I&apos;ll style my notification component with <a href="https://tailwindcss.com/">Tailwind CSS </a>- as this is purely cosmetic, you can choose any other CSS framework of course or none at all. In this project I am also using <a href="https://fontawesome.com/">Fontawesome</a> for Icons - the button to close a notification is a <code>FontawesomeIcon</code> component, you can simply replace it by any other HTML element if you are not using it.</p><p>If you have any questions while reading through this, <a href="https://twitter.com/bengincet">simply hit my Twitter DMs</a> and I&apos;ll answer them as soon as possible, promise!</p><h1 id="notification-component">Notification Component</h1><p>I did some basic styling for a notification component with different colored versions to indicate an error, warning, success or information. Here&apos;s the one for success:</p><figure class="kg-card kg-code-card"><pre><code class="language-JavaScript">&lt;div className=&quot;m-4 border-l-4 border-green-400 bg-green-100 rounded-sm shadow px-6 py-4 relative&quot;&gt;
	&lt;div onClick={props.onClick} className=&quot;absolute text-green-900 top-0 right-0 pt-2 pr-3 cursor-pointer&quot;&gt;
    	&lt;FontAwesomeIcon icon={faTimesCircle}/&gt;
    &lt;/div&gt;
  &lt;p className=&quot;text-lg font-bold text-green-900&quot;&gt;{props.title}&lt;/p&gt;
  &lt;p className=&quot;text-green-900/80&quot;&gt;{props.content}&lt;/p&gt;
&lt;/div&gt;
</code></pre><figcaption>snippet from Notification.tsx</figcaption></figure><p>I return this jsx if the <code>type</code> prop of the component is <code>&quot;success&quot;</code> and a similar version (different colors) for the other types.</p><p>You should be able to render the component in place with some static values now:</p><figure class="kg-card kg-code-card"><pre><code class="language-JavaScript">&lt;Notification title=&quot;Successfully Sent Data&quot; content=&quot;Your request has been transmitted to our side successfully. Thank you.&quot; type=&quot;success&quot; onClick={()=&gt;alert(&quot;you clicked close&quot;)}/&gt;</code></pre><figcaption>example for testing</figcaption></figure><p>This should render a notification (inline) with the given title and content that will alert you when you click its close button. </p><h1 id="notificationmanager-component">NotificationManager Component</h1><p>We don&apos;t want to render those Notification components in each component that generates them individually. This would yield the problem that if more than one component shows a notification, they would overlap because you would probably render both with a fixed position.</p><p>In order to counter that behavior (and for clean code) we are moving that into the NotificationManager. </p><p>The NotificationManager will keep an array of notifications that will be rendered, and it will also handle the <code>onClick</code> event of those rendered notifications, so it can remove them again:</p><figure class="kg-card kg-code-card"><pre><code class="language-JavaScript">// the interface export is TypeScript-specific
export interface NotificationData {
  title: string,
  content: string,
  type: &quot;success&quot; | &quot;error&quot; | &quot;warning&quot; | &quot;info&quot;
}

const NotificationManager = () =&gt; {
  const [notifications, setNotifications] = useState&lt;Array&lt;NotificationData&gt;&gt;([]);

  return (
  	&lt;div className=&quot;fixed bottom-0 max-w-xl&quot;&gt;
  		{notifications.map(notification =&gt;
  			&lt;Notification key={notification.content} {...notification}
  				onClick={() =&gt; {
  					const index = notifications.indexOf(notification)
                      const clone = notifications.slice();
                      clone.splice(index, 1);
                      setNotifications(clone);
                }}
            /&gt;
  		)}
  	&lt;/div&gt;
  );
};

export default NotificationManager;
</code></pre><figcaption>basic NotificationManager component</figcaption></figure><p>As you can see, we iterate through the notifications array and render all of them. The component itself will render in a <code>position: fixed</code> style at the bottom and with a given max width.</p><p>The <code>onClick</code> property of the Notifications is set to a function so that the clicked notification will be removed.</p><p>To test this, you can initialize the <code>notifications</code> state like this:</p><figure class="kg-card kg-code-card"><pre><code class="language-JavaScript">const [notifications, setNotifications] = useState&lt;Array&lt;NotificationData&gt;&gt;([{
	title: &quot;Successfully Sent Data&quot;,
    content: &quot;Your request has been transmitted to our side successfully. Thank you.&quot;,
    type: &quot;success&quot;
},{
	title: &quot;Error Sending Data&quot;,
    content: &quot;Your request has not been transmitted. Please try again later.&quot;,
    type: &quot;error&quot;
}]);</code></pre><figcaption>test with pre-initialized notifications</figcaption></figure><p>You should see 2 notifications and be able to remove them upon click on the cross. </p><h1 id="notificationmanager-with-context">NotificationManager with Context</h1><p>For now, we can&apos;t add notifications from other components yet. </p><p>Let&apos;s add an <code>addNotification</code> function inside the NotificationManager component just below the getter and setter of the notifications:</p><figure class="kg-card kg-code-card"><pre><code class="language-JavaScript">const addNotification = (notif: NotificationData) =&gt; {
	setNotifications(notifications.concat(notif));
}</code></pre><figcaption>addNotification function</figcaption></figure><p>We <em>could</em> pass the <code>addNotifiation</code> function down the component tree, but this is cumbersome and error-prone. Here&apos;s a quote from the <a href="https://reactjs.org/docs/context.html">context documentation</a>:</p><blockquote>Context provides a way to pass data through the component tree without having to pass props down manually at every level.</blockquote><p>This is what we need React Context for. We will create a so called <code>ContextProvider</code> with the <code>addNotification</code> function as the value and let other components consume this context in order to get the <code>addNotification</code> function.</p><p>We need this to be callable by other components, so we create a context. We create this context outside the component, since we can&apos;t export something inside a component.</p><figure class="kg-card kg-code-card"><pre><code class="language-JavaScript">export const NotificationContext = React.createContext((notif: NotificationData) =&gt; {});

const NotificationManager = ()=&gt;{
	// ...
};
export default NotificationManager;</code></pre><figcaption>create context</figcaption></figure><p>We are creating a new Context with the <code>createContext</code> method. This method takes one argument, which will later be accessible to all <code>ContextConsumers</code>.</p><p>We would like to pass our <code>addNotification</code> function here, but we can&apos;t reference it since its scope is inside the NotificationManager component.</p><p>Therefore, we just provide an empty function as the default. Then, inside the NotificationManager&apos;s output, we wrap everything inside <code>&lt;NotificationContext.Provider&gt;&lt;/NotificationContext.Provider&gt;</code> tags like this:</p><figure class="kg-card kg-code-card"><pre><code class="language-JavaScript">return (
	&lt;NotificationContext.Provider value={addNotification}&gt;
    // ....
	&lt;/NotificationContext.Provider&gt;
);</code></pre><figcaption>wrap everything with NotificationContext.Provider</figcaption></figure><p>This will allow us to change the value from the empty function to our <code>addNotification</code> function.</p><p>Since the context can only be consumed by components which are inside the <code>&lt;NotificationContext.Provider&gt;&lt;/NotificationContext.Provider&gt;</code> tags, we will make our NotificationManager component a parent component like this:</p><figure class="kg-card kg-code-card"><pre><code class="language-JavaScript">const NotificationManager: React.FC = (props: React.PropsWithChildren&lt;{}&gt;) =&gt;{
  const [notifications, setNotifications] = useState&lt;Array&lt;NotificationData&gt;&gt;([]);

  const addNotification = (notif: NotificationData) =&gt; {
    setNotifications(notifications.concat(notif));
  }

  return (
      &lt;NotificationContext.Provider value={addNotification}&gt;
        {props.children}
        &lt;div className=&quot;fixed bottom-0 max-w-xl&quot;&gt;
          {notifications.map(notification =&gt;
              &lt;Notification key={notification.content} {...notification}
                            onClick={() =&gt; {
                              const index = notifications.indexOf(notification)
                              const clone = notifications.slice();
                              clone.splice(index, 1);
                              setNotifications(clone);
                            }}
              /&gt;
          )}
        &lt;/div&gt;
      &lt;/NotificationContext.Provider&gt;
  );
};</code></pre><figcaption>Final NotificationManager component</figcaption></figure><p>You can see that we are rendering <code>{props.children}</code> inside the provider. This will be equal to whatever we will put inside the <code>&lt;NotificationManager&gt;&lt;/NotificationManager&gt;</code> tags.</p><!--kg-card-begin: html--><div style="padding: 1rem 1.5rem; background-color: #0003; color: #222; border-radius: 0.3rem;">
Notice that for this to work in TypeScript, we are required to define the props of type React.PropsWithChildren so we can use props.children in the component but are not required or even allowed to specify a children prop manually.
</div><!--kg-card-end: html--><p>We can then use the <code>NotificationManager</code> component as a wrapping component, similar to how you would use a <code>Router</code> component or other providers:</p><figure class="kg-card kg-code-card"><pre><code class="language-JavaScript">&lt;NotificationManager&gt;
	&lt;Router&gt;
    	// ...
	&lt;/Router&gt;
&lt;/NotificationManager&gt;</code></pre><figcaption>Usage of the NotificationManager component</figcaption></figure><p>After this setup, you&apos;ll end up with a very convenient way to add notifications that stack correctly and are removable by click:</p><figure class="kg-card kg-code-card"><pre><code class="language-JavaScript">const SomeComponent = () =&gt; {
  const addNotification = useContext(NotificationContext);
  
  return &lt;div&gt;
  	&lt;button onClick={()=&gt;{
    	addNotification({title: &quot;...&quot;, content: &quot;...&quot;, type: &quot;success&quot;});
    }}&gt;
    	Click Me
    &lt;/button&gt;
  &lt;/div&gt;
};</code></pre><figcaption>Usage of the addNotification function</figcaption></figure><p>We are simply fetching the <code>NotificationContext</code> with the <code>useContext</code> hook and have access to this function in our component. Every component which has our NotificationManager as <em>any </em>ancestor will be able to consume this context with the above hook.</p><h1 id="extensibility">Extensibility</h1><p>Since this is custom-built, you can customize as much as you want. For example, you could remove the messages again, after a few seconds, via a <code>setTimeout</code> inside the <code>addNotification</code> function.</p><p>If you do so, you could add some kind of indicator for how long the message will be shown (like a loading bar) or add an animation for how it appears/disappears.</p><p>Thank you for reading - if you liked this article, considering <a href="https://twitter.com/bengincet">following me on Twitter</a> to stay in to &#x1F64C;</p>]]></content:encoded></item><item><title><![CDATA[MasterCard, Visa & the Payment-Mafia - Why Payment is Messed Up]]></title><description><![CDATA[<p>We&apos;ve come to a point in time in which <em><strong>technically</strong></em>, everyone can accept payments for their online-shop via Shopify + PayPal. Every developer can integrate Stripe payments with a vast amount of security features and risk-management to protect against fraudulent transactions.</p><p>Technology is - as always - at it&</p>]]></description><link>https://blog.cetindere.de/castercard-vsia-payment-mafia/</link><guid isPermaLink="false">609bfcdb2d8f500001b5a532</guid><category><![CDATA[indie-business]]></category><dc:creator><![CDATA[Bengin Cetindere]]></dc:creator><pubDate>Fri, 24 Sep 2021 11:31:18 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1595366014461-5cf6a65345c2?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDF8fG1hZmlhfGVufDB8fHx8MTYyMjExNTM4Mg&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1595366014461-5cf6a65345c2?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDF8fG1hZmlhfGVufDB8fHx8MTYyMjExNTM4Mg&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" alt="MasterCard, Visa &amp; the Payment-Mafia - Why Payment is Messed Up"><p>We&apos;ve come to a point in time in which <em><strong>technically</strong></em>, everyone can accept payments for their online-shop via Shopify + PayPal. Every developer can integrate Stripe payments with a vast amount of security features and risk-management to protect against fraudulent transactions.</p><p>Technology is - as always - at it&apos;s peak, payments can be implemented <em><strong>technically</strong></em> easier than at any time before. Businesses can start accepting payments as professionally as huge giants like Netflix or Spotify.</p><p>But what&apos;s with this emphasis on <strong><em>technically</em></strong>? Well, there is one big fat problem.</p><h1 id="not-everyone-is-welcome">Not Everyone is Welcome</h1><p>PayPal refuses to accept payments for some people. Let&apos;s take a look at the Prohibited Activities section of their Acceptable Use Policy:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.cetindere.de/content/images/2021/05/image.png" class="kg-image" alt="MasterCard, Visa &amp; the Payment-Mafia - Why Payment is Messed Up" loading="lazy" width="1516" height="646" srcset="https://blog.cetindere.de/content/images/size/w600/2021/05/image.png 600w, https://blog.cetindere.de/content/images/size/w1000/2021/05/image.png 1000w, https://blog.cetindere.de/content/images/2021/05/image.png 1516w" sizes="(min-width: 720px) 720px"><figcaption>https://www.paypal.com/us/webapps/mpp/ua/acceptableuse-full at 27th May 2021</figcaption></figure><p>Of course some of these activities are simply illegal, so it&apos;s not necessarily bad that they ban those activities (more about that later) - but things like &quot;<strong>items that are considered obscene</strong>&quot; (2.g) or &quot;<strong>certain sexually oriented materials</strong>&quot; (2.i)? </p><p>This includes all kinds of adult content, be it sexual services or art that is of sexual nature (videography, paintings, literature, ...)! </p><p>Well, people can then just take payments via Stripe, right?</p><p>No they can&apos;t, Stripe prohibits this too:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.cetindere.de/content/images/2021/05/image-1.png" class="kg-image" alt="MasterCard, Visa &amp; the Payment-Mafia - Why Payment is Messed Up" loading="lazy" width="981" height="710" srcset="https://blog.cetindere.de/content/images/size/w600/2021/05/image-1.png 600w, https://blog.cetindere.de/content/images/2021/05/image-1.png 981w" sizes="(min-width: 720px) 720px"><figcaption>https://stripe.com/en-de/restricted-businesses at 27th May 2021</figcaption></figure><p>They, too, ban honest, adult-themed work. It&apos;s not only them though, Mollie doesn&apos;t accept such things either:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.cetindere.de/content/images/2021/05/image-2.png" class="kg-image" alt="MasterCard, Visa &amp; the Payment-Mafia - Why Payment is Messed Up" loading="lazy" width="1081" height="647" srcset="https://blog.cetindere.de/content/images/size/w600/2021/05/image-2.png 600w, https://blog.cetindere.de/content/images/size/w1000/2021/05/image-2.png 1000w, https://blog.cetindere.de/content/images/2021/05/image-2.png 1081w" sizes="(min-width: 720px) 720px"><figcaption>https://help.mollie.com/hc/en-us/articles/115000939369 at 27th May 2021</figcaption></figure><p>Every payment processor has a list of prohibited activities, and adult content is included in almost every payment processor&apos;s list.</p><p>There are only very few payment processors that willingly accept adult themed content, but in return they take somewhere between 12% and 17% of the transaction instead of the default 1.5-2.5% taken by PayPal, Stripe or Mollie.</p><p>Why do they all prohibit adult topics? Neither PayPal, Stripe, Mollie nor any other payment processor themselves are the reason for that. </p><h1 id="the-source">The Source</h1><p>Let me introduce you to MasterCard &amp; Visa. In our world, they are the ones who create and control (the majority of) credit-cards and the ones who have power over them. </p><p>The source of many of these prohibited activities lies here. What&apos;s the reason they name publicly for refusing service? The top 2 reasons are &quot;brand-damaging content&quot; and &quot;high-risk transaction&quot;. </p><h2 id="brand-damage">Brand Damage</h2><p>Brand-Damaging is hard to argue because it&apos;s not measurable, at least not on a per-case basis. But apart from that ... all the C-Level executives at MasterCard and Visa are not consuming any kind of erotica, then? Did I understand that right?</p><p>I don&apos;t like calling people out, but the courtesy ends when they call out adult content as &quot;brand-damaging&quot;. Some people do their living with stuff you call &quot;brand-damaging&quot;.</p><p>It&apos;s not even like MasterCard or Visa would really be tied to the product that could be &quot;brand damaging&quot;. Everyone knows they are buying something from xyz-site and only the payment is done via their card. No one assumes that the card-network affiliates for the content. The card is only a tool. </p><p>A hammer wouldn&apos;t be affiliated with murder, just because someone used it that way. No one would think bad about a workbench brand just because one of their tools has been used for murder in the past.</p><h2 id="high-risk-transaction">High-Risk Transaction</h2><p>The high-risk problem is something they really have to put the blame on themselves. </p><p><em>For anyone not in fin-tech: Customers that purchased via card can request a chargeback for transactions from their banks for a variety of reasons (like credit card was stolen, hacked, I didn&apos;t receive the item, and so on). </em></p><p><em>Those chargebacks are a critical hit (as if the customer rolled a 20) to the merchant because they not only have to give the money back by default, they pay a <a href="https://www.chargebackgurus.com/blog/chargeback-fees-the-true-cost-of-your-chargebacks">chargeback-fee</a> ranging from 10$ to 100$. </em></p><p><em>The merchant can fight the case to get the transaction back, but the chargeback fee is always used for paying for the dispute-resolution and dispute in general. It&apos;s like legal fees, but the merchant always has to pay, even if they&apos;re in the right and can provide evidence.</em></p><p><em>This mechanic has been implemented by Card-Networks like MasterCard and Visa in the early times, when people were too afraid of getting a card that had access to their bank account and could be hacked or stolen. It&apos;s a relic of the past and should not exist in this way anymore. </em></p><p>Why does risk even matter? Transactions should only work when authorized and with the <a href="https://www.cardgate.com/en/3d-secure-3ds-prevents-fraud-with-credit-card-payments/">3DS</a> getting mandatory in more and more countries, it should not be the merchant&apos;s fault when someone exposes their passwords or looses their cards. There should be no bailing-out of transactions without real, severe consequences.</p><p>At the moment, customers have nothing to lose by filing a chargeback with their bank, it&apos;s why this technique is also called <a href="https://www.verifi.com/chargebacks-disputes-faq/what-is-friendly-fraud/">friendly fraud</a>.</p><h1 id="why-this-is-a-bigger-problem-than-you-think">Why This is a Bigger Problem than You Think</h1><p><a href="https://www.eff.org/deeplinks/2020/12/visa-and-mastercard-are-trying-dictate-what-you-can-watch-pornhub">MasterCard recently disabled payments for Pornhub </a>because of a reason that may or may not be legitimate. The problem lies not in why they banned Pornhub or that it was Pornhub. It lies in the fact that MasterCard decided that on their own.</p><p>They are no authority nor regulator, but if they add something to their policy, everyone has to bow the knee or else. </p><p>Is it reasonable for a company to have the power to shut down businesses just like that? Why is the power to take payments centralized to just 2 or 3 very large companies (which are by the way not even authorities, but corporations)?</p><p>MasterCard and Visa if you read this, of course all the above was a joke. I love you and your decisions, pls don&apos;t cancel my card or blacklist me.</p><h1 id="epilogue">Epilogue</h1><p>Payment should not be as centralized as it is. </p><p>If two people want to transact money from one place to another, there shouldn&apos;t be some sort of company that decides whether that is acceptable use or not. </p><p>Yes, it&apos;s MasterCard&apos;s and Visa&apos;s cards, so they make the rules (that&apos;s also the only reason for this being legal), but here&apos;s a quote for ya:</p><!--kg-card-begin: markdown--><blockquote>
<p>With great power comes great responsibility</p>
</blockquote>
<!--kg-card-end: markdown--><h2 id="further-readings">Further Readings</h2><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://www.eff.org/deeplinks/2020/12/visa-and-mastercard-are-trying-dictate-what-you-can-watch-pornhub"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Visa and Mastercard are Trying to Dictate What You Can Watch on Pornhub</div><div class="kg-bookmark-description">Pornhub is removing millions of user-uploaded videos. This action comes after a New York Times column accused the website of hosting sexual videos of underage and nonconsenting women. In response to the Times&#x2019; article, Visa and Mastercard cut ties with Pornhub, making it impossible for Pornhub to...</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://www.eff.org/sites/all/themes/phoenix/apple-touch-icon-precomposed-144x144.png" alt="MasterCard, Visa &amp; the Payment-Mafia - Why Payment is Messed Up"><span class="kg-bookmark-author">Electronic Frontier Foundation</span><span class="kg-bookmark-publisher">Danny O&amp;#039;Brien and rainey Reitman</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://www.eff.org/files/issues/financial-censorship-piggy2.png" alt="MasterCard, Visa &amp; the Payment-Mafia - Why Payment is Messed Up"></div></a></figure><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://www.eff.org/issues/financial-censorship"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Financial Censorship</div><div class="kg-bookmark-description">When financial institutions and payment intermediaries shut down accounts or inhibit transactions, it can have serious ramifications for free expression online. Websites, whether they accept online donations, sell goods online, or simply have a bank account, rely on their financial institutions to e&#x2026;</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://www.eff.org/sites/all/themes/phoenix/apple-touch-icon-precomposed-144x144.png" alt="MasterCard, Visa &amp; the Payment-Mafia - Why Payment is Messed Up"><span class="kg-bookmark-author">Electronic Frontier Foundation</span><span class="kg-bookmark-publisher">rainey Reitman</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://www.eff.org/files/issues/financial-censorship-piggy2.png" alt="MasterCard, Visa &amp; the Payment-Mafia - Why Payment is Messed Up"></div></a></figure><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://thehill.com/policy/technology/548279-mastercard-updates-policy-for-adult-content-sellers"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Mastercard updates policy for adult content sellers</div><div class="kg-bookmark-description">Mastercard is updating requirements for banks that process payments for people and websites that...</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://thehill.com/apple-touch-icon-1024X1024.png" alt="MasterCard, Visa &amp; the Payment-Mafia - Why Payment is Messed Up"><span class="kg-bookmark-author">The Hill</span><span class="kg-bookmark-publisher">Chris Mills Rodrigo</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://thehill.com/sites/default/files/sexworker_042418getty.jpg" alt="MasterCard, Visa &amp; the Payment-Mafia - Why Payment is Messed Up"></div></a></figure>]]></content:encoded></item><item><title><![CDATA[How to Find Micro-SaaS Ideas: The EBIG Framework]]></title><description><![CDATA[<p><em>Once upon a time, I was a slender, thin little software developer with no ideas, just like you. </em>For real though, a few years ago, I was in search of the perfect software idea <strong>permanently</strong>. </p><p>I would constantly look at successful websites or apps and thought, &quot;Technically, this is</p>]]></description><link>https://blog.cetindere.de/find-micro-saas-ideas/</link><guid isPermaLink="false">60c8f20c2d8f500001b5a854</guid><category><![CDATA[indie-business]]></category><dc:creator><![CDATA[Bengin Cetindere]]></dc:creator><pubDate>Mon, 28 Jun 2021 11:56:00 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1465829235810-1f912537f253?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDJ8fGZyYW1ld29ya3xlbnwwfHx8fDE2MjQ4ODE1MTY&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1465829235810-1f912537f253?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDJ8fGZyYW1ld29ya3xlbnwwfHx8fDE2MjQ4ODE1MTY&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" alt="How to Find Micro-SaaS Ideas: The EBIG Framework"><p><em>Once upon a time, I was a slender, thin little software developer with no ideas, just like you. </em>For real though, a few years ago, I was in search of the perfect software idea <strong>permanently</strong>. </p><p>I would constantly look at successful websites or apps and thought, &quot;Technically, this is something I could&apos;ve done too!&quot;. If you can identify with my past self, I&apos;ll teach you how to come up with ideas that actually will attract users.</p><p>EBIG stands for environment-based-idea-generation, and the gist behind this method is to ask people in your environment for problems that they have and work on a solution-draft together with them.</p><p>Because that&apos;s where most developers go wrong. It&apos;s not having an idea and then searching for people who have the appropriate problem. It&apos;s finding a niche audience that has a specific and painful problem, so you can build a perfectly tailored solution for that problem.</p><h2 id="starting-a-conversation-to-find-problems">Starting a Conversation to Find Problems</h2><p>You start out with people in your environment. You&apos;ll always have people in your environment that have problems. Make a list of friends and family and write the niches they work in or have hobbies in. </p><p>Now comes the hard part for us developers. I know this sounds awful, but it won&apos;t be, I promise: You have to talk to a few of those peoples about problems they have in their niche.</p><p>Also, there are companies that are too large to sell to without enterprise procurement (which you really don&apos;t want, trust me). My dad for example works at a really huge parcel delivery service, and they wouldn&apos;t buy or even consider anything that one person produced.</p><p>So, you choose the first person you want to talk with and schedule a call or hang out with them. In the conversation itself, ask them (casually, nothing formal please) what their pain points in their job/hobby are. Try to control the conversation in a way, so they tell you about problems that could be solved by technology. </p><p>You could start out by saying, &quot;I can&apos;t really visualize how your typical work day looks like. Can you summarize for me?&quot; - this way you can get a conversation about this topic started, and they&apos;ll tell you about their workday.</p><h2 id="extracting-problems-from-the-conversation">Extracting Problems From the Conversation</h2><p>There is a good chance they&apos;ll tell you about some problems already. If they didn&apos;t mention specific problems, ask them, &quot;when was the last time you had a painful problem [at work/with hobby X]?&quot;.</p><p>The important thing here is to understand the why. It&apos;s very hard actually to ask the correct questions here, but there is a very helpful book, it&apos;s called <a href="l.cetindere.de/mom-test">&quot;The Mom Test&quot; by Rob Fitzpatrick</a>. </p><p>As a founder, you really absolutely must read this one, it&apos;ll save you a lot of trouble finding and especially validating ideas. Except if you are into building products for 9 months just to find out no one needs them. In this case, you are a masochist by definition, I am sorry you had to find out this way.</p><p>The super-short version of some questions you could ask: Let them explain</p><ul><li>why the problem is painful</li><li>who is affected by the problem</li><li>what happens if they wouldn&apos;t solve the problem</li><li>what they currently do to solve the problem</li></ul><p>If for the question &quot;what happens if they wouldn&apos;t solve the problem&quot; they respond something along the lines of &quot;well, I&apos;ll just do it later&quot; or &quot;We&apos;ll have to file a document.&quot;, this problem is not suited! This response means that the problem is not painful enough.</p><p>If you ask them what they currently do, and they&apos;ll respond, &quot;Well, I just invest the extra 15 minutes to do this monthly task&quot;, this is a clear sign that this product is not suited either.</p><p>I think you get the gist of what to look for. If not, the book linked above will teach you.</p><p>There is also the possibility they will point out how this old rusty software they use is so slow, has bad UX and could be so much better. Take notes! Try out the software if you can and build something better that targets the pain points they complained about. Be sure to check what requirements you&apos;d need to fulfill before they or their company would use your software. In some industries, the requirements are way too high for a solo-dev.</p><h2 id="magic-button">Magic Button</h2><p>Many people can&apos;t imagine what software is capable of and what not - this is where the magic button comes into play. It&apos;s a technique that poses a question in a way that opens up people to talk about problems, even if they don&apos;t seem particularly solvable for them. </p><p>Ask them, &quot;If you had a magic button that, if pressed, would do a part of your work for you, which part of your work would that be?&quot;</p><p>Their response will often be something that can be solved either with normal software or machine learning. It&apos;s so efficient because most people will not think software can help them with their work. Magic can, though. Additionally, magic sounds so absurd, the barrier to responding to you with a problem is much lower.</p><h2 id="requirements-for-a-successful-micro-saas-idea">Requirements for a Successful Micro-SaaS Idea</h2><p>When you have a list of problems that you think could be solvable with software, start vetting the list. The questions above should help you with that.</p><p>If you want to build a <a href="https://userguiding.com/blog/micro-saas/">Micro-SaaS</a>, be sure to check if your idea additionally fulfills these requirements:</p><h3 id="big-enough-market">Big Enough Market</h3><p>The market should be big enough so that if you can capture 1-2% of it, you should be able to make a living off of it. Make sure there are enough potential customers or people searching for a solution to that problem.</p><h3 id="small-enough-market">Small Enough market</h3><p>The market can also be too big, so make sure that it&apos;s small enough. If it&apos;s too big, you&apos;ll have to compete with bigger companies that have more budget for development and marketing than you.</p><h3 id="market-fit">Market Fit</h3><p>You should make sure that your target audience are people that have enough money to pay you. They should be in a market that is growing or at least staying steady. </p><p>A bad market, for example, would be to serve the hobby-painter market. A good market, for example, would be to serve the professional-photographer market.</p><h2 id="drafting-solutions">Drafting Solutions</h2><p>After vetting the different problems, start drafting solutions for them together with your friend or family member that introduced you to the niche. This will be very individual depending on the type of problem. </p><p>After designing a first high-level draft of the solution, you have a person who can check if this is technically viable to build (you) and someone who can say if this would actually be useful (your friend / family member).</p><h2 id="example-from-my-life">Example from My Life</h2><p>I am writing this article because a few days ago, I was talking with my girlfriend&apos;s father about his job. He does general inspections for car safety, and he was complaining about how the appointment-scheduling and payment were really unorganized. </p><p>With some precise questions, I could find out all the details and what could be done better. For example, customers can only make appointments by calling. How cool would it be if you can use an interface similar to Calendly? See which dates / times are free and book something and pay online via PayPal, card or your favorite payment method. </p><p>At the moment calling is the only option + there is no payment possibility other than cash or normal bank transfer which can take days.</p><p>At the moment (mainly because of <a href="https://minervabooks.net">Minervabooks</a>) I don&apos;t have the time for a new project, but I added it to my idea-list. Feel free to steal this one btw, I&apos;d love to see someone succeed with this idea.</p><p></p><p></p>]]></content:encoded></item><item><title><![CDATA[Your users have latency. As a developer, you should too.]]></title><description><![CDATA[I am going to introduce you to an unpopular opinion right now. Bear with me, this will help you craft experiences with better UX. So promise to read on, despite your instincts might tell you otherwise ]]></description><link>https://blog.cetindere.de/your-users-have-latency/</link><guid isPermaLink="false">5ff9bf6df640800001eeafc6</guid><category><![CDATA[webdev]]></category><category><![CDATA[ux]]></category><dc:creator><![CDATA[Bengin Cetindere]]></dc:creator><pubDate>Wed, 17 Mar 2021 08:31:13 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1501139083538-0139583c060f?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDJ8fHRpbWV8ZW58MHx8fHwxNjE1OTY5NDA1&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1501139083538-0139583c060f?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDJ8fHRpbWV8ZW58MHx8fHwxNjE1OTY5NDA1&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" alt="Your users have latency. As a developer, you should too."><p>There are a gazillion ways to optimize the speed of your website. Many of which are hard to implement and will only reduce the loading time by an unremarkable amount.</p><p>I am going to introduce you to an unpopular opinion right now. Bear with me, this will help you craft experiences with <strong>better</strong> UX. So promise to read on, despite your instincts might tell you otherwise &#x1F601;</p><p>Disclaimer: Only for web apps, not for content/SEO relevant websites: </p><blockquote>The UX difference between loading for 1 second vs loading for 0.5 seconds is not as big as the difference between loading for 1 second vs loading for 1 seconds but showing a loading indicator.</blockquote><p>Not to say you can&apos;t have both (fast loading AND good UX), but more often than not - Yes, I am especially looking at you, indiehacker / solo-founder - I see missing loading indicators, empty content that looks like the app is loading or other bad UX which could be fixed way easier than your asynchronous + deferred JavaScript loading that&apos;ll get you 31ms faster first contentful paint.</p><h2 id="got-it-use-loading-spinners-anything-else">Got it, use loading-spinners. Anything else?</h2><p>I am currently working on a web app (<a href="https://subscribee.net">Subscribee.net</a>) and I implemented a spinner component to use it as a loading indicator at different places. This way, it&apos;s very easy for me and almost no work at all to add a spinner everywhere it&apos;s needed. </p><p>After some time, I wanted to check something in production, I was messing with some environment variables, and then it hit me: When editing a post, there was no loading indicator while loading the post content and images.</p><p>I added the spinner into the respective component but after this incident, I wasn&apos;t sure if I may have overlooked something at other places too.</p><h2 id="your-users-have-latency-when-using-your-app">Your users have latency when using your app</h2><p>When you develop a web app, most of the time you&apos;ll run the backend, frontend and database on your local machine. This means that network requests go from your PC/Laptop to itself. No router latency, no (remarkable) latency due to speed of light limit, no load balancing latency, no wireless latency, [...]</p><p>Your users will have to query your frontend for HTML, CSS and JavaScript. The JavaScript on the page makes a request to your backend (which in turn fetches data from a database) with which&apos;s answer content can be finally displayed. It&apos;s impossible to reduce the latency for this chain of actions to the same level as you have on your local setup.</p><p>So what&apos;s the solution? </p><h2 id="delay-each-backend-response-artificially">Delay each backend response artificially</h2><p>Delaying each backend response in non-production environments by 250ms in NodeJS is as easy as adding this middleware:</p><pre><code class="language-JavaScript">if (!process.env.PRODUCTION) {
  app.use(&quot;/&quot;, (req, res, next) =&gt; {
    setTimeout(next, 250);
  });
}</code></pre><p>These 5 lines of code will make sure that you see your web app as a user and not only as a developer. Probably, there are similar methods to delay responses by x ms in non-prod environments for your framework and language.</p><p>Like I said, it may seem counterintuitive (to add intentional delay) but you won&apos;t ever forget a loading indicator again. I have this snippet in Subscribee&apos;s backend, and it works wonders.</p><p>Now that you&apos;ll easily spot bad UX while the page is loading, you can enhance it where needed. Keep an eye out especially for</p><ul><li>text that hasn&apos;t loaded yet</li><li>input values that are prefilled but haven&apos;t loaded yet (like in my edit-post case)</li><li>buttons that have been clicked (don&apos;t forget to disable them)</li></ul><h2 id="enhancing-the-loading-ux-even-further">Enhancing the Loading UX Even Further</h2><p>As a bonus, Michal Malewicz, UI Design lecturer and author of &quot;<a href="https://designingui.com/">Designing User Interfaces</a>&quot; explained how to improve loading experience even more. </p><p>If you remember the first few iPhones, they were pretty slow. Loading an app would often take over twenty seconds, so Apple came up with a solution that would trick the users to think the app is loading faster. </p><p>I&apos;m talking of course about Skeleton Screens, a term coined by Luke Wroblewski in 2013. What it means is that they were using a loading screen, that had parts of the layout already rendered on it. If the first app screen was a list, the resulting screen would show an empty list, but with all the boxes and UI elements marked in place. </p><p>This led to a perceived lower waiting time by the users, even when the actual time was the same as with an app without a skeleton screen. </p><p>This concept was later refined by the use of subtle animation - mostly fading in and out a little, that made the users think that something is happening.</p><p>Spinner happens in place, but is not connected to anything. Skeleton backgrounds appear to be there and all you&apos;re waiting for is their content - so it appears like half of the screen already has loaded and all we&apos;re waiting for is the data.</p><h2 id="summary-tldr">Summary - TLDR</h2><p>Main Points of this article summarized:</p><ol><li>Work on what makes a bigger impact. Only optimize loading speed until the optimizations get smaller than potential benefits you could achieve with UX optimization. Devs get lost in optimizations way too often.</li><li>Use loading spinners, so users know when they can expect more to happen and when the site has finished loading.</li><li>Add artificial latency to your local development setup, so you can experience your app like your users would.</li><li>If you want an even more optimized UX, go for skeleton screens. <a href="https://www.smashingmagazine.com/2020/04/skeleton-screens-react/">Here&apos;s more info on skeleton screens</a>.</li></ol><p></p><p></p>]]></content:encoded></item><item><title><![CDATA[How I wrote a scraper to get an RTX 3080 (OSS)]]></title><description><![CDATA[<p>As many are aware, Nvidia didn&apos;t at all manage to meet the high number of buying intents for the RTX 3080 generated by gamers, crypto-miners and others. It was the beginning of October when I realized that I am going to need a more powerful GPU for my</p>]]></description><link>https://blog.cetindere.de/how-i-wrote-a-scraper-to-get-an-rtx-3080-oss/</link><guid isPermaLink="false">5ff5cd543964180001264d34</guid><category><![CDATA[webdev]]></category><dc:creator><![CDATA[Bengin Cetindere]]></dc:creator><pubDate>Mon, 11 Jan 2021 07:07:02 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1536842409491-b3bbde0e3b66?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDh8fG1ldGFsJTIwc2NyYXBlfGVufDB8fHx8MTYyMTI2MzY5OQ&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1536842409491-b3bbde0e3b66?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDh8fG1ldGFsJTIwc2NyYXBlfGVufDB8fHx8MTYyMTI2MzY5OQ&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" alt="How I wrote a scraper to get an RTX 3080 (OSS)"><p>As many are aware, Nvidia didn&apos;t at all manage to meet the high number of buying intents for the RTX 3080 generated by gamers, crypto-miners and others. It was the beginning of October when I realized that I am going to need a more powerful GPU for my soon to arrive HP Reverb G2 (VR headset with 4320x2160 resolution). Also, Cyberpunk.</p><p>As an experienced developer I thought &quot;Well Bengin, can&apos;t be <em>that</em> hard to write a scraper that periodically calls a few sites and tells me what changed&quot;. I would only need to quickly write something that checks a list of sites every 2 minutes and email me if the result was different from the previously fetched version. Simple.</p><blockquote>Boy was I wrong.</blockquote><p>Of course, I wanted something more customizable, scalable (duh). I think it&apos;s just me, but I am not really satisfied with developing hacky solutions. I want at least some UI (even if it would be command line) and the possibility to let my friends use it too without them having to install 10 tools. </p><p>In the next sections, I walk you through the basic structure of the app and some challenges. Warning: It&apos;s going to be technical (but from a rather high level). You can also always check the source code <a href="https://github.com/xdivby0/webwatcher">on my GitHub</a>.</p><p>First, I thought about a React frontend and a NodeJS paired with a DB like MongoDB to build a multi-tenant web app. Then I realized that I don&apos;t need a React frontend. In the frontend I only want to enter some data (links, scrap-interval, etc.) and I want the frontend to be able to notify me when something changed.</p><p>I picked up Telegram earlier that year and I decided to just use a Telegram bot as the frontend - the notification would work without problems and would work really fast (as opposed to web push or email). Bonus: I could learn to write Telegram bots. &#xA0;I mean, which developer doesn&apos;t like to play with new, shiny stuff&#x1F60D;?</p><!--kg-card-begin: html--><iframe src="https://giphy.com/embed/Hf0tK0lqL3tIc" width="480" height="360" frameborder="0" class="giphy-embed" allowfullscreen></iframe><!--kg-card-end: html--><h2 id="frontend">Frontend</h2><p>As already mentioned, I used Telegram as the frontend for webwatcher. I decided to try out <a href="https://telegraf.js.org/">Telegraf, a NodeJS Framework/Wrapper</a> because of its dialogue modelling capabilities. At the end, creating a new target for webwatcher is a dialogue:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.cetindere.de/content/images/2021/01/grafik.png" class="kg-image" alt="How I wrote a scraper to get an RTX 3080 (OSS)" loading="lazy" width="515" height="843"><figcaption>Creating a webwatcher target to notify when a new blog posts on this site is available</figcaption></figure><p>As you can see, the whole usage and configuration of the bot is done via Telegram. This way I can send the bot link to friends, and they can use it instantly without installing anything.</p><p>I also added the bot into a group with some friends who also needed a new GPU. Everyone can chat with the bot privately for private notifications, and it&apos;s also possible to set up the bot in a group so many people get notified for the same things.</p><p>In the current state you can only delete targets but not modify them (which would be really useful, but yeah, I kind of don&apos;t need the webwatcher at the moment). When deleting a target, I used the inline buttons Telegram offers, to list the targets that the user can delete:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.cetindere.de/content/images/2021/01/grafik-1.png" class="kg-image" alt="How I wrote a scraper to get an RTX 3080 (OSS)" loading="lazy" width="360" height="171"><figcaption>Deletion of a target</figcaption></figure><p>The inline buttons are removed upon selection and a confirmation message is sent.</p><p>I don&apos;t want to go into detail on how I modeled the dialogue (mostly because I didn&apos;t do a great job at making it readable or maintainable). But I want to show how one part of the dialogue looks because the documentation was really not that helpful:</p><figure class="kg-card kg-image-card"><img src="https://blog.cetindere.de/content/images/2021/01/grafik-2.png" class="kg-image" alt="How I wrote a scraper to get an RTX 3080 (OSS)" loading="lazy" width="664" height="604" srcset="https://blog.cetindere.de/content/images/size/w600/2021/01/grafik-2.png 600w, https://blog.cetindere.de/content/images/2021/01/grafik-2.png 664w"></figure><p>As you can see there is a WizardScene which takes an id and then the different steps of the dialogue. Pretty neat, but took a lot of fumbling to get right.</p><h2 id="backend-infrastructure">Backend Infrastructure</h2><p>In order to store the targets and their configuration persistently, I use a MongoDB - it is pretty much only used for that.</p><p>I had difficulties finding a way to properly set up a Telegram bot development environment. Even when developing on your local machine, you have to connect to Telegram&apos;s API servers, so you can test your changes.</p><p>I created two bots, one for development and one for the live version which would run on my server. The live one would always run the latest master version of the project&apos;s repository (Jenkins webhook which builds and deploys a Docker container (Yes yes, I know, Overkill, but I love CI/CD)).</p><p>This way I could try implementing a feature or fix a bug in my local environment and if that works, I can simply commit and push, and it would be live in a minute or so. DevOps magic is really cool.</p><h2 id="backend-logic">Backend Logic</h2><p>When the NodeJS project then starts, all targets are read from the database. Then each target that has been read is set up. The same set up routine is executed for a newly, dialogue-created target, and it works like this:</p><ol><li>Fetch Website and Execute Check</li><li>SetInterval to check periodically</li><li>Put return value of setInterval into a map for stopping this specific target</li></ol><p>For the actual fetching, I use <a href="https://github.com/axios/axios">axios, my favorite http library</a>. Depending on the target&apos;s http verb (post or get), a post or get request is made. The response is then interpreted differently, depending on the target mode. I made 4 modes, each mode does a different transformation with the response. After the transformation, the result is compared against the previous result of this target. The 4 modes are</p><ul><li>RAW - simply returns the response</li><li>CONTAINS - takes a string argument and returns if the result contains the argument (for example a word) as a boolean</li><li>JSON - also takes a string argument and interprets the response as a JSON object and queries it with the argument (for example [0][&quot;title&quot;])</li><li>HTML - also takes a string argument and interprets the response as HTML, queries it with basic CSS selectors (I use <a href="https://www.npmjs.com/package/node-html-parser">node-html-parser</a> for that) like <code>.title</code> or <code>#news-wrapper</code> and returns the content of the HTML element</li></ul><p>This means that with the mode, the user can choose to get notified when anything changes (RAW), the occurrence of a word changes from yes to no or vice versa (CONTAINS), an API&apos;s JSON response changes at a specific location (JSON) or a specific HTML element&apos;s content on a site changes. After that I simply notify the user if necessary (via the saved telegram chat id in the target).</p><p>The hardest thing in the backend was actually the dialogue modelling. Not only because the documentation lacks some details but also because I created it with the intent of having a linear dialogue that just asks for some values. Due to the GET or POST question and the different modes, some questions texts depended on answers of previous questions. My implementation of that is a bit hacky, which I don&apos;t like, but it works &#x1F64C;. Telegraf surely has better ways to deal with this - if someone knows a good tutorial on that, please comment.</p><h2 id="conclusion">Conclusion</h2><p>In the end, the webwatcher was a fun side-project for learning and hey, it allowed me to buy an RTX 3080 in covid-october. And now that the Reverb G2 arrived, I got to say that Half-Life: Alyx looks dauntingly realistic o.O</p><p>Anyways, thanks for reading! If you have any questions, please feel free to comment. </p>]]></content:encoded></item><item><title><![CDATA[Make Footers Stick to the Bottom of the Page in Bulma]]></title><description><![CDATA[<p>Many websites feature a footer element which should be displayed as the last element on a website. Everyone that created a footer stumbled upon this problem at least once: For shorter pages, the footer <em>is</em> the last element, but it&apos;s not at the bottom of the screen. It&</p>]]></description><link>https://blog.cetindere.de/make-footers-stick-to-the-bottom-of-the-page-in-bulma/</link><guid isPermaLink="false">5ff5cd543964180001264d31</guid><category><![CDATA[webdev]]></category><dc:creator><![CDATA[Bengin Cetindere]]></dc:creator><pubDate>Wed, 21 Oct 2020 13:51:00 GMT</pubDate><media:content url="https://blog.cetindere.de/content/images/2021/05/Screenshot-2021-05-17-170326.png" medium="image"/><content:encoded><![CDATA[<img src="https://blog.cetindere.de/content/images/2021/05/Screenshot-2021-05-17-170326.png" alt="Make Footers Stick to the Bottom of the Page in Bulma"><p>Many websites feature a footer element which should be displayed as the last element on a website. Everyone that created a footer stumbled upon this problem at least once: For shorter pages, the footer <em>is</em> the last element, but it&apos;s not at the bottom of the screen. It&apos;s somewhere in the middle of the screen with space below it.</p><p>In Bulma, the footer class can be altered to always be at the bottom of the viewport by enlarging the element right before the footer to fill the remaining space. This ensures that there is no blank space on below the footer.</p><p>This behavior can best be achieved by using the flexbox layout. Let&apos;s dive into the details on how to implement this behavior and more:</p><h2 id="theory">Theory</h2><p>I want to quickly explain the theory behind the problem for more inexperienced developers, so they understand why this problem happens and what the idea behind the solution is. This is important to know and allows you to transfer your knowledge to similar problems more easily. But you can of course skip this section and jump straight to the Non-Theory heading below.</p><h3 id="the-problem">The Problem</h3><p>Websites have certain rules that determine how <a href="https://www.w3schools.com/jsref/dom_obj_all.asp">DOM elements</a> in general are positioned and rendered. In our case, the footer element simply gets rendered right below its sibling, which is the intended behavior for DOM elements.</p><p>For the footer however, we don&apos;t want this. The empty space below the footer is a result of all elements total height being smaller than the viewport height. This leads us to the solutions (or one of the solutions):</p><h3 id="the-solution">The Solution</h3><p>The height of the content to the top of the footer would have to be larger by the amount of space below the footer. One could add <code>&lt;br/&gt;</code> tags or something else that occupies space so the footer gets pushed down, but this would be an anti-pattern since you shouldn&apos;t modify the content in order to change the design. Those two should be separated at all times.</p><p>Rather, the solution to go for is to increase the height of the element to the top of the footer (or any element to the top of the footer really) by using CSS. Due to the existence of different display- and thus viewport-sizes, fixed heights are not a viable solution either.</p><p>Instead, we are going to make a layout that fills the remaining space automatically by using flexbox. <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout/Basic_Concepts_of_Flexbox">Flexbox</a> is a display-mode that allows an element to display its child elements in a distinct and flexible way (hence the name).</p><h2 id="non-theory">Non-Theory</h2><p>Here is a rough sketch of a basic website with header and footer:</p><figure class="kg-card kg-image-card"><img src="https://blog.cetindere.de/content/images/2021/01/Untitled-Diagram.jpg" class="kg-image" alt="Make Footers Stick to the Bottom of the Page in Bulma" loading="lazy" width="402" height="322"></figure><p>So in order to have the blue footer part always end at the bottom of the screen, the height of the red main content part has to fill the remaining height. In Bulma we can simply achieve this with the following HTML code:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.cetindere.de/content/images/2021/01/carbon2-768x304.png" class="kg-image" alt="Make Footers Stick to the Bottom of the Page in Bulma" loading="lazy" width="768" height="304" srcset="https://blog.cetindere.de/content/images/size/w600/2021/01/carbon2-768x304.png 600w, https://blog.cetindere.de/content/images/2021/01/carbon2-768x304.png 768w" sizes="(min-width: 720px) 720px"><figcaption><a href="https://codepen.io/bengin_cetindere/pen/qBaEKyj">Code on CodePen</a></figcaption></figure><p>And the following CSS code:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.cetindere.de/content/images/2021/01/carbon3-768x993.png" class="kg-image" alt="Make Footers Stick to the Bottom of the Page in Bulma" loading="lazy" width="768" height="993" srcset="https://blog.cetindere.de/content/images/size/w600/2021/01/carbon3-768x993.png 600w, https://blog.cetindere.de/content/images/2021/01/carbon3-768x993.png 768w" sizes="(min-width: 720px) 720px"><figcaption><a href="https://codepen.io/bengin_cetindere/pen/qBaEKyj">Code on CodePen</a></figcaption></figure><p>I&apos;ve also put the code <a href="https://codepen.io/bengin_cetindere/pen/qBaEKyj">on CodePen</a> for you, so you can simply copy it or look at the result. With this flexbox technique you can also build similar layouts horizontally (for a fixed width menu on the left and fill the remaining space with content) and many other things.</p><h2 id="addition-how-to-do-that-without-bulma">Addition: How to do that without Bulma</h2><p>Maybe at some point of time you&apos;ll be in the position where you have to realize this behavior without Bulma - it&apos;ll be nice to know what the Bulma classes do in the background so you can do it yourself or in other frameworks like bootstrap.</p><p>Basically the <code>columns</code> class applies <code>display-mode:flex</code> to the wrapper and the <code>is-flex-direction-column</code> class applies <code>flex-direction: column</code>. We then add <code>height: 100vh</code> to the wrapper so it always fills the screen.</p><p>The <code>column</code> class applies several CSS values but most importantly it applies <code>flex-grow: 1</code> so the child elements grow until the parent element is filled. With the class <code>is-narrow</code> we set <code>flex-grow:0</code> for the elements we don&apos;t want to grow (so they only take up as much space as they do without flex). That&apos;s it.</p><p>I hope that you learned something and that this was helpful for you. Please like, subscribe and share - jk, computer scientists like us don&apos;t have any friends to share with :P</p>]]></content:encoded></item><item><title><![CDATA[Integrate Google Analytics into Your React  Web-App (without npm packages)]]></title><description><![CDATA[<p>Integrating an analytics solution is crucial for any business in order to measure success and where it originates from. Google Analytics is probably <em>the</em> most well known tool, so in this article you will learn how to use it in your React App without any npm packages.</p><p>In order to</p>]]></description><link>https://blog.cetindere.de/integrate-google-analytics-into-your-react-web-app/</link><guid isPermaLink="false">5ff5cd543964180001264d33</guid><category><![CDATA[webdev]]></category><dc:creator><![CDATA[Bengin Cetindere]]></dc:creator><pubDate>Wed, 16 Sep 2020 13:50:00 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1551288049-bebda4e38f71?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDF8fGFuYWx5dGljc3xlbnwwfHx8fDE2MjEyNjM4NDA&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1551288049-bebda4e38f71?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDF8fGFuYWx5dGljc3xlbnwwfHx8fDE2MjEyNjM4NDA&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" alt="Integrate Google Analytics into Your React  Web-App (without npm packages)"><p>Integrating an analytics solution is crucial for any business in order to measure success and where it originates from. Google Analytics is probably <em>the</em> most well known tool, so in this article you will learn how to use it in your React App without any npm packages.</p><p>In order to integrate Google Analytics into your React app, you first have to create the respective GA Property. Then copy the script that Google Analytics provides and add a script element into the body of your React app that contains the copied script at runtime.</p><p>After that, you can track different events and page views (even for single-page-applications) with ease. Let&apos;s dive into the details of how to do the above and more for your React web app.</p><h2 id="but-there-is-a-react-npm-package-for-google-analytics">But there is a React npm package for Google Analytics</h2><p>There is an npm package for react which integrates Google Analytics and there is nothing wrong with using packages if they take off workload from you. In this case, though, the reduced workload is minimal and integrating the plain JavaScript Google Analytics version will yield the benefit of being well-documented.</p><p>If you go with the npm package, chances are that you run into issues (like me) and can&apos;t resolve them because there is only very limited resources about non-official npm packages.</p><p>If you go with the vanilla JavaScript version, however, you will be able to fix issues much faster as you can use all the resources on the internet about Google Analytics.</p><blockquote>Everything should be done as simple as possible, but not simpler - Albert Einstein</blockquote><!--kg-card-begin: html--><iframe src="https://giphy.com/embed/l3vRgnooYcEivUT0Q" width="480" height="384" frameborder="0" class="giphy-embed" allowfullscreen></iframe><!--kg-card-end: html--><p>At the end of the day, KISS (Keep it short and simple) is one of the clean code principles and adding yet another layer of complexity by adding an npm package shouldn&apos;t be the solution for this problem.</p><h2 id="this-is-the-code-i-use">This is the code I use</h2><figure class="kg-card kg-image-card"><img src="https://blog.cetindere.de/content/images/2021/01/carbon-2-1024x427.png" class="kg-image" alt="Integrate Google Analytics into Your React  Web-App (without npm packages)" loading="lazy" width="1024" height="427" srcset="https://blog.cetindere.de/content/images/size/w600/2021/01/carbon-2-1024x427.png 600w, https://blog.cetindere.de/content/images/size/w1000/2021/01/carbon-2-1024x427.png 1000w, https://blog.cetindere.de/content/images/2021/01/carbon-2-1024x427.png 1024w" sizes="(min-width: 720px) 720px"></figure><p>At its core I simply add a script element that contains the GA code to the body. The <code>async</code> attribute is important if you don&apos;t want the page to stop parsing while executing the script.</p><p>I have this code snippet inside my top level component&apos;s <code>componentDidMount</code> lifecycle method. It&apos;s that simple. This should do the trick for a very basic integration.</p><h2 id="only-track-in-production">Only Track in Production</h2><p>It&apos;s also worth mentioning that I only execute the above script when the code is running on production environment, so I don&apos;t record page views and events while I am developing.</p><p>I do this by having a different <code>.env</code> on my production environment. If you don&apos;t know how to work with environment variables, I strongly recommend you to check out <a href="https://medium.com/the-node-js-collection/making-your-node-js-work-everywhere-with-environment-variables-2da8cdf6e786">this great introduction and tutorial about environment variables</a>. Be sure to search for react-specific solutions after reading the article, there are some caveats using env files with React.</p><h2 id="tracking-page-views-correctly">Tracking Page Views Correctly</h2><p>The above snippet does not work for capturing page views that do not execute a full page reload. As most React apps don&apos;t reload the page but only swap the displayed content, Google Analytics will only capture the first page view but not the consecutive page views produced by clicking inside your app.</p><!--kg-card-begin: html--><iframe src="https://giphy.com/embed/KEkOcB5DJ4E4bhou9T" width="480" height="270" frameborder="0" class="giphy-embed" allowfullscreen></iframe><!--kg-card-end: html--><p>To track page views correctly, we have to submit a page view event on each new page view. In order to do that, we need access to the gtag function that allows us to do so. I modified the copied Google Analytics code by adding a line after the gtag function definition:</p><figure class="kg-card kg-image-card"><img src="https://blog.cetindere.de/content/images/2021/01/carbon-1-1024x533-1.png" class="kg-image" alt="Integrate Google Analytics into Your React  Web-App (without npm packages)" loading="lazy" width="1024" height="533" srcset="https://blog.cetindere.de/content/images/size/w600/2021/01/carbon-1-1024x533-1.png 600w, https://blog.cetindere.de/content/images/size/w1000/2021/01/carbon-1-1024x533-1.png 1000w, https://blog.cetindere.de/content/images/2021/01/carbon-1-1024x533-1.png 1024w" sizes="(min-width: 720px) 720px"></figure><p>This way, the gtag function always available via <code>window.gtag</code>. The following code sends a page view event &quot;manually&quot;:</p><figure class="kg-card kg-image-card"><img src="https://blog.cetindere.de/content/images/2021/01/carbon-3-1024x461-1.png" class="kg-image" alt="Integrate Google Analytics into Your React  Web-App (without npm packages)" loading="lazy" width="1024" height="461" srcset="https://blog.cetindere.de/content/images/size/w600/2021/01/carbon-3-1024x461-1.png 600w, https://blog.cetindere.de/content/images/size/w1000/2021/01/carbon-3-1024x461-1.png 1000w, https://blog.cetindere.de/content/images/2021/01/carbon-3-1024x461-1.png 1024w" sizes="(min-width: 720px) 720px"></figure><p>In my top level component, I execute this code when the location path changed. If it changed, I execute the above code. Use the <code>componentDidUpdate</code> method with a little check to listen to location path changes:</p><figure class="kg-card kg-image-card"><img src="https://blog.cetindere.de/content/images/2021/01/carbon4-768x421-1.png" class="kg-image" alt="Integrate Google Analytics into Your React  Web-App (without npm packages)" loading="lazy" width="768" height="421" srcset="https://blog.cetindere.de/content/images/size/w600/2021/01/carbon4-768x421-1.png 600w, https://blog.cetindere.de/content/images/2021/01/carbon4-768x421-1.png 768w" sizes="(min-width: 720px) 720px"></figure><p>This will result in only sending a new page view if the location has changed and the code is running on the production environment.</p><h2 id="add-event-tracking">Add Event Tracking</h2><p>Let&apos;s get to the creative part. You&apos;ll probably already have a few ideas which events you want to capture but here are some ideas what to track to get you started:</p><ul><li>click on external URL events</li><li>registration events</li><li>login events</li><li>sale events</li><li>add to cart events</li><li>subscription renewed event</li></ul><p>Be sure to also track other business-specific events that are important for your website. You&apos;ll thank me later for having the data at hand when you need it (for example to see which channel brought the most sales and how different devices / countries are performing).</p><p>You simply have to add this line of code at any point where you want to trigger an event:</p><figure class="kg-card kg-image-card"><img src="https://blog.cetindere.de/content/images/2021/01/carbon5.png" class="kg-image" alt="Integrate Google Analytics into Your React  Web-App (without npm packages)" loading="lazy" width="844" height="444" srcset="https://blog.cetindere.de/content/images/size/w600/2021/01/carbon5.png 600w, https://blog.cetindere.de/content/images/2021/01/carbon5.png 844w" sizes="(min-width: 720px) 720px"></figure><p>In this case, I send an event which has the action &quot;early-access&quot; and the event category &quot;conversions&quot;. The good part about using the vanilla JS implementation is that you can simply go to the <a href="https://developers.google.com/analytics/devguides/collection/gtagjs/events" rel="noreferrer noopener">Google Analytics documentation</a> and look how to for example specify a value or a label. Just be sure to use <code>window.gtag</code> because gtag is not in the global scope.</p>]]></content:encoded></item></channel></rss>