Repository: api-platform/admin
Pull request #224 (2019-09-19 17:14:41)

Major refactoring, and major config improvement
| Q             | A
| ------------- | ---
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | yes
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | n/a
| License       | MIT
| Doc PR        | https://github.com/api-platform/docs/pull/872

See https://github.com/api-platform/docs/pull/872 for the detailed documentation.

dunglas: Example added in the demo: https://github.com/api-platform/demo/pull/128

Patch:
diff --git a/src/AdminGuesser.js b/src/AdminGuesser.js
index 6a31740..95da5e8 100644
--- a/src/AdminGuesser.js
+++ b/src/AdminGuesser.js
@@ -3,10 +3,14 @@ import PropTypes from 'prop-types';
 import {Admin, Loading, Query, TranslationProvider} from 'react-admin';
 import {createMuiTheme} from '@material-ui/core/styles';
 import ResourceGuesser from './ResourceGuesser';
-import {existsAsChild} from './docsUtils';
 
-export const render = (
-  {blacklist = [], children, ...props},
+/**
+ * AdminGuesserComponent automatically renders an `<Admin>` component for resources exposed by a web API documented with Hydra, OpenAPI or any other format supported by `@api-platform/api-doc-parser`.
+ * If child components are passed (usually `<ResourceGuesser>` or `<Resource>` components, but it can be any other React component), they are rendered in the given order.
+ * If no children are passed, a `<ResourceGuesser>` component created for each resource type exposed by the API, in the order they are specified in the API documentation.
+ */
+export const AdminGuesserComponent = (
+  {children, includeDeprecated, ...props},
   {data, error, loading},
 ) => {
   if (loading) {
@@ -24,28 +28,26 @@ export const render = (
     return <div>Error while reading the API schema</div>;
   }
 
-  const childrenAsArray = React.Children.toArray(children);
-  data.resources
-    .filter(
-      resource =>
-        !resource.deprecated &&
-        !blacklist.includes(resource.name) &&
-        existsAsChild(children)(resource.name),
-    )
-    .forEach(resource =>
-      childrenAsArray.push(
-        <ResourceGuesser name={resource.name} key={resource.name} />,
-      ),
-    );
+  if (!children) {
+    const resources = includeDeprecated
+      ? data.resources
+      : data.resources.filter(r => !r.deprecated);
+    children = resources.map(r => (
+      <ResourceGuesser name={r.name} key={r.name} />
+    ));
+  }
 
-  return <Admin {...props}>{childrenAsArray}</Admin>;
+  return <Admin {...props}>{children}</Admin>;
 };
 
 const AdminGuesser = props => (
-  <Query type={'INTROSPECT'}>{state => render(props, state)}</Query>
+  <Query type={'INTROSPECT'}>
+    {state => AdminGuesserComponent(props, state)}
+  </Query>
 );
 
 AdminGuesser.defaultProps = {
+  includeDeprecated: false,
   theme: createMuiTheme({
     palette: {
       primary: {
@@ -60,9 +62,9 @@ AdminGuesser.defaultProps = {
 };
 
 AdminGuesser.propTypes = {
-  blacklist: PropTypes.array,
   children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
   theme: PropTypes.object,
+  includeDeprecated: PropTypes.bool,
 };
 
 export default AdminGuesser;
diff --git a/src/CreateGuesser.js b/src/CreateGuesser.js
index 33407b0..930deb4 100644
--- a/src/CreateGuesser.js
+++ b/src/CreateGuesser.js
@@ -1,32 +1,35 @@
-import React, {Children} from 'react';
+import React from 'react';
 import PropTypes from 'prop-types';
 import {Create, SimpleForm} from 'react-admin';
 import InputGuesser from './InputGuesser';
-import WithReactAdminQuery from './withReactAdminQuery';
+import IntrospectQuery from './IntrospectQuery';
 
-const CreateGuesserComponent = ({...props}) => {
-  const {fields, resourceSchema, ...filteredProps} = props;
-  const children = Children.toArray(props.children);
-
-  fields.map(field =>
-    children.push(<InputGuesser key={field.name} source={field.name} />),
-  );
+export const CreateGuesserComponent = ({
+  fields,
+  resourceSchema,
+  children,
+  ...props
+}) => {
+  if (!children) {
+    children = fields.map(field => (
+      <InputGuesser key={field.name} source={field.name} />
+    ));
+  }
 
   return (
-    <Create {...filteredProps}>
+    <Create {...props}>
       <SimpleForm>{children}</SimpleForm>
     </Create>
   );
 };
 
 const CreateGuesser = props => (
-  <WithReactAdminQuery component={CreateGuesserComponent} {...props} />
+  <IntrospectQuery component={CreateGuesserComponent} {...props} />
 );
 
-export default CreateGuesser;
-
 CreateGuesser.propTypes = {
-  children: PropTypes.object,
+  children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
   resource: PropTypes.string.isRequired,
-  inputs: PropTypes.array,
 };
+
+export default CreateGuesser;
diff --git a/src/EditGuesser.js b/src/EditGuesser.js
index 4b496d4..1d75422 100644
--- a/src/EditGuesser.js
+++ b/src/EditGuesser.js
@@ -2,32 +2,37 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import {Edit, SimpleForm} from 'react-admin';
 import InputGuesser from './InputGuesser';
-import WithReactAdminQuery from './withReactAdminQuery';
+import IntrospectQuery from './IntrospectQuery';
 
 // useful for testing (we don't need Query)
-const EditGuesserComponent = props => {
-  const {children, fields, resourceSchema, ...filteredProps} = props;
+export const EditGuesserComponent = ({
+  children,
+  fields,
+  resourceSchema,
+  ...props
+}) => {
+  if (!children) {
+    children = fields.map(field => (
+      <InputGuesser key={field.name} source={field.name} />
+    ));
+  }
 
   return (
-    <Edit {...filteredProps} undoable={false}>
-      <SimpleForm>
-        {children}
-        {fields.map(field => (
-          <InputGuesser key={field.name} source={field.name} />
-        ))}
-      </SimpleForm>
+    // FIXME: enabling the undoable feature breaks API Platform Admin...
+    // see https://github.com/api-platform/admin/pull/217
+    <Edit {...props} undoable={false}>
+      <SimpleForm>{children}</SimpleForm>
     </Edit>
   );
 };
 
 const EditGuesser = props => (
-  <WithReactAdminQuery component={EditGuesserComponent} {...props} />
+  <IntrospectQuery component={EditGuesserComponent} {...props} />
 );
 
-export default EditGuesser;
-
 EditGuesser.propTypes = {
-  children: PropTypes.object,
+  children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
   resource: PropTypes.string.isRequired,
-  inputs: PropTypes.array,
 };
+
+export default EditGuesser;
diff --git a/src/FieldGuesser.js b/src/FieldGuesser.js
index d5e97d0..8d84cd6 100644
--- a/src/FieldGuesser.js
+++ b/src/FieldGuesser.js
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, {Fragment} from 'react';
 import PropTypes from 'prop-types';
 import {
   BooleanField,
@@ -11,26 +11,22 @@ import {
   SingleFieldList,
   TextField,
   UrlField,
-  Query,
-  Loading,
 } from 'react-admin';
-import {
-  getReferenceNameField,
-  getResource,
-  getResourceField,
-} from './docsUtils';
+import {getReferenceNameField} from './docsUtils';
+import IntrospectQuery from './IntrospectQuery';
 
-const isFieldSortable = (field, resource) => {
+const isFieldSortable = (field, resourceSchema) => {
   return (
-    resource.parameters.filter(parameter => parameter.variable === field.name)
-      .length > 0 &&
-    resource.parameters.filter(
+    resourceSchema.parameters.filter(
+      parameter => parameter.variable === field.name,
+    ).length > 0 &&
+    resourceSchema.parameters.filter(
       parameter => parameter.variable === `order[${field.name}]`,
     ).length > 0
   );
 };
 
-const renderField = (resource, field, props) => {
+const renderField = (field, props) => {
   if (null !== field.reference) {
     if (1 === field.maxCardinality) {
       return (
@@ -78,61 +74,33 @@ const renderField = (resource, field, props) => {
   }
 };
 
-const FieldGuesser = props => {
-  const {source: fieldName, resource: resourceName} = props;
+export const FieldGuesserComponent = ({fields, resourceSchema, ...props}) => {
+  const field = fields.find(f => f.name === props.source);
 
-  return (
-    <Query type="INTROSPECT">
-      {({data: api, loading, error}) => {
-        if (loading) {
-          return <Loading />;
-        }
-
-        if (error) {
-          console.error(error);
-          return <div>Error while reading the API schema</div>;
-        }
-
-        const resource = getResource(api.resources, resourceName);
-
-        if (!resource || !resource.fields) {
-          console.error(
-            `Resource ${resourceName} not present inside api description`,
-          );
-          return (
-            <div>
-              Resource ${resourceName} not present inside api description
-            </div>
-          );
-        }
-
-        const field = getResourceField(resource, fieldName);
-
-        if (!field) {
-          console.error(
-            `Field ${props.source} not present inside the api description for the resource ${props.resource}`,
-          );
-
-          return (
-            <div>
-              Field ${fieldName} not present inside the api description for the
-              resource ${resourceName}
-            </div>
-          );
-        }
-
-        return renderField(resource, field, {
-          sortable: isFieldSortable(field, resource),
-          ...props,
-        });
-      }}
-    </Query>
-  );
+  if (!field) {
+    console.error(
+      `Field "${props.source}" not present inside the api description for the resource "${props.resource}"`,
+    );
+
+    return <Fragment />;
+  }
+
+  return renderField(field, {
+    sortable: isFieldSortable(field, resourceSchema),
+    ...props,
+  });
 };
 
-export default FieldGuesser;
+const FieldGuesser = props => (
+  <IntrospectQuery
+    component={FieldGuesserComponent}
+    includeDeprecated={true}
+    {...props}
+  />
+);
 
 FieldGuesser.propTypes = {
   source: PropTypes.string.isRequired,
-  resource: PropTypes.string,
 };
+
+export default FieldGuesser;
diff --git a/src/FilterGuesser.js b/src/FilterGuesser.js
index 6310b01..e5f1333 100644
--- a/src/FilterGuesser.js
+++ b/src/FilterGuesser.js
@@ -1,10 +1,10 @@
 import React from 'react';
 import {Filter} from 'react-admin';
 import InputGuesser from './InputGuesser';
-import WithReactAdminQuery from './withReactAdminQuery';
+import IntrospectQuery from './IntrospectQuery';
 import {getFiltersParametersFromResourceSchema} from './docsUtils';
 
-class FilterGuesserComponent extends React.Component {
+export class FilterGuesserComponent extends React.Component {
   constructor(props) {
     super(props);
     this.state = {
@@ -48,7 +48,7 @@ class FilterGuesserComponent extends React.Component {
 }
 
 const FilterGuesser = props => (
-  <WithReactAdminQuery component={FilterGuesserComponent} {...props} />
+  <IntrospectQuery component={FilterGuesserComponent} {...props} />
 );
 
 export default FilterGuesser;
diff --git a/src/InputGuesser.js b/src/InputGuesser.js
index e908c31..e196511 100644
--- a/src/InputGuesser.js
+++ b/src/InputGuesser.js
@@ -1,9 +1,9 @@
-import React from 'react';
+import React, {Fragment} from 'react';
+import PropTypes from 'prop-types';
 import {
   ArrayInput,
   BooleanInput,
   DateInput,
-  Loading,
   NumberInput,
   ReferenceArrayInput,
   ReferenceInput,
@@ -12,176 +12,143 @@ import {
   SelectInput,
   SimpleFormIterator,
   TextInput,
-  Query,
 } from 'react-admin';
 import {getReferenceNameField} from './docsUtils';
+import IntrospectQuery from './IntrospectQuery';
+
+export const InputGuesserComponent = ({fields, resourceSchema, ...props}) => {
+  const field = fields.find(({name}) => name === props.source);
+  if (!field) {
+    console.error(
+      `Field ${props.source} not present inside the api description for the resource ${props.resource}`,
+    );
+
+    return <Fragment />;
+  }
+
+  const validate =
+    !props.validate && field.required ? [required()] : props.validate;
+
+  if (null !== field.reference) {
+    if (1 === field.maxCardinality) {
+      return (
+        <ReferenceInput
+          key={field.name}
+          label={field.name}
+          reference={field.reference.name}
+          source={field.name}
+          validate={validate}
+          {...props}
+          allowEmpty>
+          <SelectInput optionText={getReferenceNameField(field.reference)} />
+        </ReferenceInput>
+      );
+    }
+
+    return (
+      <ReferenceArrayInput
+        key={field.name}
+        label={field.name}
+        reference={field.reference.name}
+        source={field.name}
+        validate={validate}
+        {...props}
+        allowEmpty>
+        <SelectArrayInput optionText={getReferenceNameField(field.reference)} />
+      </ReferenceArrayInput>
+    );
+  }
+
+  if ('http://schema.org/identifier' === field.id) {
+    const prefix = `/${props.resource}/`;
+
+    props.format = value => {
+      return 0 === value.indexOf(prefix) ? value.substr(prefix.length) : value;
+    };
+
+    props.parse = value => {
+      return -1 !== value.indexOf(prefix) ? prefix + value : value;
+    };
+  }
+
+  switch (field.range) {
+    case 'http://www.w3.org/2001/XMLSchema#array':
+      return (
+        <ArrayInput
+          key={field.name}
+          source={field.name}
+          validate={validate}
+          {...props}>
+          <SimpleFormIterator>
+            <TextInput />
+          </SimpleFormIterator>
+        </ArrayInput>
+      );
 
-const InputGuesser = props => (
-  <Query type="INTROSPECT" resource={props.ressource}>
-    {({data, loading, error}) => {
-      if (loading) {
-        return <Loading />;
-      }
-
-      if (error) {
-        console.error(error);
-        return <div>Error while reading the API schema</div>;
-      }
-
-      const resourceSchema = data.resources.find(
-        r => r.name === props.resource,
+    case 'http://www.w3.org/2001/XMLSchema#integer':
+      return (
+        <NumberInput
+          key={field.name}
+          source={field.name}
+          validate={validate}
+          {...props}
+        />
+      );
+
+    case 'http://www.w3.org/2001/XMLSchema#decimal':
+      return (
+        <NumberInput
+          key={field.name}
+          source={field.name}
+          step="0.1"
+          validate={validate}
+          {...props}
+        />
+      );
+
+    case 'http://www.w3.org/2001/XMLSchema#boolean':
+      return (
+        <BooleanInput
+          key={field.name}
+          source={field.name}
+          validate={validate}
+          {...props}
+        />
+      );
+
+    case 'http://www.w3.org/2001/XMLSchema#date':
+    case 'http://www.w3.org/2001/XMLSchema#dateTime':
+      return (
+        <DateInput
+          key={field.name}
+          source={field.name}
+          validate={validate}
+          {...props}
+        />
       );
 
-      if (
-        !resourceSchema ||
-        !resourceSchema.fields ||
-        !resourceSchema.fields.length
-      ) {
-        console.error(
-          `Resource ${props.resource} not present inside api description`,
-        );
-        return (
-          <div>
-            Resource ${props.resource} not present inside api description
-          </div>
-        );
-      }
-
-      const field = resourceSchema.fields.find(
-        ({name}) => name === props.source,
+    default:
+      return (
+        <TextInput
+          key={field.name}
+          source={field.name}
+          validate={validate}
+          {...props}
+        />
       );
-      if (!field) {
-        console.error(
-          `Field ${props.source} not present inside the api description for the resource ${props.resource}`,
-        );
-
-        return (
-          <div>
-            Field ${props.source} not present inside the api description for the
-            resource ${props.resource}
-          </div>
-        );
-      }
-
-      const validate =
-        !props.validate && field.required ? [required()] : props.validate;
-
-      if (null !== field.reference) {
-        if (1 === field.maxCardinality) {
-          return (
-            <ReferenceInput
-              key={field.name}
-              label={field.name}
-              reference={field.reference.name}
-              source={field.name}
-              validate={validate}
-              {...props}
-              allowEmpty>
-              <SelectInput
-                optionText={getReferenceNameField(field.reference)}
-              />
-            </ReferenceInput>
-          );
-        }
-
-        return (
-          <ReferenceArrayInput
-            key={field.name}
-            label={field.name}
-            reference={field.reference.name}
-            source={field.name}
-            validate={validate}
-            {...props}
-            allowEmpty>
-            <SelectArrayInput
-              optionText={getReferenceNameField(field.reference)}
-            />
-          </ReferenceArrayInput>
-        );
-      }
-
-      if ('http://schema.org/identifier' === field.id) {
-        const prefix = `/${props.resource}/`;
-
-        props.format = value => {
-          return 0 === value.indexOf(prefix)
-            ? value.substr(prefix.length)
-            : value;
-        };
-
-        props.parse = value => {
-          return -1 !== value.indexOf(prefix) ? prefix + value : value;
-        };
-      }
-
-      switch (field.range) {
-        case 'http://www.w3.org/2001/XMLSchema#array':
-          return (
-            <ArrayInput
-              key={field.name}
-              source={field.name}
-              validate={validate}
-              {...props}>
-              <SimpleFormIterator>
-                <TextInput />
-              </SimpleFormIterator>
-            </ArrayInput>
-          );
-
-        case 'http://www.w3.org/2001/XMLSchema#integer':
-          return (
-            <NumberInput
-              key={field.name}
-              source={field.name}
-              validate={validate}
-              {...props}
-            />
-          );
-
-        case 'http://www.w3.org/2001/XMLSchema#decimal':
-          return (
-            <NumberInput
-              key={field.name}
-              source={field.name}
-              step="0.1"
-              validate={validate}
-              {...props}
-            />
-          );
-
-        case 'http://www.w3.org/2001/XMLSchema#boolean':
-          return (
-            <BooleanInput
-              key={field.name}
-              source={field.name}
-              validate={validate}
-              {...props}
-            />
-          );
-
-        case 'http://www.w3.org/2001/XMLSchema#date':
-        case 'http://www.w3.org/2001/XMLSchema#dateTime':
-          return (
-            <DateInput
-              key={field.name}
-              source={field.name}
-              validate={validate}
-              {...props}
-            />
-          );
-
-        default:
-          return (
-            <TextInput
-              key={field.name}
-              source={field.name}
-              validate={validate}
-              {...props}
-            />
-          );
-      }
-    }}
-  </Query>
+  }
+};
+
+const InputGuesser = props => (
+  <IntrospectQuery
+    component={InputGuesserComponent}
+    includeDeprecated={true}
+    {...props}
+  />
 );
 
+InputGuesser.propTypes = {
+  source: PropTypes.string.isRequired,
+};
+
 export default InputGuesser;
diff --git a/src/withReactAdminQuery.js b/src/IntrospectQuery.js
similarity index 58%
rename from src/withReactAdminQuery.js
rename to src/IntrospectQuery.js
index d7cb451..5e071c9 100644
--- a/src/withReactAdminQuery.js
+++ b/src/IntrospectQuery.js
@@ -1,12 +1,20 @@
 import React from 'react';
-import {Query, Loading} from 'react-admin';
-import {existsAsChild} from './docsUtils';
+import PropTypes from 'prop-types';
+import {Query, Loading, TranslationProvider} from 'react-admin';
 
-const withReactAdminQuery = ({component: Component, ...props}) => (
+const IntrospectQuery = ({
+  component: Component,
+  includeDeprecated,
+  ...props
+}) => (
   <Query type="INTROSPECT" resource={props.ressource}>
     {({data, loading, error}) => {
       if (loading) {
-        return <Loading />;
+        return (
+          <TranslationProvider>
+            <Loading />
+          </TranslationProvider>
+        );
       }
 
       if (error) {
@@ -33,9 +41,9 @@ const withReactAdminQuery = ({component: Component, ...props}) => (
         );
       }
 
-      const fields = resourceSchema.fields
-        .filter(({deprecated}) => !deprecated)
-        .filter(existsAsChild(props.children));
+      const fields = includeDeprecated
+        ? resourceSchema.fields
+        : resourceSchema.fields.filter(({deprecated}) => !deprecated);
 
       return (
         <Component
@@ -50,4 +58,14 @@ const withReactAdminQuery = ({component: Component, ...props}) => (
   </Query>
 );
 
-export default withReactAdminQuery;
+IntrospectQuery.defaultProps = {
+  includeDeprecated: false,
+};
+
+IntrospectQuery.propTypes = {
+  component: PropTypes.elementType,
+  resource: PropTypes.string,
+  includeDeprecated: PropTypes.bool,
+};
+
+export default IntrospectQuery;
diff --git a/src/ListGuesser.js b/src/ListGuesser.js
index 3a71825..c2c6ed6 100644
--- a/src/ListGuesser.js
+++ b/src/ListGuesser.js
@@ -1,12 +1,12 @@
-import React, {Children} from 'react';
+import React from 'react';
 import PropTypes from 'prop-types';
 import {Datagrid, List, EditButton, ShowButton} from 'react-admin';
 import {getOrderParametersFromResourceSchema} from './docsUtils';
 import FieldGuesser from './FieldGuesser';
 import FilterGuesser from './FilterGuesser';
-import WithReactAdminQuery from './withReactAdminQuery';
+import IntrospectQuery from './IntrospectQuery';
 
-class ListGuesserComponent extends React.Component {
+export class ListGuesserComponent extends React.Component {
   constructor(props) {
     super(props);
 
@@ -33,18 +33,21 @@ class ListGuesserComponent extends React.Component {
 
   render() {
     const {resourceSchema, fields, ...props} = this.props;
-    const children = Children.toArray(props.children);
+
+    if (!props.children) {
+      props.children = fields.map(field => (
+        <FieldGuesser
+          key={field.name}
+          source={field.name}
+          sortable={this.state.orderParameters.includes(field.name)}
+        />
+      ));
+    }
+
     return (
       <List {...props}>
         <Datagrid>
-          {children}
-          {fields.map(field => (
-            <FieldGuesser
-              key={field.name}
-              source={field.name}
-              sortable={this.state.orderParameters.includes(field.name)}
-            />
-          ))}
+          {props.children}
           {props.hasShow && <ShowButton />}
           {props.hasEdit && <EditButton />}
         </Datagrid>
@@ -54,18 +57,17 @@ class ListGuesserComponent extends React.Component {
 }
 
 const ListGuesser = props => (
-  <WithReactAdminQuery component={ListGuesserComponent} {...props} />
+  <IntrospectQuery component={ListGuesserComponent} {...props} />
 );
 
-export default ListGuesser;
-
 ListGuesser.propTypes = {
-  children: PropTypes.object,
+  children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
   resource: PropTypes.string.isRequired,
-  fields: PropTypes.array,
   filters: PropTypes.object.isRequired,
 };
 
 ListGuesser.defaultProps = {
   filters: <FilterGuesser />,
 };
+
+export default ListGuesser;
diff --git a/src/ShowGuesser.js b/src/ShowGuesser.js
index 7108eb4..f75ed7d 100644
--- a/src/ShowGuesser.js
+++ b/src/ShowGuesser.js
@@ -2,32 +2,35 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import {Show, SimpleShowLayout} from 'react-admin';
 import FieldGuesser from './FieldGuesser';
-import WithReactAdminQuery from './withReactAdminQuery';
+import IntrospectQuery from './IntrospectQuery';
 
 // useful for testing (we don't need Query)
-export const ShowGuesserComponent = props => {
-  const {children, fields} = props;
+export const ShowGuesserComponent = ({
+  fields,
+  children,
+  resourceSchema,
+  ...props
+}) => {
+  if (!children) {
+    children = fields.map(field => (
+      <FieldGuesser key={field.name} source={field.name} addLabel={true} />
+    ));
+  }
 
   return (
     <Show {...props}>
-      <SimpleShowLayout>
-        {children}
-        {fields.map(field => (
-          <FieldGuesser key={field.name} source={field.name} addLabel={true} />
-        ))}
-      </SimpleShowLayout>
+      <SimpleShowLayout>{children}</SimpleShowLayout>
     </Show>
   );
 };
 
 const ShowGuesser = props => (
-  <WithReactAdminQuery component={ShowGuesserComponent} {...props} />
+  <IntrospectQuery component={ShowGuesserComponent} {...props} />
 );
 
-export default ShowGuesser;
-
 ShowGuesser.propTypes = {
-  children: PropTypes.object,
+  children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
   resource: PropTypes.string.isRequired,
-  fields: PropTypes.array,
 };
+
+export default ShowGuesser;
diff --git a/src/docsUtils/index.js b/src/docsUtils/index.js
index e590eb0..0359211 100644
--- a/src/docsUtils/index.js
+++ b/src/docsUtils/index.js
@@ -1,95 +1,3 @@
-import {Children} from 'react';
-
-export function getResource(resources, resourceName) {
-  return resources.find(({name}) => resourceName === name);
-}
-
-export function getResourceField(resource, resourceFieldName) {
-  return resource.fields.find(({name}) => resourceFieldName === name);
-}
-
-export function replaceInput(resource, resourceFieldName, replaceInput) {
-  const resourceField = getResourceField(resource, resourceFieldName);
-
-  if (undefined === resourceField) return;
-
-  resourceField.input = replaceInput;
-}
-
-export function replaceField(resource, resourceFieldName, replaceField) {
-  const resourceField = getResourceField(resource, resourceFieldName);
-
-  if (undefined === resourceField) return;
-
-  resourceField.field = replaceField;
-}
-
-export function replaceResource(resources, replaceResource) {
-  const resource = getResource(resources, replaceResource.name);
-
-  if (undefined !== replaceResource.listFields) {
-    resource.listFields = replaceResource.listFields;
-  }
-
-  if (undefined !== replaceResource.showFields) {
-    resource.showFields = replaceResource.showFields;
-  }
-
-  if (undefined !== replaceResource.editFields) {
-    resource.editFields = replaceResource.editFields;
-  }
-
-  if (undefined !== replaceResource.createFields) {
-    resource.createFields = replaceResource.createFields;
-  }
-
-  if (undefined !== replaceResource.fields) {
-    replaceResource.fields.forEach(resourceField => {
-      if (resourceField.field) {
-        replaceField(resource, resourceField.name, resourceField.field);
-      }
-
-      if (resourceField.input) {
-        replaceInput(resource, resourceField.name, resourceField.input);
-      }
-    });
-  }
-
-  if (undefined !== replaceResource.list) {
-    resource.list = replaceResource.list;
-  }
-
-  if (undefined !== replaceResource.show) {
-    resource.show = replaceResource.show;
-  }
-
-  if (undefined !== replaceResource.create) {
-    resource.create = replaceResource.create;
-  }
-
-  if (undefined !== replaceResource.edit) {
-    resource.edit = replaceResource.edit;
-  }
-
-  if (undefined !== replaceResource.icon) {
-    resource.icon = replaceResource.icon;
-  }
-}
-
-export function replaceResources(resources, replaceResources) {
-  replaceResources.forEach(resource => {
-    replaceResource(resources, resource);
-  });
-}
-
-export function existsAsChild(children) {
-  const childrenNames = new Set(
-    Children.map(children, child => child.props.name),
-  );
-
-  return ({name}) => !childrenNames.has(name);
-}
-
 export function getReferenceNameField(reference) {
   const field = reference.fields.find(
     field => 'http://schema.org/name' === field.id,
diff --git a/src/hydra/HydraAdmin.js b/src/hydra/HydraAdmin.js
index 91716cd..dea3310 100644
--- a/src/hydra/HydraAdmin.js
+++ b/src/hydra/HydraAdmin.js
@@ -5,7 +5,7 @@ import {createHashHistory} from 'history';
 import withContext from 'recompose/withContext';
 import {createAdminStore} from 'react-admin';
 
-import dataProviderFactory from './hydraClient';
+import dataProviderFactory from './dataProvider';
 import AdminGuesser from '../AdminGuesser';
 
 const history = createHashHistory();


Test patch:
diff --git a/src/AdminGuesser.test.js b/src/AdminGuesser.test.js
index fe13732..9d7ffe2 100644
--- a/src/AdminGuesser.test.js
+++ b/src/AdminGuesser.test.js
@@ -2,7 +2,7 @@ import React from 'react';
 import ShallowRenderer from 'react-test-renderer/shallow';
 import {createStore} from 'redux';
 import {Provider} from 'react-redux';
-import {render} from './AdminGuesser';
+import {AdminGuesserComponent} from './AdminGuesser';
 import {resources} from './__fixtures__';
 
 describe('<AdminGuesser />', () => {
@@ -12,7 +12,10 @@ describe('<AdminGuesser />', () => {
   test('renders errors', () => {
     const tree = renderer.render(
       <Provider store={store}>
-        {render({}, {error: 'Failed to fetch documentation', loading: false})}
+        {AdminGuesserComponent(
+          {},
+          {error: 'Failed to fetch documentation', loading: false},
+        )}
       </Provider>,
     );
 
@@ -21,7 +24,9 @@ describe('<AdminGuesser />', () => {
 
   test('renders loading', () => {
     const tree = renderer.render(
-      <Provider store={store}>{render({}, {loading: true})}</Provider>,
+      <Provider store={store}>
+        {AdminGuesserComponent({}, {loading: true})}
+      </Provider>,
     );
 
     expect(tree).toMatchSnapshot();
@@ -30,7 +35,7 @@ describe('<AdminGuesser />', () => {
   test('renders without custom resources', () => {
     const tree = renderer.render(
       <Provider store={store}>
-        {render({}, {data: {resources}, loading: false})}
+        {AdminGuesserComponent({}, {data: {resources}, loading: false})}
       </Provider>,
     );
 
diff --git a/src/ShowGuesser.test.js b/src/ShowGuesser.test.js
index 929969d..64c19a2 100644
--- a/src/ShowGuesser.test.js
+++ b/src/ShowGuesser.test.js
@@ -7,6 +7,27 @@ import {API_INPUT_DATA, API_FIELDS_DATA} from './common-test/common-data-test';
 import {ShowGuesserComponent} from './ShowGuesser';
 
 describe('<ShowGuesser />', () => {
+  test('renders with no children', () => {
+    const wrapper = shallow(
+      <ShowGuesserComponent
+        resource="user"
+        data={API_INPUT_DATA}
+        fields={API_FIELDS_DATA}
+        id="ShowComponentId"
+      />,
+    );
+
+    expect(wrapper).toContainReact(
+      <FieldGuesser source="fieldA" addLabel={true} />,
+    );
+    expect(wrapper).toContainReact(
+      <FieldGuesser source="fieldB" addLabel={true} />,
+    );
+    expect(wrapper).toContainReact(
+      <FieldGuesser source="deprecatedField" addLabel={true} />,
+    );
+  });
+
   test('renders with custom fields', () => {
     const wrapper = shallow(
       <ShowGuesserComponent
@@ -29,14 +50,5 @@ describe('<ShowGuesser />', () => {
     expect(wrapper).toContainReact(
       <TextField source="body" label={'label of body'} />,
     );
-    expect(wrapper).toContainReact(
-      <FieldGuesser source="fieldA" addLabel={true} />,
-    );
-    expect(wrapper).toContainReact(
-      <FieldGuesser source="fieldB" addLabel={true} />,
-    );
-    expect(wrapper).toContainReact(
-      <FieldGuesser source="deprecatedField" addLabel={true} />,
-    );
   });
 });
diff --git a/src/hydra/hydraClient.js b/src/hydra/dataProvider.js
similarity index 100%
rename from src/hydra/hydraClient.js
rename to src/hydra/dataProvider.js
diff --git a/src/hydra/hydraClient.test.js b/src/hydra/dataProvider.test.js
similarity index 98%
rename from src/hydra/hydraClient.test.js
rename to src/hydra/dataProvider.test.js
index 69e7061..bef8c5a 100644
--- a/src/hydra/hydraClient.test.js
+++ b/src/hydra/dataProvider.test.js
@@ -8,7 +8,7 @@
 // import hydraClient, {
 //   transformJsonLdDocumentToReactAdminDocument,
 // } from './hydraClient';
-import {transformJsonLdDocumentToReactAdminDocument} from './hydraClient';
+import {transformJsonLdDocumentToReactAdminDocument} from './dataProvider';
 
 describe('map a json-ld document to an admin on rest compatible document', () => {
   const JSON_LD_DOCUMENT = {