Skip to content

Conversation

gursimran-singh
Copy link
Collaborator

Description

This PR is a proof of concept (POC) demonstrating a potential improvement to the tag structure in apiSlice for better type-safety, autocomplete, and organization.

It explores two approaches:

  1. Defining tags as objects.
  2. Defining tags as arrays of strings with type-safety.

Inline comments provide explanations and usage examples for both approaches.

Jira link:https://lite-farm.atlassian.net/browse/LF-4978

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

How Has This Been Tested?

  • Passes test case
  • UI components visually reviewed on desktop view
  • UI components visually reviewed on mobile view
  • Other (please explain)

Checklist:

  • I have commented my code, particularly in hard-to-understand areas
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • The precommit and linting ran successfully
  • I have added or updated language tags for text that's part of the UI
  • I have ordered translation keys alphabetically (optional: run pnpm i18n to help with this)
  • I have added the GNU General Public License to all new files

@gursimran-singh gursimran-singh requested review from a team as code owners October 14, 2025 07:39
@gursimran-singh gursimran-singh requested review from Duncan-Brain and kathyavini and removed request for a team October 14, 2025 07:39
@Duncan-Brain
Copy link
Collaborator

Duncan-Brain commented Oct 14, 2025

Just caught up on the conversation. Overall I am personally good with the object approach as it keeps that single source of truth.

I also hsaw another option -- I was looking at it and it bothered me that we could not define the tag 'Weather' in weatherAPI.ts and use the const in the apiSlice without creating a circular reference. If its modular I sort of want to define my variables there.

So if that bothered you too, we could consider if we should modularize differently without using injectEndpoints. I forget if we chose a specific way for a reason. We are apparently not limited to a central api and could do something like:

// modular code
export const weatherApi = createApi({
  reducerPath: 'weatherApi',
  baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
  tagTypes: [TAGS.WEATHER],
  endpoints: (builder) => ({
    updateWeather: builder.mutation({
      query: (data) => ({
        url: `weather/${data.id}`,
        method: 'PATCH',
        body: data,
      }),
      invalidatesTags: (result, error, { id, farmId }) => [
        { type: TAGS.WEATHER, id },
        { type: TAGS.FARM, id: farmId }, // Invalidate Farm tag
      ],
    }),
  }),
});

// store
export const store = configureStore({
  reducer: {
    [weatherApi.reducerPath]: weatherApi.reducer,
    [farmApi.reducerPath]: farmApi.reducer,
   ...
  },
});

Looks great otherwise once you choose a direction!

Just might consider moving the object manipulation to the tags file too:

// yield put(invalidateTags([...Object.values(FARM_TAGS), ...Object.values(FARM_LIBRARY_TAGS)]));
yield put(invalidateTags(ALL_FARM_STATE_TAGS));

@gursimran-singh
Copy link
Collaborator Author

That could be definitely a point of discussion, as there are around 50 sagas and finalizing the architecture at the initial stage is very crucial. Let summarize pros and cons of both Independent and shared base API structure to finalize the structure.

Single Base API

PROS:

  • Shared configuration: One baseQuery, headers, error handling, and middleware logic — easier to maintain consistency (auth, retry, logging, etc.).
  • Shared cache: All endpoints share one normalized cache → prevents duplicate requests and improves performance.
  • Cross-feature invalidation: You can invalidate tags across features easily (e.g., invalidating Weather from a Animal endpoint).
  • Simpler store setup: Only one reducer (api.reducer) and one middleware (api.middleware).
  • Lightweight: Fewer slices = smaller Redux state footprint and simpler debugging.

CONS:

  • Large type size: The generated TypeScript types can get heavy due to all endpoints being merged.
  • Versioning: If different features need different baseUrl or headers logic, this setup needs conditional logic or wrappers.

Multiple Independent APIs

PROS:

  • Full isolation: Each feature is entirely independent — different baseUrl, headers, and even middleware if needed.
  • No naming conflicts: Endpoints are scoped by API slice, hence same function name can be used in different APIs.
  • Small type size: Smaller, faster TS builds since each API has its own generated types.

CONS:

  • Duplicated setup: Each API must have its own baseQuery, auth handling, and error logic — higher maintenance.
  • Multiple reducers/middleware: Must add each API’s reducer and middleware to the store. Tedious with 50 APIs.
  • No shared cache: Two APIs calling the same endpoint will refetch separately; no deduplication or tag invalidation across APIs.
  • Harder to coordinate invalidation: Can’t invalidate tags across APIs (e.g., Weather can’t be invalidated by Animals).

In my opinion, using a single base API offers greater benefits for our setup since we have a mono-repo and a single API URL. The independent API approach is more suitable for multi-backend or micro-services architectures.

I’d be happy to discuss this further and get your thoughts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants