jxnblk / macro-components

Create flexible layout and composite UI components without the need to define arbitrary custom props
MIT License
484 stars 24 forks source link

RFC: Explicit API #8

Closed gaearon closed 6 years ago

gaearon commented 6 years ago

After a discussion in https://github.com/jxnblk/macro-components/issues/3 I think I have a better idea of what you're aiming at. Here's a proposal of how we could change the API. I think it allows us to:

The main idea of the proposal is that since the "macro" component knows which children it allows, it should just put them on itself as fields. Then we don't have to "guess" which component corresponds to which "slot" by its name because the mapping is explicit.

Rewritten README examples with the proposed API:

import React from 'react'
import styled from 'styled-components'
import macro from 'macro-components'
import { space, fontSize, color } from 'styled-system'

// Define some styled-components
const Box = styled.div`${space} ${fontSize} ${color}`
const Heading = styled.h2`${space} ${fontSize} ${color}`
const Text = styled.div`${space} ${fontSize} ${color}`

// Create a macro-component
const MediaObject = macro(
  // Define "slots" => "components" map
  { Image, Heading, Text },
  // Define where to render them
  ({ Image, Heading, Text }) => (
    <Flex p={2} align='center'>
      <Box width={128}>
        {Image}
      </Box>
      <Box>
        {Heading}
        {Text}
      </Box>
    </Flex>
));

// ----------------------------------
// The code below will typically be in another file
// ----------------------------------

// Use the macro-component by passing the components as children
const { Image, Heading, Text } = MediaObject;

const App = props => (
  <div>
    <MediaObject>
      <Image src='kitten.png' />
      <Heading>
        Hello
      </Heading>
      <Text>
        This component keeps its tree structure but still allows for regular composition.
      </Text>
    </MediaObject>
  </div>
)

Note how there are two changes:

This solution is versatile. Here is the second example:

const Banner = macro(
  { Heading, Subhead },
  ({ Heading, Subhead }) => (
  <Box p={3} color='white' bg='blue'>
    {Heading}
    {Subhead}
  </Box>
)
const { Heading, Subhead } = Banner;
<Banner>
  <Heading>Hello</Heading>
  <Subhead>Subhead</Subhead>
</Banner>

You can always be even more explicit too if you prefer this style:

<Banner>
  <Banner.Heading>Hello</Banner.Heading>
  <Banner.Subhead>Subhead</Banner.Subhead>
</Banner>

But what if we want to have multiple elements of the same type? No name prop, we just customize the mapping.

const Banner = macro(
  {
    Heading,
    // The slot called "Subhead" also holds a Heading
    Subhead: Heading
  },
  ({ Heading, Subhead }) => (
  <Box p={3} color='white' bg='blue'>
    {Heading}
    {Subhead}
  </Box>
)
const { Heading, Subhead } = Banner;
<Banner>
  <Heading>Hello</Heading>
  <Subhead>Subhead</Subhead>
</Banner>

There is no need for a special API to use the component type. It is already enforced automatically.

I hope this makes sense!

jole78 commented 6 years ago

I really like direction where this is going. I would say that agree with you both @gaearon and @jxnblk, you both have valid points. Coming from an asp.net background (yes it's been a while but anyway) I should say that I've always liked the way asp.net webforms did its components with "layout slots" like GridView.Header etc and then handed over the actual rendering of the header slot to the user but still kept the basic layout of GridView to be handled by GridView. I've tried to do these things in react and I've always came up with solutions that uses styled-components (or what not) to handle positioning via plain css or something. Or the prop way that react generally offers (which of course works but...again...well) I do hope that macro components seem to at least "solve" some of the "confusion" around this. Thanks!

xexys commented 6 years ago

Hello! What about key property for doing such things? It is very easy to map children with key, and it seems that it is right way for mapping seeblings.

gaearon commented 6 years ago

I do agree that key is preferable to a custom "name" prop here. But I think explicit "holes" are even better.

jxnblk commented 6 years ago

Published v2 with an API largely based on this proposal. Closing this out, but feel free to continue the discussion here or in other issues