The reason why you should NOT import dependencies into your components

A while ago, I published a small post on LinkedIn talking about my thoughts about using React Query + Router for data-fetching, and I posted a simple code repo showing a quick possible implementation. However, there was a specific point in the code that several people were finding it a little wierd...

The point I'm talking about is:

The router's loaders need to access react-query's queryClient, right?? The way that I used was: I create the queryClient instance inside the <App/> component, & then pass it as an argument to the loaders functions.

But what some people were wondering: "Isn't this just overcomplicating the code?! Wouldn't it have been easier if you had just created a global queryClient instance, & then just simply import it directly in each loader file?!"

I think the answer for this question is quiet interesting, so here's the answer:

It's actually true that creating a global instance of an object/service/store & then importing it directly wherever it's being used would result in less & simpler code, however... This "simpler" approach can turn into something very ugly when your project starts growing big.

Mainly, when you will need to be able to provide different implementaions for the same service under different conditions.

"But why would I want to do that in the first place??"

Well, here's the 2 most common cases for that:

Running our tests in atomicity

We all know that good tests are:

  • Order independent
  • Neither rely on nor cause side effects.
  • Neither rely on nor alter some shared state.

So using a single global service like react-query client by all the tests is clearly violating the above mentioned rules. (The result will be bugs that are very hard to really diagnose or fix)

A much better solution would be to create a new "clean" service instance for each test case, & pass it down to the consumers. Doing this will guarantee that your tests aren't bleeding into each other.

"Just wait a minute! Couldn't we just reset/recreate this global service before each test case using beforeEach for example??"

Yeah, we could do that... However, you won't be able to run your tests concurrently anymore (multiple tests at the same time), which could reduce the execution time a lot.

Swapping implementations easily under different environments.

You might want to run a different implementation of some service in different runtime envs (ex: provide mocked services in tests, real services in prod). Or you might want to test a new service implemntation but only for a segment of your users.

So if your service consumers were directly importing the service, then you will have to change all the consumers to import & use the right service under the right condition. (Or you'll need to create a service proxy)

BUT, if you were instead providing the service to the consumers, then you'll only need to make a few changes in the provider of the service, without touching a single line of code in any of the consumers!!

I know that this might seem a little "too much" the first time you read about it, but the benefits becomes very obvious once your project needs any of the above.

I personally feel that this pattern is quite useful & deserves more light, so I'm going to write another post soon that covers some things that I wasn't able to touch on in this post, so stay tuned.

That's everything for now folks.

Until next time,
And have a great day! 👋

My Photo

About Me

I'm a freelance web developer who specializes in frontend.
I have a special love for React. And my personal goal is: Building things that are Awesome! ✨
If you are someone who is building a project where high quality is a MUST, then Let's Chat! I'll be glad to help you with it.

© 2023-present Mohammed Taher Ghazal. All Rights Reserved.