diceline-chartmagnifiermouse-upquestion-marktwitter-whiteTwitter_Logo_Blue

Today I Learned

Netlify CMS relation widget with a list

As I was using Netlify CMS, I wanted to use a relation widget that would dynamically add content depending on the other collection title field.

It's really easy to do this, but I struggled a little bit with it because the documentation wasn't so explicit, and I could not find something useful on the internet.

So here's how you do it:

This is the collection that will have dynamic content, depending on how many call-to-actions you'll create.

  - name: 'call-to-action'
    label: 'Call to action'
    path: "{{slug}}"
    create: true
    folder: 'content/call-to-action'
    fields:
      - {label: 'CTA Category', name: 'ctaCategory', widget: 'string'}
      - label: 'CTA Content'
        name: 'cta'
        widget: 'list'
        fields:
        - {label: 'Title', name: 'title', widget: 'string' }
        - {label: 'Content', name: 'content', widget: 'string' }
        - {label: 'cover', name: 'cover', widget: 'image', media_folder: '/assets/images/covers/'}
        - label: 'Button'
          name: 'button'
          widget: 'object'
          fields:
          - {label: 'url', name: 'url', widget: 'string'}
          - {label: 'title', name: 'title', widget: 'string'}

And now we have to link this collection to a field in the blog collection. The field in the blog collection looks like this :

- { label: 'Call to action category',
 name: 'ctaCategory',
 widget: 'relation', 
 collection: 'call-to-action', 
 search_fields: ["ctaCategory"], 
 value_field: "cta.*", 
 display_fields: ["ctaCategory"]}

The search will be done depending on what "ctaCategory" you select, and to get all the items in the list widget from the "call-to-action" collection, we need to specify in the value field that we want to get every item, and you do this with ".*".

That's all .

React custom hook for form state management

Goal: defining a custom useForm hook for managing the state of our forms. This can have some initial state, mainly used for precompleting update forms, but can also provide empty form fields. 




Problem: when we want to operate on a precompleted form, altough we pass all the data for our fields, the form is not being precompleted and its fields are empty.

export default function useForm(initial = {}) {
  const [inputs, setInputs] = useState(initial);



  const handleChange = (e) => {
    let { value, name, type } = e.target;

    if (type === 'number') {
      value = parseInt(value);
    }
    if (type === 'file') {
      [value] = e.target.files;
    }

    setInputs({
      ...inputs,
      [name]: value,
    });
  };

  const resetForm = () => {
    setInputs(initial);
  };

  const clearForm = () => {
    const blankState = Object.fromEntries(
      Object.entries(inputs).map(([key, value]) => [key, ''])
    );

    setInputs(blankState);
  };

  return {
    inputs,
    clearForm,
    resetForm,
    handleChange,
  };
}


This is what happens:

  1. We pass an initial state object which is undefined (server-side rendered) until the GQL query loads.
  2. This initial object (which is undefined) populates the form fields making them empty.
  3. After the query loads, the initial state object is repassed to the useForm hook, but the DOM is not rerendered => a possible solution is to make use of the useEffect() hook for forcing rerendering.
  4. We cannot watch for changes directly on the initial object and reassign it using setInputs, because it triggers the useEffect callback once again and again and again when altering its value, causing an infinite loop.
  5. The solution is to watch for changes on a string joined by the values of the initial object. When that changes from undefined to the GraphQL query results, the useEffect callback is called and it initializes and rerenders the fields correspondingly.


An example implementation could be:

const initialValues = Object.values(initial).join('');
useEffect(() => {
  setInputs(initial);
}, [initialValues]);



Now the form precompletion works fine using our custom useForm() hook.

Vuex composition helpers utility package

One big advantage of the Composition API over the Options API is that it lets us group our variables and methods how we want. Most of the time we group them together by feature/functionality, as they use/call one another and it's convenient to have them grouped together this way.

For all apps where we use Vuex for state management, we probably take advantage of the mapState, mapGetters, mapActions and mapMutations binding helpers. Unfortunately, this does not work as expected with the Composition API. But there is also a way of how we can replicate this behavior and also maintain the structure of our components as described at the beginning as an advantage.

We have to install the vuex-composition-helpers utility package:

npm i vuex-composition-helpers@next

and use it as follows in our component:

<script setup lang="ts">

import { useState, useActions, useGetters, useMutations } from 'vuex-composition-helpers'

