Skip to content

Commit

Permalink
feat: replace ComponentRef props with ComponentLookup components
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisvxd committed Sep 29, 2018
1 parent 8221585 commit d77e080
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 53 deletions.
50 changes: 34 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,6 @@ npm install --save react-from-json

## Usage

### With recursion

```jsx
import React from "react";
import ReactFromJSON from "react-from-json";
Expand Down Expand Up @@ -77,11 +75,37 @@ const Example = () => {
};
```

### Without recursion
### Flat trees

`react-from-json` also supports flat, non-recursive structures via the special `<ComponentLookup />` component. This is useful when working with typed systems like GraphQL, and you need to avoid unions.

The `<ComponentLookup />` simply maps to another component defined in a `components` object. If you were using it in React, you would use it like:

```jsx
<ComponentLookup componentType="Button" componentIndex={0} />
```

which would look up the `Button` component at index `0` in the `components` object, resolving to:

```jsx
<Button>Hello, World!</Button>
```

For `react-from-json` we use JSON, so we would write this:

```json
{
"type": "ComponentLookup",
"props": {
"componentType": "Button",
"componentIndex": 0
}
}
```

`react-from-json` also supports non-recursive structures via the special `ComponentRef` prop. This is useful when working with typed systems like GraphQL, and you need to avoid unions.
#### Example

Here's the same example as above, instead using a `ComponentRef` for `entry.props.patty`, and providing a separate `components` object.
Here's the same example as above, instead using a `<ComponentLookup />` for `entry.props.patty`, and providing a separate `components` object.

```jsx
import React from "react";
Expand All @@ -92,8 +116,11 @@ const entry = {
props: {
chain: "Wahlburger",
patty: {
componentIndex: 0,
componentType: "Patty"
type: "ComponentLookup",
props: {
componentIndex: 0,
componentType: "Patty"
}
}
}
};
Expand Down Expand Up @@ -126,15 +153,6 @@ const Example = () => {
};
```

The `ComponentRef` prop looks like:

```ts
interface ComponentRef {
componentIndex: number;
componentType: string;
}
```

## With TypeScript

`react-from-json` supports generic types for use with TypeScript.
Expand Down
35 changes: 25 additions & 10 deletions src/__helpers__/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,18 +46,27 @@ export const flatEntry = {
props: {
chain: "Wahlburger",
bun: {
componentType: "Bun",
componentIndex: 0
type: "ComponentLookup",
props: {
componentType: "Bun",
componentIndex: 0
}
},
cheese: true,
children: [
{
componentType: "Patty",
componentIndex: 0
type: "ComponentLookup",
props: {
componentType: "Patty",
componentIndex: 0
}
},
{
componentType: "Patty",
componentIndex: 1
type: "ComponentLookup",
props: {
componentType: "Patty",
componentIndex: 1
}
}
]
}
Expand All @@ -78,8 +87,11 @@ export const flatComponents: Components = {
props: {
size: "large",
ingredient: {
componentType: "PattyIngredient",
componentIndex: 0
type: "ComponentLookup",
props: {
componentType: "PattyIngredient",
componentIndex: 0
}
}
}
},
Expand All @@ -88,8 +100,11 @@ export const flatComponents: Components = {
props: {
size: "large",
ingredient: {
componentType: "PattyIngredient",
componentIndex: 1
type: "ComponentLookup",
props: {
componentType: "PattyIngredient",
componentIndex: 1
}
}
}
}
Expand Down
62 changes: 35 additions & 27 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,19 @@ export interface Component {
props: object;
}

export interface ComponentRef {
interface ComponentLookupProps {
componentType: string;
componentIndex: number;
}

export interface ReactFromJSONProps<MappingType, ComponentsType> {
export interface ComponentLookup {
props: ComponentLookupProps;
}

export interface ReactFromJSONProps<
MappingType = object,
ComponentsType = object
> {
components?: ComponentsType;
entry: Component;
mapping: MappingType;
Expand All @@ -25,22 +32,36 @@ class ReactFromJSON<
> extends React.Component<ReactFromJSONProps<MappingType, ComponentsType>> {
public counter = {};

public internalMapping: object = {};

constructor(props: any) {
super(props);

this.internalMapping = {
ComponentLookup: this.ComponentLookup
};
}

ComponentLookup = ({
componentIndex,
componentType
}: ComponentLookupProps) => {
const { components } = this.props;

if (!components) {
throw "Detected `ComponentLookup` prop on a component, but `components` is undefined. You need to define `components` if using `ComponentLookup` props.";
}

const component = components[componentType][componentIndex];

return this.renderComponent(component);
};

resolveProp = (prop: any): any => {
if (Array.isArray(prop)) {
return prop.map(this.resolveProp);
} else if (typeof prop === "object") {
// Handle componentRef
if (
// Typeguard
prop["componentType"] !== undefined &&
prop["componentIndex"] !== undefined
) {
const componentRef: ComponentRef = prop;

return this.componentLookup(componentRef);

// Handle nested components
} else if (
// Typeguard
prop["type"] !== undefined &&
prop["props"] !== undefined
Expand All @@ -54,19 +75,6 @@ class ReactFromJSON<
return prop;
};

componentLookup = (componentRef: ComponentRef) => {
const { components } = this.props;
const { componentIndex, componentType } = componentRef;

if (!components) {
throw "Detected `ComponentRef` prop on a component, but `components` is undefined. You need to define `components` if using `ComponentRef` props.";
}

const component = components[componentType][componentIndex];

return this.renderComponent(component);
};

getNextKey(type: string) {
this.counter[type] = this.counter[type] || 0;
return `${type}_${this.counter[type]++}`;
Expand All @@ -87,7 +95,7 @@ class ReactFromJSON<
resolvedProps[propKey] = this.resolveProp(prop);
}

const MappedComponent = mapping[type];
const MappedComponent = this.internalMapping[type] || mapping[type];

if (typeof MappedComponent === "undefined") {
throw `Tried to render the "${type}" component, but it's not specified in your mapping.`;
Expand Down

0 comments on commit d77e080

Please sign in to comment.