GithubHelp home page GithubHelp logo

Alternative API proposals about react-redux HOT 79 CLOSED

reduxjs avatar reduxjs commented on April 27, 2024 2
Alternative API proposals

from react-redux.

Comments (79)

gaearon avatar gaearon commented on April 27, 2024 3

Normal arguments:

Vanilla form

connect(
  state => ({ state.counter }),
  dispatch => ({
    increment: () => dispatch(CounterActions.increment())
  })
)(Counter) 

wrapActionCreators sugar

connect({
  state => ({ state.counter }),
  wrapActionCreators(CounterActions)
})(Counter)

I want just the state

connect(
  state => ({ state.counter })
)(Counter) 

I want just some action creators

connect(
  null, // means "no need to subscribe at all"
  wrapActionCreators(CounterActions)
)(Counter) 

I want just the dispatch function

connect(
  null,
  dispatch => ({ dispatch })
)(Counter) 

I want to pass several action creator groups down

function wrapActionCreatorMap(map) {
  return dispatch => mapValues(map, actionCreators => wrapActionCreators(actionCreators)(dispatch));
}

connect(
  state => state.counter,
  wrapActionCreatorMap({
    counterActions: CounterActions,
    userActions: UserActions
  })
)(Counter)

I want to merge several action creator groups

connect(
  state => state.counter,
  wrapActionCreators(...CounterActions, ...UserActions)
)(Counter)

I want to pass actions down in a single object

function merge(counter, actions, props) {
  return { counter, actions, ...props };
}

connect(
  state => state.counter,
  wrapActionCreators(CounterActions),
  merge
)(Counter)

I want to use props for getting state and tweaking actions

function merge(counters, actions, props) {
  const { counterId, ...rest } = props;
  const { increment, decrement } = actions;
  return {
    counter: counters[counterId],
    increment: () => increment(counterId),
    decrement: () => decrement(counterId),
    ...rest
  };
}

connect(
  state => state.counters,
  wrapActionCreators(CounterActions),
  merge
)(Counter)

from react-redux.

faassen avatar faassen commented on April 27, 2024 1

I like the general direction in which this is going -- it makes it possible to entirely keep any Redux related stuff out of plain React components, including the whole actions mechanism.

Getting props to tweak action creators is an important use case, I think, so it's important it's made easy. In hypermedia-based UIs you often pass URLs around as properties and then pass them into action creators Note that this is distinct from the parameterized action creator use case where you want to parameterize a whole bunch of related actions.

I'm not sure I fully understand the consequences of merge yet. Is the primary reason 'merge' is not the second argument (replacing the actions arg with a dispatch arg) for performance reasons or for usability reasons? If performance reasons, does this mean that any kind of parameterized action will degrade an application's performance because props vary more? Action tweaking in merge creates new functions, which could potentially cause props to be different. But should merge be called at all if the state and props haven't changed since the last time? Could merge be memoized?

merge is responsible for creating the entire prop object that goes into the underlying component. That makes it very flexible and I appreciate that, but I would like to explore approaches where the developer doesn't have to worry about all that. I'd like to support something like this:

function tweak(counters, actions, props) {
  const { counterId } = props;
  const { increment, decrement } = actions;
  return {
    counter: counters[counterId],
    increment: () => increment(counterId),
    decrement: () => decrement(counterId),
  };
}

You could write a higher order function that does this:

function createMerge(func) {
   return (state, actions, props) => {
        const tweaked = func(state, actions, props);
        return { ...props, ...tweaked };
  };
}

const merge = createMerge(tweak);

Are performance optimization opportunities lost this way?

From the readability perspective I have a slight preference for named parameters. But named parameters have the drawback that is supports some uses cases (like binding actions without state) I understand are less than optimal without signalling the developer (by requiring a null arg) that they are taking a suboptimal approach. Named parameters have the benefit of extensibility: it would make it easy to add a tweak parameter. But I think that's the kind of extensibility you don't want at this level. So I can see reasons for using normal arguments in this case.

from react-redux.

gaearon avatar gaearon commented on April 27, 2024

For example, we could drop <Connector> altogether and encourage people to use @connect decorator as a function in another module.

Common case

A dumb component would look exactly as it does now.
A smart component would look like

import React from 'react';
import { connect } from 'react-redux';
import Counter from '../components/Counter';
import * as CounterActions from '../actions/CounterActions';

export default connect(
  state => ({
    counter: state.counter
  }),
  dispatch => ({
    increment: dispatch(CounterActions.increment()),
    decrement: dispatch(CounterActions.decrement())
  })
)(Counter);

Note that the smart component doesn't have to be declared as a component. Also note that state => ... and dispatch => ... is all it accepts.

Case with more control

Want more customization? Want a componentDidUpdate hook? Want to select different things depending on the current props? Well, maybe you need to put a component in the middle then:

import React from 'react';
import connect from './connect';
import { bindActionCreators } from 'redux';
import Counter from '../components/Counter';
import * as CounterActions from '../actions/CounterActions';

class CounterContainer {
  componentDidUpdate() {
    ...
  }

  render() {
    const props = somehowSelectChildProps(this.props);
    return <Counter {...props} />
  }
}

export default connect(
  state => ({
    counter: state.counter
  }),
  dispatch => ({
    increment: () => dispatch(CounterActions.increment()),
    decrement: () => dispatch(CounterActions.decrement())
  })
)(CounterContainer);

This is an โ€œexplicitโ€ smart component that is required for more advanced cases.
Note that you didn't have to move files or refactor anything.
You just put a component in the middle into the same file.

Shortcuts

Finally, we can still offer bindActionCreators, but with a actionCreators => dispatch => obj signature, so that the result is usable as the second parameter:

import React from 'react';
import { connect } from 'react-redux';
import Counter from '../components/Counter';
import * as CounterActions from '../actions/CounterActions';

export default connect(
  state => ({  counter: state.counter }),
  bindActionCreators(CounterActions)
)(Counter);

