Reputation: 10772
I am refactoring an existing react-leaflet application that is written with NextJS and redux. I am introducing redux-sagas to clean up the api calling logic. In some of the sagas, I would like to use some leaflet geo functions:
function* handleGeocode(): Generator {
if (window) {
try {
const response = yield call(axios.get, `nominatim-url-blah-blah`);
const { lat, lon: lng } = response[0];
const bounds = L.latLngBounds([ // Use leaflet function here
[lat - 0.5, lng - 0.5],
[lat + 0.5, lng + 0.5],
]);
yield put(
ActionCreators.requestDataInBounds(bounds))
);
} catch (e) { }
}
}
function* watchHandleGeocode() {
yield takeEvery(ActionTypes.REQ_GEOCODE, handleGeocode);
}
function* searchSagas() {
yield all([fork(watchHandleGeocode),
}
The use of L.latLngBounds
runs some leaflet code, which deep down, eventually needs access to the window object.
I am getting the error ReferenceError: window is not defined
.
I know this has been an issue with react-leaflet before. I've read the question Leaflet with next.js, but that is not my issue. My actual Map component is already being imported using dynamic:
const Map = dynamic(() => import("../components/Map"), {
ssr: false,
});
I am not having an issue with this. My issue arises as soon as I introduce leaflet-native code into the sagas. Its alost as if the sagas are all being run as soon as the saga middleware is used:
const sagaMiddleware = createSagaMiddleware();
function initStore(preloadedState) {
const store = createStore(
rootReducer,
preloadedState,
composeWithDevTools(applyMiddleware(sagaMiddleware))
);
return store;
}
export const initializeStore = (preloadedState = {}) => {
let _store = store ?? initStore(preloadedState);
// After navigating to a page with an initial Redux state, merge that state
// with the current state in the store, and create a new store
if (preloadedState && store) {
_store = initStore({
...store.getState(),
...preloadedState,
});
// Reset the current store
store = undefined;
}
// For SSG and SSR always create a new store
if (typeof window === "undefined") return _store;
if (typeof window !== "undefined") {
sagaMiddleware.run(rootSaga);
}
// Create the store once in the client
if (!store) store = _store;
return _store;
};
export function useStore(initialState) {
const store = useMemo(() => initializeStore(initialState), [initialState]);
return store;
}
So despite my only running the sagaMiddleware if window !== "undefined"
, and the saga itself being wrapped inside of a if (window)
statment, I am still getting this error. Its as if the code is being run and running the L.latLngBounds
function, even though the action that calls the handleGeocode
saga is not even currently being called anywhere in the code! As soon as I remove the call to L.latLngBounds, the error goes away.
What is going wrong here? How does NextJS run this code and even reach this type of error, if the action which runs this saga is not even being called? How can I rewire my sagas so that I can use native leaflet functions within them?
Upvotes: 2
Views: 748
Reputation: 10772
I found a way to do this - I'll drop this here in the rare event that someone else runs into this issue. In any saga that needs to call leaflet L.something
methods, you can't import leaflet directly. However, you can pull this trick:
let L;
if (typeof window !== "undefined") {
L = require("leaflet");
}
This is not ideal for a few reasons. You lose all intellisense for L
, so you better know what you're doing. Also, if you import any functions that use L.something
methods, you need to do the same thing. For example:
import { convertBoundsToCorners } from './some/utils';
function* handleGeocode(): Generator {
try {
const response = yield call(axios.get, `nominatim-url-blah-blah`);
const { lat, lon: lng } = response[0];
const bounds = L.latLngBounds([
[lat - 0.5, lng - 0.5],
[lat + 0.5, lng + 0.5],
]);
const corners = convertBoundsToCorners(bounds);
yield put(ActionCreators.requestDataInBounds(corners)));
} catch (e) { }
}
In the imported method, if you use L.something
methods, you have to do the masked import as well:
// utils.js
let L;
if (typeof window !== "undefined") {
L = require("leaflet");
}
export const convertBoundsToCorners = bounds => {
// do some stuff here that involves L.latLngBounds methods
}
So there are some clear disadvantages to this approach, but its the only one that I can get working without things crashing. Now I can use nice leaflet logic within my sagas!
This seems to be a universal way to include libraries that require access to the window object, as I had to do the same thing with device-uuid.
Upvotes: 0