I am trying to decide if I am using RTKQuery to keep my Stencil.js components' state in sync with the DB correctly.
The behaviour I have is that my component will fetch the data using RTK query and store.dispatch() and assign it to its local state. Then the user mutates the component, which is also a request made using the rtk query api via a store dispatch() function.
The only way I have managed to get my component to rerender is by using the componentWIllLoad()
lifecycle mthod to subscribe to the store and pass in a fetch function store.dispatch(api.endpoints.fetchFunction.initiate())
as the callback.
While this keeps the state in sync very well, it does cause an infinite invocation cycle between the fetchFunction() which is dispatched as an action and invokes the subscription and which invokes the fetchFunction() and so on. This can be seen with a simple console.log() statement in the subsciption.
While this behaviour is not the end of the world, it does not feel very elegant. Can it be improved upon?
RTK Query setup: I have an API:
- api.ts
export const oracleApi = createApi({
reducerPath: 'oracleApi',
baseQuery: fetchBaseQuery({
baseUrl: 'http://localhost:8000/api/v1/',
prepareHeaders: async headers => {
try {
console.log(await localForage.getItem('CHLJWT'))
const token = await getToken(localForage)
if (token) {
headers.set('Authorization', `CHLJWT ${token.access}`)
}
console.log('HEADERS Authorization: ', headers.get('Authorization'))
return headers
} catch (error) {
console.error('Login Required: ', error)
}
},
}),
tagTypes: ['Spaces', 'Auth', 'Users', 'Documents', 'Figures', 'Organisations'],
endpoints: build => ({
//Auth
login: build.mutation<CHLTokenData, CHLLoginData>({
query(body) {
return {
url: `auth/jwt/create/`,
method: 'POST',
body,
}
},
invalidatesTags: [{ type: 'Auth', id: 'LIST' }],
}),
a redux store:
- store.ts
export const store = configureStore({
reducer: {
// Add the generated reducer as a specific top-level slice
[api.reducerPath]: api.reducer,
},
// Adding the api middleware enables caching, invalidation, polling,
// and other useful features of `rtk-query`.
middleware: getDefaultMiddleware => getDefaultMiddleware().concat(api.middleware),
})
// optional, but required for refetchOnFocus/refetchOnReconnect behaviors
// see `setupListeners` docs - takes an optional callback as the 2nd arg for customization
setupListeners(store.dispatch)
and an index.ts file to combine them
-index.ts
export const api = {
//Spaces
getSpace: async (id: SpaceId) => {
try {
const space = await store.dispatch(api.endpoints.getSpace.initiate(id))
return space
} catch (error) {
console.error(error)
}
},
getSpaces: async (data?) => {
try {
const spaces = await store.dispatch(api.endpoints.getSpaces.initiate())
return spaces
} catch (error) {
console.error(error)
}
},
deleteSpace: async (id: SpaceId) => {
try {
await store.dispatch(api.endpoints.deleteSpace.initiate(id))
} catch (error) {
console.error(error)
}
},
createSpace: async data => {
try {
const res = await store.dispatch(api.endpoints.addSpace.initiate(data))
return res
} catch (error) {
console.error(error)
}
},
updateSpace: async (space, data) => {
try {
const id = space.id
const res = await store.dispatch(api.endpoints.updateSpace.initiate({ id, ...data }))
return res
} catch (error) {
console.error(error)
}
},
}
Finally, I have a stencil.js component
import { store } from 'server_state/store'
import { api } from 'server_state/index'
@Component({
tag: 'app-topbar',
styleUrl: 'app-topbar.css',
})
export class AppTopbar {
private unsubscribe: () => void
@State() space: Space
async componentWillLoad() {
this.spaceId = Router.activePath.slice(8, 44) as SpaceId
this.unsubscribe = store.subscribe(async () => {
await this.loadData()
})
await this.loadData()
}
disconnectedCallback() {
this.unsubscribe()
}
async loadData() {
try {
console.log('Loading data:app-topbar')
api.getSpace(this.spaceId)
this.space = spaceResult.data
} catch (error) {
console.error(error)
}
}
render() {
///
}
}
Along with improving this pattern, I would specifically be interested in whether it is a possible to use the createApi from redux to fetch data without invoking the store.subscribe() callback.
Thanks!