Perhaps we can even go further and bind automatically if an object is passed.

import React from 'react';
import { connect } from 'react-redux';
import Counter from '../components/Counter';
import * as CounterActions from '../actions/CounterActions';

export default connect(
  state => ({  counter: state.counter }),
  CounterActions
)(Counter);

โ€œWait!โ€, I hear you say. What if an action depends on some prop from the state? Well, in this case you put a component in the middle like I described above.

import React from 'react';
import { connect } from 'react-redux';
import Counter from '../components/Counter';
import * as CounterActions from '../actions/CounterActions';

class CounterContainer {
  increment() {
    this.props.increment(this.props.id);
  }

  render() {
    return <Counter {...this.props} increment={this.increment} />
  }
}

export default connect(
  state => ({  counter: state.counter }),
  CounterActions
)(CounterContainer);

Any sufficiently complicated case => component in the middle. Easy!

Am I missing something?

from react-redux.

acdlite avatar acdlite commented on April 27, 2024

๐Ÿ‘ I like this. It's essentially the same API as https://github.com/acdlite/redux-rx#createconnectorselectstate-render, just without streams.

from react-redux.

acdlite avatar acdlite commented on April 27, 2024

Ah, only thing I see missing is a way to access props from owner โ€” e.g. if you're wrapped by Relay.

from react-redux.

gaearon avatar gaearon commented on April 27, 2024

Can you clarify?
Isn't this โ€œCase with more controlโ€ above?

from react-redux.

acdlite avatar acdlite commented on April 27, 2024

That works, I'm just not sure I like it... Seems like too common a case to necessitate what is essentially two smart components.

from react-redux.

acdlite avatar acdlite commented on April 27, 2024

Could we just pass the owner props as the second argument?

from react-redux.

gaearon avatar gaearon commented on April 27, 2024

From the user point of view they're just declaring one โ€œrealโ€ component so no big deal IMO. On the other hand once you start selecting data in a tricky way, you begin to want finer control over perf optimizations, lifecycle hooks and maybe passing data down in a trickier way so it's likely you'll want a component anyway.

Once we start passing props to the state getter, we'll also probably want to pass props to the action creators getter. However, this forces us to bind on every prop change, which is a perf hit and unfriendly to shouldComponentUpdate optimizations down the rendering chain.

Example: you might want to read from props if you have a route handler that gets specific data related to the current route parameters. But then you already want to fire an action in componentWillReceiveProps and componentDidMount to fetch the data! So you already need an intermediate component anyway.

from react-redux.

skevy avatar skevy commented on April 27, 2024

So are you just removing the idea of a higher order component here? The signature of "connect" seems the same as what was proposed in #86 (at least the shorthand one you suggested where you just pass an object of actions as the second param).

from react-redux.

acdlite avatar acdlite commented on April 27, 2024

@gaearon

On the other hand once you start selecting data in a tricky way, you begin to want finer control over perf optimizations, lifecycle hooks and maybe passing data down in a trickier way so it's likely you'll want a component anyway.

I really think accessing props from the owner is a much more common case that using lifecycle hooks. Props passing is the fundamental contract of React. We'll soon live in a world where pure functions are valid React components. The fewer "smart" components the better โ€” creating class components will start to become a low-level implementation detai. Function components will be the new default. (At least that's what should happen. We'll see if the community catches on.)

However, this forces us to bind on every prop change, which is a perf hit and unfriendly to shouldComponentUpdate optimizations down the rendering chain.

This seems like a micro-optimization but okay. You could get around this by binding once if an object is passed ("bind automatically if an object is passed") but bind every time if its a function. Also if an action creator depends on a prop it's going to lead to updates further down the rendering chain, anyway.

from react-redux.

acdlite avatar acdlite commented on April 27, 2024

^ The reason I say it's a micro-optimization is you're rarely going to pass action creators more than one level down the component tree.

from react-redux.

skevy avatar skevy commented on April 27, 2024

@acdlite also, it's easy enough to prevent constant rebinding with memoization. We had explored this a bit in redux#86

from react-redux.

ryancole avatar ryancole commented on April 27, 2024

I also agree with the sentiment that you'll rarely pass action creators more than one level down a component tree. This makes me question the over all usefulness of bindActionCreators. It seems to contrast the simplicity and obviousness of redux in general. It seems like it'd be clearer to just force users to pass dispatch down as a prop to every component, as described in the current readme file.

As a user, it seems like if dispatch is always needed to call an action, then maybe some way to remove the onus on the developer to pass dispatch down the tree. It'd be cool if you could just import your action methods and call them, no?

from react-redux.

acdlite avatar acdlite commented on April 27, 2024

@ryancole I still think bindActionCreators() (or some equivalent) is useful. You shouldn't pass dispatch() to a dumb component; you should bind it in a smart component first. E.g. your dumb components should look like this:

class CounterButton extends Component {
  render() {
    const { increment } = this.props;
    <button onClick={increment} />; 
  }
}

Rather than this:

class CounterButton extends Component {
  render() {
    const { dispatch } = this.props;
    <button onClick={dispatch(increment())} />; 
  }
}

It may seem like a trivial difference, but the first version is more separated from the implementation details of how it receives its callback. This leads to more scalable, maintainable, testable code.

It'd be cool if you could just import your action methods and call them, no?

You have to bind them somewhere. Remember, action creators in Redux are simply pure functions. Either we bind them in smart components, or we have to bind them at store creation time, in which case we'd need some sort of API for accessing the bound components. Or you could use singletons, but yuck.

from react-redux.

acdlite avatar acdlite commented on April 27, 2024

I like @gaearon's idea of passing action creators as the second param and auto-binding:

export default connect(
  state => ({  counter: state.counter }),
  CounterActions
)(CounterContainer);

That way we only need to bind once, and the user doesn't need to worry about bindActionCreators.

I would amend that proposal to also support a second form, where you pass a function that maps to unbound action creators:

export default connect(
  state => ({  counter: state.counter }),
  (state, props) => ({
    increment: () => CounterActions.increment(props.something)
  })
)(CounterContainer);

that way you can access the store state and props, if needed. bindActionCreators() becomes an implementation detail.

from react-redux.

ryancole avatar ryancole commented on April 27, 2024

@acdlite I agree with how you explained why bindActionCreators is needed, now. I wasn't thinking in terms of smart and dumb components.

Although something about the idea of a tree of components having most of the parent, outer-most components as smart components and then all the edge node components as dumb (this is what I think this smart / dumb component pattern lends itself to) kind of seems like a stink. I don't have an optimal pattern in mind, and I know smart / dumb components are a current popular pattern, but this as a pattern seems like it creates scenarios where if a dumb component is way down the tree you'll have to pass down action methods or dispatch all the way down the tree to get to it, thus making the components on the way to that component possibly carry along unneeded props just to satisfy their children. Maybe this is result of bad component tree design or something, though, on my part.

from react-redux.

aaronjensen avatar aaronjensen commented on April 27, 2024

Just thinking outside the box here, but what if bound actions were just a part of the state:

export default connect(
  state => ({ counter: state.counter,
    increment: state.actions.counter.increment(state.something) })
)(CounterContainer);

or, less radical:

export default connect(
  (state, bind) => ({ counter: state.counter, 
    increment: bind(CounterActions.increment(state.something)) })
)(CounterContainer);

It seems weird to me to do generate two separate objects that are ultimately merged into the child's props.

from react-redux.

acdlite avatar acdlite commented on April 27, 2024

@aaronjensen Regarding your first proposal:

First of all, the state object is not necessarily a plain JavaScript object. Redux makes no assumptions about the type of state returned from the reducer. For instance, you could use an Immutable Map.

Second of all, where are the action creators coming from? You say they're part of the state, but how did they get there? We'd need some sort of system for registering action creators at the global level.

from react-redux.

aaronjensen avatar aaronjensen commented on April 27, 2024

@acdlite Yep, that's right. We have a system for registering reducers, it didn't seem a long stretch to have one for actions. And you're right re: state not being a plain JS object of course. In that case two arguments could come along: (state, actions) or actions could have their own reducer but that seems a little odd.

Tbh, I can't think of a particularly compelling reason for it other than slight convenience at that point at the cost of required registration.

from react-redux.

acdlite avatar acdlite commented on April 27, 2024

How is globally registering action creators more elegant than this?

export default connect(
  state => ({  counter: state.counter }),
  CounterActions
)(CounterContainer);

EDIT: nevermind, I see that you changed your mind :)

from react-redux.

aaronjensen avatar aaronjensen commented on April 27, 2024

Yeah, that solution does look good. It didn't sit well with me at first that props were being combined in secret, but being able to handle the binding automatically makes it worth it. I also like your proposal for the second form which takes a function.

It'd probably be a bad idea to assume that all functions are action creators, yea?

export default connect(
  (state, props) => ({  
    counter: state.counter,
    increment: () => CounterActions.increment(props.something)
  })
)(CounterContainer);

If not, then it allows for everything w/ one argument:

export default connect(
  state => ({
    counter: state.counter
    ...CounterActions,
    ...OtherActions,
  }),
)(CounterContainer);

You could also make it an n-arity function and just merge all of the objects (binding all functions automatically).

This only works if it's safe to assume all functions are action creators though...

from react-redux.

emmenko avatar emmenko commented on April 27, 2024

we could drop <Connector> altogether and encourage people to use @connect decorator

I think it's a good idea, and we can do the same for Provider. This will simplify the API and create less confusion.

This leads to more scalable, maintainable, testable code

@acdlite I was also thinking of dropping bindActionCreators, as it's just syntactic sugar for dispatch(myAction()), but you make a valid point.

And passing the actions as a second argument of connect makes it a good API, given that the binding becomes an implementation detail of the decorator and the user doesn't care about it.

One thing I would also like to have is namespacing props. Basically instead of just spreading the actions or whatever to this.props, we can have a actions object that contains all the actions, and just pass the object to props. Same thing could be done for state. I think this is important when you start having other data in props (e.g.: router) and helps avoiding possible conflicts when merging props.
Here an example:

// Instead of a flat structure like
this.props = {
  children: Object,

  dispatch: Function,
  // state
  todos: Object,
  counter: Object,
  // actions
  addTodo: Object,
  increment: Object,
  decrement: Object,

  // router
  isTransitioning: Boolean,
  location: Object,
  params: Object,
  route: Object,
  routeParams: Object,

  // custom props
  foo: Object,
  onClick: Function
}


// we could have a much cleaner structure
this.props = {
  children: Object,

  dispatch: Function,
  // state
  state: {
    todos: Object,
    counter: Object
  },
  // actions
  actions: {
    addTodo: Object,
    increment: Object,
    decrement: Object
  },

  // router
  router: {
    isTransitioning: Boolean,
    location: Object,
    params: Object,
    route: Object,
    routeParams: Object
  },

  // custom props
  foo: Object,
  onClick: Function
}

Thoughts?

from react-redux.

gaearon avatar gaearon commented on April 27, 2024

I really think accessing props from the owner is a much more common case that using lifecycle hooks.

Generally connecting to Redux should be done at route handler level, and in this case you need the hooks too. Smart components close to the top level rarely receive props that somehow uniquely identify them, so even if they have props, I doubt these props are so relevant to selecting the state. For example, you won't connect <TodoItem>โ€”you'll probably connect the whole <TodoList> in which case props are irrelevant, as you want to select the whole related slice of state.

Can you please help me by providing a few examples where props are important at the connect level?

This seems like a micro-optimization

It's really not. :-) It seems like a micro-optimization but it's the beginning of death by a thousand cuts. Redux connector sits close to the top of the application, and if at this level we're getting new functions on every prop change, no component down the chain can benefit from shouldComponentUpdate. The whole subtree is contaminated by a few changing functions.

Surely you won't see this problem at the beginning, but as soon as you get a perf bottleneck in one of your components, adding PureRenderMixin to it won't โ€œjust workโ€ anymore because Redux rebinds these actions on every prop change. We don't want people to end up in this situation.

You could get around this by binding once if an object is passed ("bind automatically if an object is passed") but bind every time if its a function.

I can.. But then changing two seemingly equivalent signatures will have bad perf consequences for the whole rendering chain. It's too easy to make this mistake and later have no idea how to optimize your app because shouldComponentUpdate stopped helping anywhere down the chain.

On the other hand, if we force user to create a component, this won't be a problem, as they will pass the component's functions down. And the component's functions can look into props just fine.

Also if an action creator depends on a prop it's going to lead to updates further down the rendering chain, anyway.

Yes, but in a way totally manageable by shouldComponentUpdate! If unrelatedToHeavyComponents state changes too often, but <HeavyComponents> down the chain accept increment, they won't update every time unrelatedToHeavyComponents changes. On the other hand, if we go with binding on prop change, <HeavyComponents> will receive new increment every time unrelatedToHeavyComponents changes and they'll have to re-render. We don't know for sure which props are used by which action creators. I still think never binding is the easiest solution. It's not too hard to write a component, and you're in full control if you do.

^ The reason I say it's a micro-optimization is you're rarely going to pass action creators more than one level down the component tree.

Can you elaborate on that? I usually pass them several levels down (at some point they turn into dumb on* props but I still pass them down).

it's easy enough to prevent constant rebinding with memoization

Memoization only avoids โ€œrebind on every renderโ€ in favor of โ€œrebind on every prop changeโ€. It's certainly better than nothing, but worse than โ€œnever rebindโ€.

from react-redux.

gaearon avatar gaearon commented on April 27, 2024

@emmenko Namespacing will also kill shouldComponentUpdate optimizations because you can't shallowly compare props anymore and nobody will write code to handle namespaces separately.

from react-redux.

emmenko avatar emmenko commented on April 27, 2024

@gaearon right, haven't consider this. I guess there's no much way around it then...

from react-redux.

gaearon avatar gaearon commented on April 27, 2024

How about we add a third parameter: merge.

Default:

export default connect(
  state => ({ counter: state.counter }),
  CounterActions,
  (state, actions, props) => ({
    ...state,
    ...actions
  })
)(Counter);

But you can also...

export default connect(
  state => ({ counter: state.counter }),
  CounterActions,
  (state, actions, props) => ({
    ...state,
    ...actions,
    increment: (...args) => actions.increment(props.counterId, ...args)
  })
)(Counter);

This gives you the same control, but any binding is explicit and performed by you.

from react-redux.

gaearon avatar gaearon commented on April 27, 2024

In fact as long as you don't care for lifecycle hooks you can do this:

export default connect(
  state => ({ counters: state.counters }),
  CounterActions,
  (state, actions, props) => ({
    counter: state.counters[props.counterId],
    increment: () => actions.increment(props.counterId)
  })
)(Counter);

Binding is performed by you instead of the library, so you can easily find where it happens if you have shouldComponentUpdate problems. If this begins to work bad performance-wise, you have a clear upgrade path: turn merge function into a component.

from react-redux.

emmenko avatar emmenko commented on April 27, 2024

So to be clear, connect would have following signature now?

function connect (state: Function, actions: Object, merge: Function)

This gives you the same control, but any binding is explicit and performed by you.

Not sure exactly what do you mean by binding though. This has nothing to do with "binding the dispatch", right?

you have a clear upgrade path: turn merge function into a component

You mean by putting a component in the middle like in your first example? Or do you mean something else?

Thanks!

from react-redux.

skevy avatar skevy commented on April 27, 2024

You said above that "connecting" components using usually happens at or near the route handler level.

I wholeheartedly agree with this, and is definitely what I've experienced in general. When you bind to Redux (or really, binding to any other Flux library) too far down, then changes are difficult to trace and weird things start to happen.

However, that doesn't mean that you wouldn't want to provide the action creators deeper in the tree. That leads me to believe that maybe we can keep "connect" simple - don't worry about binding any action creators with it - and then introduce some other helper that can be a convienence to bind action creators on a component level.

I worry that connect is becoming too heavy, and originally I really liked how simple connect was to understand. It's almost self-documenting.

from react-redux.

skevy avatar skevy commented on April 27, 2024

