Reflux/React - Managing state with asynchronous data - reactjs

I think this is a trivial use case but I am still getting to grips with aspects of Flux and React. In my example I am using Reflux and React Router.
I have a basic component that displays the details of a sales lead. Using the lead id from the URL parameter, it makes a call to my service to get the lead:
var React = require('react'),
Reflux = require('reflux');
var leadStore = require('../stores/lead'),
actions = require('../actions');
var Lead = React.createClass({
render : function () {
var p = this.props.lead;
return (
<div>
<h2>{p.details}</h2>
<p>{p.description}</p>
</div>
);
}
});
module.exports = React.createClass({
mixins: [Reflux.connect(leadStore)],
componentDidMount : function () {
actions.loadLead(this.props.routeParams.id);
},
render : function () {
if (!this.state.lead) {
return (
<Lead lead={this.state.lead} />
);
}
return (
<div>Loading</div>
);
}
});
If I don't include the conditional check then on the first pass the render method will fire and kick off an error because there's nothing in state. I could set some default state parameters but what I would ultimately like to do is show a pre-loader.
The approach I've taken feels wrong and I was wondering what the common practice was for this situation? All the examples I've seen use default states or pre-load mixins. I'm interested in knowing if there's a better way to make use of the component life-cycle?

Aside from if (!this.state.lead) should be if (this.state.lead) it looks ok. Another way would be.
module.exports = React.createClass({
mixins: [Reflux.connect(leadStore)],
componentDidMount : function () {
actions.loadLead(this.props.routeParams.id);
},
render : function () {
var returnIt;
if (this.state.lead) {
returnIt = (<Lead lead={this.state.lead} />);
} else {
returnIt = (<div>Loading</div>);
}
return (returnIt);
}
});

React Wait to Render is worth checking out. It's a tiny component that will display a loader until all of the component's props are defined, e.g. while we wait for lead data. Good for keeping the render clean, as easy as:
<WaitToRender
wrappedComponent={Lead}
loadingIndicator={LoadingComponent}
lead={this.state.lead} />
You can also use it to decorate your Lead component.

Related

React subscriptions which depend on state

