Today I Learned

hashrocket A Hashrocket project

77 posts by jackrosa

Get React Native Component's Dimensions

In React Native you can use the onLayout prop along with the nativeEvent's layout to get the dimensions (in pixels) of a rendered component.

const [viewDimensions, setViewDimensions] = useState({height: 0, width: 0})
return (
  <View
     onLayout={(e) => setViewDimensions(e.nativeEvent.layout)}
  />
)
React JSX

Now we have an object containging our view's height and width saved as viewDimensions

Expo Reactive Native Apps on IOS Simulator

With Expo's CLI, the command npx expo run:ios will build and run your application in xcode's IOS simulator. You will need to have Xcode installed in order for the command to work. If you receive an error stating Xcode must be fully installed before you can continue, you may need to navigate to Xcode's settings and set a version of command line tools under the locations tab. image

Enum Reject vs Filter

If you're anything like me, you regularly forget if Enum.filter/2 keeps elements that return true or filters them out.

Let existence of Enum.reject/2 be your reminder.

reject/2 does the same thing as filter/2 but discards truthy elements instead of keeping them. I wish that filter/2 was renamed to keep/2, then the 2 functions would feel more like logical opposites and perhaps more readable.

Using Enum Sort with multiple keys

You can sort an enum by multiple keys at once. For example, If we wanted to sort a list of 'Vehicle' structs by type and model at the same time, we could do this:

vehicle_list = [
  %Vehicle{type: "van", model: "Odyssey"},
  %Vehicle{type: "truck", model: "Avalanche"},
  %Vehicle{type: "van", model: "Pacifica"},
  %Vehicle{type: "truck", model: "Bronco"}
]

Enum.sort_by(vehicle_list, &{&1.type, &1.model})
#=>
[
  %Vehicle{type: "truck", model: "Avalanche"},
  %Vehicle{type: "truck", model: "Bronco"},
  %Vehicle{type: "van", model: "Odyssey"},
  %Vehicle{type: "van", model: "Pacifica"},
]
Elixir

sort_by is first sorting the vehicles by the :type key and then sorting by the :model key.

Restrict Phoenix Component Attr Values

If you want to limit the permissible values of a Phoenix Component attribute, you can use the values keyword list option when you call attr/3. Here's is an example that restricts which atoms are being passed to the component:

use MyApp.Component

attr :acceptable_color, :atom, values: [:green, :blue]
Elixir

if you were to pass any atom other than :green or :blue to this component, the compiler will warn you.

Add an error to an Ecto Changeset

If you want to create custom validations with ecto changeset, chances are you are going to need to add errors to the changeset to be shown to the user.

Using add_error/4 allows you to add an error to a changeset.

changeset = change(%BlogPost{}, %{content: "Hello World ...cont"})
add_error(changeset, :content, "Your blog content is too long!")

changeset.errors
[content: {"Your blog content is too long!", []}]
Elixir

Compare NaiveDateTimes by a specific unit

In Elixir, you can get the difference between two NaiveDateTimes using the NaiveDateTime.diff/3 function.

The third arg allows you to pass a unit such as :microsecond, :millisecond, or any unit returned from System.time_unit. By default, the unit is set to :second.

NaiveDateTime.diff(~N[2014-10-02 00:29:18], ~N[2014-10-02 00:29:10])
#=> 8
NaiveDateTime.diff(~N[2014-10-04 00:29:18], ~N[2014-10-02 00:29:10], :day)
#=> 2
Elixir

Interestingly enough :day works as a unit, but not :month, or :year. 🤔

String Concat Pattern Matching In Elixir

This is a neat pattern matching trick in elixir, its best explained with a simple example:

invoice_message = "You owe $34"
"You owe " <> dollar_amount = invoice

IO.inspect(dollar_amount)
# => "$34"
Elixir

With a slightly different situation, It may seem like you could do this:

invoice_message = "You owe 34 dollars"
"You owe " <> dollar_amount  <> " dollars"= invoice

IO.inspect(dollar_amount)
# => ** (ArgumentError) the left argument of <> operator inside
# a match should always be a literal binary because its size can't be verified. Got: dollar_amount
Elixir

But sadly you'll need to use regex to do that because elixir will throw an error.

Custom Sigils In Elixir

You can create custom sigils by following the sigil_{character} definition pattern. Let's make an addition sigil sigil_a that sums up space separated numbers.

defmodule CustomSigil do
  def sigil_a(string, []) do
    string
    |> String.split(" ")
    |> Enum.map(& String.to_integer(&1))
    |> Enum.sum()
  end
end