An example: if I have a , obviously I want to bind to Redux near the top. Perhaps with the component itself. But what if (because I'm making a sufficiently complicated TodoList) I have the button that I want to check off the todo three levels deeper. In that case, if I'm not connecting the component that deep (which would be silly anyway), I would have to pass "dispatch" down three levels explicitly in props so that I could call the action creator when the button is clicked.

The only thing I really want to get around here is just that explicit passing. I think the dispatching is an implementation detail (albeit an important one). As the user of Redux, I just want to be able to call my action creator, not pass dispatch three layers deep explicitly through props.

from react-redux.

gaearon avatar gaearon commented on April 27, 2024

So to be clear, connect would have following signature now?

function connect (state: Function, actions: Object, merge: Function)

To be honest I'd still rather go with:

connect(
  State => StateProps,
  ?(Dispatch => DispatchProps),
  ?(StateProps, DispatchProps, ParentProps => Props)
)

The reason I don't want โ€œmagicโ€ behavior for objects is people will see this once and use everywhere. It won't be obvious that you don't have to pass an object.

In most cases I'll do this:

connect(
  select,
  bindActionCreators(CounterActions)
)

But there are cases where another option is nicer, for example:

connect(
  select,
  dispatch => ({ dispatch })
)(MySmartComponent) // I want to use vanilla this.props.dispatch

See, I might not want binding a particular set of action creators at all!

Or maybe parameterized actions:

connect(
  select,
  dispatch => dispatch,
  (state, dispatch, props) => ({
    ...state,
    ...bindActionCreators(createMyActions(props.apiUrl))
  })
)(MySmartComponent)

(Again, this is not super performant but my conscience is clear because binding occurs in user code.)

So I'd rather not have the shortcut, and instead force user to understand how bindActionCreator works as a utility. This goes in line with some other shortcuts we're removing in Redux 1.0 API in favor of explicit equivalents.

Not sure exactly what do you mean by binding though. This has nothing to do with "binding the dispatch", right?

Why, that's exactly what I mean :-). In my example, increment: (...args) => actions.increment(props.counterId, ...args) is akin to function binding, but it's performed in your code instead of library code, so it's easier to find this as a perf regression culprit.

You mean by putting a component in the middle like in your first example? Or do you mean something else?

Yes. As soon as you have perf problems you just โ€œupgradeโ€ (or maybe โ€œdowngradeโ€ :-) your function to a proper component. It's similar to how you'd probably โ€œupgradeโ€ pure functions to components after React 0.14 if there are some perf optimizations you can't do in a pure function for whatever reason.

from react-redux.

emmenko avatar emmenko commented on April 27, 2024

@gaearon @acdlite considering another approach, what would be the pros / cons of using context?

Currently we have getState and dispatch there. Would it make sense to have also getActions? Then you don't have to pass them around anymore via props, you can just get them from the context. This would also keep connect a simple component that subscribes to changes.

I don't know, just thinking out loud...

from react-redux.

skevy avatar skevy commented on April 27, 2024

@emmenko this is kind of what I'm getting at as well, but admittedly have not though through possible cons of the "context" idea.

from react-redux.

gaearon avatar gaearon commented on April 27, 2024

However, that doesn't mean that you wouldn't want to provide the action creators deeper in the tree.

I think binding action creators at different tree levels is an anti-pattern. You should strive to do this at the top. Passing additional props as callbacks is really what React is all about.

You can always get around it by having connect(() => ({}), bindActionCreators(MyActions)) in the middle of the tree but it looks weird and I think that's a good thing, as it will encourage people to follow React's explicit way.

I think the dispatching is an implementation detail (albeit an important one). As the user of Redux, I just want to be able to call my action creator, not pass dispatch three layers deep explicitly through props.

That leads me to believe that maybe we can keep "connect" simple - don't worry about binding any action creators with it - and then introduce some other helper that can be a convienence to bind action creators on a component level.

I used to think about connect as a way of subscribing to changes, but I don't anymore. I think connect should be the boundary between Redux-aware and Redux-unaware parts of your component tree. Therefore it fits it to be slightly more powerful, so that components below are completely untied of Redux. Now I think that wherever you materialize the state, you should materialize the actions too.

from react-redux.

gaearon avatar gaearon commented on April 27, 2024

Would it make sense to have also getActions?

Action creators are not a core concept of Redux. There is no reason for them to live at the top. They are just convenience. Sometimes you want a factory of action creators that depends on some props. Can't do this at the very top.

After all if you really want those action creators to be in context, you can do this yourself: https://gist.github.com/mattapperson/db45538f14d6de52f6ad

But it's on your conscience. :-)

from react-redux.

gaearon avatar gaearon commented on April 27, 2024

I'd love to hear some thoughts from @faassen here.

from react-redux.

gaearon avatar gaearon commented on April 27, 2024

Also at this point wrapActionCreators might be a better naming than bindActionCreators.

from react-redux.

emmenko avatar emmenko commented on April 27, 2024

After all if you really want those action creators to be in context, you can do this yourself

Sure, I'm just always a bit afraid how much is it ok to use context. But just to understand, if actions were a core concept, would it have been ok to put them into context and just access them wherever I want?

Anyway, are we heading now towards this connect signature and give it more responsibilities, as you said?

connect(
  State => StateProps,
  ?(Dispatch => DispatchProps),
  ?(StateProps, DispatchProps, ParentProps => Props)
)

from react-redux.

gaearon avatar gaearon commented on April 27, 2024

But just to understand, if actions were a core concept, would it have been ok to put them into context and just access them wherever I want?

Can you rephrase? I don't understand what exactly you are asking.

Anyway, are we heading now towards this connect signature and give it more responsibilities, as you said?

This signature is what feels right to me at the moment. Waiting for @acdlite to take a look.

from react-redux.

gaearon avatar gaearon commented on April 27, 2024

In fact this is more correct signature:

connect(
  State => StateAny,
  ?(Dispatch => DispatchAny),
  ?(StateAny, DispatchAny, ParentProps => Props)
)

We don't really care if they are objects until the third function is applied.

For example, you can do:

connect(
  state => state.counter,
  wrapActionCreators(CounterActions),
  (counter, actions) => ({ counter, ...actions })
)

or:

connect(
  state => state.counter,
  dispatch => dispatch,
  (counter, dispatch) => ({ counter, dispatch })
)

but also:

connect(
  state => ({ counter: state.counter }),
  wrapActionCreators(CounterActions)
)

as before.

from react-redux.

emmenko avatar emmenko commented on April 27, 2024

Just wanted to know how much one is it ok to use context and if it would have been a good approach, in case actions were considered an important part of redux.
Just for my understanding of designing a good API :)

Hope it's clear now, thanks!

from react-redux.

gaearon avatar gaearon commented on April 27, 2024

Named arguments:

Vanilla form

connect({
  state: state => ({ state.counter }),
  actions: dispatch => ({
    increment: dispatch(CounterActions.increment())
  })
})(Counter) 

wrapActionCreators sugar

connect({
  state: state => ({ state.counter }),
  actions: wrapActionCreators(CounterActions)
})(Counter)

I want just the state

connect({
  state: state => ({ state.counter })
})(Counter) 

I want just some action creators

connect({
  actions: wrapActionCreators(CounterActions)
})(Counter) 

