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 Issues

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)

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.

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?

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.

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.

`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.

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?

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.

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

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

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.

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.

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)} 
  }

[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?

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

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.

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.

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.

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.

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.

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

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?

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

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 :(

[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.

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?

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'

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.

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.

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

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.

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.

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.

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.

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

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

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;
};

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.

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.

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)

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.