What is the most concise way to trigger route changes based on a change to a state store, using Fluxible and react router?
An example component might take some user input and call an Action on a click event (shortened for brevity)
class NameInput extends React.Component {
constructor (props) {
super(props);
this.state = props.state;
this.handleClick = this.handleClick.bind(this);
}
handleClick (event) {
this.context.executeAction(setName, {name:'Some User Value'});
}
render () {
return (
<div>
<input type="button" value="Set Name" onClick={this.handleClick} />
</div>
);
}
}
export default Home;
The handleClick method executes an Action which can update a Store with our new value.
But what if I also want this to trigger a navigation after the Store is updated? I could add the router context type and directly call the transition method after executing the Action:
this.context.executeAction(setName, {name:'Some User Value'});
this.context.router.transitionTo('some-route');
But this assumes that the setName Action is synchronous. Is this conceptually safe, on the assumption that the new route will re-render once the Action is completed and the Store is updated?
Alternatively, should the original Component listen for Store Changes and start the route transition based on some assessment of the store state?
Using the Fluxible, connectToStores implementation, I can listen for discreet changes to Store state:
NameInput = connectToStores(NameInput, [SomeStore], function (stores, props) {
return {
name: stores.SomeStore.getState().name
}
});
How a could a Store listener of this type be used to initiate a Route change?
I've noticed in my own application that for these kinds of flows it's usually safer to let actions do all the hard work. Annoying thing here is that the router isn't accessible from actions.
So, this is what I'd do:
Create a meta-action that does both: setNameAndNavigate. As payload you use something like this:
{
name: 'Value',
destination: {to: 'some-route', params: []},
router: this.context.router
}
Then, in your action, do the navigating when the setName completes.
It's not ideal, especially the passing of the Router to the action. There's probably some way to attach the Router to the action context, but that's not as simple as I had hoped. This is probably a good starting point though.
Extra reading:
Why do everything in actions? It's risky to execute actions in components in response to store changes. Since Fluxible 0.4 you can no longer let actions dispatch inside another dispatch. This happens a lot faster than you think, for example, executing an action in response to a change in a store, without delaying it with setImmediate or setTimeout, will kill your application since store changes happen synchronously during a dispatch.
Inside actions however, you can easily execute actions and dispatches, and wait for them to complete before executing the next one.
The end result of working this way is that most of your application logic has moved to actions, and your components turn into simple views that set-and-forget actions only in response to user interactions (clicks/scrolling/hover/..., as long as it's not in response to a store change).
The Best way is to create a new action as #ambroos suggested,
setNameAndNavigate. For navigation though, use the navigateAction
https://github.com/yahoo/fluxible/blob/master/packages/fluxible-router/lib/navigateAction.js, you would only have to give the url as argument.
Something like this,
import async from 'async';
import setName from 'some/path/setName';
export default function setNameAndNavigate(context, params, done) {
async.waterfall([
callback => {
setName(context, params, callback);
},
(callback) => {
navigate(context, {
url: '/someNewUrl',
}, callback);
}
], done);
}
let your actions be the main workers as much as possible.
Related
I am currently working on a simple React app with a very common workflow where users trigger Redux actions that, in turn, request data from an API. But since I would like to make the results of these actions persistent in the URL, I have opted for React Router v4 to help me with the job.
I have gone through the Redux integration notes in the React Router documentation but the idea of passing the history object to Redux actions just doesn't feel like the most elegant pattern to me. Since both Redux and Router state changes cause React components to be re-rendered, I'm a little worried the component updates could go a bit out of control in this scenario.
So in order to make the re-rendering a bit more predictable and sequential, I have come up with the following pattern that attempts to follow the single direction data flow principle:
Where I used to trigger Redux actions as a result of users' interactions with the UI, I am now calling React Router's props.history.push to update the URL instead. The actual change is about updating a URL parameter rather than the whole route but that's probably not that relevant here.
Before:
// UserSelector.jsx
handleUserChange = ({ target: selectElement }) => {
// Some preliminary checks here...
const userId = selectElement.value
// Fire a Redux action
this.props.setUser(userId)
}
After:
// UserSelector.jsx
handleUserChange = ({ target: selectElement }) => {
// Some preliminary checks here...
const userId = selectElement.value
// Use React Router to update the URL
this.props.history.push(`/user-selector/${userId}`)
}
The userId change in the URL causes React Router to trigger a re-render of the current route.
Route definition in App.jsx:
<Route path="/user-selector/:userId?" component={UserSelector} />
During that re-render, a componentDidUpdate lifecycle hook gets invoked. In there I am comparing the previous and current values of the URL parameter via the React Router's props.match.params object. If a change is detected, a Redux action gets fired to fetch new data.
Modified UserSelector.jsx:
componentDidUpdate (prevProps) {
const { match: { params: { userId: prevUserId } } } = prevProps
const { match: { params: { userId } } } = this.props
if (prevUserId === userId) {
return
}
// Fire a Redux action (previously this sat in the onChange handler)
this.props.setUser(userId)
}
When the results are ready, all React components subscribed to Redux get re-rendered.
And this is my attempt to visualise how the code's been structured:
If anyone could verify if this pattern is acceptable, I would be really grateful.
For step 3, I suggest a different approach which should be more in line with react-router:
react-router renders a component based on a route
this component should act as the handler based on the particular route it matches (think of this as a container or page component)
when this component is mounted, you can use componentWillMount to fetch (or isomorphic-fetch) to load up the data for itself/children
this way, you do not need to use componentDidUpdate to check the URL/params
Don't forget to use componentWillUnmount to cancel the fetch request so that it doesn't cause an action to trigger in your redux state
Don't use the App level itself to do the data fetching, it needs to be done at the page/container level
From the updated code provided in the question:
I suggest moving the logic out, as you would most likely need the same logic for componentDidMount (such as the case when you first hit that route, componentDidUpdate will only trigger on subsequent changes, not the first render)
I think it's worth considering whether you need to store information about which user is selected in your Redux store and as part of URL - do you gain anything by structuring the application like this? If you do, is it worth the added complexity?
I am using React + Flux for my application. General flow is I will call action method from JSX component and that action method will invoke store which updates state and emits change event and the state will be updated in JSX component.
Now the problem is, I have separated actions and stores into different files based on their work(2 action files and 2 store files).
Can I write this kind of code in JSX component ? (action2.method2() call is waiting for result of actions1.method1() ). If not, how can I improve this ? method1() and method2() are two different actions methods, so we can't call actions2.method2() inside body of actions1.method1() and also method2 needs access to state updated by method1().
this.getFlux().actions.actions1.method1()
.then(() => {
this.getFlux().actions.actions2.method2(this.state.method1UpdatedState);
})
.catch((error) => { console.log("error : ", error)} );
I would recommend keeping actions as simple as possible, and move all business logic to your stores. So, if you need data from store1 in store2 just inject store1 in your store2 as a dependency and listen to the specific event, generated by action1 in your store2. You definitely, should avoid implementing this logic in your component.
For example, this how store2.js should look
import store1 from './store1';
store2.dispatchToken = AppDispathcer.register((payload) => {
var action = payload.action;
switch(action) {
case 'ACTION_1':
AppDispathcer.waitFor([store1.dispatchToken]);
let data = store1.getDataFromAction1();
// ... here you can update store2 data
// ... and call store2.emitChange();
break;
}
});
Update
Thanks to #Dominic Tobias and #gabdallah for spotting my embarrassing mistake.
The correct answer is of course;
so try checking action.payload.
The other comments regarding the switch statement and the action object we're referring to errors I made in my example, which I've since corrected.
Imagine I've combined the the following two reducers;
import { combineReducers } from 'redux'
import { routerStateReducer } from 'redux-router'
import entries from './entries'
export default combineReducers({
router: routerStateReducer,
entries
})
I would like to mutate the entries state based on another part of the global state, in this case; the router state provided by redux-router in order for example to implement pagination.
How could I do something like this?
// entries.js
import { ROUTER_DID_CHANGE } from 'redux-router/lib/constants'
const initialState = {}
function entries (state = initialState, action) {
switch (action.type) {
case ROUTER_DID_CHANGE:
// How can I access `state.router` here in order to do something like this;
if (routerState.location.pathname === '/entries') {
return {
...state,
page: routerState.location.query.page || state.page,
limit: routerState.location.query.limit || state.limit
}
}
return state
}
}
Some other approaches that come to mind;
connect the router state to the Entries route component, use the componentWillMount lifecycle method to check router state and call an action creator with the page and limit values mutating the entries state in turn. This would work; however I'm using some transition middleware to call a static fetchData method on the route component prior to mounting it, so the data get's fetched, then the pagination action creator would be called afterwards; not the desired behaviour.
listen to router actions somewhere else (i.e a dedicated router redux module), call an action creator on the entries store, but I'm not sure how well this fits with redux-router or how I would get access to the router part of the global store.
don't do this at all; simply query the router state in the static fetchData method
Other useful info;
The implementation in question is Universal App heavily inspired by react-redux-universal-hot-example
Relevant deps
react 0.14.2
redux 3.0.3
react-router 1.0.0-rc3
redux-router 1.0.0-beta3
How can I achieve this or similar behaviour? Am I even thinking about this the right way?
Back in the old days before things got simpler for a developer people would listen to the popstate event on the history object ;)
It looks like the required info is on the action?
history.listen((error, nextRouterState) => {
...
store.dispatch(routerDidChange(nextRouterState));
and the action:
export function routerDidChange(state) {
return {
type: ROUTER_DID_CHANGE,
payload: state
};
}
So try checking action.payload.
However your switch statement is using action instead of action.type so there's something fishy going on there.. You shouldn't need to do action = {} either - see http://redux.js.org/docs/basics/Reducers.html
I am working with React and I am trying to understand the lifecycle. I am doing a componentWillMount method in order to get the props I need before the render occurs. I need to know how to update the state when the view loads.
All I am trying to do is a GET request in order to get a list of dealers for a Casino Game. Basically, I am missing 1 or 2 steps which are for render the dealers's list in the DOM
I will show what I am doing with my code and after that I will explain what I want
Actions part
getDealerActions.js
class GetDealersActions {
constructor () {
this.generateActions('dealerDataSuccess', 'dealerDataFail');
}
getDealers (data) {
const that = this;
that.dispatch();
axios.get('someroute/get-dealers/get-dealers')
.then(function success (response) {
that.actions.dealerDataSuccess({...response.data});
})
}
};
then we move to the stores
getDealersStore.js
class GetDealersStore {
constructor () {
this.state = {
dealerData : null,
};
}
#bind(GetDealersActions.dealerDataSuccess)
dealerDataSuccess (data) {
this.setState({
dealerData : data,
});
console.log(this.state.dealerData);
}
}
in this case that console.log(this.state.dealerData); returns something like this which is exactly what I need
Object {dealersData: Array[3]}
the problems comes in the component part, honestly because I don't know how to handle the data here
#connectToStores
export default class Dealers extends Component {
static contextTypes = {
router : React.PropTypes.func,
}
constructor (props) {
super(props);
this.state = {}
}
static getStores () {
return [ GetDealersStore ];
}
static getPropsFromStores () {
return GetDealersStore.getState();
}
componentWillMount () {
console.log('###', this.props);
GetDealersActions.getDealers();
}
render () {
console.log('>>>', this.props);
let content;
if (this.state.dealerData) {
content = this.state.dealerData.map((item) => {
return <div key={item.CardId}>{item}</div>;
});
} else {
content = <div>Loading . . .</div>;
}
return (
<div>
<div>{content}</div>
</div>
);
}
}
all I get here <div>{content}</div> is Loading . . . because this.state is coming like this Object {}
A weird situation I am getting here, is that this view is rendering twice, the 1st time is rendering, and the console.log('>>>', this.props); returns this >>> Object {params: Object, query: Object} and the second time it renders, fires this >>> Object {params: Object, query: Object, dealerData: Object} which is what I need.
So, why componentWillMount is waiting the render method in order to get fired ?
It's not weird at all. componentWillMount will fire before render, and in the first-pass you are invoking an action to get the dealers GetDealersActions.getDealers(); which is basically an async command. Since it is async, the component will render once before it gets data, and then again after the store publishes a changed event, which will re-trigger rendering.
Here is an approximation of the sequence of actions happening in your example:
componentWillMount invokes getDealers command (which is async)
initial render with default component state
Async operation completed in action creator and store is set with dealer data
store publishes a changed event, which re-triggers rendering
second render invoked with the dealer data in component state.
The problem is that React will run it's lifecycle methods in a certain sequence, not caring about you invoking some async method. So basically you don't have a way to stop rendering just because you invoked a command to get the dealers. That is a limitation of react (or a feature), which surfaces when combined with async programming and you should accept it as is.
If you accept the fact that React will render twice, you can utilize that in your favor, so on first render you could just show a loading indicator (e.g. a spinning wheel) and when the data loads you just display it in the second render.
However, if you are not convinced and still want to avoid double-rendering in the initial load, you could do prefetching of the data before you mount the application component, which would ensure that initial data is loaded in the store before the first render, which would mean that you wouldn't have to invoke getDealers in componentWillMount since the data would already be in the store on the first render.
As a reminder, double-rendering is not a significant performance problem, like it would be in Angular.js or Ember.js, since React is very efficient at DOM manipulation, but it could produce some UX issues if not handled properly.
I am currently working on a prototype application using the flux pattern commonly associated with ReactJS.
In the Facebook flux/chat example, there are two stores, ThreadStore and UnreadThreadStore. The latter presents a getAll method which reads the content of the former synchronously.
We have encountered a problem in that operations in our derived store would be too expensive to perform synchronously, and would ideally be delegated to an asynchronous process (web worker, server trip), and we are wondering how to go about solving this.
My co-worker suggests returning a promise from the getter i.e.
# MyView
componentDidMount: function () {
defaultState = { results: [] };
this.setState(defaultState);
DerivedStore.doExpensiveThing()
.then(this.setState);
}
I'm not entirely comfortable with this. It feels like a break with the pattern, as the view is the primary recipient of change, not the store. Here's an alternative avenue we've been exploring - in which the view mounting event dispatches a desire for the derived data to be refreshed (if required).
# DerivedStore
# =========================================================
state: {
derivedResults: []
status: empty <fresh|pending|empty>
},
handleViewAction: function (payload) {
if (payload.type === "refreshDerivedData") {
this.state.status = "pending"; # assume an async action has started
}
if (payload.type === "derivedDataIsRefreshed") {
this.state.status = "fresh"; # the async action has completed
}
this.state.derivedResults = payload.results || []
this.notify();
}
# MyAction
# =========================================================
MyAction = function (dispatcher) {
dispatcher.register(function (payload) {
switch (payload) {
case "refreshDerivedData":
doExpensiveCalculation()
.then(function(res) {
dispatcher.dispatch({
type: "derivedDataIsRefreshed",
results: res
})
})
);
}
});
};
# MyView
# =========================================================
MyView = React.createClass({
componentDidMount: function () {
if (DerivedStore.getState().status === "empty") {
Dispatcher.dispatch("refreshDerivedData");
}
},
getVisibility: function () {
return DerivedStore.getState().status === "pending" ? "is-visible" : ""
},
render: function () {
var state = DerivedStore.getState()
, cx = React.addons.classSet
, classes = cx({
"spinner-is-visible": state.status === "pending"
});
return <div {classes}>
<Spinner /> # only visible if "spinner-is-visible
<Results results={state.derivedResults}/> # only visible if not...
</div>;
}
});
# MyService
# =========================================================
# ensure derived data is invalidated by updates in it's source?
OriginalStore.addListener(function () {
setTimeout(function () {
dispatcher.dispatch({
type: "refreshDerivedData"
})
}, 0);
});
What I like about this approach is that the view treats the DerivedStore as it's view model, and views of this ilk are primarily interested in the freshness of their view model. What concerns me however is the potential for stores coming out of sync.
My question(s) then:
is the promise approach acceptable?
is the second approach better/worse? If so, why?
is there an existing "canonical" approach to this problem?
PS: sorry if there are any fundamental linting errors in this code, I've been working in Coffeescript for the last 3 months and it's destroyed my linting powers...
All async actions should be caused by the creation of an action. The completion of an async action should be signaled by the creation of another action. Stores may listen to these actions, and emit a change event.
In your component you listen to a DerivedStore for changes. An action can be created from anywhere, such as in your component or another store. The data is (eventually) derived, the store is updated, a change event is emitted, and your component(s) apply the event payload to state.
All in all, your component doesn't actually know if what's happening behind the scenes is sync or async. This is great because it allows you to make these performance changes behind the scenes without risk of breaking your components.
Pure stores usually only have one public function which gets the state of the store. In your components you should only call this in getInitialState, or better yet: have a mixin which does this and adds the change listener for you.
It sounds like a combination of the following discussions on github could help you.
store.getItem() which may require an async server call:
https://github.com/facebook/flux/issues/60
Managing amount of client-side data:
https://github.com/facebook/flux/issues/62
Essentially getting the store data is synchronous, the component could then tell the store to do the long running task but then forgets about it.
Once the task is completed in the store an action is created and the flow thing happens at which time the component can synchronously get the required information from the store.
Does that make sense?
If I was going to create an async process in the most Flux way possible, I would approach it much like an XHR request -- kick off the async process in either the Action Creator or the Store (whichever makes the most sense for the app) and then call a new Action Creator to dispatch a new action when the async process completes. This way, multiple stores can respond to the completed expensive async process, and the data flow is still emanating from an Action.
You could also just add a handler to your Store that gets called when a certain event is emited in your store
So lets say in your store you have a method:
Store = { ...
addUnreadDoneListener:function(callback){
this.on(SOME_EVENT_CONSTANT, callback);
},
...}
In your componentWillMount you can register to this "addUnreadDoneListener" with a function of your component, which then gets called everytime your store emits this certain event.
I personally do this aswell in my project. And I think its pretty easy to manage this way
Hope this helped.
EDIT: I forgot to mension... I use Eventemitter to do this.