Vasili's Blog

In the first half we talk about managers, their roles, why there are so few younger ones in this domain, and other related things.

In the second half we go over some ways of identifying experienced React developers and how the complexity of various frameworks have changed over time. Check it out!

In video editing proxies are used in place of high-resolution footage to reduce the strain on your system while editing. It makes timeline scrubbing much faster.

For the podcast, I usually use the BlackMagic Proxy Generator Lite, and create half-res versions of the source files. For combined ~2 hours of footage (one of myself, and another of my guest) it takes about 30 minutes to generate the new video files.

But using this tool also means that I have to manually open the tool, and drag-n-drop stuff around...

I'm already using some trivial powershell scripts to do other bulk conversions, but I've not wired that up for generating proxies. Until today.

mkdir Proxy;
foreach ($input in Get-ChildItem *.mp4) {
  $output = [io.path]::ChangeExtension($input.FullName, '.mov');
  ffmpeg -hwaccel d3d11va -i $input.FullName -c:v h264_amf -s 960x540 -an $output;
};
mv *.mov Proxy;

I'm using the AMD video card, so I'm leveraging the DirectX 11 for decoding and AMF acceleration for encoding in ffmpeg. This script it only takes about 5 minutes to generate the proxy files, and Davinci Resolve seems to be happy with them.

I still use Lossless Cut to split the files into individual tracks, but pretty sure I can leverage ffmpeg for it as well, sometime in the future.

#podcast #resolve #ffmpeg #proxies

We talk about architectures and methodologies. Check it out.

#podcast

How to find better developers for your team. A continuation of the conversation started (and derailed) in episode 46. Touches interviewing and some tangential questions. Check it out.

#podcast #interviewing

After a brief hiatus I'm happy to present the most recent episode. Check it out.

Burning question of the day – Should the IT industry be regulated to mandate better quality? Let me know what you think?

We chat about what qualities a Senior Dev should possess. What are some of my approaches to refactoring, and how you should make your programming language work for you. Check it out.

Podcast Episode 44 is up. We have a new guest, Geetika Varma, who recently graduated from BCIT's CST program. We chat about her experiences with studying CS, doing internships and preparing for the Bachelor's degree.

In this episode we are going to refactor our code a little bit, add the ability to both increment and decrement the counter, ability to do so by arbitrary amounts, let the counter start with a predefined value, and we will also add some tests. You can follow along in the blog post, or check out the episode3 tag in the repository, which I'll link in the description. Let's get started.

Clean-up

Ok, to start we will clean up our code a little bit. The old hooks-based code has got to go, and we'll remove all of the bundler logos. We love you vite, but this is about another star player, namely – Effector.

The code for App.tsx should look something like this

function App() {
  return (
    <>
      <Counter counter={counterOne} />
      <Counter counter={counterTwo} />
    </>
  );
} 

We will also clean up unused imports, and so on…

Now I want to do some clean-ups in the model file. I like to have my factory names capitalized, and my instances to start with a lower case, makes it easier to tell what's the blueprint and what's the actual item that the factory produces.

export const CounterFactory = ...

Now, it would be better if the output type was defined and exported here, so that everyone can just grab it trivially. Let's add it to our model file

export type CounterModel = ReturnType<typeof CounterFactory>;

Let's go back to the App.tsx and fix the import errors and naming errors.

import { CounterFactory, type CounterModel } from './model';

Now let's fix the invoke-actions.

const counterOne = invoke(CounterFactory);
const counterTwo = invoke(CounterFactory);

Let us make the component declaration a bit nicer

export const Counter: FunctionComponent<{ counter: CounterModel }> = ({ counter }) => ...

The rest remains the same.

Additions

Great, we got rid of the unnecessary things, reduced the noise and now we can focus on adding functionality. For starters, let's make the counter start from an arbitrary value, since sometimes we might want not start with 0. Let's make the following change in our model file.

export type CounterSettings = {
  initialValue?: number;
}

export const CounterFactory = (( initialValue = }: CounterSettings) => {
   const $counter = createStore(initialValue);
   
   ...
}

Now we can try to set up our second counter with something other than 0.

In our App.tsx let's modify the invocations of the factories.