We are currently refactoring to use higher-order components. For the most part this is making everything much simpler.
We have HOCs for fetching data and listening to stores. For example, we have connectStores, which takes a list of stores to subscribe to and a function to fetch the data (to pass as extra props):
connectStores(FooComponent, [FooStore], function (props) {
return {
foo: FooStore.get(props.id),
};
});
However, there are a few places where the process of fetching the data from the store depends upon the state. For example, we have a SelectFooPopup the presents the user with a list of items to select from. But there is also a search box to filter the list, so at the moment the component listens directly to the store and then fetches the data itself like this:
componentDidMount() {
var self = this;
this.listenTo(FooStore, 'change', function () {
self.forceUpdate();
});
}
render() {
var items = FooStore.search(this.state.searchText);
// render...
}
(this.listenTo is a mixin which we're trying to replace with HOCs so we can use ES6 classes)
I can think of a few options, but I don't like any of them:
Option 1: Remove listenTo and cleanup the listener manually
componentDidMount() {
var self = this;
this.listener = function () {
self.forceUpdate();
};
FooStore.on('change', this.listener);
}
componentWillUnmount() {
if (this.listener) {
FooStore.removeListener('change', this.listener);
}
}
render() {
var items = FooStore.search(this.state.searchText);
// render...
}
I really hate having to do this manually. We did this before we had the listenTo mixin and it's far too easy to get wrong.
This also doesn't help when the subscription has to fetch the data from the server directly rather than using a pre-filled store.
Option 2: Use connectStores but don't return any extra data
class SelectFooPopup extends React.Component {
render() {
var items = FooStore.search(this.state.searchText);
}
}
connectStores(SelectFooPopup, [FooStore], function (props) {
// Just to forceUpdate
return {};
});
This just feels wrong to me. This is asking for trouble when we start optimising for pure components and suddenly the child component doesn't re-render anymore.
Option 3: Use connectStores to fetch all the data and then filter it in render
class SelectFooPopup extends React.Component {
render() {
var items = filterSearch(this.props.items, this.state.searchText);
}
}
connectStores(SelectFooPopup, [FooStore], function (props) {
return {
items: FooStore.getAllItems(),
};
});
But now I have to have a completely separate filterSearch function. Shouldn't this be a method on the store?
Also, it doesn't make much difference in this example, but I have other components with a similar issue where
they are fetching data from the server rather than subscribing to a pre-filled store. In these cases the
data set is far too large to send it all and filter later, so the searchText must be available when fetching the data.
Option 4: Create a parent component to hold the state
Sometimes this is the right solution. But it doesn't feel right here. The searchText is part of the state of this component. It belongs in the same place that renders the search box.
Moving it to a separate component is confusing and artificial.
Option 5: Use a "parentState" HOC
function parentState(Component, getInitialState) {
class ParentStateContainer extends React.Component {
constructor(props) {
super();
this.setParentState = this.setParentState.bind(this);
if (getInitialState) {
this.state = getInitialState(props);
} else {
this.state = {};
}
}
setParentState(newState) {
this.setState(newState);
}
render() {
return <Component {...this.props} {...this.state} setParentState={ this.setParentState } />;
}
}
return ParentStateContainer;
}
// Usage:
parentState(SelectFooPopup, function (props) {
return {
searchText: '',
};
});
// In handleSearchText:
this.props.setParentState({ searchText: newValue });
This also feels really wrong and I should probably throw this away.
Conclusion
In React we have 2 levels: props and state.
It seems to me that there are actually 4 levels to think about:
props
data that depends on props only
state
data that depends on props and state
render
We can implement layer 2 using HOCs. But how can we implement layer 4?

Dispatched and re-rendered callback?

It's easiest to explain what I'm trying to accomplish with an example:
addContact = ev => {
ev.preventDefault();
this.props.setField('contacts', contacts => update(contacts, {$push: [{name: 'NEW_CONTACT'}]}));
this.props.setFocus(`contacts.${this.props.data.contacts.length-1}.name`);
};
In this example, this.props.setField dispatches an action which causes an extra field to be added to my form.
this.props.setFocus then attempts to focus this new field.
This won't work because the form hasn't re-rendered yet when setFocus is called.
Is there any way to get a callback for when my component has been re-rendered after a dispatch call?
If you need to see it, setField looks like this:
setField(name, value) {
if(_.isFunction(value)) {
let prevValue = _.get(data, name);
if(prevValue === undefined) {
let field = form.fields.get(name);
if(field) {
prevValue = field.props.defaultValue;
}
}
value = value(prevValue);
}
dispatch(actions.change(form.id, name, value));
},
I would put
this.props.setFocus(`contacts.${this.props.data.contacts.length-1}.name`);
in componentDidUpdate and I would call it on some condition. Like let's say, prevProps.data.contact.length < this.props.data.contacts.
UPDATE
You should keep this:
addContact = ev => {
ev.preventDefault();
this.props.setField('contacts', contacts => update(contacts, {$push: [{name: 'NEW_CONTACT'}]}));
};
In a parent component, and in that component you will render all the sub components:
render() {
return {
<div>
{contacts.map(c => <ContactComponent key='blah' contact={c}>)}
<a onClick={addContact}>Add Contact</a>
</div>
};
}
Then your contact component, will be as you like, the same goes for all the other elements you want to accommodate with this functionality.
At that point, you're asking:
Where is the focus thingy?
What you need for this abstraction-ish is higher order composition. I will give you an example, but please make time to read about HOCs.
This will be you HOC:
function withAutoFocusOnCreation(WrappedComponent) {
// ...and returns another component...
return class extends React.Component {
componentDidMount() {
// contacts string below can be changed to be handled dynamically according to the wrappedComponent's type
// just keep in mind you have access to all the props of the wrapped component
this.props.setFocus(`contacts.${this.props.data.contacts.length-1}.name`);
}
render() {
return <WrappedComponent {...this.props} />;
}
};
}
And then in each child component you can use it as a decorator or just call it with your HOC and that's all. I won't write more, but do make the time to read more about HOCs, here is the official documentation's page
official documentation's page. But you can check Dan Abramov's video on egghead as well. I hope my answer helps you, please accept it if it does :) Take care!

React Isomorphic Rendering - handle window resize event

I would like to set the state of a component based on the current size of the browser window. The server-side rendering has been used (React+Redux). I was thinking about using the Redux store as a glue - just to update the store on resize.
Is there any other/better solution that doesn't involve Redux.
Thanks.
class FocalImage extends Component {
// won't work - the backend rendering is used
// componentDidMount() {
// window.addEventListener(...);
//}
//componentWillUnmount() {
// window.removeEventListener('resize' ....);
//}
onresize(e) {
//
}
render() {
const {src, className, nativeWidth, nativeHeight} = this.props;
return (
<div className={cn(className, s.focalImage)}>
<div className={s.imageWrapper}>
<img src={src} className={_compare_ratios_ ? s.tall : s.wide}/>
</div>
</div>
);
}
}
I have a resize helper component that I can pass a function to, which looks like this:
class ResizeHelper extends React.Component {
static propTypes = {
onWindowResize: PropTypes.func,
};
constructor() {
super();
this.handleResize = this.handleResize.bind(this);
}
componentDidMount() {
if (this.props.onWindowResize) {
window.addEventListener('resize', this.handleResize);
}
}
componentWillUnmount() {
if (this.props.onWindowResize) {
window.removeEventListener('resize', this.handleResize);
}
}
handleResize(event) {
if ('function' === typeof this.props.onWindowResize) {
// we want this to fire immediately the first time but wait to fire again
// that way when you hit a break it happens fast and only lags if you hit another break immediately
if (!this.resizeTimer) {
this.props.onWindowResize(event);
this.resizeTimer = setTimeout(() => {
this.resizeTimer = false;
}, 250); // this debounce rate could be passed as a prop
}
}
}
render() {
return (<div />);
}
}
Then any component that needs to do something on resize can use it like this:
<ResizeHelper onWindowResize={this.handleResize} />
You also may need to call the passed function once on componentDidMount to set up the UI. Since componentDidMount and componentWillUnmount never get called on the server this works perfectly in my isomorphic App.
My solution is to handle resize event on the top-most level and pass it down to my top-most component, you can see full code here, but the gist is:
let prevBrowserWidth
//re-renders only if container size changed, good place to debounce
let renderApp = function() {
const browserWidth = window.document.body.offsetWidth
//saves re-render if nothing changed
if (browserWidth === prevBrowserWidth) {
return
}
prevBrowserWidth = browserWidth
render(<App browserWidth={browserWidth} />, document.getElementById('root'))
}
//subscribing to resize event
window.addEventListener('resize', renderApp)
It obviously works without Redux (while I still use Redux) and I figured it would be as easy to do same with Redux. The advantage of this solution, compared to one with a component is that your react components stay completely agnostic of this and work with browser width as with any other props passed down. So it's a localized place to handle a side-effect. The disadvantage is that it only gives you a property and not event itself, so you can't really rely on it to trigger something that is outside of render function.
Besides that you can workaround you server-side rendering issue by using something like:
import ExecutionEnvironment from 'exenv'
//...
componentWillMount() {
if (ExecutionEnvironment.canUseDOM) {
window.addEventListener(...);
}
}

redux connecting old states

Kind of an interesting problem...
I have a set of components that all use connect to get the state. These components are often children of each other, sometimes deeply nested children.
I want these components to shouldComponentUpdate only when the state changes, otherwise return false. The states are immutable Maps, and I use the is(...)to verify equality. The problem is that when the state changes, some of them see the change, and some of them appear to get an old state, and see no changes. If I complete another action that changes the state, they see the previous state, but not the most recent.
Any ideas? No middleware here.
*Edit... Code. There are a lot of pieces here so bear with me
function checkNewState(nextProps, instance){
return !is(nextProps.state.reducerName, instance.props.state.reducerName)
}
const StupidParent = React.createClass({
shouldComponentUpdate: function(nextProps){
return checkNewState(nextProps, instance)
},
render: function(){
return <p>{this.props.state.reducerName.get('name')}
{this.props.replace(this)}
</p>
}
})
const StupidChild = React.createClass({
shouldComponentUpdate: function(nextProps){
return checkNewState(nextProps, instance);
},
render: function(){
return <p onClick={changeStateNameProperty}>
{this.props.state.reducerName.get('name')}
</p>
}
})
function mapStateToProps(state){
return {state}
}
export const Parent = connect(mapStateToProps)(StupidParent);
export const Child = connect(mapStateToProps)(StupidChild);
<Parent replace={(parent)=>{
return <Child />
}} />
Just a guess, but check if you use the parameter of the function shouldComponentUpdate and not the "this.prop" or "this.state" from your component. The current props/state will give you the old props/state. The new prosp/state is in the import parameters like nextProps/nextState.
shouldComponentUpdate: function(nextProps, nextState) {
return nextProps.id !== this.props.id;
}

Refs in dynamic components with ReactJS

I'm facing a issue and I haven't found any documentantion related.
This is my component's render method:
render()
{
return (
<div refs="myContainer">
</div>
);
}
Additionally I have a method to get data from my Java server:
getAsyncData()
{
$.get('...URL...')
.done(function(response) {
var list = JSON.parse(response); // This is an objects array
var elementsList = [];
list.forEach(function(item) {
elementsList.push(<div ref={item.id}>{item.name}</div>);
});
React.render(elementsList, React.findDOMNode(this.refs.myContainer));
});
}
In my componentDidMount method I just start the async method:
componentDidMount()
{
this.getAsyncData();
}
So I'm getting this error from ReactJS:
Only a ReactOwner can have refs. This usually means that you're trying
to add a ref to a component that doesn't have an owner (that is, was
not created inside of another component's render method). Try
rendering this component inside of a new top-level component which
will hold the ref.
So this means that I'm not able to use my dynamic elements, additionally think that instead of a simple DIV I would have a complex component with methods within.
How can I deal this?
Thank you!
How can I deal this?
By writing the component how it is supposed to. Keep the data in the state. .render() is called when the state changes, updating the output.
Example:
var Component = React.createClass({
getInitialeState() {
return {items: []};
},
getAsyncData() {
$.get(...).done(
response => this.setState({items: JSON.parse(response)})
);
},
render() {
var list = this.state.items.map(
item => <div key={item.id} ref={item.id}>{item.name}</div>
);
return <div>{list}</div>;
}
});
That's what React is all about: Describing the output based on the data. You are not adding or removing elements dynamically, you update the state, rerender and let React figure out how reconcile the DOM.
However, it's unclear to me why you need a ref in this situation.
I recommend to read https://facebook.github.io/react/docs/thinking-in-react.html

Resources