Hi There...!!đź‘‹

I'm Adib Firman, I'm software engineer from 🇮🇩 (Indonesia) day-by-day working and learn a fun things about Web Ecosystem, and occasionally planting seed on my own digital garden.

Recent blogs

Let’s meet Zod: TypeScript-first schema validation

A short journey about what I have learned about Zod a TypeScript-first schema validation with static type inference.A thumbnail of the blogFrom last week on the Weekend until this week (On the Weekend too) mostly I’m learning about Zod; basically, it’s a schema validation that can be run on the runtime too, which mostly I use TypeScript just for the type-checking for the development purpose but, according to the documentation the name of the “schema” it’s broadly referring to any data type, from a simple string to a complex nested object.In the above statements, I also told you that Zod also will validate the schema on the runtime too not just check the type of the object, array, etc. The cool thing is, it will throw us an error if we do not meet the schema that we already defined it. Let’s take an example from string API from Zod.Validate An Unknown DataWe know if we still use JavaScript as our main code, we cannot make sure the parameters of our function it’s a data that we need or not. The same like in TypeScript too, if we write a function that accepts arguments with type unknown, we need to make sure the parameters or data that we will proceed on our function.So in Zod, we can make sure that with just write a simple two line of code like this.import { z } from 'zod'function submitAddressUser(address: unknown) { const getAddress = z.string(); getAddress.parse(address) return address}We can see above code, the parameters that we write are unknown — which is we assume that we don’t know that value exactly so if we run that function and give an argument that we don’t want it like numberit('should run our app', () => { const testIt = submitAddressUser(123412) expect(testIt).toBe('cool address')})If we run itAn error in the function “submitAddressUser”We will receive a throwing error because we don’t give a proper argument of a function submitAddressUser this should be an error on runtime if we run it on the browser.A Simple User StoryPreviously, we already write a simple Zod API which is string but in that API there’s also a lot of functionality that we can use, for example:import { z } from 'zod'function testTheApp(firstName: string) { const getTheFirstName = z.string().min(3); getTheFirstName.parse(firstName) return firstName}If we try to input our form which does not meet that schema, then we should get an error.it('should run our app', () => { const getMyFirstName = testTheApp("Ad") expect(getMyFirstName).toBe('Ad')})Here’s the error that was returned from Zod.An error if does not meet a minimal character of the first nameThat indicates that we got a schema validation of our function testTheApp with minimal character is three.Mix it With Rest APIThe most interesting in this part, we can catch it even on the runtime let’s assume that we call a Rest API with TypeScript and normal fetch.import { z } from 'zod'async function getDetailTodo() { const fetchData = await fetch('https://jsonplaceholder.typicode.com/todos/1') .then(response => response.json()) .then(json => json) console.log(fetchData)}As you know unlike GraphQL when we use fetch then the data from the variable fetchData should become unknown or any which is we don’t know the response of it.A type from a fetch that returned unknown data.Fortunately, with Zod, we can create a schema that should return what we need from that response of API.import { z } from 'zod'async function getDetailTodo() { const validateResponse = z.object({ title: z.string() }) const fetchData = await fetch('https://jsonplaceholder.typicode.com/todos/1') .then(response => response.json()) .then(json => json) const data = validateResponse.parse(fetchData) return data}For the explanation of the above code, we have a variable validateResponse to create a schema that just gives us a field title from the response of that API, if we take a look at the API there are several fields from the response{ "userId": 1, "id": 1, "title": "delectus aut autem", "completed": false}But in our schema which is validateResponse we just want title apart from other fields, if we try to console ita return from our schemaZod also filters the rest of the fields that are not needed on our schema, but, if we change the schema and then let’s say we want a field subTitleimport { z } from 'zod'async function getDetailTodo() { const validateResponse = z.object({ title: z.string(), subTitle: z.string() }) const fetchData = await fetch('https://jsonplaceholder.typicode.com/todos/1') .then(response => response.json()) .then(json => json) const data = validateResponse.parse(fetchData) return data}If we run our appAn error from Zod that the field subTitle does not existWe can see that Zod will throw us an error which is we need to change the response of that API to make sure our schema meets the response of the API.Infer The TypeWe already see the power from Zod with API object which is it can be like a schema validation to make sure what we need it’s what we got too. But, let’s say back to the previous topic we use this functionimport { z } from 'zod'async function getDetailTodo() { const validateResponse = z.object({ title: z.string(), }) const fetchData = await fetch('https://jsonplaceholder.typicode.com/todos/1') .then(response => response.json()) .then(json => json) const data = validateResponse.parse(fetchData) return data}As we know TypeScript will infer the return type of the function based on what we return it from our function, but let’s say we have a case that we need the type of the schema too and use that type on other function helpers for example, we can use the API from Zod that called inferimport { z } from 'zod'const validateResponse = z.object({ title: z.string(),})export type DetailTodoData = Promise<z.infer<typeof validateResponse>>async function getDetailTodo(): DetailTodoData { const fetchData = await fetch('https://jsonplaceholder.typicode.com/todos/1') .then(response => response.json()) .then(json => json) const data = validateResponse.parse(fetchData) return data}ArrayIn Zod, we also have an API that we can interact with the data type Array — For example, we have this functionasync function getListTodo() { const fetchData = await fetch('https://jsonplaceholder.typicode.com/todos') .then(response => response.json()) .then(json => json) return fetchData}If we trying hit that API we will see a lot of the data of todos[ { "userId": 1, "id": 1, "title": "delectus aut autem", "completed": false }, { "userId": 1, "id": 2, "title": "quis ut nam facilis et officia qui", "completed": false }, ...]Then, let’s say in that list we just want to show a field title to the user, we can also use an API from Zod that is called arrayimport { z } from 'zod'async function getListTodo() { const validateTheResponse = z.array(z.object({ title: z.string() })) const fetchData = await fetch('https://jsonplaceholder.typicode.com/todos') .then(response => response.json()) .then(json => json) const data = validateTheResponse.parse(fetchData) return data}If we try to see the return of our function, it will automatically filter the data based on the schema that we created above.Result after we create a schema of API list todoDefault Value and Optional SchemaWe know on TypeScript we can also make our type optional then within the function we can give a default value.function generateHelloThere(name?: string) { const getName = name || 'There,' return `Hello ${getName}`}Then let’s say we have a test case like thisit('should return a hello name', () => { const welcomeMessage = generateHelloThere() expect(welcomeMessage).toBe('Hello There,')})It should give us a default valueA green test case of function generateHelloThereBut, in Zod, they already provide us with an API to make a default value and mark it as optional.import { z } from 'zod'function generateHelloThere(name?: string) { const schemaName = z.string().optional().default('There,') const getName = schemaName.parse(name) return `Hello ${getName}`}Then after we run our test caseA green test case of function genereateHelloThere with Zod APIThe lack of this API, we need to make sure the .optional() The API need to call it first before .default('There,') API. The default() API should be the last resort of the schema, if not our test case would give us an errorAn error if we are wrong to put the .default() API from ZodThe ConsAfter we talked about the API that was provided by Zod, this package also has cons from my point of view. When we check the bundle size of this package from bundlejs siteA result of the bundle size of package ZodIt’s kinda big around 13KB even though it’s already has been gzip.ConclusionZod, it’s totally game-changer if we need to make sure our type on our function will catch it too on the runtime, but as we mentioned above there’s a cons to this package, but if you don’t think about the bundle size of your app, I think you can consider shipping this package to help to you as a guardian too on the runtime.Apart from we write it on TypeScript, Zod will also help you too on your app if already written with JavaScript in my perspective.Also, there’s so much API that we did not mention here you can take a look by yourself from the documentation Zod.https://github.com/colinhacks/zod#optional