I want just the dispatch function

connect({
  actions: dispatch => ({ dispatch })
})(Counter) 

I want to pass several action creator groups down

function wrapActionCreatorMap(map) {
  return dispatch => mapValues(map, actionCreators => wrapActionCreators(actionCreators)(dispatch));
}

connect({
  state: state => state.counter,
  actions: wrapActionCreatorMap({
    counterActions: CounterActions,
    userActions: UserActions
  })
})(Counter)

I want to merge several action creator groups

connect({
  state: state => state.counter,
  actions: wrapActionCreators(...CounterActions, ...UserActions)
})(Counter)

I want to pass actions down in a single object

function merge(counter, actions, props) {
  return { counter, actions, ...props };
}

connect({
  state: state => state.counter,
  actions: wrapActionCreators(CounterActions)
}, merge)(Counter)

I want to use props for getting state and tweaking actions

function merge(counters, actions, props) {
  const { counterId, ...rest } = props;
  const { increment, decrement } = actions;
  return {
    counter: counters[counterId],
    increment: () => increment(counterId),
    decrement: () => decrement(counterId),
    ...rest
  };
}

connect({
  state: state => state.counters,
  actions: wrapActionCreators(CounterActions)
}, merge)(Counter)

from react-redux.

emmenko avatar emmenko commented on April 27, 2024

I like this! ๐Ÿ‘

from react-redux.

emmenko avatar emmenko commented on April 27, 2024

