CSS Paint Order (fill & stroke)
There is a CSS property that allows you to change the order in which the browser paints text fill and stroke: paint-order
.
You could draw the stroke first, then fill over top.
I learnt this from @argyleink
There is a CSS property that allows you to change the order in which the browser paints text fill and stroke: paint-order
.
You could draw the stroke first, then fill over top.
I learnt this from @argyleink
You can use the following command to pop just one file from it. Replace stash@{n}
with the actual stash reference you want to use, and replace file/path with the path to the specific file you want to pop:
git checkout stash@{n} -- file/path
For example, if you want to pop a file named "example.txt" from the most recent stash (stash@{0}
), you can do:
git checkout stash@{0} -- example.txt
A way of keeping the keyboard at the bottom of the page when the virtual keyboard appears on mobile you can use:
.nav {
bottom: max(0px, env(keyboard-inset-height, 0) - 100px);
}
You can see here the original post with that idea and here is the CodePen.
I learnt this from @shadeed9
In JavaScript you can reference a previous argument from a function but on TypeScript the first argument can reference the second argument!
type Indices = 1 | 2 | 3;
type GroupedKeys<T> = T extends `${infer U}${Indices}` ? U : never;
function add<
Obj extends Record<`${BaseKey}${Indices}`, any>,
BaseKey extends GroupedKeys<keyof Obj>
>(_obj: Obj, _baseKey: BaseKey) {
return null as any;
}
const order = {
price1: 10,
price2: 20,
price3: 30,
};
add(order, "price");
The code above is avaiablable in this playground.
You can read the full article from Maxime
I learnt this from @maxime1992
The third argument seems to be needed to correlate return/input types with the function body.
const greet: {
(name: string): string
(hellos: number): string
(arg: string | number): string
} = (arg) => {
if (typeof arg === "string") {
return `Hello ${arg}!`;
}
return Array.from(Array(arg)).map(() => "Hello").join(' ');
}
greet("John");
greet(3);
// Outputs:
// Hello John!
// Hello Hello Hello
I learnt this from @steveruizok
Not sure what you'd need this but...
enum Status {
RUNNING = "RUNNING",
FINISHED = "FINISHED",
FAILURE = "FAILURE"
}
const StatusValues = {
...Status,
PENDING: "PENDING" as const
}
const st = StatusValues.RUNNING;
I learnt this from @steveruizok
As simple as this:
enum Status {
RUNNING = "RUNNING",
FINISHED = "FINISHED",
FAILURE = "FAILURE"
}
type StatusUnion = `${Status}`;
I learnt this from @MCapoz
You can check if a type is any using this utility:
type IsAny<T> = 0 extends (1 & T) ? true : false;
Some checks:
// Returns true
type R = IsAny<any>;
type J = IsAny<ReturnType<typeof JSON.parse>>;
// Returns fale
type A = IsAny<"A">;
type B = IsAny<{}>;
type C = IsAny<unknown>;
I learnt this from @WrocTypeScript
Normally, we use forms with only one submit button, but because they are just inputs, you can define multiple of them.
The key to knowing which button was clicked or submitted when you hit enter is to give them all the same name.
interface AcceptRejectSubmitEvent extends SubmitEvent {
readonly submitter: HTMLButtonElement;
}
export default function App() {
const [chosenButton, setChosenButton] = React.useState<string>(null);
const onSubmit = (
e: React.SyntheticEvent<HTMLFormElement, AcceptRejectSubmitEvent>
) => {
e.preventDefault();
const submitter = e.nativeEvent.submitter;
setChosenButton(submitter.value);
};
return (
<div>
<form onSubmit={onSubmit}>
<p>I agree with the terms ...</p>
<div>
<label htmlFor="name">Email: </label>
<input type="email" name="email" id="email" required />
</div>
<div className="buttons" style={{ display: "flex" }}>
<button
type="submit"
name="commit"
value="reject"
style={{ order: 1 }}
>
Reject
</button>
<button type="submit" name="commit" value="accept">
Accept
</button>
</div>
{chosenButton && <output>You choose {chosenButton}</output>}
</form>
</div>
);
}
Examine the following lines of code:
e.nativeEvent.submitter
. I used order
to change the order of appearance, but it wasn't taken into account.SubmitEvent
method. Because submitter
is of type HTMLElement
by default, we extend the SubmitEvent
to override the submitter
type to avoid casting.If you use action
and method
to handle the submit action on the server, you will get the attribute commit
with the value accept
or reject
.
There's a new method released in Safari 16.4 that allows to unwrap all promises from an array: Array.fromAsync
.
I found and example from @mgechev:
async function* generate(total, throttle) {
for (let i = 0; i < total; i++) {
await new Promise((resolve) => setTimeout(resolve, throttle));
yield i + 1;
}
}
// [1, 2, 3, 4, 5]
console.log(await Array.fromAsync(generate(5, 200)));
As you can see it returns a Promise
, so what happens when a promise is rejected? Yes, you have to handle it. In the next example, the error is caught and the expression of Array.fromAsync
will return undefined
. We could use ??
to return a fallback array:
async function withErrorExample() {
const err = new Error();
const badIterable = {
[Symbol.iterator]() {
throw err;
}
};
// This returns a promise that will reject with `err`.
const withErrorResult = await Array.fromAsync(badIterable)
.catch((error) => {
console.log("This is executed when there's an error.", error);
}) ?? ["falback"];
console.log("Result", withErrorResult);
}
withErrorExample();
As with Array.from
there's also a second argument which is a map
function:
class Array {
static fromAsync(
asyncItems: AsyncIterable | Iterable | ArrayLike,
mapfn?: (value: any, index: number) => any,
thisArg?: any
): Array;
}
I learnt this from @mgechev
While reviewing the Safari 16.4 release notes I noticed a new method for array and array like objects.
Basically it iterates throught an array and returns a Map
object indexed by the returned value of the groupToMap
callback.
const posts = [
{ slug: "something-new", favorites: 3 },
{ slug: "weird-bug-thingy", favorites: 2 },
{ slug: "some-cash-please", favorites: 2 },
{ slug: "unpopular-opinion", favorites: 1 },
{ slug: "how-to-make-money", favorites: 3 }
];
posts.groupToMap(({ favorites }) => favorites);
That will return a Map
object like this:
[
[
3,
[
{ slug: "something-new", favorites: 3 },
{ slug: "how-to-make-money", favorites: 3 }
]
],
[
2,
[
{ slug: "weird-bug-thingy", favorites: 2 },
{ slug: "some-cash-please", favorites: 2 }
]
],
[1, [{ slug: "unpopular-opinion", favorites: 1 }]]
]
Until it's officially polyfilled, here's an ambient declaration that you could add to your global.d.ts
file to make it work with TypeScript:
declare global {
interface Array<T> {
groupToMap<GroupName>(
cb: (value: T) => GroupName,
index?: number,
origArray?: T[]
): Map<GroupName, T>;
}
}
Note: On sparse arrays empty values are treated as undefined
.
I came across a tweet of cpojer where he claimed he liked the native browser form validation and in the snippet I found a TypeScript utility I haven't seen before: React.DetailedHTMLProps
. It seems it's like React.HTMLAttributes
but it also adds key
and ref
as optional props.
function App() {
return (
<div className="App">
<Form>
<label htmlFor="name">Name:</label>
<input name="name" placeholder="Type here your name" required />
</Form>
</div>
);
}
type FormProps = React.DetailedHTMLProps<
React.FormHTMLAttributes<HTMLFormElement>,
HTMLFormElement
>;
function Form({ onBlur: onBlurBase, ...rest }: FormProps) {
const onBlur: React.FocusEventHandler<HTMLFormElement> = (e) => {
e.target.classList.add("validate");
onBlurBase?.(e);
};
return <form {...rest} onBlur={onBlur} />;
}
If you want to play with the code above you can do it here.
I found the answer to my question in Stackoverflow thanks to Marcin. I copy here the response because it's worthwile knowing it.
interface ComponentProps1
extends React.DetailedHTMLProps<
React.HTMLAttributes<HTMLDivElement>,
HTMLDivElement
> {}
interface ComponentProps2 extends React.HTMLAttributes<HTMLDivElement> {}
type Dif = Omit<ComponentProps1, keyof ComponentProps2>;
type Dif = {
ref?: LegacyRef<HTMLDivElement>;
key?: string | number;
};
I learnt this from @cpojer
You can set up a JavaScript project to emit .d.ts
files automatically from JSDoc tags, without having to maintain them manually.
To do so, you have to follow these steps:
tsconfig.json
file with this content:{
// Change this to match your project
"include": ["src/**/*"],
"compilerOptions": {
// Tells TypeScript to read JS files, as
// normally they are ignored as source files
"allowJs": true,
// Generate d.ts files
"declaration": true,
// This compiler run should
// only output d.ts files
"emitDeclarationOnly": true,
// Types should go into this directory.
// Removing this would place the .d.ts files
// next to the .js files
"outDir": "dist",
// go to js file when using IDE functions like
// "Go to Definition" in VSCode
"declarationMap": true
}
}
.d.ts
files for JavaScript files.package.json
to reference the types.I learnt this from @orta
It's possible to see whether a CPU is x86 based using the following snippet.
function isX86() {
const f = new Float32Array(1);
const u8 = new Uint8Array(f.buffer);
f[0] = Infinity;
f[0] = f[0] - f[0];
return u8[3] == 255;
}
The exaplanation of this is commented in the Tensorflow codebase and is worthwhile to read:
Unlike most other architectures, on x86/x86-64 when floating-point instructions have no NaN arguments, but produce NaN output, the output NaN has sign bit set. We use it to distinguish x86/x86-64 from other architectures, by doing subtraction of two infinites (must produce NaN per IEEE 754 standard).
I learnt this from @robknight\_
When you do an uncontrolled form in React and you want to access a field inside of the elements
attribute, for instance username
, you'll get an error like this:
Property 'username' does not exist on type 'FormElements'
You can extend HTMLFormControlsCollection
and HTMLFormElement
to add all the input elements like this:
interface FormElements extends HTMLFormControlsCollection {
username: HTMLInputElement;
}
interface UsernameFormElement extends HTMLFormElement {
readonly elements: FormElements;
}
function UsernameForm({
onSubmitUsername,
}: {
onSubmitUsername: (value: string) => void;
}) {
const [error, setError] = React.useState<string | null>(null);
function onSubmit(evt: React.FormEvent<UsernameFormElement>) {
evt.preventDefault();
onSubmitUsername(evt.currentTarget.elements.username.value);
}
function onChange({
currentTarget: { value },
}: React.ChangeEvent<HTMLInputElement>) {
const isLowerCase = value === value.toLowerCase();
setError(isLowerCase ? null : "Username must be lower case");
}
return (
<form onSubmit={onSubmit}>
<div>
<label htmlFor="username">Username:</label>
<input id="username" onChange={onChange} />
</div>
<div role="alert" style={{ color: "red" }}>
{error}
</div>
<button type="submit" disabled={Boolean(error)}>
Submit
</button>
</form>
);
}
That way you avoid casting the elements
object.
I learnt this from @kentcdodds
/* 🛑 Invalid selectors */
.char {
p {
...;
}
}
/* ✅ Valid selectors */
.chart {
& p {
...;
}
}
.chart {
> p {
...;
}
}
.chart {
:is(p) {
...;
}
}
.chart {
.message {
...;
}
}
.chart {
.message & {
...;
}
}
I learnt this from @argyleink
You can have a local .gitignore
file to avoid files getting checked into a repo.
Just add the files you want to ignore in .git/info/exclude
.
There's also a global gitignore at ~/.config/git/ignore
.
I learnt this from @mgattozzi
I always forget this gem piece of TypeScript that allows you to force VSCode to show properties inside of a type. So instead of seeing a reference to the type name you get the full properties inside of the type
type Prettify<Obj> = { [k in keyof Obj]: Obj[k] } & {};
So, instead of seeing this:
You see this:
Also when you have intersections like this:
type Intersected = { a: number } & { b: number };
Instead of seeing this:
You see this:
I learnt this from @mattpocockuk
I always forget the formula so here are the steps:
First, download the certificate from the domain using Chrome or Firefox.
Next, import the certificate in the Java VM Keytool:
sudo keytool -import -alias whatever_alias -keystore /Library/Java/JavaVirtualMachines/jdk-17.0.2.jdk/Contents/Home/lib/security/cacerts -file path/to/certificate.cer
In VSCode I also had to add the path to my JVM installation In VSCode Settings
> Sonarlint › Ls: Java Home
. At the time or writing the value is /Library/Java/JavaVirtualMachines/jdk-17.0.2.jdk/Contents/Home
To use the connected mode I add this piece of JSON to .vscode/settings.json
"sonarlint.connectedMode.project": {
"projectKey": "CODE_PROJECT_HERE"
}
After restarting VSCode it should be in connected mode.
Another thing from Stefan Judis weekly newsletter I came across is this thing. He also refers to this blog post.
Basically when cookies are disabled, you also loose access to localStorage
, sessionStorage
and service workers
.
I learnt this from @stefanjudis
I have read another TIL from Stefan Judis that explains this way better.
It seems it's to prevent tracking the user scroll position becaue a server could place images strategically and know how much of them were downloaded and at which time.
I learnt this from @stefanjudis
The Clear-Site-Data
header allows to clear the browser data of a website. You can specify which data you want to clear:
*
.See MDN Clear-Site-Data
docs for more info.
I learnt this from @stefanjudis
Suppose you are using a Callout component like this:
<>
<Callout data-status="success">Some success message</Callout>
<Callout data-status="warning">Some warning message</Callout>
<Callout data-status="error">Some error message</Callout>
</>
You can style your Callout
component doing this:
function Callout({children}) {
return (
<div className={clsx(
"p-4",
"font-medium",
"rounded-lg",
"data-[status=success]:bg-green-200"
"data-[status=warning]:bg-yellow-200"
"data-[status=error]:bg-red-200"
)}>
<p className="text-green-800">
{children}
</p>
</div>
);
}
You can combine this with the group
class to also styling nested elements based on the container.
Then, the resulting code will look like this:
function Callout({children}) {
return (
<div className={clsx(
"group", // Add the group class
"p-4",
"font-medium",
"rounded-lg",
"data-[status=success]:bg-green-200"
"data-[status=warning]:bg-yellow-200"
"data-[status=error]:bg-red-200"
)}>
<p className={clsx(
"group-data-[status=success]:text-green-800",
"group-data-[status=warning]:text-yellow-800",
"group-data-[status=error]:text-red-800",
)}>
{children}
</p>
</div>
);
}
I saw this from Simon Vrachliotis
I learnt this from @simonswiss
Today I found a tweet from Erikras mentioning this.
Date.prototype.getDay() returns a value 0-6, with 0 being the 🇺🇸-centric first day of week: Sunday. But
Intl.Locale.prototype.weekInfo.firstDay
is from 1-7, with 1=Monday, 7=Sunday.
So the thing is that a function to get the first day of week could look like this:
function isFirstDayOfWeek(date: Date): boolean {
const locale = new Intl.Locale(navigator.language);
const firstDayOfWeek = locale.weekInfo.firstDay;
const dayOfWeek = date.getDay();
return dayOfWeek === firstDayOfWeek % 7;
}
There not seems to be a way of getting the first day of week defined by the system:
I learnt this from @erikras
The way you can preserve your encondig in an Excel file
is to use a BOM_ORDER_MARK
which is a character used by Excel to know the file encondig.
You can create a utility function to download a CSV file like this:
const BYTE_ORDER_MARK = "%EF%BB%BF";
export default function downloadCsv(csv: string, fileName: string) {
const anchor = Object.assign(document.createElement("a"), {
href: `data:text/csv;charset=utf-8,${BYTE_ORDER_MARK}` + encodeURI(csv),
download: `${fileName}.csv`,
});
anchor.click();
}
To handle .csv
files, Microsoft Excel uses the List separator defined in Windows Regional settings.
In North America and some other countries, the default list separator is a comma, so you get CSV comma delimited.
In European countries, a comma is reserved for the decimal symbol, and the list separator is generally set to semicolon. That is why the result is CSV semicolon delimited.
You can configure the separator used in an CSV file by adding this as the first line:
sep=,
I've noticed that adding the sep line will make enconding break, so it's better not to add.
There's a built-in way of scrolling to top without JS.
<a href="#TOP"></a>
It should be case-insensitive.
Also with the scroll-behaviour: smooth
, JS is not needed anymore for this.
One of the fetch
API options is keepalive
. This has the same behaviour as sendBeacon API to send analytics or simply calls to the server you don't want to be aborted when navigating though pages.
The reason to prefer keepalive: true
instead of sendBeacon
is that adBlocks seems to be blocking sendBeacon requests.
I learnt this from @stefanjudis
You don't need to provide an initial value to the reduce method. That way you're saving one iteration that you may not need.
[1,2,3,4].reduce((previousValue, currentValue) =>
previousValue + currentValue;
);
The iteration starts assigning 1 to previousValue and 2 to currentValue.
I learnt this from @stefanjudis
This template was presented in React Alicante 2022. It tries to solve common monorepos problem like versioned dependencies, ci build times, ... It can work with Remix
and with Next.js
.
Samsung devices have some sort of resolution selection that will make width and height returned from Dimensions.get("window") not to work as expected. To avoid it, we have to use this scaling factor:
const { width, height } = Dimensions.get("window");
const scaleFactor =
Dimensions.get("screen").scale / Dimensions.get("window").scale;
export const screenWidth = width * scaleFactor;
export const screenHeight = height * scaleFactor;
Instead of creating an element and calling setAttribute
multiple times, you can just use Object.assign
.
function downloadJson(json: string, fileName: string) {
const blob = new Blob([json], { type: "application/json" as const });
const href = URL.createObjectURL(blob);
const anchor = Object.assign(document.createElement("a"), {
href,
download: `${fileName}.json`,
});
anchor.click();
}
I learnt this from @jh3yy
Whenever you have to do a getBy*
query to look for an element that was added to the DOM
in response to an async operation like a fetch request, you can directly use findBy*
queries.