@nebula-note/redux-hooks
Version:
Simplify Redux usage: no actions, no reducers, only hooks.
190 lines (189 loc) • 5.39 kB
JavaScript
import _ from "lodash";
import { combineReducers, configureStore as configureStore$1, createSlice } from "@reduxjs/toolkit";
import { useMemo, useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
const takerList = [];
let recordId = 0;
const removeCallback = (referenceId) => {
_.remove(takerList, (taker) => taker.referenceId === referenceId);
};
const takeMiddleware = () => (next) => (action) => {
const result = next(action);
takerList.forEach((taker) => {
if ((taker == null ? void 0 : taker.actionType) === action.type) {
taker.callback();
}
});
return result;
};
const addTaker = (actionType, callback) => {
const existObj = takerList.find((taker) => taker.referenceId === callback.name);
let referenceId = existObj == null ? void 0 : existObj.referenceId;
if (!existObj) {
recordId++;
const takeObj = {
referenceId: callback.name ? callback.name : recordId.toString(),
actionType,
callback
};
takerList.push(takeObj);
referenceId = takeObj.referenceId;
}
return () => removeCallback(referenceId);
};
function createReducerManager(initialReducers = {}) {
const reducers = { ...initialReducers };
let combinedReducer = combineReducers(reducers);
let keysToRemove = [];
return {
getReducerMap: () => reducers,
reduce: (state, action) => {
if (keysToRemove.length > 0) {
state = { ...state };
keysToRemove.forEach((key) => {
state == null ? true : delete state[key];
});
keysToRemove = [];
}
return combinedReducer(state ?? {}, action);
},
add: (key, reducer) => {
if (!key || reducers[key]) return;
reducers[key] = reducer;
combinedReducer = combineReducers(reducers);
},
remove: (key) => {
if (!key || !reducers[key]) return;
delete reducers[key];
keysToRemove.push(key);
combinedReducer = combineReducers(reducers);
}
};
}
let store;
const getStore = () => store;
const configureStore = (options = {}) => {
if (store) {
return store;
}
const reducerHolder = {
__holder: (state = "1.0.0") => state
};
const reducerManager = createReducerManager((options == null ? void 0 : options.reducer) ?? reducerHolder);
store = configureStore$1({
...options,
reducer: reducerManager.reduce,
middleware: (getDefaultMiddleware) => {
if (options.middleware) {
const middlewares = options.middleware(getDefaultMiddleware);
return middlewares.concat(takeMiddleware);
}
return getDefaultMiddleware().concat(takeMiddleware);
}
});
reducerManager.remove("__holder");
store.reducerManager = reducerManager;
store.addTaker = addTaker;
return store;
};
const createSliceInstance = (sliceName, initialState) => createSlice({
name: sliceName,
initialState,
reducers: {
setState: (state, action) => {
return action.payload;
},
updateState: (state, action) => {
return _.mergeWith({}, state, action.payload, (objValue, srcValue) => {
if (Array.isArray(objValue)) {
return srcValue;
}
});
}
}
});
const useRedux = (stateName, initialState) => {
const store2 = getStore();
const dispatch = useDispatch();
const reducerInstance = useMemo(
() => createSliceInstance(stateName, initialState),
[stateName, initialState]
);
if (!store2) {
throw new Error("store is not initialized");
}
useEffect(() => {
store2.reducerManager.add(stateName, reducerInstance.reducer);
}, []);
const state = useSelector((storeState) => {
if (Object.prototype.hasOwnProperty.call(storeState, stateName)) {
return storeState[stateName];
}
return initialState;
});
const take = (actionType) => {
let resolveHandle;
const promiseHandle = new Promise((resolve) => {
resolveHandle = resolve;
});
const removeTaker = store2.addTaker(`${stateName}/${actionType}`, () => {
resolveHandle(removeTaker);
});
return promiseHandle;
};
const takeOnce = (actionType) => {
let resolveHandle;
const promiseHandle = new Promise((resolve) => {
resolveHandle = resolve;
});
const removeTaker = store2.addTaker(`${stateName}/${actionType}`, () => {
resolveHandle();
removeTaker();
});
return promiseHandle;
};
const getStateSync = () => {
return store2.getState()[stateName];
};
const setState = (payload) => {
if (typeof payload === "function") {
const ownState = getStateSync();
const newState = payload(ownState);
dispatch(reducerInstance.actions.setState(newState));
return;
}
dispatch(reducerInstance.actions.setState(payload));
};
const setStateSync = (payload) => {
(async () => {
const takeHandle = takeOnce("setState");
setState(payload);
await takeHandle;
})();
};
const updateState = (payload) => {
dispatch(reducerInstance.actions.updateState(payload));
};
const updateStateSync = (payload) => {
(async () => {
const takeHandle = takeOnce("updateState");
updateState(payload);
await takeHandle;
})();
};
return {
state,
getStateSync,
setState,
setStateSync,
updateState,
updateStateSync,
take,
takeOnce
};
};
export {
configureStore,
getStore,
useRedux
};