const counterOne = invoke(CounterFactory, {});
const counterTwo = invoke(CounterFactory, { initialValue: 10 });

If we were to refresh the page, the second counter should start at 10 instead of 0.

Now let's look at having the ability to decrement the counter. It is going to be similar to incrementing it. In our model.ts let's add the following:

const decrement = createEvent();

sample({
  source: $counter,
  clock: decrement,
  fn(current) {
    return current - 1;
  },
  target: $counter
});

and let's not forget to add that to the factory's output

return {
  $counter,
  increment,
  decrement
}

Now we can go back to our App.tsx file and wire up the button. Let's get the decrement event from the factory

const { $counter, increment, decrement } = useUnit(counter);

And let's add the button and re-shuffle the card a bit

...
return (
  <div className="card">
    <button onClick={() => decrement()}>-</button>
    <span>Counter value is {$counter}</span>
    <button onClick={() => increment()}>+</button>
  </div>
);

Now we can verify that our increment and decrement work.

With this in mind, how about we add some functionality to be able to increment and decrement by an arbitrary amount.

There are a few different ways we can do this, but we'll follow the pattern we already have, and maybe get fancier in the later episodes of this series.

For now we need to modify the event payload type. By default any event created is having a parameter type of void, which translates to “no parameters necessary”. If we wish to pass them, we should be a bit more explicit. Let's modify the increment and decrement events in our model file.

const increment = createEvent<number>();
const decrement = createEvent<number>();

Now both events require a payload, but where would we be able to use this payload?

Well for that to work we can pull the payload in the second argument in our sample's fn function.

The first argument to fn will always point to the source (if present), and the second one will contain the payload for the clock (if present). There are many different ways one can slice and dice sample, and there's a nice little matrix in the documentation. So let's make those changes now.

sample({
  source: $counter,
  clock: increment,
  fn(current, increment) {
    return current + increment;
  },
  target: $counter
})

sample({
  source: $counter,
  clock: decrement,
  fn(current, decrement) {
    return current - decrement;
  },
  target: $counter
})

And now, because we changed the arity of both our events, we'd have to fix up our template. So let's go back to App.tsx and make changes there

<button onClick={() => decrement(1)}>-</button>
...
<button onClick={() => increment(1)}>+</button>

We might as well add more buttons with different values

<button onClick={() => decrement(5)}>-5</button>
<button onClick={() => decrement(1)}>-</button>
...
<button onClick={() => increment(1)}>+</button>
<button onClick={() => increment(5)}>+5</button>

Now we can increment or decrement our counters 5 times faster

Tests

Now let's add tests. This will show a small sliver of the the goodness that is testing effector business logic. First we need to do some prep

npm install -D vitest

and now let's also add the test command to our package.json

{
   "scripts": {
     "test": "vitest"
   }
}

Let's create our test file

touch src/model.test.ts

And let's start writing some tests

import { beforeEach, describe, expect, test } from "vitest";
import { CounterFactory, type CounterModel } from "./model";

import { allSettled, fork } from "effector";
import { invoke } from "@withease/factories";

Let's make sure that our world starts in correct state by writing some initialization tests

describe("Initialization tests", () => {
  test("Counter without initial value is initialized to 0", () => {
    const subject = invoke(CounterFactory, {});
    const scope = fork();

    expect(scope.getState(subject.$counter)).toBe(0);
  });

  test("Counter with initial value is initialized to that value", () => {
    const subject = invoke(CounterFactory, { initialValue: 10 });
    const scope = fork();

    expect(scope.getState(subject.$counter)).toBe(10);
  });
});

Let's run our test suite so we can monitor our progress

npm run test

Okay, everything seems to be going well, let's test the functionality of the increment and decrement

describe("Incrementing behaviours work", () => {
  let subject: CounterModel;
  beforeEach(() => {
    subject = invoke(CounterFactory, {});
  });

  test("Incrementing by 1 works", async () => {
    const scope = fork();

    await allSettled(subject.increment, { scope, params: 1 });

    expect(scope.getState(subject.$counter)).toBe(1);
  });

  test("Incrementing by 5 works", async () => {
    const scope = fork();

    await allSettled(subject.increment, { scope, params: 5 });

    expect(scope.getState(subject.$counter)).toBe(5);
  });
});

