Commit 0223fb20 authored by Florent Chehab's avatar Florent Chehab Committed by Florent Chehab
Browse files

dropped(redux)

parent 1451f9ca
......@@ -10,7 +10,7 @@ import { makeStyles } from "@material-ui/styles";
import SameLine from "../common/SameLine";
import UserInfoForm from "./UserInfoForm";
import classNames from "../../utils/classNames";
import RequestParams from "../../redux/api/RequestParams";
import RequestParams from "../../utils/api/RequestParams";
import DeleteAccount from "./DeleteAccount";
import { CURRENT_USER_ID } from "../../config/user";
import withNetworkWrapper, {
......
import React from "react";
import TextField from "../edition/fields/TextField";
import BooleanField from "../edition/fields/BooleanField";
import { getLatestReadDataFromStore } from "../../redux/api/utils";
import FormLevelError from "../../utils/editionRelated/FormLevelError";
import FormInfo from "../../utils/editionRelated/FormInfo";
import { getLatestApiReadData } from "../../hooks/usePersistentState";
const userInfoFormLevelErrors = [
new FormLevelError(
["allow_sharing_personal_info"],
sharingAllowed => {
const userData = getLatestReadDataFromStore("userDataOne");
const userData = getLatestApiReadData("userData-one");
const { owner_level: userLevel } = userData;
return userLevel > 0 && !sharingAllowed;
},
......
......@@ -3,14 +3,12 @@ import "regenerator-runtime/runtime";
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import { BrowserRouter as Router } from "react-router-dom";
import { SnackbarProvider } from "notistack"; // provider to easily handle notifications across the app
import green from "@material-ui/core/colors/green";
import amber from "@material-ui/core/colors/amber";
import CssBaseline from "@material-ui/core/CssBaseline";
import { makeStyles } from "@material-ui/styles";
import store from "../redux/store";
import App from "../components/app/App";
import ThemeProvider from "../components/common/theme/ThemeProvider";
import SnackbarCloseButton from "../components/app/SnackbarCloseButton";
......@@ -71,18 +69,14 @@ function SubEntry() {
function MainReactEntry() {
return (
<Provider store={store}>
{/* <React.StrictMode> */}
<OfflineThemeProvider>
{/* We make sure to have at least one theme active, that's why there is an offline theme too */}
<ThemeProvider>
<Router ref={NavigationService.setComponent}>
<SubEntry />
</Router>
</ThemeProvider>
</OfflineThemeProvider>
{/* </React.StrictMode> */}
</Provider>
<OfflineThemeProvider>
{/* We make sure to have at least one theme active, that's why there is an offline theme too */}
<ThemeProvider>
<Router ref={NavigationService.setComponent}>
<SubEntry />
</Router>
</ThemeProvider>
</OfflineThemeProvider>
);
}
......
import { setDisplayName } from "recompose";
import React from "react";
import PropTypes from "prop-types";
import RequestParams from "../redux/api/RequestParams";
import useSingleApiData from "../hooks/useSingleApiData";
import RequestParams from "../utils/api/RequestParams";
import Loading from "../components/common/Loading";
import useSingleApiData from "../hooks/wrappers/useSingleApiData";
export function getApiPropTypes(...keys) {
const shape = {};
......@@ -23,7 +23,8 @@ export class NetWrapParam {
/**
* @param {string} routeName - Route from where to get the data
* @param {"all"|"one"} variant - Is it a "all" or a "one" (GET all or GET one object)
* @param {string} [propKey] - On what prop is the data mapped
* @param {string} [propKey] - On wha // todo better setup ?
t prop is the data mapped
* @param {RequestParams|function(props):RequestParams} [params] - RequestParams for the first request. If a function, takes as argument the props.
*/
constructor(
......@@ -53,10 +54,13 @@ export default function withNetworkWrapper(
LoadingComponent = Loading
) {
if (process.env.NODE_ENV !== "production") {
if (parameters.some(el => !(el instanceof NetWrapParam)))
if (parameters.some(el => !(el instanceof NetWrapParam))) {
// eslint-disable-next-line no-console
console.error(parameters);
throw new Error(
"Only NetWrapParam instances are accepted as parameter of withNetworkWrapper"
);
}
}
return Component =>
......
import { useDispatch } from "react-redux";
import { useCallback, useMemo } from "react";
import getActions from "../redux/api/getActions";
import RequestParams from "../redux/api/RequestParams";
/**
* Hook that can be used to create api instance
*
* @param {string} routes
* @returns {Function}
*/
function useCreateOne(route) {
const dispatch = useDispatch();
const createOne = useCallback(
(data, onSuccess) => {
dispatch(
getActions(route).create(
RequestParams.Builder.withData(data)
.withOnSuccessCallback(onSuccess)
.build()
)
);
},
[dispatch]
);
return useMemo(() => createOne, [createOne]);
}
export default useCreateOne;
import { useDispatch } from "react-redux";
import { useCallback, useMemo } from "react";
import getActions from "../redux/api/getActions";
import RequestParams from "../redux/api/RequestParams";
/**
* Hook that can be used to delete api instance
*
* @param {string} routes
* @returns {Function}
*/
function useDeleteOne(route) {
const dispatch = useDispatch();
const deleteOne = useCallback(
(id, onSuccess) => {
dispatch(
getActions(route).delete(
RequestParams.Builder.withId(id)
.withOnSuccessCallback(onSuccess)
.build()
)
);
},
[dispatch]
);
return useMemo(() => deleteOne, [deleteOne]);
}
export default useDeleteOne;
import React, { useCallback } from "react";
import { useDispatch, useSelector } from "react-redux";
import FullScreenDialogService from "../services/FullScreenDialogService";
import Editor from "../components/edition/Editor";
import getMapDispatchToPropsForEditor from "../utils/editionRelated/getMapDispatchToPropsForEditor";
import getMapStateToPropsForEditor from "../utils/editionRelated/getMapStateToPropsForEditor";
import useEditorDispatch from "./wrappers/useEditorDispatch";
import useEditorState from "./wrappers/useEditorState";
/**
* Hook that provides an easy access to an editor.
......@@ -18,16 +17,13 @@ function useEditor(formInfo, onClose = () => {}) {
const { route, license, Form, formLevelErrors } = formInfo;
const InternalEditor = () => {
const dispatch = useDispatch();
const { saveData, clearSaveError } = getMapDispatchToPropsForEditor(
route
)(dispatch);
const { saveData, clearSaveError } = useEditorDispatch(route);
const {
savingHasError,
lastUpdateTimeInModel,
hasPendingModeration
} = useSelector(getMapStateToPropsForEditor(route));
} = useEditorState(route);
return (
<Editor
......@@ -35,7 +31,7 @@ function useEditor(formInfo, onClose = () => {}) {
Form={Form}
formLevelErrors={formLevelErrors}
rawModelData={modelData}
// Redux related
// state api related
saveData={saveData}
clearSaveError={clearSaveError}
savingHasError={savingHasError}
......
import { useDispatch } from "react-redux";
import { useCallback, useMemo } from "react";
import getActions from "../redux/api/getActions";
/**
* Hook that can be used to invalidate api data from "all"
*
* @param {string} routes
* @returns {Function}
*/
function useInvalidateAll(...routes) {
const dispatch = useDispatch();
const invalidateData = useCallback(() => {
routes.forEach(route => {
dispatch(getActions(route).invalidateAll());
});
}, [dispatch]);
return useMemo(() => invalidateData, [invalidateData]);
}
export default useInvalidateAll;
import { useDispatch } from "react-redux";
import { useCallback, useMemo } from "react";
import getActions from "../redux/api/getActions";
/**
* Hook that can be used to invalidate api data from "one"
*
* @param {string} routes
* @returns {Function}
*/
function useInvalidateOne(...routes) {
const dispatch = useDispatch();
const invalidateData = useCallback(() => {
routes.forEach(route => {
dispatch(getActions(route).invalidateOne());
});
}, [dispatch]);
return useMemo(() => invalidateData, [invalidateData]);
}
export default useInvalidateOne;
import { useState } from "react";
import { useCallback, useState } from "react";
import { getLatestRead } from "../utils/api/utils";
const globalState = new Map();
export const globalState = new Map();
/**
* TODO
* @param {string} key - route / sing or plur
*/
export function getLatestApiReadData(key) {
return getLatestRead(globalState.get(`api-${key}`)).data; // TODO move helper functions elsewhere
}
/**
* Hook that stores the state in a persistent state
*
* @param {string} key
* @param {*} initialValue
* @returns {[*, Function]}
*/
function usePersistentState(key, initialValue) {
const [storedValue, setStoredValue] = useState(() => {
if (globalState.has(key)) return globalState.get(key);
return initialValue;
});
const setValue = value => {
const valueToStore = value instanceof Function ? value(storedValue) : value;
// Save state
setStoredValue(valueToStore);
globalState.set(key, valueToStore);
};
const setValue = useCallback(
value => {
const valueToStore =
value instanceof Function ? value(storedValue) : value;
// Save state
globalState.set(key, valueToStore);
setStoredValue(valueToStore);
},
[globalState]
);
return [storedValue, setValue];
}
......
import { useCallback, useEffect, useMemo, useReducer } from "react";
import useSharedState from "./useSharedState";
/**
* Hook that provides a shared and persistent reducer base state management
*
* @param {string} key - id for the state
* @param {function} reducer - Reducer to be used
* @param {*} initialState - initial state value (if not already stored)
*/
function useSharedReducer(key, reducer, initialState) {
const [savedState, setSavedState] = useSharedState(key, initialState);
const [state, dispatch] = useReducer(reducer, savedState);
useEffect(() => {
setSavedState(state);
}, [state, setSavedState]);
const dispatchOut = useCallback(action => {
// built-in support for action that take dispatch as argument
if (typeof action === "function") {
action(dispatchOut);
} else {
if (process.env.NODE_ENV !== "production") {
const { type } = action;
let backgroundColor;
if (type.includes("STARTED")) backgroundColor = "cyan";
else if (type.includes("SUCCEEDED")) backgroundColor = "green";
else if (type.includes("INVALIDATED")) backgroundColor = "orange";
else if (type.includes("FAILED")) backgroundColor = "red";
else backgroundColor = "black";
// eslint-disable-next-line no-console
console.log(
`%c ACTION %c ${action.type}`,
`background: ${backgroundColor}; color: white; font-weight: bold;`,
""
);
}
dispatch(action);
}
}, []);
// make sure to return the saved state to prevent bugs when directly accessing the saved data
return useMemo(() => [savedState, dispatchOut], [savedState, dispatchOut]);
}
export default useSharedReducer;
import { useCallback, useEffect, useState } from "react";
import usePersistentState from "./usePersistentState";
export 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(() => {
// Subscribe as a listener
if (!listenersByKey.has(key)) listenersByKey.set(key, []);
const listOfListeners = listenersByKey.get(key);
listOfListeners.push(setState);
listenersByKey.set(key, listOfListeners);
return () => {
// Cleaning listeners list on unmount
listenersByKey.set(
key,
listenersByKey.get(key).filter(el => el !== setState)
);
};
}, [listenersByKey]);
const setStateOut = useCallback(
v => {
// update the persisted state
setPersistedState(v);
// Tell all the listeners to update
listenersByKey.get(key).forEach(f => {
f(v);
});
},
[listenersByKey]
);
return [state, setStateOut];
}
export default useSharedState;
import { useCallback, useMemo } from "react";
import useSharedReducer from "../useSharedReducer";
import CrudReducers from "../../utils/api/CrudReducers";
import CrudActions from "../../utils/api/CrudActions";
import RequestParams from "../../utils/api/RequestParams";
// TODO optimize duplicate information read on start
/**
* TODO
*
* @param {string} route
* @param {"one"|"all"} variant
* @returns {[*, function, CrudActions]}
*/
export function useApi(route, variant) {
const [reducer, defaultState] = useMemo(() => {
const internal = new CrudReducers(route);
if (variant === "all")
return [internal.getForAll(), CrudReducers.defaultAllState];
return [internal.getForOne(), CrudReducers.defaultOneState];
}, []);
const [state, dispatch] = useSharedReducer(
`api-${route}-${variant}`,
reducer,
defaultState
);
const actions = useMemo(() => new CrudActions(route), []);
return [state, dispatch, actions];
}
/**
* TODO
* @param route
* @param variant
* @returns {}
*/
export function useApiRead(route, variant) {
const [data, dispatch, actions] = useApi(route, variant);
const action = useMemo(
() => (variant === "all" ? actions.readAll : actions.readOne),
[]
);
const read = useCallback(newParams => {
dispatch(action(newParams));
}, []);
return useMemo(() => [data, read], [data]);
}
/**
* TODO
* @param route
* @param variant
* @returns {Function}
*/
function _useApiInvalidate(route, variant) {
const [, dispatch, actions] = useApi(route, variant);
const action = useMemo(
() => (variant === "all" ? actions.invalidateAll : actions.invalidateOne),
[]
);
return useCallback(() => {
dispatch(action());
}, []);
}
export function useApiInvalidateOne(route) {
return _useApiInvalidate(route, "one");
}
export function useApiInvalidateAll(route) {
return _useApiInvalidate(route, "all");
}
/**
* TODO
* @param route
* @private
*/
function _useDeleteAction(route) {
const [, dispatch, actions] = useApi(route, "one");
return useCallback(params => dispatch(actions.delete(params)), []);
}
/**
* Hook that can be used to delete api instance
*
* @param {string} routes
* @returns {Function}
*/
export function useApiDelete(route) {
const performDelete = _useDeleteAction(route);
const invalidateOne = useApiInvalidateOne(route);
const invalidateAll = useApiInvalidateAll(route);
const deleteOne = useCallback((id, onSuccess = () => {}) => {
performDelete(
RequestParams.Builder.withId(id)
.withOnSuccessCallback(any => {
// auto invalidate data
invalidateAll();
invalidateOne();
// Call on success callback
onSuccess(any);
})
.build()
);
}, []);
return useMemo(() => deleteOne, [deleteOne]);
}
/**
* Hook that can be used to create api instance
*
* @param {string} route
* @returns {Function}
*/
export function useApiCreate(route) {
const [, dispatch, actions] = useApi(route, "one");
const invalidateOne = useApiInvalidateOne(route);
const invalidateAll = useApiInvalidateAll(route);
const createOne = useCallback(
(data, onSuccess = () => {}) => {
dispatch(
actions.create(
RequestParams.Builder.withData(data)
.withOnSuccessCallback(any => {
invalidateOne();
invalidateAll();
onSuccess(any);
})
.build()
)
);
},
[dispatch]
);
return useMemo(() => createOne, [createOne]);
}
// todo better setup ?
/**
* Hook that can be used to update an api instance
*
* @param {string} route
* @returns {Function}
*/
export function useApiUpdate(route) {
const [, dispatch, actions] = useApi(route, "one");
const invalidateOne = useApiInvalidateOne(route);
const invalidateAll = useApiInvalidateAll(route);
const updateOne = useCallback(
(id, data, onSuccess = () => {}) => {
dispatch(
actions.update(
RequestParams.Builder.withData(data)
.withId(id)
.withOnSuccessCallback(any => {
invalidateOne();
invalidateAll();
onSuccess(any);
})
.build()
)
);
},
[dispatch]
);
return useMemo(() => updateOne, [updateOne]);
}
import getActions from "../../redux/api/getActions";
import RequestParams from "../../redux/api/RequestParams";
import { useMemo } from "react";