GithubHelp home page GithubHelp logo

acdlite / recompose Goto Github PK

View Code? Open in Web Editor NEW
14.8K 14.8K 1.3K 1.29 MB

A React utility belt for function components and higher-order components.

License: MIT License

JavaScript 98.90% HTML 1.10%

recompose's Introduction

A Note from the Author (acdlite, Oct 25 2018):

Hi! I created Recompose about three years ago. About a year after that, I joined the React team. Today, we announced a proposal for Hooks. Hooks solves all the problems I attempted to address with Recompose three years ago, and more on top of that. I will be discontinuing active maintenance of this package (excluding perhaps bugfixes or patches for compatibility with future React releases), and recommending that people use Hooks instead. Your existing code with Recompose will still work, just don't expect any new features. Thank you so, so much to @wuct and @istarkov for their heroic work maintaining Recompose over the last few years.

Read more discussion about this decision here.


Recompose

build status coverage code climate npm version npm downloads

Recompose is a React utility belt for function components and higher-order components. Think of it like lodash for React.

Full API documentation - Learn about each helper

Recompose Base Fiddle - Easy way to dive in

npm install recompose --save

πŸ“Ί Watch Andrew's talk on Recompose at React Europe. (Note: Performance optimizations he speaks about have been removed, more info here)

Related modules

recompose-relay β€” Recompose helpers for Relay

You can use Recompose to...

...lift state into functional wrappers

Helpers like withState() and withReducer() provide a nicer way to express state updates:

const enhance = withState('counter', 'setCounter', 0)
const Counter = enhance(({ counter, setCounter }) =>
  <div>
    Count: {counter}
    <button onClick={() => setCounter(n => n + 1)}>Increment</button>
    <button onClick={() => setCounter(n => n - 1)}>Decrement</button>
  </div>
)

Or with a Redux-style reducer:

const counterReducer = (count, action) => {
  switch (action.type) {
  case INCREMENT:
    return count + 1
  case DECREMENT:
    return count - 1
  default:
    return count
  }
}

const enhance = withReducer('counter', 'dispatch', counterReducer, 0)
const Counter = enhance(({ counter, dispatch }) =>
  <div>
    Count: {counter}
    <button onClick={() => dispatch({ type: INCREMENT })}>Increment</button>
    <button onClick={() => dispatch({ type: DECREMENT })}>Decrement</button>
  </div>
)

...perform the most common React patterns

Helpers like componentFromProp() and withContext() encapsulate common React patterns into a simple functional interface:

const enhance = defaultProps({ component: 'button' })
const Button = enhance(componentFromProp('component'))

<Button /> // renders <button>
<Button component={Link} /> // renders <Link />
const provide = store => withContext(
  { store: PropTypes.object },
  () => ({ store })
)

// Apply to base component
// Descendants of App have access to context.store
const AppWithContext = provide(store)(App)

...optimize rendering performance

No need to write a new class just to implement shouldComponentUpdate(). Recompose helpers like pure() and onlyUpdateForKeys() do this for you:

// A component that is expensive to render
const ExpensiveComponent = ({ propA, propB }) => {...}

// Optimized version of same component, using shallow comparison of props
// Same effect as extending React.PureComponent
const OptimizedComponent = pure(ExpensiveComponent)

// Even more optimized: only updates if specific prop keys have changed
const HyperOptimizedComponent = onlyUpdateForKeys(['propA', 'propB'])(ExpensiveComponent)

...interoperate with other libraries

Recompose helpers integrate really nicely with external libraries like Relay, Redux, and RxJS

const enhance = compose(
  // This is a Recompose-friendly version of Relay.createContainer(), provided by recompose-relay
  createContainer({
    fragments: {
      post: () => Relay.QL`
        fragment on Post {
          title,
          content
        }
      `
    }
  }),
  flattenProp('post')
)

const Post = enhance(({ title, content }) =>
  <article>
    <h1>{title}</h1>
    <div>{content}</div>
  </article>
)

...build your own libraries

Many React libraries end up implementing the same utilities over and over again, like shallowEqual() and getDisplayName(). Recompose provides these utilities for you.

// Any Recompose module can be imported individually
import getDisplayName from 'recompose/getDisplayName'
ConnectedComponent.displayName = `connect(${getDisplayName(BaseComponent)})`

// Or, even better:
import wrapDisplayName from 'recompose/wrapDisplayName'
ConnectedComponent.displayName = wrapDisplayName(BaseComponent, 'connect')

import toClass from 'recompose/toClass'
// Converts a function component to a class component, e.g. so it can be given
// a ref. Returns class components as is.
const ClassComponent = toClass(FunctionComponent)