So, there are few things to pay attention to:

  • A few new players have entered the arena, namely fork and allSettled. fork allows us to literally fork the world. And while it's doing so, it can also tweak the world to your liking. Imagine being able to say fork({ values: { $bankBalance: 1_000_000_000 }, handlers: { buyCarFx: () => return 'Maybach' }})? Wouldn't that be nice? Probably not, but hey, a man can dream. We aren't using that functionality at the moment, we just get the exact copy.
  • allSettled allows us to push that first domino that starts the computation graph. It waits until all computations (and asynchronous effects, for that matter) resolve. How cool is that? Once they all have resolved – we can poke at the state of the world and make sure it matches our expectations.
  • In the case of our incrementing – that first domino is the increment event. We trigger it with various values and observe that the world has settled in a new state and our expectations were correct

Let's add the complementary part for decrement to ensure that we test everything

describe("Decrementing behaviours work", () => {
  let subject: CounterModel;
  beforeEach(() => {
    subject = invoke(CounterFactory, {});
  });

  test("Decrementing by 1 works", async () => {
    const scope = fork();

    await allSettled(subject.decrement, { scope, params: 1 });

    expect(scope.getState(subject.$counter)).toBe(-1);
  });

  test("Decrementing by 5 works", async () => {
    const scope = fork();

    await allSettled(subject.decrement, { scope, params: 5 });

    expect(scope.getState(subject.$counter)).toBe(-5);
  });
});

Combine store data

As a last little thing we can see how we can use the both counters to have a quick way to see the total count

I'm not going to build a factory at this point as I'd like to wrap this up, but here's what we can do. In our App.tsx file we can add a new component and a derived store


const $counterTotal = combine(counterOne.$store, counterTwo.$store, (a, b) => a + b);

const Total: FunctionComponent = () => {
  const total = useUnit($counterTotal);
  
  return (<div>Total: ${total}</div>);
}

And now we add it to our main component

function App() {
  return (
    <>
      <Counter counter={counterOne} />
      <Counter counter={counterTwo} />
      <Total />
    </>
  );
}

Once we get this we can see our total sum of both counters. And if we have our re-render overlay from react dev tools, we can see that only the parts that were affected by the computation are getting re-rendered. All that without having to do useMemo

In the next episodes of this series we'll start exploring more aspects of effector. Make sure you check out the blog post which should contain everything from this video pretty much verbatim, get the code from the github repository, and leave a comment if you have any questions that you'd like answered.

I will see you in the next one. I hope you have a great rest of your day. Good bye.

Hello everyone, and welcome to the After Hours. This is the Effector Series, episode 2. In this episode we will be covering the basics of getting Effector added into a react project. All scripts and steps will be published on my blog, which will be linked in the description. We will be using Vite, but usually the bundler choice is not relevant as much to Effector ecosystem. More advanced subjects, such as integration with SSR might require some special consideration and they will be covered separately in future episodes. For the rest of this episode I'll assume that you're on a latest LTS version of node and we'll be using NPM. This should work with other package managers as well, but I'm not going to be covering the differences here.

Link to the Effector documentation: https://effector.dev/

Let's begin by getting the scaffolding going.

npm create vite@latest effector-app -- --template react-ts
cd effector-app
npm install
npm run dev

Excellent, we have an application with a counter implemented using hooks. So let's refactor it with using effector instead.

npm install effector{,-react}
npm install eslint-plugin-effector

Let's update the eslint configuration. We will be using the recommended and react presets.

{
  "plugins": ["effector"],
  "extends": ["plugin:effector/recommended", "plugin:effector/react"]
}

We also need to add the required imports in our code.

import { createStore, createEvent } from "effector";
import { useUnit } from "effector-react";

Let's create our store that would hold the value for the counter.

const $counter = createStore(0);

Ok, so first thing you would notice, is that the store is created outside of the component. Second, is that there's a $ prefix, which is a suggested naming convention. The eslint plugin would tell us if we are not naming our units according to it. Stores are prefixed with $, Effects are postfixed with Fx and events are simply verbs without any additional sigils. Let's see if we can observe the value of the store by watching it.

