13th week of 2021

Highlights of the week.

Table of contents

👨‍💼 Work

React: updating context outside of a component

Let's say I have a React component called MyComponent which uses a React context called MyContext. When the value of the context changes, the component does something, e.g. makes an Adobe Analytics tracking call:

function MyComponent() {
const myContext = useContext(MyContext)

useEffect(() => {
window._satellite.track(myContext)
}, [myContext])

return null
}

And then I want to write unit tests for that... But how can I do that since React contexts can't be easily updated outside of React components?

I realized that in the test file, I can pass an empty object for the wrapper component (App in this case) as a prop (named contextUpdater). Inside App, I can store a reference to the context value setter function (setValue in this case) under the given prop object. Then, in a test case, I can call contextUpdater.setValue() – outside of the component. Like so:

/* MyComponent.test.js */

describe('MyComponent', () => {
function App({ contextUpdater = {} }) {
const [value, setValue] = useState('initial value')

// This hack allows us to update the context value outside of the component
contextUpdater.setValue = setValue

return (
<MyContext.Provider value={[value, setValue]}>
<MyComponent />
</MyContext.Provider>
)
}

beforeEach(() => {
global._satellite = { track: jest.fn() }
})

it('makes a tracking call when MyContext changes', () => {
const contextUpdater = {}

render(<App contextUpdater={contextUpdater} />)
expect(global._satellite.track).toHaveBeenCalledTimes(1)

act(() => contextUpdater.setValue('new value'))
expect(global._satellite.track).toHaveBeenCalledTimes(2)

act(() => contextUpdater.setValue('third value'))
expect(global._satellite.track).toHaveBeenCalledTimes(3)
})
})

It works! IQ 200! Proud moment of the week. 😄

So yeah, actually this feels very hacky, probably because this is hacky.

Another question is whether the test is too implementation-specific. On the other hand, MyComponent directly depends on MyContext, so I'm not sure.

Anyway, the test passes, so I think this is good enough for now. 😁

👨‍🚀 Personal projects

Same thing as in the last two weeks: continued doing Clojure exercises on Exercism.

👨‍🎓 Learnings

Excel: timedate data is stored in days

Eh, did you expect to see Excel tricks in my blog? Me neither.

A friend of mine consulted me with this problem: he was copy-pasting a bunch of time durations to Excel and wanted to calculate their average duration. The values were not in Excel-friendly format, and he didn't want to modify them by hand, so Excel's AVERAGE function didn't work out of the box.

Here's an example of a single time duration:

1:39.811

That's one minute, 39 seconds and 811 milliseconds.

I first tried to change the formatting of the data, hoping that Excel would correctly identify the data as time durations. But no, I couldn't get that to work.

I then tried to parse the text and pass its parts to the TIME function which takes three parameters: hours, minutes and seconds. But passing a decimal number as the third parameter (i.e. seconds with milliseconds) didn't work.

In the end, I found it easiest to convert the data into milliseconds using Excel functions. Here's the formula for the B column, assuming that the original data resides in the A column:

// A1 contains a duration, e.g. "1:39.694"

// B1 contains the duration in milliseconds.
// In this case it would be "99694"
=
// Minutes,
// i.e. the first character from the left.
// Doesn't support 10+ minute durations,
// but that's probably OK with this data.
LEFT(A1; 1)*1000*60

// Seconds,
// i.e. starting from the third character (1-indexed),
// take two characters in the middle.
+ MID(A1; 3; 2)*1000

// Milliseconds,
// i.e. the three rightmost characters.
+ RIGHT(A1; 3)

Now that the B column contains the time durations in milliseconds, it's easily to calculate the average value... But how to format the result as a time duration?

Luckily, someone had asked "[How to] convert milliseconds to hh:mm:ss.000" on Reddit four years ago, and someone called sqylogin had shared this nugget of wisdom (formatted for clarity):

Excel stores data in days.

Therefore, 1 millisecond = 1/24/60/60/1,000 = 1/86,400,000.

To convert 586 milliseconds to an Excel "day," divide it by 86.4 million, and in the resulting cell, use this custom number format: HH:MM:SS.000.

Ah, so calculating the average time duration is as easy as this:

=AVERAGE(B1:B999) / 86400000

Plus the formatting of the cell displaying the average value needs to be set to this:

m:ss.000

Egg-cellent! Now my friend can keep copy-pasting values to the A column, and the average value will be updated automatically. He can even hide the B column since it's only used as an intermediate step to calculate the average value.

The actual learning: Excel is often a very powerful and quick tool to use. But sometimes it doesn't work like I would want it to, so I end up fighting it and coming up with hacky solutions like this. (To be fair, maybe that applies to all tools and even programming in general. 😅)

CSS: class selectors have higher specificity than tag selectors

Which of the following CSS rules wins?

<style>
.foo {
color: pink;
}
p {
color: teal;
}
</style>
<p class="foo">Hello</p>

I.e. what's the color of the text: pink or teal?

I already spoiled the answer in the section heading, but yeah: class selectors have higher specificity than tag selectors, so the color of the text is pink.

This is such basic knowledge, but somehow I thought that the specificities of the two are the same. 😅 Maybe because I usually use mostly class selectors. (I did know that IDs have much higher specificity than classes.)

🕵️‍♂️ Cool stuff

CSS: z-indexes and stacking contexts

If you have ever fighted with z-indexes in CSS, or if you ever have to, I can highly recommend Josh W Comeau's article What the heck, z-index??

The article explains clearly how z-indexes work, and how to create stacking contexts which are related to z-indexes. Spoiler: you don't always have to use position: absolute to create a new stacking context!

This guide has already helped me once when I had to play with z-indexes at work, but I didn't want to use position: absolute.

💁‍♂️ More Weekly log entries

Next week: 14th week of 2021

Previous week: 12th week of 2021