...and more

API docs

Read them here

Flow support

Read the docs

Translation

Traditional Chinese

Why

Forget ES6 classes vs. createClass().

An idiomatic React application consists mostly of function components.

const Greeting = props =>
  <p>
    Hello, {props.name}!
  </p>

Function components have several key advantages:

  • They help prevent abuse of the setState() API, favoring props instead.
  • They encourage the "smart" vs. "dumb" component pattern.
  • They encourage code that is more reusable and modular.
  • They discourage giant, complicated components that do too many things.
  • They allow React to make performance optimizations by avoiding unnecessary checks and memory allocations.

(Note that although Recompose encourages the use of function components whenever possible, it works with normal React components as well.)

Higher-order components made easy

Most of the time when we talk about composition in React, we're talking about composition of components. For example, a <Blog> component may be composed of many <Post> components, which are composed of many <Comment> components.

Recompose focuses on another unit of composition: higher-order components (HoCs). HoCs are functions that accept a base component and return a new component with additional functionality. They can be used to abstract common tasks into reusable pieces.

Recompose provides a toolkit of helper functions for creating higher-order components.

Usage

All functions are available on the top-level export.

import { compose, mapProps, withState /* ... */ } from 'recompose'

Note: react is a peer dependency of Recompose. If you're using preact, add this to your webpack.config.js:

resolve: {
  alias: {
    react: "preact"
  }
}

Composition

Recompose helpers are designed to be composable:

const BaseComponent = props => {...}

// This will work, but it's tedious
let EnhancedComponent = pure(BaseComponent)
EnhancedComponent = mapProps(/*...args*/)(EnhancedComponent)
EnhancedComponent = withState(/*...args*/)(EnhancedComponent)

// Do this instead
// Note that the order has reversed β€” props flow from top to bottom
const enhance = compose(
  withState(/*...args*/),
  mapProps(/*...args*/),
  pure
)
const EnhancedComponent = enhance(BaseComponent)

Technically, this also means you can use them as decorators (if that's your thing):

@withState(/*...args*/)
@mapProps(/*...args*/)
@pure
class Component extends React.Component {...}

Optimizing bundle size

Since 0.23.1 version recompose got support of ES2015 modules. To reduce size all you need is to use bundler with tree shaking support like webpack 2 or Rollup.

Using babel-plugin-lodash

babel-plugin-lodash is not only limited to lodash. It can be used with recompose as well.

This can be done by updating lodash config in .babelrc.

 {
-  "plugins": ["lodash"]
+  "plugins": [
+    ["lodash", { "id": ["lodash", "recompose"] }]
+  ]
 }

After that, you can do imports like below without actually including the entire library content.

import { compose, mapProps, withState } from 'recompose'

Debugging

It might be hard to trace how does props change between HOCs. A useful tip is you can create a debug HOC to print out the props it gets without modifying the base component. For example:

make

const debug = withProps(console.log)

then use it between HOCs

const enhance = compose(
  withState(/*...args*/),
  debug, // print out the props here
  mapProps(/*...args*/),
  pure
)

Who uses Recompose

If your company or project uses Recompose, feel free to add it to the official list of users by editing the wiki page.

Recipes for Inspiration

We have a community-driven Recipes page. It's a place to share and see recompose patterns for inspiration. Please add to it! Recipes.

Feedback wanted

Project is still in the early stages. Please file an issue or submit a PR if you have suggestions! Or ping me (Andrew Clark) on Twitter.

Getting Help

For support or usage questions like β€œhow do I do X with Recompose” and β€œmy code doesn't work”, please search and ask on StackOverflow with a Recompose tag first.

We ask you to do this because StackOverflow has a much better job at keeping popular questions visible. Unfortunately good answers get lost and outdated on GitHub.

Some questions take a long time to get an answer. If your question gets closed or you don't get a reply on StackOverflow for longer than a few days, we encourage you to post an issue linking to your question. We will close your issue but this will give people watching the repo an opportunity to see your question and reply to it on StackOverflow if they know the answer.

Please be considerate when doing this as this is not the primary purpose of the issue tracker.

Help Us Help You

On both websites, it is a good idea to structure your code and question in a way that is easy to read to entice people to answer it. For example, we encourage you to use syntax highlighting, indentation, and split text in paragraphs.

Please keep in mind that people spend their free time trying to help you. You can make it easier for them if you provide versions of the relevant libraries and a runnable small project reproducing your issue. You can put your code on JSBin or, for bigger projects, on GitHub. Make sure all the necessary dependencies are declared in package.json so anyone can run npm install && npm start and reproduce your issue.

recompose's People

Contributors

acchou avatar acdlite avatar amilajack avatar andarist avatar andersdjohnson avatar avocadowastaken avatar billyjanitsch avatar chicoxyzzy avatar chrisui avatar deepsweet avatar dtinth avatar evenchange4 avatar grabbou avatar haradakunihiko avatar istarkov avatar joshacheson avatar maarekj avatar matthieuprat avatar pex avatar pilaas2 avatar py-in-the-sky avatar samsch avatar siegrift avatar svenheden avatar timkindberg avatar trysound avatar vic avatar vildehav avatar wuct avatar zeakd 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  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

recompose's Issues

Question: `lifecycle` and server-side rendering?

If I have a component which should be able to return markup on the server but then hook into a browser API after being mounted I don't believe lifecycle() will work because setup() is called in the constructor and will throw when instantiated on the server.

A work-around would be to create an abstraction over setting up the component's client-side behavior which is just a no-op on the server, but React already provides componentDidMount() for this case and so seems less complex and more idiomatic.

Is there a plan for allowing lifecycle setup within componentDidMount() instead of the constructor?

[Question] Clarification of example.

Sorry to ask you this directly but would you mind clarifying a bit of your example?

mapProps(({ setCounter, ...rest }) => ({
  increment: () => setCounter(n => n + 1),
  decrement: () => setCounter(n => n - 1),
  ...rest
 }))

So far I have this when translated to ES5 by hand.

mapProps(function({setCounter: setCounter, /* ...rest??? */})) {
  return {
    increment: function() { return setCounter(n => n + 1) },
    decrement: function() { setCounter(n => n - 1) },
    // ...rest?????
  }
})