$counter.watch(current => console.log("Current value of $counter is", current));

This subscription will execute once the store settles. If we open the console we shall see the output.

Okay, now let's start doing the plumbing. First, we need to get the value of the store displayed on the page. For this purpose we shall be using the useUnit hook from the effector-react package. Because this is a hook, it has to reside within the component's scope.

const effectorCount = useUnit($count);

useUnit is more flexible and allows to wire up multiple stores and events in one call, but we will look at the advanced use cases later.

If we look at the type of effectorCount, we shall see that is is a number, which makes sense, since the default value of the store – 0 is a number.

We can now insert it into our template.

<div className="card">
  <button>effectorCount is {effectorCount}</button>
</div>

We now see the button, and it does display “Effector count is 0” indeed. Now let's make it functional. Stores are able to react to events. So, let's create an event.

const increment = createEvent();

We can actually watch an event as well. In effector ecosystem you can watch any unit, and stores, events, and effects are all units.

increment.watch(() => console.log('Increment event was triggered'));

Now when we press the button we can see the message in the console.

Let's tie the store and event together with some business logic. There are a couple of ways we can do this, one is more universal, but a bit more complex, another is simple, but does not work for all scenarios.

The simple version looks like this

$count.on(increment, (currentValue) => currentValue + 1);

The more complex version is going to be leveraging one of the most commonly used functions in effector, namely sample.

import { sample } from "effector";

Let's look at how wiring it up with sample is going to look like.

sample({
  clock: increment,
  source: $count,
  fn(current) {
    return current + 1;
  },
  target: $count
});

It reads as follows – “When increment is triggered, take the current value from $count, pass it through fn, and send the result of that call to the $count store”.

If you were to keep both variants of the code and press the button, the store would increment by 2 (however through the magic of effector, this causes only one re-render).

Now let's take this one step further, and extract all the logic code into it's own file to fully decouple it from the UI.

touch src/model.ts

Here's the contents of our model file

import { createStore, createEvent, sample } from "effector";
export const $count = createStore(0);
export const increment = createEvent();

sample({
  clock: increment,
  source: $count,
  fn: current => current + 1,
  target: $count
});

And we'll modify our component code to simply import all the necessary bits from the model.

import { $count, increment } from './model';
const { $count: effectorCount, increment: inc } = useUnit({ $count, increment });

But what if we want to re-use this logic for a separate counter? Well, effector does require to create all links between units ahead of time. It does not work well with dynamically created stores, though there are several developments on that front.

Right now the best way to encapsulate this is by creating a factory. A factory is simply a function that takes parameters and returns some units… In order to make it a bit easier for ourselves we can leverage the @with-ease/factores library, that helps a bit with this. This library mostly helps in more advanced scenarios, that I will not be covering now, but we might get into the habit right away.

npm install @withease/factories

Let's rewrite our model code to leverage the factory pattern:

import { createStore, createEvent, sample } from "effector";
import { createFactory } from "@withease/factories";

export const counterFactory = createFactory(() => {
  const $count = createStore(0);
  const increment = createEvent();

  sample({
    clock: increment,
    source: $count,
    fn(current) {
      return current + 1;
    },
    target: $count,
  });

  return { $count, increment };
});

And now let's modify the component

import { invoke } from "@withease/factories";
import { counterFactory } from "./model";

const counterOne = invoke(counterFactory);

And in the component body we'll make the following changes:

const { $count: effectorCount, increment: inc } = useUnit(counterOne);

Now, if we wanted to add another counter, we can do it trivially:

const counterTwo = invoke(counterFactory);

Now we can lift the counter itself into a separate component, that would take the counter business logic via props.

function Counter({ counter }: { counter: ReturnType<typeof counterFactory> }) {
  const { $count, increment } = useUnit(counter);

  return (
    <div className="card">
      <button onClick={() => increment()}>Current count is {$count}</button>
    </div>
  )   
}

And now we use it

<Counter counter={counterOne} />
<Counter counter={counterTwo} />

Two fully independent counters backed by the same business logic.

#effector

Click here to learn Mo.

In today's episode we go over Mo' Claudius journey from Bachelors in Nigeria and Masters in Canada, to the present day and future PhD aspirations in Computer Science. Check it out!