I mean the named args (sorry, didn't see you posted the other approach)

from react-redux.

gaearon avatar gaearon commented on April 27, 2024

I prefer the second version because:

  • We can avoid calling them actions (because they are actionCreators but that's tedious to type)
  • merge argument looks more naturally as the third argument
  • null is like an explicit cry for โ€œdon't subscribe!โ€

from react-redux.

acdlite avatar acdlite commented on April 27, 2024

๐Ÿ‘ I prefer the second version, too. It addresses my primary concern from above, which is to access to owner props without needing an extra smart component in the middle, without sacrificing default performance optimizations.

from react-redux.

gaearon avatar gaearon commented on April 27, 2024

@acdlite

If we agree on that, what do you think is reasonable release-wise? We could release this version as [email protected], or we could release the current version as 1.0 but jump to 2.0 with this proposal.

It probably makes sense to use the new version in new docs..

from react-redux.

emmenko avatar emmenko commented on April 27, 2024

@gaearon do you think connect is still a correct name for the new signature?

from react-redux.

acdlite avatar acdlite commented on April 27, 2024

@gaearon I think we should hold of moving react-redux to 1.0 for a while until we've settled on the new API. The current API is not a 1.0 in my view, and I don't see any problem in keeping react-redux at 0.12 (or whatever) even while Redux gets a 1.0 release. That's the advantage of separating the projects.

from react-redux.

FAQinghere avatar FAQinghere commented on April 27, 2024

Random musings...

<Provider> is a bit generic, would <Redux> work? For somebody new to the framework, it helps them immediately get oriented.

I like 'connect', as in 'connect to Redux, the state manager.' Just brainstorming, but 'inject' might also work... inject (state and wrapped action creators).

Regarding:

connect({
  state: state => ({ state.counter }),
  actions: wrapActionCreators(CounterActions)
})(Counter)

I know it's longer, but I think it would be better to use actionCreators: wrapActionCreators(...). First, this is already the less-preferred, more typerific version of the proposed API. Second, elsewhere we are careful to differentiate between actions and action creators, so I think the value in maintaining the distinction in the API is worth the extra typing for those who want to use this more explicit syntax.

Also, in this version of the API, could we not just pass an array of actionCreators?

connect({
  state: state => ({ state.counter }),
  actionCreators: CounterActions
})(Counter)

Regarding the <Connector> syntax, select= feels a little off for some reason. Hmmm, maybe it's an echo from using Ruby years ago, but select makes me feel like the function is filtering an enumeration, rather than just directly grabbing a slice of the passed in state parameter. Perhaps slice or pick or something along those lines. Then again we're passing in a function, so maybe the term should make that clear, so more like slicer= or picker=? Eg. the slicer function returns the slice of state we want access to.

Just out of curiosity, why was wrapActionCreators better than bindActionCreators?

from react-redux.

taylorhakes avatar taylorhakes commented on April 27, 2024

I don't like the <Redux>. I think it is confusing with v0.12 createRedux() (which wasn't a React component) and makes this component stand out too much.

I am a big fan of the named params on the connect. I think that is less confusing and allows the user to specify the arguments in any order. It also allows for adding to the API later.

I also agree that select doesn't quite fit. My experience with select is linq in C# https://msdn.microsoft.com/en-us/library/bb397927.aspx (Ahh Microsoft!) I like slice or pick, those are familiar from a lot of the JS util libraries. What about sliceFn or pickFn? I know we have docs and type annotations coming, but it makes it very clear what it needs to be just by seeing the param name.

Edit: typo

from react-redux.

sporto avatar sporto commented on April 27, 2024

I personally don't like binding actions creators at the top level and passing them, I cannot see how is that an anti-pattern. Your top component has to know too much about what the children need.

I prefer passing the dispatch function to my components, and have them call it directly by importing the action declaration themselves. In this way I can postpone the decision of what actions I need until I actually need to trigger one down the line. I can even pass the dispatch function to something else.

So please allow for this use case.

from react-redux.

vslinko avatar vslinko commented on April 27, 2024

๐Ÿ‘ for "Normal arguments"

from react-redux.

jlongster avatar jlongster commented on April 27, 2024

Hey all,

I have what's probably a dumb question. I've understood and followed Flux for a long time, but I've never actually built a large app with it, so I don't have as much experience with advanced use cases. For server-side rendering, do you ever actually fire actions on the server? I'm not sure why you would, it seems like action firing is only in response to some external event.

Server-rendering seems like one of the main reasons to not create global action creators that are already bound to your store. Although, another argument is probably that it's clearer to list the actions that a component depends on. But I'm wondering when the former matters, if you don't fire actions on the server? I get that listing actions per-component is clearer though.

from react-redux.

skevy avatar skevy commented on April 27, 2024

@jlongster you definitely can fire actions on the server. For instance, what if you have an action that "gets" data. You may want to do that when bootstrapping a component for instance. And you'd want that to work on both the client and server.

from react-redux.

gaearon avatar gaearon commented on April 27, 2024

Hey @jlongster, thanks for coming by!

For server-side rendering, do you ever actually fire actions on the server? I'm not sure why you would, it seems like action firing is only in response to some external event.

You want to fire actions to prepare the stored state.
This way you can render actual state before sending it to the client.

Server-rendering seems like one of the main reasons to not create global action creators that are already bound to your store.

Yes, this is the main reason why we don't bind right away.

from react-redux.

gaearon avatar gaearon commented on April 27, 2024

I prefer passing the dispatch function to my components, and have them call it directly by importing the action declaration themselves. In this way I can postpone the decision of what actions I need until I actually need to trigger one down the line. I can even pass the dispatch function to something else.

So please allow for this use case

This use case is allowed by the current proposal. You can definitely grab dispatch and pass it down. I guess it comes down to how reusable you want your components to be, and how often the same component do different actions in different contexts. It depends on the app.

from react-redux.

jlongster avatar jlongster commented on April 27, 2024

@skevy @gaearon Thanks! I'm rewriting my blog admin to use Redux and finally making time to think through this.

Currently, all my components can define a static method which can fetch data (performed when I resolve all the routes and stuff), and this data is automatically available. Other than data fetching, are there any other use cases? I'm trying to reconcile Relay with this, and I see some opportunities. With Relay that data fetching happens automatically for you. Since Relay isn't released yet, I don't know how you perform updates, but I think the general flux-style flow of actions works for that.

If I solve the data fetching part, are there any other reasons why I'd need redux on the backend?

EDIT: May just have to wait for Relay, and I could just follow the patterns here and use Redux for data fetching too. Still thinking through.

from react-redux.

gaearon avatar gaearon commented on April 27, 2024

We def want to integrate with Relay when it's out.
Redux can then specialize in handling local state and complex local mutations.

from react-redux.

guillaume86 avatar guillaume86 commented on April 27, 2024

The bindActionsCreators and dispatch thing got me thinking a bit.
I'm currently creating my store in a dedicated file and bind all my actions creators at the same place (the lazy method :) ).

Something like that:

store.js

const store = createStore(...);
export default store;
export const actions = bindActionCreators(..., store.dispatch);

and just reference this module to call actions in smart components (this part could use a decorator to pass arbitrary data into props to make it more explicit that this is a smart component):

MyComponent.js

import { actions } from './store';

class MyComponent {
  handleClick() {
    actions.doSomething();
  }

  ...
}

(Side note: I'm not the only one doing something like that, see https://github.com/jhewlett/react-reversi/blob/master/js/actions/gameActions.js)

That got me thinking, what would be the downside in removing <Provider/> and @provide and use the store instance directly in the @connect decorator like this:

MyComponent.js

import store, { actions } from './store';

@connect(store, state => ...)
class MyComponent {
  handleClick() {
    actions.doSomething();
  }

  ...
}

Maybe I'm missing something (any advantages in having the store in context?) but it seems more simple than the current API and easier to explain, it's actually one level "lower" than the current API, and the current API could be rebuilt on this in a separate lib.

It could also be simplified a bit by using a method to enrich the store with the react connect method:

store.js

const store = reactRedux.makeConnectable(createStore(...));
export default store;
export const actions = bindActionCreators(..., store.dispatch);

MyComponent.js

import store, { actions } from './store';

@store.connect(state => ...)
class MyComponent {
  handleClick() {
    actions.doSomething();
  }

  ...
}

Any thoughs on this? It's just an idea I don't use React/redux much except for a toy project so I'm not really a reference in this area but it never hurt to share ideas.

from react-redux.

vslinko avatar vslinko commented on April 27, 2024

export default store; is singletone.
Singletones doesn't work for universal apps, because on server each client needs personal store.

from react-redux.

guillaume86 avatar guillaume86 commented on April 27, 2024

Ha ok I see the advantage of having a short lived store instance in the components context in that case.

from react-redux.

quirinpa avatar quirinpa commented on April 27, 2024

Hey guys i love alternative API, so what format of it is available in redux-rc?

from react-redux.

gaearon avatar gaearon commented on April 27, 2024

@quirinpa

Redux 1.0 RC does not include React bindings. We separated them in this repository precisely because we want to iterate on them separately.

React-Redux 0.2.1 still has the old API. If you'd like to implement the API proposed here, you're welcome to send a PR! We can then publish it as 0.3.0.

from react-redux.

quirinpa avatar quirinpa commented on April 27, 2024

@gaearon Thanks man but i think i still i don't have the knowledge to :P you have developed awesomeness, thanks!

from react-redux.

ForbesLindesay avatar ForbesLindesay commented on April 27, 2024

One small thought, but shouldn't:

connect(
  state => ({ state.counter }),
  dispatch => ({
    increment: dispatch(CounterActions.increment())
  })
)(Counter) 

be

connect(
  state => ({ state.counter }),
  dispatch => ({
    increment: (...args) => dispatch(CounterActions.increment(...args))
  })
)(Counter) 

Otherwise you would be firing the increment action immediately?

from react-redux.

gaearon avatar gaearon commented on April 27, 2024

@ForbesLindesay Right, my typo.

from react-redux.

quirinpa avatar quirinpa commented on April 27, 2024

Just a substitute until Keats solution is merged :P

import React from 'react';
import { bindActionCreators } from 'redux';
import { Connector } from 'react-redux';
import { isEqual } from 'lodash';

export function connect(select, createActions = () => ({}), merge = (s, a, p) => ({...s, ...a, ...p})) {
    const subscribing = select ? true : false;

    return DecoratedComponent2 => class ConnectorDecorator2 {
        shouldComponentUpdate(nextProps) {
            return subscribing && !isEqual(this.props, nextProps);
        }
        render() {
            return (
                <Connector {...(select ? { select: state => select(state, this.props) } : {})}>
                    {stuff => {
                        const { dispatch, ...rest } = stuff;
                        const actions = (typeof createActions === 'function') ?
                            createActions(dispatch) :
                            bindActionCreators(createActions, dispatch);
                        return <DecoratedComponent2 {...merge(rest, actions, this.props)} />;
                    }}
                </Connector>
            );
        }
    };
}

Edit: I've made a few changes. It now supports the following;

// -------------- EXAMPLE --------------------

import { connect } from 'tools/redux';

@connect(
  null, // provide select or null
  CounterActions // provide actionCreators or function that binds to dispatch, for example:
     // dispatch => ({ incrementCounter: (...args) => dispatch(CounterActions.increment(...args))})
     // or bindActionCreators.bind(undefined, CounterActions)
)
export default class Counter { /* . . . */ }

@gaearon i'm probably doing something wrong and i'm not very confortable with git, so i'd rather hear from you before i send a PR... Cheers.

Edit: Now supports merge :)

from react-redux.

Keats avatar Keats commented on April 27, 2024

Since we are removing Connector in #16, should we also remove Provider ?
This way this package only exposes 2 functions that can be used as decorators or plain functions.

from react-redux.

gaearon avatar gaearon commented on April 27, 2024

@Keats Yeah we might. I haven't looked into it yet, want to get the Connector stuff sorted out first.

from react-redux.

gaearon avatar gaearon commented on April 27, 2024

@Keats To clarify, I don't propose anybody actually remove it before we discuss. There are many concerns: universal rendering, hot reloading, DevTools, etc, and we want to make sure we handle all these scenarios gracefully.

from react-redux.

gaearon avatar gaearon commented on April 27, 2024

I think we should remove provide(). It doesn't work for server rendering (store is different on each request). It also doesn't work for Redux DevTools because they need a separate provider. It locks you into bad patterns.

The only downside of <Provider> is this function-as-a-child gotcha. It will be solved in React 0.14, so I'm convinced that removing provide() is the way forward.

from react-redux.

mmerickel avatar mmerickel commented on April 27, 2024

Late to the conversation but wanted to add an alternative I haven't seen discussed for an action api. One thing I've noticed when writing my first couple redux apps is that redux does a fantastic job of getting almost all of the coupling of my model out of my components. I wanted to continue this trend by removing any coupling to the exact actions creators I'm invoking. The idea was to bind the actions to the dispatcher at the same/similar time that I "bind" the store to the react component tree. In this way I didn't need to import and/or know exactly which action creator I was invoking. I simply use it by name just like I use the state.

@connect(
    (state, props) => ({
        todos: state.todos,
    }),
    (actions/*, dispatch? */) => ({
        addTodo: actions.addTodo,
    }),
)
class MyAppContainer extends React.Component {
    render() {
        const {addTodo, todos} = this.props;
        return (
            <div>
                <button click={addTodo}>Add Todo</button>
                <ul>{todos.map(::this.renderTodo)}</ul>
            </div>
        );
    }
}

const store = createStore(reducer);
const actions = {
    addTodo(name) {
        return {type: ADD_TODO, name};
    }
};

React.render(<Provider store={store} actions={actions}>{() => <MyAppContainer />}</Provider>, document.getElementById('app'));

This is nice because action creators tend to store most of the business logic in the app and they often need things like api utils, etc. This pre-binding is a perfect opportunity to configure your action creators with things like an apiUtils object and then that stuff doesn't need to be known about in the view components and your action creator also doesn't need to be coupled to a specific apiUtils singleton like I see in many examples.

To be clear the Provider would be responsible for binding all actions passed into it to the store. This could be separated into a StoreProvider and ActionProvider if desired, and do not need to be done at the same time but I think that may be too much. The Provider then just adds the store and actions context values allowing @connect to access them.

from react-redux.

 avatar commented on April 27, 2024

I second the idea @mmerickel describes above. I was actually going to suggest exactly the same thing since I have been binding the actionCreators at the top level and passing them down. I'd love if actions could be "selected" in the same way as slices of state in the connect decorator.

I've been staying away from using dispatch in my dumb components and instead just using pre-bound actionCreators from props, but I like that there is a very easy way in his sample above to get at the dispatcher. To me this supports both styles of working with actions, and because the api would be very similar to what is already understood for selecting state, it would reduce cognitive load required to start being productive with the react-redux bindings.

from react-redux.

gaearon avatar gaearon commented on April 27, 2024

@danmartinez101 @mmerickel

One of the reasons I don't want to do this is because code splitting will be harder.
People are likely to keep wanting the same structure, and will do hacks to make it work with code splitting.
The current proposal works with code splitting with no modifications.

I feel this is more opinionated than I'm willing to go. I'm happy to see this explored in alternative bindings though!

Everyone, thanks for the discussion, let's continue in #16.

from react-redux.

mmerickel avatar mmerickel commented on April 27, 2024

@gaearon Can you clarify what you mean by code splitting? My assumption by exposing the dispatch to the action getter was that this would help if you didn't want to do <Provider actions={actions}>. Maybe exposing a binder instead of dispatch works better which seems quite similar to what your original proposals have been above.

from react-redux.

gaearon avatar gaearon commented on April 27, 2024

@mmerickel Code splitting = when your app's modules are loaded by demand. As result, not all actions are available from the beginning.

from react-redux.

mmerickel avatar mmerickel commented on April 27, 2024

That's an issue with the single store as well isn't it? Presumably when you add reducers you can subscribe to that change and add actions as well, no?

from react-redux.

Related Issues (20)

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.