Intro
Here's a 4th round of random problems and quick tips for working around them (previous: 3, 2, 1).
The issues:
CORS errors while fetching files from Firebase storage bucket
If you upload files to Firebase cloud storage you might be able to get them with curl or open directly through a link in a browser but it doesn't mean that your app will be able to fetch them. For that to work you need to setup CORS.
If you have some environment where CORS is already working as expected you can use gsutil command to obtain current settings with:
gsutil cors get gs://example1.firebasestorage.app > cors.json
and then apply the exported cors.json on another environment:
gsutil cors set cors.json gs://example2.firebasestorage.app
You can obtain the gs:// link to your storage bucket on Storage page in Firebase Console:

If you don't have any cors.json yet, this might be a starter (you might want to tighten the origin).
[
{
maxAgeSeconds: 3600,
method: ["GET"],
origin: ["*"],
responseHeader: ["Content-Type"],
},
]
Leaking RTL styling in styled-components 5
If you are using styled-components 6 you can create styling that mixes conditional style blocks and [dir="rtl"] attribute selector for right to left text direction.
Take this as example:
import styled, { css } from "styled-components";
const Text = styled.div<{ $first?: boolean; $last?: boolean }>`
color: ${({ $first, $last }) =>
$first ? "purple" : $last ? "green" : "unset"};
margin: 5px;
${({ $first }) =>
$first &&
css`
border: 1px solid purple;
[dir="rtl"] & {
border: 1px dashed purple;
}
`}
${({ $last }) =>
$last &&
css`
border: 1px solid green;
[dir="rtl"] & {
border: 1px dashed green;
}
`}
`;
export const RtlTest = () => {
return (
<div style={{ border: "1px solid gray", margin: 20 }}>
<Text $first>aaa</Text>
<Text>bbb</Text>
<Text $last>ccc</Text>
</div>
);
};
The RtlTest component aggregates 3 styled divs (Text components). Text applies different styles depending on $first and $last properties and it also varies style when page is in RTL mode.

In styled-components 6.0.0 it all works well but results get messy in version 5 (5.3.11 is the last last 5.x.x version):
Notice how the green dashed border styling, which is supposed to be only on Text instance with $last property, got applied to all Text instances! This happens because RTL styling for each conditional block goes to main sc- class and the last injected one wins.
This is simplified example but at work I've noticed it on complex component that applied some transform: rotate, and when such things start to add up you have some fun time debugging ;)
If upgrade to version 6 is not possible, you can avoid adding conditional blocks and instead inject custom attributes and write selectors that use these attributes:
import styled, { css } from "styled-components";
const Text = styled.div.attrs<{ $first?: boolean; $last?: boolean }>(
({ $first, $last }) => ({
"data-first": $first || undefined,
"data-last": $last || undefined,
}),
)<{
$first?: boolean;
$last?: boolean;
}>(
({ $first, $last }) => css`
color: ${$first ? "purple" : $last ? "green" : "unset"};
margin: 5px;
&[data-first] {
border: 1px solid purple;
}
[dir="rtl"] &[data-first] {
border: 1px dashed purple;
}
&[data-last] {
border: 1px solid green;
}
[dir="rtl"] &[data-last] {
border: 1px dashed green;
}
`,
);
export const RtlTest = () => {
return (
<div style={{ border: "1px solid gray", margin: 20 }}>
<Text $first>aaa</Text>
<Text>bbb</Text>
<Text $last>ccc</Text>
</div>
);
};
With this approach styles that depend on property passed to Text component will not leak out in RTL mode.
Missing trace in console log wrapper
Let's say you want to create a custom logger that will automatically add a prefix to logged messages so you could easily spot logs coming from your code vs log entries added by some dependencies.
Here are two ways to do it:
const PREFIX = "[example]";
export const loggerWithoutBind = {
log: (...args: unknown[]) => console.log(PREFIX, ...args),
};
export const loggerWithBind = {
log: console.log.bind(console, PREFIX),
};
Both loggers will automatically prefix passed message and retain ability to add more data to log entries:
import { loggerWithoutBind, loggerWithBind } from "./logger.ts";
loggerWithoutBind.log("Lalala (without bind)", { a: "aaa", b: [1, 2] });
loggerWithBind.log("Lalala (with bind)", { a: "aaa", b: [1, 2] });
...but, the first one has a critical flaw. The entries will be marked as coming from the log wrapper function instead of the line that logged the message, which makes the trace/link useless:

The bind technique avoids this issue and should work in modern browsers (I've checked in Chrome and Firefox).
Header not found in Axios response interceptor
By the specs, HTTP header names are case-insensitive and you could use Fetch API get function to read headers in case-insensitive way.
For example, assuming that server added X-My-Example header with abc as value, you can read it both in original case and in lowercase:
// Fetch API
console.log(res.headers.get("x-my-example")); // abc
console.log(res.headers.get("X-My-Example")); // abc
If you list all headers names by:
console.log([...res.headers.keys()]);
You will see the names in lowercase...
Watch out though if you try to read the headers with Axios, for example in response interceptor:
// Axios
console.log(res.headers["x-my-example"]); // abc
console.log(res.headers["X-My-Example"]); // undefined
The names in Axios response headers object are normalized to lowercase!
Putting a commend with name in original casing may be good idea (one day you might run case-sensitive search/replace in client and server code)...