# ~a(2 4 6)
#=> 12
# ~a(12 12)
#=> 24
Elixir

Elixir's Info function

The elixir Kernel module has an interesting function i/1 that returns information about whatever you pass to it.

It will provide the argument's data type, byte size, raw representation, a description, and reference modules

i("hello world")

#=>
# Term
#  "hello world"
# Data type
#  BitString
# Byte size
#  11
# Description
#  This is a string: a UTF-8 encoded binary. It's printed surrounded by
#  "double quotes" because all UTF-8 encoded code points in it are printable.
# Raw representation
#  <<104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100>>
# Reference modules
#  String, :binary
# Implemented protocols
#  Collectable, IEx.Info, Inspect, List.Chars, String.Chars
Elixir

Elixir Data Type Comparison

Elixir has an interesting defined sorting order for its data types. The order is as follows:

number < atom < reference < function < port < pid < tuple < map < list < bitstring
Elixir

For example:

random_data = [["Hello World"], {:hello, :world}, 16, %{key: "value"}, :atom]
Enum.sort(random_data)
#=> [16, :atom, {:hello, :world}, %{key: "value"}, ["Hello World"]]
Elixir

Or even

4 < :atom
#=> true
Elixir

Unix Timestamps in Elixir

Unix timestamps are a very simple way to compare times in an integer format. They are the total seconds elapsed since January 1st, 1970 UTC (The Unix Epoch).

In elixir, if you want to do some operations on a DateTime struct, and want to keep things simple, you can convert a DateTime struct to an integer with to_unix/2. By default, it will use seconds as the unit.

Command Line Wildcards

The * character can be used as a wildcard to match sequences of unknown characters in the command line.

For example, lets say my elixir project has a few tests that I want to run in a directory: MyProject/tests. The folder is filled with a bunch of random files, but the ones that i want to run have a similar name, tests/user_views_home and tests/user_views_show. We could use a wild card to match on both of these file names and run the tests (assuming there are no other files that match) like this:

mix test MyProject/tests/user_views*

The Word Count Command

By using the wc command, you can print out word count information to the terminal. By default, if you use the wc command along with a file path, the command will return 3 values: the line count, word count, and character count of that file.

You can also pipe the wc command into any other terminal command to receive the word count information about the previous command. For instance, you could use the command ls | wc to see the word count info for the current directory's file list.

If you want the wc command to only output either the line count, word count, or char count, you can pass it the following flags: wc -l for line count, wc -w for word count, and wc -c for the char count.

Macro Guard Clauses in Elixir

Have you ever wanted to pass your own custom function into a guard clause?

Lets start by looking at a super basic guard clause here:

def greater_than_10(num) when num > 10, do: true
def greater_than_10(_), do: false
Elixir

Let's say we want to get more specific with the operation inside of the when guard. This is overkill for our situation, but lets say we want the when to use a custom function of ours instead of a basic operation, but when guards don't allow you to directly pass functions to them.

Elixir has a fancy macro called defguard that allows you to define custom guards like functions. Let's change our function up and define a guard that checks if our argument is both a integer and even.

defmodule Guards do
  defguard is_even_num(num) when is_integer(value) and rem(value, 2) == 0
end
Elixir

Let's use that guard in our function

import Guards

def even_num_greater_than_10(num) when is_even_num(num) do
  case num > 10 do
    true -> true
    _ -> false
  end
end
Elixir

Even though this example is a bit overkill, the option to use custom guards is a cool elixir feature.

Casting Associations in Phoenix

If you want to cast changes on a struct's associations, you can use the changeset function cast_assoc/3. This allows you to make changes to a parent struct and its associations at the same time.

Example

Lets say you have a struct called Artist and an associated schema called Album. The schema for the Album looks something like this:

def module MyApp.Music.Album do
 use Ecto.Schema
 alias MyApp.Music.Artist

  schema "albums" do
    field :title, :string
    
    belongs_to :artist, Artist
  end

  def changeset(artist, params \\ %{}) do
    artist
    |> cast(params, [:title, :artist_id])
    |> validate_required([:title])
  end
end
Elixir

and the schema for artist looks like this:

def module MyApp.Music.Artist do
 use Ecto.Schema
 alias MyApp.Music.Album

  schema "albums" do
    field :name, :string
    
    has_many :albums, Album
  end
end
Elixir

Now, if you wanted to build a changeset for the Artist where you can change the artist's name and the title field on an associated album:

def changeset(artist, params \\ %{}) do
    artist
    |> cast(params, [:name])
    |> cast_assoc(:albums, with: &MyApp.Album.changeset/2)
    |> validate_required([:name])
end
Elixir