Verified Commit ef3efc31 authored by Florent Chehab's avatar Florent Chehab
Browse files

feat(hooks): merged 'shared' and 'persistant' into 'global'

* Will enable cleaner use in useGlobalReducer
* Prevents setState on unmount components
parent ffe7be76
......@@ -10,7 +10,7 @@ import MenuItem from "@material-ui/core/MenuItem";
import Chip from "@material-ui/core/Chip";
import fuzzysort from "fuzzysort";
import { makeStyles } from "@material-ui/styles";
import usePersistentState from "../../hooks/usePersistentState";
import useGlobalState from "../../hooks/useGlobalState";
const useStyles = makeStyles(theme => ({
root: {
......@@ -122,7 +122,7 @@ function DownshiftMultiple({
return out;
}, [optionsFromProps]);
const [cache, setCache] = usePersistentState(`app-downshift-${cacheId}`, {
const [cache, setCache] = useGlobalState(`app-downshift-${cacheId}`, {
value: valueFromProps,
inputValue: inputValueFromProps
});
......
......@@ -9,7 +9,7 @@ import SelectField from "./SelectField";
import CountryService from "../../../services/data/CountryService";
import CurrencyService from "../../../services/data/CurrencyService";
import LanguageService from "../../../services/data/LanguageService";
import { getLatestApiReadData } from "../../../hooks/usePersistentState";
import { getLatestApiReadData } from "../../../hooks/useGlobalState";
export function TitleField() {
return <TextField required fieldMapping="title" label="Titre" />;
......
......@@ -7,7 +7,7 @@ import Typography from "@material-ui/core/Typography";
import uuid from "uuid/v4";
import { makeStyles } from "@material-ui/styles";
import DownshiftMultiple from "../common/DownshiftMultiple";
import usePersistentState from "../../hooks/usePersistentState";
import useGlobalState from "../../hooks/useGlobalState";
import FilterService from "../../services/FilterService";
import FilterStatus from "./FilterStatus";
import { useSetSelectedUniversities } from "../../hooks/wrappers/useSelectedUniversities";
......@@ -43,13 +43,13 @@ function Filter() {
const classes = useStyles();
const saveSelectedUniversities = useSetSelectedUniversities();
const [isOpened, setIsOpened] = usePersistentState("filter-open", false);
const [countries, setCountries] = usePersistentState("filter-countries", []);
const [semesters, setSemesters] = usePersistentState(
const [isOpened, setIsOpened] = useGlobalState("filter-open", false);
const [countries, setCountries] = useGlobalState("filter-countries", []);
const [semesters, setSemesters] = useGlobalState(
"filter-semesters",
[...FilterService.defaultSemesters].reverse()
);
const [majorMinors, setMajorMinors] = usePersistentState(
const [majorMinors, setMajorMinors] = useGlobalState(
"filter-major-minors",
[]
);
......
......@@ -11,7 +11,7 @@ import green from "@material-ui/core/colors/green";
import makeStyles from "@material-ui/core/styles/makeStyles";
import useEditor from "../../../../hooks/useEditor";
import FormInfo from "../../../../utils/editionRelated/FormInfo";
import { getLatestApiReadData } from "../../../../hooks/usePersistentState";
import { getLatestApiReadData } from "../../../../hooks/useGlobalState";
const useStyles = makeStyles(theme => ({
root: {
......
......@@ -3,7 +3,7 @@ import TextField from "../edition/fields/TextField";
import BooleanField from "../edition/fields/BooleanField";
import FormLevelError from "../../utils/editionRelated/FormLevelError";
import FormInfo from "../../utils/editionRelated/FormInfo";
import { getLatestApiReadData } from "../../hooks/usePersistentState";
import { getLatestApiReadData } from "../../hooks/useGlobalState";
const userInfoFormLevelErrors = [
new FormLevelError(
......
import { useCallback, useMemo, useState } from "react";
import { getLatestRead } from "../utils/api/utils";
import useOnBeforeComponentMount from "./useOnBeforeComponentMount";
import useOnComponentUnMount from "./useOnComponentUnMount";
/**
* @type {Map<string, any>}
*/
export const globalState = new Map();
/**
* Direct access to a persisted value in the global state
*
* @param {string} key
* @returns {any}
*/
export function getPersistedValue(key) {
return globalState.get(key);
}
if (process.env.NODE_ENV !== "production") {
window.globalRexDriState = globalState;
}
/**
* Gets the latest data retrieved from the api for the key
* @param {string} key - route / sing or plur
* @type {Map<string, Set<Function>>}
*/
export function getLatestApiReadData(key) {
return getLatestRead(globalState.get(`api-${key}`)).data;
const globalStateListeners = new Map();
/**
* Helper to update all the listeners on the global state key
*
* @param key
* @param newValue
* @private
*/
function updateGlobalStateListeners(key, newValue) {
if (globalStateListeners.has(key)) {
globalStateListeners.get(key).forEach(f => {
f(newValue);
});
}
}
/**
* Direct access to a persisted value in the global state
* Helper to update the values in the global state and tel the associated listeners
*
* @param {string} key
* @returns {any}
* @param key
* @param newValue
*/
export function getPersistedValue(key) {
return globalState.get(key);
export function updateGlobalState(key, newValue) {
const valueInStore = getPersistedValue(key);
if (valueInStore !== newValue) {
globalState.set(key, newValue);
updateGlobalStateListeners(key, newValue);
}
}
/**
* Gets the latest data retrieved from the api for the key
* @param {string} key - route / sing or plur
*/
export function getLatestApiReadData(key) {
return getLatestRead(globalState.get(`api-${key}`)).data;
}
/**
......@@ -33,7 +71,7 @@ export function getPersistedValue(key) {
* @param {*} initialValue
* @returns {[*, Function]}
*/
function usePersistentState(key, initialValue) {
function useGlobalState(key, initialValue) {
// initializing the global state if needed
useOnBeforeComponentMount(() => {
if (!globalState.has(key)) globalState.set(key, initialValue);
......@@ -41,20 +79,28 @@ function usePersistentState(key, initialValue) {
const [state, setState] = useState(() => getPersistedValue(key));
useOnBeforeComponentMount(() => {
if (!globalStateListeners.has(key))
globalStateListeners.set(key, new Set());
globalStateListeners.get(key).add(setState);
});
useOnComponentUnMount(() => {
if (globalStateListeners.has(key)) {
globalStateListeners.get(key).delete(setState);
}
});
const setPersistentState = useCallback(value => {
const currentGlobalValue = getPersistedValue(key);
const valueToStore =
value instanceof Function ? value(currentGlobalValue) : value;
// Save global state
globalState.set(key, valueToStore);
// save local state
setState(valueToStore);
updateGlobalState(key, valueToStore);
}, []);
// Make sure to return the latest data
return useMemo(() => [state, setPersistentState], [state]);
}
export default usePersistentState;
export default useGlobalState;
import { useCallback, useEffect, useMemo, useReducer } from "react";
import useSharedState from "./useSharedState";
import useGlobalState from "./useGlobalState";
/**
* Hook that provides a shared and persistent reducer base state management
......@@ -9,7 +8,7 @@ import useSharedState from "./useSharedState";
* @param {*} initialState - initial state value (if not already stored)
*/
function useSharedReducer(key, reducer, initialState) {
const [sharedState, setSharedState] = useSharedState(key, initialState);
const [sharedState, setSharedState] = useGlobalState(key, initialState);
const [state, dispatch] = useReducer(reducer, sharedState);
......
import { useCallback, useEffect, useMemo, useState } from "react";
import usePersistentState, { getPersistedValue } from "./usePersistentState";
import useOnBeforeComponentMount from "./useOnBeforeComponentMount";
/**
* @type {Map<string, Set.<function>>}
*/
const listenersByKey = new Map();
/**
* Hook that provides a sharedState : when the setState is used in one component,
* All component using the state from that hook (identfied by a key) are updated too.
*
* @param {string} key - id for the state
* @param {*} valueIfNoPrevious - initial state value
*/
function useSharedState(key, valueIfNoPrevious) {
const [persistedState, setPersistedState] = usePersistentState(
key,
valueIfNoPrevious
);
// A bit of optimization in order no to save to the persisted state every time
const [state, setState] = useState(persistedState);
useEffect(() => {
return () => {
// Cleaning listeners list on unmount
if (listenersByKey.has(key)) {
listenersByKey.get(key).delete(setState);
}
};
}, [listenersByKey]);
useOnBeforeComponentMount(() => {
// Subscribe as a listener
if (!listenersByKey.has(key)) listenersByKey.set(key, new Set());
listenersByKey.get(key).add(setState);
// small check to make sure the value is coherent
const valueInStore = getPersistedValue(key);
if (valueInStore !== state) setState(valueInStore);
});
const setStateOut = useCallback(
v => {
// update the persisted state
setPersistedState(v);
// Tell all the listeners to update
listenersByKey.get(key).forEach(f => {
f(v);
});
},
[listenersByKey, setPersistedState]
);
return useMemo(() => [state, setStateOut], [state, setStateOut]);
}
export default useSharedState;
import { useMemo } from "react";
import { getLatestRead } from "../../utils/api/utils";
import useSharedState from "../useSharedState";
import CrudReducers from "../../utils/api/CrudReducers";
import useGlobalState from "../useGlobalState";
/**
* Function to create the mapStateToProps function for editor in a "generic way"
......@@ -13,7 +13,7 @@ import CrudReducers from "../../utils/api/CrudReducers";
function useEditorState(route) {
const subKey = `${route}-one`;
const [state] = useSharedState(`api-${subKey}`, CrudReducers.defaultOneState);
const [state] = useGlobalState(`api-${subKey}`, CrudReducers.defaultOneState);
let lastUpdateTimeInModel = null;
let hasPendingModeration = null;
......
import { useMemo } from "react";
import useSharedState from "../useSharedState";
import useGlobalState from "../useGlobalState";
export function useSelectedUniversities() {
return useSharedState("app-selected-universities", null);
return useGlobalState("app-selected-universities", null);
}
export function useSetSelectedUniversities() {
......
import arrayOfInstancesToMap from "../../utils/arrayOfInstancesToMap";
import { getLatestApiReadData } from "../../hooks/usePersistentState";
import { getLatestApiReadData } from "../../hooks/useGlobalState";
class CityService {
/**
......
import arrayOfInstancesToMap from "../../utils/arrayOfInstancesToMap";
import { getLatestApiReadData } from "../../hooks/usePersistentState";
import { getLatestApiReadData } from "../../hooks/useGlobalState";
class CountryService {
/**
......
import arrayOfInstancesToMap from "../../utils/arrayOfInstancesToMap";
import { getLatestApiReadData } from "../../hooks/usePersistentState";
import { getLatestApiReadData } from "../../hooks/useGlobalState";
class CurrencyService {
/**
......
import arrayOfInstancesToMap from "../../utils/arrayOfInstancesToMap";
import { getLatestApiReadData } from "../../hooks/usePersistentState";
import { getLatestApiReadData } from "../../hooks/useGlobalState";
class LanguageService {
/**
......
import CityService from "./CityService";
import CountryService from "./CountryService";
import arrayOfInstancesToMap from "../../utils/arrayOfInstancesToMap";
import { getLatestApiReadData } from "../../hooks/usePersistentState";
import { getLatestApiReadData } from "../../hooks/useGlobalState";
class UniversityService {
/**
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment