Coder Social home page Coder Social logo

epitath's Introduction

epita✞h

All Contributors

In memoriam HOCs and Render Props

Also, we think you may want to take a look on React Hooks API now

import epitath from 'epitath'
...

const App = epitath(function*() {
  const { loading, data } = yield <Query />
  const { time } = yield <Time />

  return (
    <div className="App">
      {loading ? (
        <h1>Loading</h1>
      ) : (
        <div>
          <h1>{`Hello, ${data.user.name}`}</h1>
          <h2>The time is {time.toLocaleString()}!</h2>
        </div>
      )}
    </div>
  )
})

npm package

Compose HOCs imperatively like async/await. No callback hell!

Live demo Source of demo

Install

yarn add epitath

or

npm install --save epitath

Why

Render props are amazing for providing more functionality but once you need to stack a bunch of them you get what recalls a painful callback hell.

<Query>
  {({ data }) =>
    <Mutation>
      {({ mutate, result })=>
        <Form>
        etc
        </Form>
      }
    </Mutation>
  }
</Query>

How

Wait, we just mentioned "callback hell". So what if we had a function that would allow us to have a kind of sugar for continuation-passing-style à la async/await?

And that's exactly what epitath is, it just takes care of the callbacks for you. The whole code is this:

import React from 'react'
import immutagen from 'immutagen'

export default component => {
  const generator = immutagen(component)

  const compose = context => {
    const value = context.value
    return context.next
      ? React.cloneElement(value, null, values => compose(context.next(values)))
      : value
  }

  function Epitath(props) {
    return compose(generator(props))
  }

  Epitath.displayName = `EpitathContainer(${component.displayName || 'anonymous'})`

  return Epitath
}

Note that epitath will only yield the first argument of the render function. In order to consume multiple arguments, we recommend creating a wrapper component:

const MutationWrapper = ({ children, ...props }) =>
  <Mutation {...props}>{(mutate, result) => children({ mutate, result })}</Mutation>

const { mutate, result } = yield <MutationWrapper />

How is this different from Suspense?

Suspense only allows you to evalulate a promise once. It does not allow you to trigger a re-render for a state update. And with epitath you can even use Formik, Apollo optimistic, React Powerplug and Smalldots tooling and etc!

BTW it's epitaph not "epitath"

"These Astrocoders dudes simply don't know how to spell words in English!"

Actually it was intended, for 2 reasons:

  1. We wanted to put a cross as icon of the package
  2. Epitaph is already taken in NPM

Contributing

Steps to get it running

Install the deps

yarn install

Boot the demo

yarn start

Things missing that we would like a little help

  • Tests
  • TypeScript support

Acknowledgements

Thanks @jamiebuilds for the suggestions on how simplifying the API

Contributors

Thanks goes to these wonderful people (emoji key):


Jamie

🤔 💻

Eli Perelman

🤔 💻

Gabriel Rubens

🤔 💻

Medson Oliveira

🤔 💻

George Lima

🤔 💻

Eliabe Júnior

💻 🎨

Guilherme Decampo

🤔

gtkatakura

🤔 💬 💡

Erjan Kalybek

📖

Jack Hanford

📖

Haz

📖

This project follows the all-contributors specification. Contributions of any kind welcome!

epitath's People

Contributors

diegohaz avatar eliperelman avatar erjanmx avatar fakenickels avatar guilhermedecampo avatar hanford avatar williamboman avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

epitath's Issues

A Better Implementation

import immutagen from "immutagen";

const compose = ({ next, value }) =>
  next
    ? React.cloneElement(value, null, (...args) => compose(next(args)))
    : value;

export function epitath(gen_fn, _this) {
  if (arguments.length < 2) {
    _this = this;
  }
  gen_fn = immutagen(gen_fn.bind(_this));
  return (...args) => compose(gen_fn(...args));
}

That's all!!!
It can avoid creating new Component in every render.

Ideas

I really like what you've done with regenerator, I've been playing around with some ideas:

https://github.com/jamiebuilds/renderator

yield <Component/>

By leveraging React.cloneElement() you can eliminate the extra functions being created in each yield:

// before:
let { time } = yield props => <Time {...props}/>;
// after:
let { time } = yield <Time/>;

Instead of switching on typeof value === 'function' you can switch on immutagen's context.next function being non-existant. Then modify the props of the context.value with React.cloneElement

if (!context.next) {
  return context.value;
} else {
  return React.cloneElement(context.value, null, values => {
    return compose(context.next(values));
  });
}

I think it would be good to do this because it requires a lot less typing and is a lot easier to read.

displayName

You can make the returned component look better in React DevTools by providing a displayName which wraps the previous generator's name:

RenderatorComponent.displayName = 'renderator(' + (generator.displayName || generator.name || 'anonymous') + ')';

Rename regenerator

Regenerator is already a super similar project which is in wide use. It's actually a requirement to use @astrocoders/regenerator if you want to transform generators functions for the browser today because it's what Babel uses.