I have three questions.

  1. Is this reasonably accurate?
  2. I get that ...will spead the values in restinto the object but I'm really confused as to where rest comes from.
  3. Why are the arguments wrapped in an object literal? I'm sure I've translated this part poorly.

I don't think many people will find it easy to understand what this example does. Might want to cool it with the ({(({}) => ({}))}) and some of the more advanced ES2015 features.

Many thanks.

documentation feedback

We finally made it to React 14, so I've gotten to use recompose in a large app for about a week now. Per the twitter request, here's a running list of thoughts...all of which I believe could be addressed through documentation. The functionality itself is amazing 🌲 🌲 🌲 . I'm happy to create PRs if these seem reasonable.

  • onlyUpdateForPropTypes initially looks like an obvious win when combined with something like eslint-plugin-react's prop-types rule, but you can get yourself into trouble if you're passing your props through to children components using destructuring
<ChildComponent {...this.props}/>
// this will pass lint, but means your child component wil not update as expected
// if you wrap your component in onlyUpdateForPropTypes
  • Links from the api back to the relavant examples on the main page would be nice. I'm constantly switching between the two.
  • Put the api link at the top. It might be just me, but I ctrl+f api a dozen times a day.
  • More examples. In particular, using withContext and getContext is super powerful, but it took me a bit to figure out. For instance, I end up propagating props that originated in my context. Perhaps a diagram showing the wrapping classes and how the context/props are changed at each level. In general, context is sort of hard to reason about, so all the hocs that deal with it could use a couple examples imo.
  • Testing components that use hocs. This is probably unrelated to using recompose vs hand rolling hocs, but its way harder to test containers than dumb components. Maybe linking to enzyme examples or something would be enough.

[Question] "Mimic" Some Aspects of Relay

First of all thank you for open source this amazing library!

I've been using Flux for many projects (Alt, Redux, NuclearJS, Baobab).