Mar 05, 2023
See Detail

React Server Component, What It’s?

React Server Component, What It is?My recap learning what are React Server Component, and why this will be a game-changer from React.A Featured ImageIn the last year of 2020 almost 2 years back, React core team has been announced about React Server Component (RSC) indeed at that time, it was not compatible with React yet, but until now (2022) this method has been adopted on the Next.JS framework (Just this framework as far as I know).I will not try to give an example of the implementation on Next.JS itself, but I will try to explain the basic knowledge about RSC, at a glance in the terms of the name we can imagine that our React component will run under SSR (Server Side Rendering) — this was explained detail by React core team in here.Again, at a glance, this is an alike way that we render our component on a server and we send it to the client through the renderToString from react-dom API and the result will give to the user in an HTML format that the user just renders on the browser, but this is (RSC) slightly different from SSR, let’s see the example of RSC result on the network browser below:According to the image above, seems like RSC will respond to the client in something not like an HTML format instead, which looks like an object that React will try to handle that stuff and magically will render to the browser with proper UI.Keep in mind, because the name is React Server Component — which contains the word “Component” that we already know within the component we can use useState or other API from React to make our component re-render or use any side-effect to call a DOM.In this RSC we cannot do that on the server, like usual we render our component on the server, we just served it to the browser in the first time of render in the browser, according to the React core team RSC will help us to solve a bundle-size problem also will help on the performance things.There is an interesting part of this RSC we can use 3 types of files that we can use it:.server.js.client.js.js (Shared)I will try to explain the difference between each of the above files..client.jsThe new extension name front .js which is .client.js the file will not make us satisfied, because we already mostly use that such as to make our component re-render or make interaction with DOM and etc.In this file, we also cannot call or import from .server.js (I will try to explain it below) but we could smoothly get data that was already constructed from the server and pass it to this file..server.jsBasically, we can call any server call in this file such as get data from the Database or read a file and etc. Let’s try to see this example of code below:// Note.server.jsimport db from "db.server";function Note(props) { const { id, isEditing } = props; const note = db.posts.get(id); // let's say this is a process to get a data from Database return ( <div> <h1>{note.title}</h1> <section>{note.body}</section> </div> );}As you can see, we call a DB directly — in fact, we also make a result of that component with zero-bundle size which means that I already told you before, React core team will solve a bundle size problem through RSC, let’s take a look this example code below:// NoteWithMarkdown.jsimport marked from 'marked'; // 35.9K (11.2K gzipped)import sanitizeHtml from 'sanitize-html'; // 206K (63.3K gzipped)function NoteWithMarkdown({text}) { const html = sanitizeHtml(marked(text)); return (/* render */);}Let’s say, the above code with the name NoteWithMarkdown.js before (RSC) now we talk about it, mostly our component will import some third-party library out there isn’t it?That will make a user download more size of JS files on their network which effected to the user, this means a user will take much time to download or wait for our component basically, there will leave this is bad from the UX perspective.But, let's say the above code we change it with RSC terms, we change it with NoteWithMarkdown.server.js take a look an example code below:// NoteWithMarkdown.server.jsimport marked from 'marked'; // 0K atau zero-bundle sizeimport sanitizeHtml from 'sanitize-html'; // 0K atau zero-bundle sizefunction NoteWithMarkdown({text}) { const html = sanitizeHtml(marked(text)); return (/* render */);}As you can see, this will make that component as zero-bundle size component because we smoothly put it that process on the server and just give to the client with no additional JS file size.The good thing with this file extension we can call a client file which is .client.js (not like a client that we cannot import it). If you are familiar with Next.JS this process maybe slightly same, let’s take an example below code:// NoteWithMarkdown.server.jsimport marked from "marked"; // 0K atau zero-bundle sizeimport sanitizeHtml from "sanitize-html"; // 0K atau zero-bundle sizeimport NoteEditor from "./NoteEditor.client";function NoteWithMarkdown({ text }) { const html = sanitizeHtml(marked(text)); return <NoteEditor html={html} />;}What does the above code mean? does the user will not download the .client.js file too? — the answer is No, because after the user executes it on the server they will still need to download .client.js because we already mark that file ends with .client.js that’s it..js (Shared)This file extension just like a normal component that we already use it day-by-day, but after the RSC comes to the production this component will automatically call as .server.js file.ConclusionThis will help us to make sure there’s no more additional file size on the client that the user will need to more extra to download it. This also comes with the downside which means it will cost more extra on the server which means we need to make sure our infrastructure it’s enough to use this RSC.Also as far as I know (at the time of this writing) it’s still not yet full supported to the production, but the framework that already use it is Next.JS you can check this documentation.

