Hang on, I'm not saying flux is dead, or that redux isn't amazing.
Okay ...
Most everybody is familiar with flux by now:
User Interaction --event data-->
Action Creator --action-->
Dispatcher --payload-->
Stores --change listeners-->
Views Update
There's two types of data in an app:
- Shared State (auth data, etc)
- Component State (how many "attendees" to a meeting in a form)
There are also two types of actions:
- Application actions (triggers change to shared state)
- Component actions (triggers change to component state)
I turned to flux to solve problem (1) in both cases above.
- Share state between components with stores, get updates via subscriptions.
- Avoid passing callbacks several levels down the component tree for application actions.
I then ended up forcing (2) into flux and started getting grumpy. But using flux for some state, but not all state, and for some actions, but not all actions, feels just as bad to me.
I wanted to just go back to component state, its much easier to deal with. But what about shared state and application actions?
Turns out, React already has a solution for this with context
.
class App extends React.Component {
constructor (props, context) {
super(props, context)
// App is your "global store"
this.state = {
auth: null
}
}
handleDispatch (action) {
// Handle dispatched actions by changing state
if (action.type === 'AUTH')
this.setState({ auth: action.auth })
}
render () {
// new state goes to `AppContext`
return (
<AppContext
state={this.state}
onDispatch={(action) => this.handleDispatch(action)}
>
<AppView/>
</AppContext>
)
}
}
class AppContext extends React.Component {
// receives state and dispatch from `App`
static propTypes = {
state: object,
onDispatch: func,
}
// makes app state and dispatch available to all components
// via context
static childContextTypes = {
state: object,
dispatch: func,
}
getChildContext () {
return {
state: this.props.state,
dispatch: (action) => this.props.onDispatch(action)
}
}
render () {
return this.props.children
}
}
class AppView extends React.Component {
render () {
return (
<div>
<Header/>
<Content/>
</div>
)
}
}
class Header extends React.Component {
// ask for global state with context types
static contextTypes = {
state: object,
dispatch: func
}
constructor (props, context) {
super(props, context)
// can have local state
this.state = {
isLoggingIn: false
}
}
login () {
this.setState({ isLoggingIn: true })
login((auth) => {
// dispatch change to app state
this.context.dispatch({ type: 'AUTH', auth })
this.setState({ isLoggingIn: false })
})
}
render () {
let { auth } = this.context.state
return (
<div>
<h1>
{this.context.state.auth ?
`Welcome ${auth.name}` :
'You are not logged in'
}
</h1>
{this.context.state.auth === null ? (
<button
onClick={() => this.login()}
disabled={this.state.isLoggingIn}
>Login</button>
) : (
<button onClick={() => this.logout()}>Logout</button>
)}
</div>
)
}
}
- click "Login" button
- local state changes
dispatch
method available inHeader
because ofAppContext
- app action is dispatched
App
handles action, sets stateAppContext
gets newApp
state, is now available on context- Entire app can now access
auth
state fromApp
via context.
You want middleware? How about just overloading React.Component.prototype.setState
?
Anyway, I love redux, I'm just thinking out loud, and looking for the dumbest thing that will work.
Or, another way to say that, when I add a feature I don't want to have to edit 6 files when all 6 files only affect one or two components.
Or maybe, what you really needs, is a way to "reduce" your 6 files? Maybe something like https://github.com/erikras/ducks-modular-redux
I don't like the "default" idea of "break" the application by types (/constants, / stores, / actions) rather than by context (/activities.js, users.js, etc.)... but I think I'm alone in this opinion;)