I like Flux, but, at least for me, I don't think it's fun to write when project grows. The main reason is the boilerplate (it's just personal taste, I know that some people like boilerplate, also I know I can reduce boilerplate, anyway...).

I want to know if I can use Recompose to "mimic" some aspects of Relay.

My idea is to add data fetching inside containers and pass it to the component with "withState".

For state that need to be shared across all components I'll create an AppContainer (withState('usersOnline', 'getUsersOnline', []), withState('loggedUser', 'getLoggedUser', { isLoading: false, data: {}, error: null })), then I'll create subcontainers, for example NotificationContainer (withState('notifications', 'getNotifications', [])).

Do you think I can proceed this way?

createEventHandler should accept a function as an argument

When a FORM element submit event occurs, you need to return false and call event.preventDefault(). Otherwise the form will attempt to submit the form in the background before processing other event subscribers.

The FuncSubject from https://github.com/fdecampredon/rx-react allows developers to define an immediate callback, which allows developers to prevent form submission. I'll can create a pull-request with the change.

Great work on this project, and your work in react-rx-component. Quite a beautiful way to build components.

toClass() helper

This would be a helper that takes a function component and wraps it in a class. This can be used as a fallback for libraries that need to add a ref to a component, like Relay.

  • It should return the base component if it is already a class.
  • It should call the function component inside of render(), rather than creating a new element from it.
  • It should copy the function component's propTypes, contextTypes, defaultProps, and displayName to the new class component.
  • It should correctly pass both props and context to the base component.

This would be a good first issue for anyone who would like to contribute.

Production build is broken

In production, createHelper is set to lodash's curry here, which has signature:
curry(func, arity).

In development, it uses a custom implementation here with signature:
curry(func, helperName, arity, setDisplayName)

Note the different arity argument position.

createHelper is used to create setStatic here with the arity as the 3rd argument. This works fine in development, but in production, Lodash returns the function without actually implement currying.

setStatic, expecting to be curried, is called with a single argument here. This works fine in development but throws in production because it tries to access a key on an undefined variable.

This all happens during library setup, so even just including recompose in production throws the cryptic error: Uncaught TypeError: Cannot set property 'propTypes' of undefined.

Static Class Properties breaks ie8

Static Class Properties compile down to use of Object.defineProperty which breaks in ie8. I'm happy to do the refactoring work to do things the (unfortunately less pretty) oldschool way.

Let me know if this sounds good - otherwise I'll have to fork :(

add mount option to lifecycle

lifecycle(
  setup: (component: ReactComponent) => void,
  mount: (component: ReactComponent) => void,
  teardown: (component: ReactComponent) => void,
  BaseComponent: ReactElementType
): ReactElementType

Add mount option to lifecycle that calls your function during componentDidMount. This will allow use for isomorphic/universal apps when you only want things ran on client and not on server.

Let me know what you think. I could probably get a PR in this week.

shadowEqual does not work when value is an array

    console.log('testA', shallowEqual(['a'], ['a']));
    console.log('testB', shallowEqual([1], [1]));
    console.log('testC', shallowEqual(
      { a: 1 },
      { a: 1 }
    ));
    console.log('testD', shallowEqual(
      { a: [1] },
      { a: [1] }
    ));
    console.log('testE', shallowEqual(
      { a: ['a'] },
      { a: ['a'] }
    ));

Above codes will output:
testA true
testB true
testC true
testD false
testE false

request: Add HOC to support "get async data on mount" pattern.

I have this pattern all over my codebase:

const ComponentA = React.createClass({

    propTypes: {
        id: PropTypes.string,
    },

    contextTypes,

    getInitialState() {
        return {};
    },

    componentDidMount() {
        http.get(this.context.rootUrl + "SomeAPI", {id: this.props.id})
            .then(config => {
                if (this.isMounted())
                    this.setState({data});
            });
    },

    render() {
        return this.state.data
            ? (<ComponentB {...this.props} {...this.state.data}/>)
            : (<Spinner/>);
    }
});

A component, such as ComponentA, has to make an ajax call on mount, and then pass that data to another component on completion. I'm imagining a HOC signature that looks something like this:

onMount(
    getData: (props: Object, context: Object) => Promise,
    BaseComponent: ReactElementType
): ReactElementType

Internal state without setState

There are situations at almost all controls I've wrote when I need to hold some state but this state isn't React or Flux state. Because I don't want additional rerender.

For example:

  _onMouseEnter(poly) {
    clearTimeout(this.mouseLeaveTimeoutHandle_);
    onHoveredChange(poly);
  }

  _onMouseLeave( /* poly */ ) {
    this.mouseLeaveTimeoutHandle_ = setTimeout(
      () => onHoveredChange(null),
      1000 / 60
    );
  }

Here I use this.mouseLeaveTimeoutHandle_ to prevent rerender if Mouse enter new object.
Sometimes such state used as cache, sometimes to support imperative DOM calls.

May be recompose needs something like withState but with getter functions support and without setState calls?

(For such state Getter Setter functions (or one function) are passed through props, not a value)

To curry, or not to curry?

I'm considering dropping the automatic currying of Recompose helpers.

  • It doesn't play nicely with Flow or TypeScript
  • It makes it more costly for libraries to import individual modules, because it bloats the bundle size. For example, if you import recompose/withProps, the code that implements currying is larger than the code needed to implement withProps() itself.
  • It's not clear how much utility it actually provides. In my own use, I almost always call helpers of n arity with n or n-1 arguments.
  • Arguably, it adds to the learning curve, and makes Recompose seem more special or foreign than it really is.

Instead, I propose that we remove automatic currying and instead move toward an API like React Redux's connect().

// Before: all these forms work
helper(a, b, c)(BaseComponent)
helper(a, b, c, BaseComponent)
helper(a)(b)(c)(BaseComponent)
// etc.

// After: only this form
helper(a, b, c)(BaseComponent)

Note that this is still compatible with compose()

What do you think?

API doc clarification

Although it has the same effect, using the defaultProps() HoC is not the same as setting the static defaultProps property directly on the component.

Intriguing, but unsatisfying. I think this statement merits a word on how it's not the same.

Replace `createSpy()` with proper testing library

createSpy() is a temporary hack I came up with until a proper solution for testing function components emerged. I would really like to get rid of it.

Once there is stable, widely-used testing framework that supports functional components, let's remove createSpy(). Candidates:

  • enzyme β€” Maintained by Airbnb. Currently doesn't work in browser environments (e.g. mocha), which could be a dealbreaker
  • teaspoon β€” Works really well in my limited testing. Leaning toward this right now.

Controlled text input using withState throws error

I'm using withState to manage the value of a controlled input. Here's how i'm using it:

import { compose, withState, mapProps } from 'recompose';

const MyComponent = ({
  originalTitle,
  inputOnChange,
  title
}) => {
  return (
    <div>
      <input type="text" value={ title } onChange={ inputOnChange }/>
    </div>
  );
};

export default compose(
  withState('title', 'changeTitle', (props) => props.originalTitle),
  mapProps((props) => _.assign(props, {
    inputOnChange: (e) => props.changeTitle(() => e.target.value)
  }))
)(MyComponent);

And the error i'm getting is:

Warning: This synthetic event is reused for performance reasons. If you're seeing this, you're accessing the property `target` on a released/nullified synthetic event. This is set to null. If you must keep the original synthetic event around, use event.persist(). See https://fb.me/react-event-pooling for more information.

Uncaught TypeError: Cannot read property 'value' of null

Now, I read about Event Pooling, and saw some recommendations to use event.persist(), but I couldn't figure out how to use it in this setup.
Any help will be greatly appreciated!
Thanks

createElement does not pass context to functional components

Per react docs, it is expected that a context will be provided to a stateless functional component: https://facebook.github.io/react/docs/context.html#referencing-context-in-stateless-functional-components

I'm not sure if there's another "point of failure" aside from createElement for this behaviour not being supported by recompose right now, but it seems liek the sort of thing that should be supported.

Personally, I love the getContext helper and am using it in production, but I think it's important that helpers don't strictly enforce the "context as props" model and support the idiomatic React behaviour as well.

Happy to do the work on this if you think it gels with the project vision/opinions - let me know what you think

observeProps surprising behavior

When using observeProps, the props stream uses only the initial prop values for the first render.

In my case, I am using Recompose.lifecycle to load or fetch some cached data.However when I had a cached value, ie WORLD, the template was still rendering with the default value HELLO

SmartFooTemplate = Recompose.compose(
  observeProps( (props$) ->
    foo$ = new Rx.BehaviorSubject("HELLO")
    return Rx.Observable.combineLatest(
      props$, foo$
      (props, foo) ->
        Object.assign {},
          props
          foo:foo
          foo$:foo$
    )
  )
  Recompose.lifecycle(
    (self) ->
       foo$.onNext('WORLD')
    (self) ->
       #Stuff I need to run when the component is unmounted.
  )
)
DumbFooTemplate = React.createClass
    render: ->
       @props.foo # = 'HELLO'
       @props.foo$.value #= 'WORLD'

helper for partially applying props

This idea comes from Pete Hunt's jsxstyle: a helper to partially apply props to a component.

Here's a rough sketch of what it could look like for a stateless base component:

import omit from 'lodash/object/omit';

const applyProps = (partialProps, baseComponent) => {
  const NewComponent = (moreProps, context) =>
    baseComponent({ ...partialProps, ...moreProps }, context);

  NewComponent.propTypes = omit(
    baseComponent.propTypes,
    key => key in partialProps
  );

  NewComponent.defaultProps = baseComponent.defaultProps;

  return NewComponent;
};

And that might not be significantly different from this:

const applyProps = (partialProps, baseComponent) => {
  const NewComponent = (moreProps, context) =>
    baseComponent(moreProps, context);

  NewComponent.propTypes = baseComponent.propTypes;

  NewComponent.defaultProps = {
    ...(baseComponent.defaultProps || {}),
    ...partialProps
  };

  return NewComponent;
};

running tests broken

There seems to be a dependency issue, possibly related to babel-relay-plugin.

If you do a fresh clone and npm install then npm test you get a lot of these errors:

Module build failed: ReferenceError: Unknown plugin "./src/packages/recompose-relay/babelRelayPlugin" specified in "base" at 0

any thoughts? it is currently blocking all PR's

Question: propTypes?

I love this.

My only question about using function components, how do you specify propTypes? For documentation purposes chiefly, but they also help catch stupid mistakes.

This works but it doesn't look that great:

const Text = ({ children }) => 
  <p>{children}</p>
Text.propTypes = { children: React.PropTypes.string };
Text.defaultProps = { children: 'Hello World!' };

I saw that you use Flow type notation in the documentation. Is that a possible route? Replacing propTypes with Flow annotations entirely?

reverse props spread order for mapPropsOnChange

While working with mapPropsOnChange recently we noticed that this.props get spread over computed props.

https://github.com/acdlite/recompose/blob/master/src/packages/recompose/mapPropsOnChange.js#L24

It appears the original plan was to spread computed props last?
#60 (comment)

Example:
I create an items prop with mapPropsOnChange

mapPropsOnChange(['thing'], ({thing}) => ({items: thing.count()}))

but if items was already a prop being passed down the new computed prop of items will get overwritten by this.props.items.

I would expect my outputted computed props to be what gets passed down.

Let me know what you think, and I could get a PR in with tests this week.

mapPropsOnUpdate add childProps to propsMapper call

Add childProps to propsMapper call here
https://github.com/acdlite/recompose/blob/master/src/mapPropsOnUpdate.js#L19

Or allow additional check

Sometimes i need to do additional checks to decide do i need to recalculate props or can return previous value.

For example

@mapPropsOnUpdate(
  ['a', 'b'],
  (props) => {
    if (a<b) { // it can be computationally intensive check 
        return {...props, result: I_WANT_TO_RETURN_PREV_RESULT} 
    }
    return {...props, result: calcNewResult(props)} 
  }

with childProps it will be easy

@mapPropsOnUpdate(
  ['a', 'b'],
  (props, childProps) => {
    if (a<b) { // it can be computationally intensive check 
        return {...props, result: childProps.result} 
    }
    return {...props, result: calcNewResult(props)} 
  }

`withProps` documentation + potential fix

Hello and a quick thanks for the library! I've been enjoying it a lot.

I noticed that the withProps function is documented as only accepting an object, but seems to be written to also accept a function:

Docs:

withProps()

withProps(
  props: Object,
  BaseComponent: ReactElementType
): ReactElementType

Src:

const withProps = (input, BaseComponent) => {
  let getProps
  const props = isFunction(input)
    ? input(getProps)
    : input

  return ownerProps => {
    getProps = () => ownerProps
    return createElement(BaseComponent, {
      ...ownerProps,
      ...props
    })
  }
}

Secondly, I don't think the function form will work as expected because at the time of the function call, getProps is still undefined. If we were to move it below the getProps definition I think it'd work:

const withProps = (input, BaseComponent) => {
  let getProps
  let props

  return ownerProps => {
    getProps = () => ownerProps
    props = isFunction(input)
      ? input(getProps) 
      : input
    return createElement(BaseComponent, {
      ...ownerProps,
      ...props
    })
  }
}

I'd be happy to open up a PR with some fixes but just wanted to check first to make sure it was the intended idea.

Minor correction in docs

There is a minor correction in recompose/docs/API.md.

At https://github.com/acdlite/recompose/blob/master/docs/API.md#flattenprop, signature reads as renameProps( propName: string, BaseComponent: ReactElementType ): ReactElementType

While, it should be
flattenProp( propName: string, BaseComponent: ReactElementType ): ReactElementType

renameProps should be replaced with flattenProp.

I am new to this git environment, so don't know how to create a pull request. (Tried to read the github help for that, but did not get what do they mean by 'upstream branch' and 'your branch'. Also, what I think is, we need to repair the doc in 'master' branch itself. But github does not allow pull requests in 'master' branch.

React 15.0.0-rc1 key warning

React 15.0.0-rc1 add a new warning.

  • Elements will now warn when attempting to read ref and key from the props.

recompose seems touch key from props, so this warning shows up

key is not a prop. Trying to access it will result in undefined being returned. If you need to access the same value within the child component, you should pass it as a different prop.

README.md might be confusing

let ContainerComponent = onWillReceiveProps(..., BaseComponent);
ContainerComponent = mapProps(..., ContainerComponent);
ContainerComponent = withState(..., ContainerComponent);

I was thinking it is valid JS syntax since the dots look exactly like spread operators. Maybe substitute them with /**/ comments would be better.

Similar Library

Hi,

I wanted to build a container creation utility for React few weeks ago. I searched for an existing one, but I couldn't figure out a one.

I didn't find this one.

So, I created a one on my own called react-komposer and I just discovered this one.

Surprisingly, core APIs are just like the same.

Just wanted to mention this. Feel free to close this issue.

Can't make `branch` work

I tried to use the branch function for 2 hours now, I can't figure out what I'm doing wrong. I'm trying to render a Loader if some props are missing, or the Page component. That's exactly what is done in the docs.

// I create a stateless component as always
const Page = ({ title }) => {
  return (
    <h1>{title}</h1>
  )
}

// I create my Loader component
const Loader = () => {
  return (
    <span>Loading ...</span>
  )
}

// Then, here comes the branch function, to render the relevant one
export default branch(
  props => !! props.title,
  renderComponent(Page),
  renderComponent(Loader)
)

I keep getting ReactCompositeComponent.js:587 Uncaught TypeError: inst.render is not a function

Am I doing something wrong ?

What's returned from the branch function is a function names PartialFunc from createHelper.js

PS : I'm using the version 0.15.0

Smart compose

Some developers I work with dislike that recompose creates HOCs on each operation, so the tree in react tools is too deep.

As an example:
This code
https://github.com/istarkov/google-map-clustering-example/blob/master/src/GMap.js#L57-L126

creates this tree
image

The real examples we have at work has more deeper trees. (I does not use react tools at work, but others use it a lot)

And Yes I know that I can combine here 3 call to withState into one or/and mapProps.. but this affects code readability for me.

I think that some of that multiple calls can easily (or not ;-)) be combined at compose level, as some optimization operation.
For example sequential calls to mapPropsOnChange can be combined into one HOC automatically and etc....

May be it's too early to talk about this, but it's just an opinion of some developers.

Testing for `createSink` result

I have a component structured something like:

const nothing = () => createSink(() => null)
const MyComponentContainer = branch(
  props => props.some_flag,
  withProps(...),
  nothing)(MyComponent)

Unfortunately testing this with ReactShallowRenderer seems a bit tough, here's the render result:

{ '$$typeof': Symbol(react.element),
  type: [Function: Sink],
  key: null,
  ref: null,
  props: { some_flag: false },
  _owner: null,
  _store: {} }

I have no idea what kind assertion to make to determine whether this is in fact the "nothing" result.

Conditional or Switch HOC?

Love the work you're doing on this.

Would it makes sense within the context of this library to provide helpers for rendering different components based on props?

For instance conditional() could work like branch() but instead of applying left/right HOC to the base component, it would render base component if test returns true and null if false.

Or switch(), which would get an array of objects: { test: (props: Object) => boolean, component: Class<ReactComponent> } and renders the first one that passes it's test. Base component could act as the default in switch statements.

I use something similar to the above on my current project and would be happy to take a crack at a PR for recompose if this is something that makes sense to you.

expose `createElement` and `createHelper`

These two private/util methods are really useful. It might be nice to either publish them as separate libraries on npm or expose them to consumers of recompose. Seems to me that people wanting to create their own helpers wont be a rare case, and these utilities are a huge help in that process.

invariant like HOC

The idea is to create 'invariant like' HOC to check properties at dev builds.

The signature:

invariant(
  propsValidator: (ownerProps: Object) => Bool,
  message: String,
  BaseComponent: ReactElementType
): ReactElementType

Realization example (uses invariant lib):

import invariant from 'invariant';
import createHelper from 'recompose/createHelper';
import createElement from 'recompose/createElement';

const invariantHOC = (props2Condition, message, BaseComponent) =>
  props => (
    invariant(props2Condition(props), message),
    createElement(BaseComponent, props)
  );

export default createHelper(invariantHOC, 'invariant');

But may be for performance reasons instead of invariant behavior, fully throw away this checks at release builds.

What are you think?

withState don't pass stateUpdaterName and stateName if already defined in props

In withState
if stateUpdaterName and stateName are already defined in the props, pass props only here.
(without local updater and value.)

It allows to create controlled and uncontrolled components

Like

Component source

@withState('value', 'onChange', 0)
export default MyInput extends .... {
...
    this.props.onChange(newValue);
...
}

Uncontrollable usage

<MyInput />

Controllable usage

<MyInput value={0} onChange={(newVal) => this.setState(newVal)} />

PS:
As I understand it can be done with branch now.

bind callbacks to props

It's easy for any class component to get props in any event callback,
having reference to callback not change across render calls.

class Blabla ... {
  onClick = () => {
    const props = this.props;
    ... do Some work based on props;
  }
  render() {
     // this.onClick is same every render
     return <BlaBla onClick={this.onClick} />;  
  }
}

mapProps cause callback refs to change at every render call (if I need to have access to props inside
callback)

Like

mapProps(props => {
  ...props,
  // reference to onClick here changed at every render call
  onClick() { 
  // use props here, also i can't call other methods defined here 
  }
}

May It's good to create something like bindToProps HOC,

onClick(props, args) {
}

bindToProps({onClick})
...

Because every time I start to write stateless component I need to write something like

  <BlaBla onClick={(...args) => onClick(props, ...args) } />

And I dislike the Idea that every time BlaBla receives a new function reference and lost some of his shouldComponentUpdate optimization.

Can't run tests

I have:
os: OSX Captain
node: v0.12.2
npm: 2.10.1

I run

npm install
npm run test

output

Chrome 46.0.2490 (Mac OS X 10.11.1) ERROR
  Uncaught Error: Cannot find module "mock-modules"
  at /Users/ice/temp/recompose/tests.webpack.js:143093 <- webpack:///src/packages/recompose-relay/~/react/~/envify/~/jstransform/src/__tests__/jstransform-test.js:19:0

mapPropsOnChange

recompose does not allow to use most used pattern similar to mapPropsOnChange

What if I need to create computationally intensive work only if some props is changed, but pass other props as is. So I get all props online, and compute results of intensive tasks only when it needed.

For example I have property x and data, I need to make some intensive calculus if data changed but also I need to get current version of x in my component. With mapPropsOnChange I get the value of x on the moment data has changed. But every time I use this pattern I need new x values.

This could be solved creating a HOC based on mapPropsOnChange but changing this line

https://github.com/acdlite/recompose/blob/master/src/packages/recompose/mapPropsOnChange.js#L23

on this

return createElement(BaseComponent, {...this.props, ...this.childProps})

What are you think about
HOC with name and signature

withPropsOnChange(
  depdendentPropKeys: Array<string>,
  propsMapper: (ownerProps: Object) => Object,
  BaseComponent: ReactElementType
): ReactElementType

HOC Idea: `keyed` components

At @taskworld, when we write components that communicate with the store upon mounting, we must do it in three different places: componentDidMount, componentWillUnmount, and componentDidUpdate. For example:

var MessagesContainer = React.createClass({
  propTypes: {
    threadId: React.PropTypes.string.isRequired,
  },
  componentDidMount () {
    this.subscription = subscribeToThread(this.props.threadId)
  },
  componentDidUpdate (lastProps) {
    if (lastProps.threadId !== this.props.threadId) {
      if (this.subscription) this.subscription.unsubscribe()
      this.setState(this.getInitialState())
      this.subscription = subscribeToThread(this.props.threadId)
    }
  },
  componentWillUnmount () {
    if (this.subscription) this.subscription.unsubscribe()
  },
  // ...
})

What if a component can reject prop changes?

When the threadId changes, the old component that subscribes to the old thread is simply unmounted, and the new one with the new threadId is mounted in its place.

With that, we don’t need to handle the change in threadId anymore:

var MessagesContainer = React.createClass({
  propTypes: {
    threadId: React.PropTypes.string.isRequired,
  },
  componentDidMount () {
    this.subscription = subscribeToThread(this.props.threadId)
  },
  componentWillUnmount () {
    if (this.subscription) this.subscription.unsubscribe()
  },
  // ...
})

Turns out it is possible with the special key prop in React:

<MessagesContainer threadId={threadId} key={threadId} />

Now the MessagesContainer will be replaced instead of having props sent to it. Just what I want. However, other people reviewing my code may not understand why I need to put a key prop there.

Instead of relying on the users of MessagesContainer to specify the key prop to ensure correct subscribe/unsubscribe behavior, we can simply wrap MessagesContainer in a higher-order component which automatically adds the key to the wrapped component.

This is our implementation of keyed.

function keyed (propsToKey) {
  return function createKeyedComponent (Component) {
    const KeyedComponent = React.createClass({
      render () {
        return <Component {...this.props} key={propsToKey(this.props)} />
      },
    })
    return KeyedComponent
  }
}

And here is how to use it:

export default keyed(props => props.threadId)(Messages)

How about a withPropTypes?

great library! So, I had been doing prop types, at least with stateless functions, like so:

const component1 = (props) => <div {...props} />;

component1.propTypes = { ... };

export default component1;

Which looks ugly. Maybe it's even too simple for this lib, but a withPropTypes could look like

const withPropTypes = curry((propTypes, component) => component.propTypes = propTypes);

Would still support non-stateless components while fitting nicely in the final compose.

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.