I think this project would do better if it had a name that was more accessible. You've got some great words to play with too. The -rator from generator lets you do fun stuff. My first idea was "Renderator"

React RFC

I think I'm going to turn this into an RFC for React to add it directly.

function* Example() {
  let { time } = yield <Time/>;
  return (
    <p>Current time: {time.toLocaleString()}</p>
  );
}

Someone on the React team (I forget who) already brought up potentially adding a syntax like this.

Can this technique be reliably used against `Component`s?

Really interested in the technique being used here, and I am curious if this could also be extended for use against Components. I wrote a HOC to experiment with the idea, using the same demo code as the README, using Components instead of functions:

import React, { Component, cloneElement } from 'react';
import immutagen from 'immutagen';

const compose = ({ value, next }) => next
  ? cloneElement(value, null, values => compose(next(values)))
  : value;

export default UnyieldableComponent => {
  const original = UnyieldableComponent.prototype.render;
  let generator;

  UnyieldableComponent.prototype.render = function() {
    if (!generator) {
      generator = immutagen(original.bind(this));
    }

    return compose(generator(this.props));
  };

  return class YieldableComponent extends Component {
    render() {
      return <UnyieldableComponent {...this.props} />;
    }
  };
};
// used as legacy decorator, but can use as HOC as well
@withGeneration
export default class App extends Component {
  * render() {
    console.log('Rendering again!');
    const { loading, data } = yield <Query />;
    const { time } = yield <Time/>;

    if (loading) {
      return <h1>Loading</h1>;
    }

    const {
      values,
      touched,
      errors,
      handleChange,
      handleBlur,
      handleSubmit,
      isSubmitting,
    } = yield (
      <WrapFormik
        initialValues={{
          // Use data from other HOCs!
          email: data.user.email,
          password: '',
        }}
        validate={values => {
          // same as above, but feel free to move this into a class method now.
          let errors = {};

          if (!values.email) {
            errors.email = 'Required'
          } else if (
            !/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(values.email)
          ) {
            errors.email = 'Invalid email address';
          }

          return errors;
        }}
      />
    );

    return (
      <div className="App">
        <h1>{`Hello, ${data.user.name}`}</h1>
        <h2>The time is {time.toLocaleString()}!</h2>

        <form onSubmit={handleSubmit}>
          <input
            type="email"
            name="email"
            onChange={handleChange}
            onBlur={handleBlur}
            value={values.email}
          />
          {touched.email && errors.email && <div>{errors.email}</div>}
          <input
            type="password"
            name="password"
            onChange={handleChange}
            onBlur={handleBlur}
            value={values.password}
          />
          {touched.password && errors.password && <div>{errors.password}</div>}
          <button type="submit" disabled={isSubmitting}>
            Submit
          </button>
        </form>
      </div>
    );
  }
}

It seemed to work pretty well; do you see any concerns with this approach?

Typescript support

I've been looking into adding Typescript support to this library. Here are the issues I encountered.

  1. JSX factory invocations (eg: <Foo />) return ReactElement<any> (but a fix is on the way), which means we can't infer the prop types of any returned elements

  2. @types/react does not allow the children prop to be a function (at least with "stateless components") This is only true if you explicitly type your stateless component using React.SFC

  3. Typescript cannot infer what yield returns, which means render props are untyped by default microsoft/TypeScript#26959

Now I'm wondering if "yield to wrap the next yield or return" is worth the lack of type safety, or if I should bite the bullet on using render props without this library. 😞

If generators were always immutable in Javascript, this would be a lot easier.

How to consume render callbacks with multiple arguments?

From reading the code, it looks like epitath only passes the first render prop argument to the generator - how can a render prop component that calls the render function with multiple arguments be consumed? Is this an oversight sorry for wording or is it by design?

Is it possible to yield all the arguments without having to wrap component?

Having to do this:

const MutationWrapper = ({ children, ...props }) =>
  <Mutation {...props}>{(mutate, result) => children({ mutate, result })}</Mutation>

const { mutate, result } = yield <MutationWrapper />

when you want to access all arguments (and it's always the case with Mutation) negotiates the main benefit of epitath (at least for me) - conciseness.

I tried to read the code and understand if it is possible to return all the arg by default, but unfortunately I failed to do this quickly, so I decided to open this thread.

immutagen execute all generator for any component (accumulates all calls)

const App = regenerator(function*() {
  console.log('This code snippet is called each time the component Time is updated')
  console.log('This behavior occurs because of immutagen')

  const { loading, data } = yield props => <Query {...props} />;
  const { time } = yield props => <Time {...props} />;

  // If you uncomment this code, there will be two calls of the whole code with each
  // update of the Time
  // yield ({ children }) => <div>{children()}</div>

  return (
    <div className="App">
      {loading ? (
        <h1>Loading</h1>
      ) : (
        <div>
          <h1>{`Hello, ${data.user.name}`}</h1>
          <h2>The time is {time.toLocaleString()}!</h2>
        </div>
      )}
    </div>
  );
});

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.