Dec 23, 2022
See Detail

Don’t write an anonymous function in JavaScript

This is a warning for you that never try to write an anonymous function on JavaScript!A featured imageBasically the “Anonymous” we know it properly, like someone we don’t know the exact information about who they are? where’s them? and so on. In this article, we will try to explain the relation of the “Anonymous” term in JavaScript.Mostly we work with JavaSript day-by-day cannot be separated from writing “function” either we just do a sum like a + b + cor handle something like a side-effect. Basically, there’s no exact pattern that “hey, you should write your function like this” there’s not, but JavaScript served some type that we can follow, such as:Function as declarationFunction as expressionFunction as constructorIn this article, we will not try to understand the meaning of each type above, it depends on us which type fits on our team or make use comfortable to write it on, but we try bold the term “Anonymous” in JavaScript, for example(function () { throw new Error("here");})();Aforecode, we use IIFE (Immediately Invoked Function Expression) it tells the browser to run this as soon as possible, and when we try to open the console on the browser we gonna see thisImagine that our code is large and has 1001 functions that must be executed and then there is an unwanted error in the function no 900th, because we wrote the function anonymously so we don’t know what the name of the function is and where the error is.This will be difficult for us to debug. This is what I said that anonymous functions can even be an enemy during development even when we wrote that function by ourself maybe in the back time.Mostly we will have this question on our mind, what that function means? when that function has been executed? where that function we place it on our repository? perhaps this question will rarely comes up if we just build by ourself (freelance) but, let say we coming from a big team? and then another member try to fix that problem, it will much took their time to find the root cause of the error.But, its a different story if we try give the name of our function something like this(function thisIsOurFunction() { throw new Error("oops");})();Then, if we try to take a look on the console browserAforeimage, we see that error also print our function name which is thisIsOurfunction that indicates where we can fix that error.Is this just applied on React, Vue, Angular or Svelte only? No, this is the agnostic approach that we must applied it on our code to make sure if there’s something hiccup on the production, we can trace it easily even for ourself on other members that trying to fix that bug.If your project/repo coming with the ESLint config you can try to install this plugins eslint-plugin-import/no-anonymous it can make us easy to detect it while we writing our code.

Dec 22, 2022
See Detail