Skip to content

Commit

Permalink
Merge pull request #2924 from marmelab/typescript-migration-core
Browse files Browse the repository at this point in the history
[RFR] Migrate Core Components to TypeScript
  • Loading branch information
fzaninotto authored Mar 1, 2019
2 parents 35a65f9 + 46d5f64 commit dc3935e
Show file tree
Hide file tree
Showing 26 changed files with 413 additions and 249 deletions.
3 changes: 3 additions & 0 deletions packages/ra-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
],
"main": "lib/index",
"module": "esm/index.js",
"types": "esm/index.d.ts",
"sideEffects": false,
"authors": [
"François Zaninotto",
Expand All @@ -31,6 +32,8 @@
"@types/react-router": "^4.4.1",
"@types/recompose": "^0.27.0",
"@types/redux-form": "^7.5.2",
"@types/react-router": "^4.4.4",
"@types/react-router-dom": "^4.3.1",
"cross-env": "^5.2.0",
"enzyme": "~3.7.0",
"enzyme-adapter-react-16": "~1.6.0",
Expand Down
77 changes: 47 additions & 30 deletions packages/ra-core/src/CoreAdmin.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
import React, {
createElement,
Component,
ReactNode,
ComponentType,
} from 'react';
import React, { createElement, Component, ComponentType } from 'react';
import PropTypes from 'prop-types';
import { Provider } from 'react-redux';
import { History } from 'history';
Expand All @@ -15,37 +10,58 @@ import withContext from 'recompose/withContext';
import createAdminStore from './createAdminStore';
import TranslationProvider from './i18n/TranslationProvider';
import CoreAdminRouter from './CoreAdminRouter';
import { AuthProvider, I18nProvider, DataProvider } from './types';
import {
AuthProvider,
I18nProvider,
DataProvider,
TitleComponent,
LoginComponent,
LayoutComponent,
AdminChildren,
CatchAllComponent,
CustomRoutes,
DashboardComponent,
} from './types';

export type ChildrenFunction = () => ComponentType[];

interface Props {
appLayout: ComponentType;
authProvider: AuthProvider;
children: ReactNode | ChildrenFunction;
catchAll: ComponentType;
customSagas: any[];
customReducers: object;
customRoutes: any[];
dashboard: ComponentType;
export interface AdminProps {
appLayout: LayoutComponent;
authProvider?: AuthProvider;
children?: AdminChildren;
catchAll: CatchAllComponent;
customSagas?: any[];
customReducers?: object;
customRoutes?: CustomRoutes;
dashboard?: DashboardComponent;
dataProvider: DataProvider;
history: History;
i18nProvider: I18nProvider;
initialState: object;
i18nProvider?: I18nProvider;
initialState?: object;
loading: ComponentType;
locale: string;
loginPage: ComponentType | boolean;
logoutButton: ComponentType;
menu: ComponentType;
theme: object;
title: ReactNode;
locale?: string;
loginPage: LoginComponent | boolean;
logoutButton?: ComponentType;
menu?: ComponentType;
theme?: object;
title?: TitleComponent;
}

interface AdminContext {
authProvider: AuthProvider;
}

class CoreAdmin extends Component<Props> {
class CoreAdminBase extends Component<AdminProps> {
static contextTypes = {
store: PropTypes.object,
};

static defaultProps: Partial<AdminProps> = {
catchAll: () => null,
loading: () => null,
loginPage: false,
};

reduxIsAlreadyInitialized = false;
history = null;

Expand Down Expand Up @@ -96,12 +112,12 @@ React-admin requires a valid dataProvider function to work.`);
<TranslationProvider>
<ConnectedRouter history={this.history}>
<Switch>
{loginPage !== false ? (
{loginPage !== false && loginPage !== true ? (
<Route
exact
path="/login"
render={props =>
createElement(loginPage as ComponentType, {
createElement(loginPage, {
...props,
title,
theme,
Expand All @@ -118,7 +134,6 @@ React-admin requires a valid dataProvider function to work.`);
customRoutes={customRoutes}
dashboard={dashboard}
loading={loading}
loginPage={loginPage}
logout={logout}
menu={menu}
theme={theme}
Expand Down Expand Up @@ -167,9 +182,11 @@ React-admin requires a valid dataProvider function to work.`);
}
}

export default withContext(
const CoreAdmin = withContext<AdminContext, AdminProps>(
{
authProvider: PropTypes.func,
},
({ authProvider }) => ({ authProvider })
)(CoreAdmin);
)(CoreAdminBase) as ComponentType<AdminProps>;

export default CoreAdmin;
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import React, { Children, Component, cloneElement, createElement } from 'react';
import React, {
Children,
Component,
cloneElement,
createElement,
ComponentType,
CSSProperties,
} from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Route, Switch } from 'react-router-dom';
Expand All @@ -7,52 +14,95 @@ import getContext from 'recompose/getContext';

import { AUTH_GET_PERMISSIONS } from './auth/types';
import { isLoggedIn } from './reducer';
import { userLogout } from './actions/authActions';
import { userLogout as userLogoutAction } from './actions/authActions';
import RoutesWithLayout from './RoutesWithLayout';

const welcomeStyles = {
import {
Dispatch,
AuthProvider,
AdminChildren,
CustomRoutes,
CatchAllComponent,
LayoutComponent,
LayoutProps,
ResourceProps,
RenderResourcesFunction,
ResourceElement,
} from './types';

const welcomeStyles: CSSProperties = {
width: '50%',
margin: '40vh',
textAlign: 'center',
};

export class CoreAdminRouter extends Component {
state = { children: [] };
export interface AdminRouterProps extends LayoutProps {
appLayout: LayoutComponent;
catchAll: CatchAllComponent;
children?: AdminChildren;
customRoutes?: CustomRoutes;
loading: ComponentType;
}

interface EnhancedProps {
authProvider?: AuthProvider;
isLoggedIn?: boolean;
userLogout: Dispatch<typeof userLogoutAction>;
}

interface State {
children: ResourceElement[];
}

export class CoreAdminRouter extends Component<
AdminRouterProps & EnhancedProps,
State
> {
static defaultProps: Partial<AdminRouterProps> = {
customRoutes: [],
};

state: State = { children: [] };

componentWillMount() {
this.initializeResources(this.props);
}

initializeResources = nextProps => {
initializeResources = (nextProps: AdminRouterProps & EnhancedProps) => {
if (typeof nextProps.children === 'function') {
this.initializeResourcesAsync(nextProps);
}
};

initializeResourcesAsync = async props => {
initializeResourcesAsync = async (
props: AdminRouterProps & EnhancedProps
) => {
const { authProvider } = props;
try {
const permissions = await authProvider(AUTH_GET_PERMISSIONS);
const { children } = props;

const childrenFuncResult = children(permissions);
if (childrenFuncResult.then) {
childrenFuncResult.then(resolvedChildren => {
this.setState({
children: resolvedChildren
.filter(child => child)
.map(child => ({
...child,
props: {
...child.props,
key: child.props.name,
},
})),
});
});
const resolveChildren = props.children as RenderResourcesFunction;

const childrenFuncResult = resolveChildren(permissions);
if ((childrenFuncResult as Promise<ResourceElement[]>).then) {
(childrenFuncResult as Promise<ResourceElement[]>).then(
resolvedChildren => {
this.setState({
children: resolvedChildren
.filter(child => child)
.map(child => ({
...child,
props: {
...child.props,
key: child.props.name,
},
})),
});
}
);
} else {
this.setState({
children: childrenFuncResult.filter(child => child),
children: (childrenFuncResult as ResourceElement[]).filter(
child => child
),
});
}
} catch (error) {
Expand Down Expand Up @@ -122,20 +172,22 @@ export class CoreAdminRouter extends Component {
return <Route path="/" key="loading" component={loading} />;
}

let childrenToRender =
const childrenToRender =
typeof children === 'function' ? this.state.children : children;

return (
<div>
{// Render every resources children outside the React Router Switch
// as we need all of them and not just the one rendered
Children.map(childrenToRender, child =>
cloneElement(child, {
key: child.props.name,
// The context prop instructs the Resource component to not render anything
// but simply to register itself as a known resource
context: 'registration',
})
Children.map(
childrenToRender,
(child: React.ReactElement<ResourceProps>) =>
cloneElement(child, {
key: child.props.name,
// The context prop instructs the Resource component to not render anything
// but simply to register itself as a known resource
context: 'registration',
})
)}
<Switch>
{customRoutes
Expand All @@ -153,24 +205,26 @@ export class CoreAdminRouter extends Component {
<Route
path="/"
render={() =>
createElement(appLayout, {
children: (
<RoutesWithLayout
catchAll={catchAll}
children={childrenToRender} // eslint-disable-line react/no-children-prop
customRoutes={customRoutes.filter(
route => !route.props.noLayout
)}
dashboard={dashboard}
title={title}
/>
),
dashboard,
logout,
menu,
theme,
title,
})
createElement(
appLayout,
{
dashboard,
logout,
menu,
theme,
title,
},
<RoutesWithLayout
catchAll={catchAll}
customRoutes={customRoutes.filter(
route => !route.props.noLayout
)}
dashboard={dashboard}
title={title}
>
{childrenToRender}
</RoutesWithLayout>
)
}
/>
</Switch>
Expand All @@ -179,27 +233,6 @@ export class CoreAdminRouter extends Component {
}
}

const componentPropType = PropTypes.oneOfType([
PropTypes.func,
PropTypes.string,
]);

CoreAdminRouter.propTypes = {
appLayout: componentPropType,
authProvider: PropTypes.func,
catchAll: componentPropType,
children: PropTypes.oneOfType([PropTypes.func, PropTypes.node]),
customRoutes: PropTypes.array,
dashboard: componentPropType,
isLoggedIn: PropTypes.bool,
loading: componentPropType,
logout: PropTypes.node,
menu: componentPropType,
theme: PropTypes.object,
title: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
userLogout: PropTypes.func,
};

const mapStateToProps = state => ({
isLoggedIn: isLoggedIn(state),
});
Expand All @@ -210,6 +243,6 @@ export default compose(
}),
connect(
mapStateToProps,
{ userLogout }
{ userLogout: userLogoutAction }
)
)(CoreAdminRouter);
)(CoreAdminRouter) as ComponentType<AdminRouterProps>;
File renamed without changes.
Loading

0 comments on commit dc3935e

Please sign in to comment.