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"; ...@@ -10,7 +10,7 @@ import MenuItem from "@material-ui/core/MenuItem";
import Chip from "@material-ui/core/Chip"; import Chip from "@material-ui/core/Chip";
import fuzzysort from "fuzzysort"; import fuzzysort from "fuzzysort";
import { makeStyles } from "@material-ui/styles"; import { makeStyles } from "@material-ui/styles";
import usePersistentState from "../../hooks/usePersistentState"; import useGlobalState from "../../hooks/useGlobalState";
const useStyles = makeStyles(theme => ({ const useStyles = makeStyles(theme => ({
root: { root: {
...@@ -122,7 +122,7 @@ function DownshiftMultiple({ ...@@ -122,7 +122,7 @@ function DownshiftMultiple({
return out; return out;
}, [optionsFromProps]); }, [optionsFromProps]);
const [cache, setCache] = usePersistentState(`app-downshift-${cacheId}`, { const [cache, setCache] = useGlobalState(`app-downshift-${cacheId}`, {
value: valueFromProps, value: valueFromProps,
inputValue: inputValueFromProps inputValue: inputValueFromProps
}); });
......
...@@ -9,7 +9,7 @@ import SelectField from "./SelectField"; ...@@ -9,7 +9,7 @@ import SelectField from "./SelectField";
import CountryService from "../../../services/data/CountryService"; import CountryService from "../../../services/data/CountryService";
import CurrencyService from "../../../services/data/CurrencyService"; import CurrencyService from "../../../services/data/CurrencyService";
import LanguageService from "../../../services/data/LanguageService"; import LanguageService from "../../../services/data/LanguageService";
import { getLatestApiReadData } from "../../../hooks/usePersistentState"; import { getLatestApiReadData } from "../../../hooks/useGlobalState";
export function TitleField() { export function TitleField() {
return <TextField required fieldMapping="title" label="Titre" />; return <TextField required fieldMapping="title" label="Titre" />;
......
...@@ -7,7 +7,7 @@ import Typography from "@material-ui/core/Typography"; ...@@ -7,7 +7,7 @@ import Typography from "@material-ui/core/Typography";
import uuid from "uuid/v4"; import uuid from "uuid/v4";
import { makeStyles } from "@material-ui/styles"; import { makeStyles } from "@material-ui/styles";
import DownshiftMultiple from "../common/DownshiftMultiple"; import DownshiftMultiple from "../common/DownshiftMultiple";
import usePersistentState from "../../hooks/usePersistentState"; import useGlobalState from "../../hooks/useGlobalState";
import FilterService from "../../services/FilterService"; import FilterService from "../../services/FilterService";
import FilterStatus from "./FilterStatus"; import FilterStatus from "./FilterStatus";
import { useSetSelectedUniversities } from "../../hooks/wrappers/useSelectedUniversities"; import { useSetSelectedUniversities } from "../../hooks/wrappers/useSelectedUniversities";
...@@ -43,13 +43,13 @@ function Filter() { ...@@ -43,13 +43,13 @@ function Filter() {
const classes = useStyles(); const classes = useStyles();
const saveSelectedUniversities = useSetSelectedUniversities(); const saveSelectedUniversities = useSetSelectedUniversities();
const [isOpened, setIsOpened] = usePersistentState("filter-open", false); const [isOpened, setIsOpened] = useGlobalState("filter-open", false);
const [countries, setCountries] = usePersistentState("filter-countries", []); const [countries, setCountries] = useGlobalState("filter-countries", []);
const [semesters, setSemesters] = usePersistentState( const [semesters, setSemesters] = useGlobalState(
"filter-semesters", "filter-semesters",
[...FilterService.defaultSemesters].reverse() [...FilterService.defaultSemesters].reverse()
); );
const [majorMinors, setMajorMinors] = usePersistentState( const [majorMinors, setMajorMinors] = useGlobalState(
"filter-major-minors", "filter-major-minors",
[] []
); );
......
...@@ -11,7 +11,7 @@ import green from "@material-ui/core/colors/green"; ...@@ -11,7 +11,7 @@ import green from "@material-ui/core/colors/green";
import makeStyles from "@material-ui/core/styles/makeStyles"; import makeStyles from "@material-ui/core/styles/makeStyles";
import useEditor from "../../../../hooks/useEditor"; import useEditor from "../../../../hooks/useEditor";
import FormInfo from "../../../../utils/editionRelated/FormInfo"; import FormInfo from "../../../../utils/editionRelated/FormInfo";
import { getLatestApiReadData } from "../../../../hooks/usePersistentState"; import { getLatestApiReadData } from "../../../../hooks/useGlobalState";
const useStyles = makeStyles(theme => ({ const useStyles = makeStyles(theme => ({
root: { root: {
......
...@@ -3,7 +3,7 @@ import TextField from "../edition/fields/TextField"; ...@@ -3,7 +3,7 @@ import TextField from "../edition/fields/TextField";
import BooleanField from "../edition/fields/BooleanField"; import BooleanField from "../edition/fields/BooleanField";
import FormLevelError from "../../utils/editionRelated/FormLevelError"; import FormLevelError from "../../utils/editionRelated/FormLevelError";
import FormInfo from "../../utils/editionRelated/FormInfo"; import FormInfo from "../../utils/editionRelated/FormInfo";
import { getLatestApiReadData } from "../../hooks/usePersistentState"; import { getLatestApiReadData } from "../../hooks/useGlobalState";
const userInfoFormLevelErrors = [ const userInfoFormLevelErrors = [
new FormLevelError( new FormLevelError(
......
import { useCallback, useMemo, useState } from "react"; import { useCallback, useMemo, useState } from "react";
import { getLatestRead } from "../utils/api/utils"; import { getLatestRead } from "../utils/api/utils";
import useOnBeforeComponentMount from "./useOnBeforeComponentMount"; import useOnBeforeComponentMount from "./useOnBeforeComponentMount";
import useOnComponentUnMount from "./useOnComponentUnMount";
/**
* @type {Map<string, any>}
*/
export const globalState = new Map(); 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") { if (process.env.NODE_ENV !== "production") {
window.globalRexDriState = globalState; window.globalRexDriState = globalState;
} }
/** /**
* Gets the latest data retrieved from the api for the key * @type {Map<string, Set<Function>>}
* @param {string} key - route / sing or plur
*/ */
export function getLatestApiReadData(key) { const globalStateListeners = new Map();
return getLatestRead(globalState.get(`api-${key}`)).data;
/**
* 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 * @param key
* @returns {any} * @param newValue
*/ */
export function getPersistedValue(key) { export function updateGlobalState(key, newValue) {
return globalState.get(key); 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) { ...@@ -33,7 +71,7 @@ export function getPersistedValue(key) {
* @param {*} initialValue * @param {*} initialValue
* @returns {[*, Function]} * @returns {[*, Function]}
*/ */
function usePersistentState(key, initialValue) { function useGlobalState(key, initialValue) {
// initializing the global state if needed // initializing the global state if needed
useOnBeforeComponentMount(() => { useOnBeforeComponentMount(() => {
if (!globalState.has(key)) globalState.set(key, initialValue); if (!globalState.has(key)) globalState.set(key, initialValue);
...@@ -41,20 +79,28 @@ function usePersistentState(key, initialValue) { ...@@ -41,20 +79,28 @@ function usePersistentState(key, initialValue) {
const [state, setState] = useState(() => getPersistedValue(key)); 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 setPersistentState = useCallback(value => {
const currentGlobalValue = getPersistedValue(key); const currentGlobalValue = getPersistedValue(key);
const valueToStore = const valueToStore =
value instanceof Function ? value(currentGlobalValue) : value; value instanceof Function ? value(currentGlobalValue) : value;
// Save global state updateGlobalState(key, valueToStore);
globalState.set(key, valueToStore);
// save local state
setState(valueToStore);
}, []); }, []);
// Make sure to return the latest data // Make sure to return the latest data
return useMemo(() => [state, setPersistentState], [state]); return useMemo(() => [state, setPersistentState], [state]);
} }
export default usePersistentState; export default useGlobalState;
import { useCallback, useEffect, useMemo, useReducer } from "react"; import useGlobalState from "./useGlobalState";
import useSharedState from "./useSharedState";
/** /**
* Hook that provides a shared and persistent reducer base state management * Hook that provides a shared and persistent reducer base state management
...@@ -9,7 +8,7 @@ import useSharedState from "./useSharedState"; ...@@ -9,7 +8,7 @@ import useSharedState from "./useSharedState";
* @param {*} initialState - initial state value (if not already stored) * @param {*} initialState - initial state value (if not already stored)
*/ */
function useSharedReducer(key, reducer, initialState) { function useSharedReducer(key, reducer, initialState) {
const [sharedState, setSharedState] = useSharedState(key, initialState); const [sharedState, setSharedState] = useGlobalState(key, initialState);
const [state, dispatch] = useReducer(reducer, sharedState); 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 { useMemo } from "react";
import { getLatestRead } from "../../utils/api/utils"; import { getLatestRead } from "../../utils/api/utils";
import useSharedState from "../useSharedState";
import CrudReducers from "../../utils/api/CrudReducers"; import CrudReducers from "../../utils/api/CrudReducers";
import useGlobalState from "../useGlobalState";
/** /**
* Function to create the mapStateToProps function for editor in a "generic way" * Function to create the mapStateToProps function for editor in a "generic way"
...@@ -13,7 +13,7 @@ import CrudReducers from "../../utils/api/CrudReducers"; ...@@ -13,7 +13,7 @@ import CrudReducers from "../../utils/api/CrudReducers";
function useEditorState(route) { function useEditorState(route) {
const subKey = `${route}-one`; const subKey = `${route}-one`;
const [state] = useSharedState(`api-${subKey}`, CrudReducers.defaultOneState); const [state] = useGlobalState(`api-${subKey}`, CrudReducers.defaultOneState);
let lastUpdateTimeInModel = null; let lastUpdateTimeInModel = null;
let hasPendingModeration = null; let hasPendingModeration = null;
......
import { useMemo } from "react"; import { useMemo } from "react";
import useSharedState from "../useSharedState"; import useGlobalState from "../useGlobalState";
export function useSelectedUniversities() { export function useSelectedUniversities() {
return useSharedState("app-selected-universities", null); return useGlobalState("app-selected-universities", null);
} }
export function useSetSelectedUniversities() { export function useSetSelectedUniversities() {
......
import arrayOfInstancesToMap from "../../utils/arrayOfInstancesToMap"; import arrayOfInstancesToMap from "../../utils/arrayOfInstancesToMap";
import { getLatestApiReadData } from "../../hooks/usePersistentState"; import { getLatestApiReadData } from "../../hooks/useGlobalState";
class CityService { class CityService {
/** /**
......
import arrayOfInstancesToMap from "../../utils/arrayOfInstancesToMap"; import arrayOfInstancesToMap from "../../utils/arrayOfInstancesToMap";
import { getLatestApiReadData } from "../../hooks/usePersistentState"; import { getLatestApiReadData } from "../../hooks/useGlobalState";
class CountryService { class CountryService {
/** /**
......
import arrayOfInstancesToMap from "../../utils/arrayOfInstancesToMap"; import arrayOfInstancesToMap from "../../utils/arrayOfInstancesToMap";
import { getLatestApiReadData } from "../../hooks/usePersistentState"; import { getLatestApiReadData } from "../../hooks/useGlobalState";
class CurrencyService { class CurrencyService {
/** /**
......
import arrayOfInstancesToMap from "../../utils/arrayOfInstancesToMap"; import arrayOfInstancesToMap from "../../utils/arrayOfInstancesToMap";
import { getLatestApiReadData } from "../../hooks/usePersistentState"; import { getLatestApiReadData } from "../../hooks/useGlobalState";
class LanguageService { class LanguageService {
/** /**
......
import CityService from "./CityService"; import CityService from "./CityService";
import CountryService from "./CountryService"; import CountryService from "./CountryService";
import arrayOfInstancesToMap from "../../utils/arrayOfInstancesToMap"; import arrayOfInstancesToMap from "../../utils/arrayOfInstancesToMap";
import { getLatestApiReadData } from "../../hooks/usePersistentState"; import { getLatestApiReadData } from "../../hooks/useGlobalState";
class UniversityService { 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