const { globalMessage, onlineUsers } = useState(['globalMessage', 'onlineUsers'])
const { getGlobalMessage } = useGetters(['getGlobalMessage'])
const { UPDATE_MESSAGE, ADD_USER } = useMutations(['UPDATE_MESSAGE', 'ADD_USER'])
const { updateMessage, addOnlineUser } = useActions(['updateMessage', 'addOnlineUser'])


</script>

DefineEmits and DefineProps compiler macros with default values in TypeScript

This is how we:


a. use the defineEmits() and defineProps() compiler macros with Composition API's Script Setup and TypeScript

b. pass default values to our component's props

<script setup>
const props = withDefaults(
  defineProps<{
    message: string, 
    isActive: boolean
  }>(), 
  {
    message: 'Default message', 
    isActive: false
  }
);

const emit = defineEmits<{
  (e: "customEventName", payload?: object) : void
}>();
</script>

Script setup syntactic sugar for the Composition API in SFCs

Problem: When using the Composition API, we need to return all (reactive) variables from the setup() method in order to use them in the template section. This can make components huge, harder to read and maintain and also cause unwanted bugs if we forget to return any variables used in the template. More than that, we can use variables only by accessing their "value" property.

This is the basic structure of a component that we want to improve:

<script>
//Long list of imports 

import Child1 from 'child1-location';
import Child2 from 'child2-location';
import { ref } from 'vue';

export default defineComponent({
  name: 'CustomComponent',
  components: { Child1, Child2 },
		
  setup() {
	const variable1 = ref(1);
	const variable2 = ref(2);
	//...
	const variable100 = ref(100);

	//altering variables
	variable1.value = 'new value for variable1';

	return {
		variable1,
		variable2,
		.
		.
		.
		variable100,
	}
  }
});
</script>

<template>
	<Child1 />
	<Child2 />
	{{ variable1 }}
	...
	{{ variable100 }}
</template>

Solution: The structure and performance of this component can be improved by using the Script Setup syntactic sugar. 
The Script Setup is a compile-time syntactic sugar that is recommended to be used in SFCs with the Composition API. Let's rewrite the component from above using the Script Setup:

<script setup> 
	
import Child1 from 'child1-location';
import Child2 from 'child2-location';
import { ref } from 'vue';
	
const variable1 = ref(1);
const variable2 = ref(2);
//...
const variable100 = ref(100);

variable1 = 'new value for variable1';
</script>

<template>
        <Child1 />
	<Child2 />
	{{ variable1 }}
	...
	{{ variable100 }}
</template>

Important differences and advantages: 



  1. We don't need to return any variables from our script in order to use them in the template, reducing the number of lines and increasing readability and operability
  2. We don't need to use .value to access and modify reactive variable values
  3. We can directly use imported Child Components in our template without registering them
  4. Better runtime performance due to the behind the hood implementation of the template (as a render function without an intermediate proxy)
  5. TypeScript support for defining props and emitted events (will be described in a future TIL)

How to use Laravel's Tappable trait to achieve fluency

Given the following schemas:

Schema::create('skills', function (Blueprint $table) {
    $table->id();
    $table->string("name");
});
//pivot table
Schema::create('category_skill', function (Blueprint $table) {
    $table->id();
    $table->foreignId("skill_id")->constrained();
    $table->foreignId("category_id")->constrained();
});
//the categories table will be omitted for brevity

Suppose you have to write a method which created a skill, attached multiple categories to it and returned the created skill afterwards:

class CreateSkillAction
{
    public function execute(SkillDTO $skillData): Skill
    {
       ...
    }
}

You could easily achieve this with the following code:

public function execute(SkillDTO $skillData): Skill
{
    $skill = Skill::create($skillData->attributes());
    $skill->categories()->attach($skillData>categories);
    return $skill;
}

But if you wanted to make it a bit more fluent - akin to using fluent interfaces - you could do the following:

1. Add the Tappable trait to you model:

class Skill extends Model
{
    use \Illuminate\Support\Traits\Tappable;
    ...
}

2. Rewrite the execute method to use the tap method - now present on the model:

public function execute(SkillDTO $skillData): Skill
{
    return Skill::create($skillData->attributes())
        ->tap(fn($skill)=>$skill->categories()->attach($skillData->categories));
}

How to configure path aliases in Jest

If you use webpack aliases for your imports you need to let Jest know about them before you can test your code. You can do this in a jest configuration file - jest.config.js for instance:

module.exports = {
  moduleNameMapper: {
    //an import like: '@/interfaces/Foo.ts' 
    //becomes '[your-root]/src/interfaces/Foo.ts'
    '@/(.*)$': '<rootDir>/src/$1',
  },
  ...
};

If you want to learn how to configure webpack aliases you can read this post

How to trigger model events on pivot tables in Laravel

Suppose we have an app where we have a couple of tasks and users and we want to be able to assign users to tasks.

But what if at the same time we want to know who assigned each user to each task? We can easily achieve this by taking advantage of Laravel's model events - for pivot tables.

We would need 3 tables: one for tasks, one for users and one for the assigning users to tasks:

Schema::create('tasks', function (Blueprint $table) {
    $table->id();
    $table->string("title");
    ...
    $table->timestamps();
});

Schema::create('users', function (Blueprint $table) {
    $table->id();
    $table->string("username");
    ...
    $table->timestamps();
});

Schema::create('task_user', function (Blueprint $table) {
    $table->id();
    $table->foreignId("assignee_id")->constrained("users");
    $table->foreignId("skill_id")->constrained();
    $table->foreignId("user_id")->constrained();
    $table->timestamps();
});

1. Create the corresponding TaskUser pivot model.

 php artisan make:model TaskUser --pivot

2. Add your event handlers.

class TaskUser extends Pivot
{
    protected static function booted()
    {
        static::creating(function ($pivot_model) {
            // your implementation here
        });
        ...
    }
}

3. Let Laravel know which model it should use for the pivot table.

This is the key - without this step the callback described above will not be executed!

//in the User model
public function tasks()
{
    return $this->belongsToMany(Task::class)
      ->using(TaskUser::class);
}

Now, you can do:

$user->tasks()->attach($tasks);

That's it! Your event callbacks or corresponding observer methods should now be executed.

File validation in vueJS with vee-validate

As we know, "v-model" does not work on an input that has a type of "file". So to validate a file is a little bit tricky.

For this validation, we will use vee-validate -> https://vee-validate.logaretm.com/v4/

To do this, you need to add a method that triggers the "on change" event on your input and to add on the "ValidationProvider" a ref.

import { ValidationProvider } from 'vee-validate/dist/vee-validate.full.esm'

<ValidationProvider ref="provider" rules="required">
 <input
 ref="file"
 type="file"
 @change="onFileAdd"
 />
</ValidationProvider>

Also you need to go and create the "onFileAdd" method , which will contain the following code:

export default {
 data() {
  return {
    fileData: '',
  }
 }

 methods: {
  async onFileAdd(e){
   const { valid } = await this.$refs.provider.validate(e)
   if (valid) {
    const file = this.$refs.file.files[0]
    this.fileData = file
   }
  }
 }
}

The "fileData" in the data object is the file that will be sent when the form is submitted.

Commit your changes. That's all.

JavaScript event processing and bubbling

JavaScript event listeners and handlers can have some unnatural behaviour if we are not aware of how things work behind the hood. Let's understand what these unexpected behaviours could be and why things actually happen as they happen.

Let's pretend that we have the following DOM structure with 3 divs: grandparent, parent and child.

<div class="grandparent"></div>
  <div class="parent">
    <div class="child"></div>
  </div>
</div>

All 3 divs have their custom event handlers, like follows:

grandparent.addEventListener('click', () =>  console.log('Grandparent'));
parent.addEventListener('click', () =>  console.log('Parent'));
child.addEventListener('click', () =>  console.log('Child'));

If we click on the inner div (the child div), we would expect to call the child div's event handler, but actually we will see in the console that all 3 event handlers have been called, in the following order:

Child
Parent
Grandparent

Let's understand why this happens. Mainly, there are 3 phases in processing JavaScript events:

  1. Capturing phase
  • the DOM tree is parsed downwards until the caller element is found. Throughout its way, if there are nodes that explicitly require event execution in the capturing phase, their event handlers will be executed right away.
  1. Targeting phase
  • the caller element that triggered the event is being targeted
  1. Bubbling phase
  • the DOM tree is now parsed upwards until finding the document element, starting from the targeted/caller element. If on the way up we find elements with event handlers, they will be automatically called.

If we want to stop this behaviour of calling parent elements event handlers, we can use event.stopPropagation() on our last event handler that we want to be executed.