How can I have a global variable in a component that all the methods have access to it? I can't use the state because I have nested rendering and by changing the state of a variable I'm getting the warning that the render should be pure and don't use the setState inside render....! So I need to have a regular global variable accessible by all methods of the component and be able to change the value of it wherever is needed. Can anyone help me please?
P.S. Why I shouldn't change a state like this: this.state.variable = true;?
So here is my simplified code. this.setState({hidden: true}); will make the warning because this component is rendering inside another component. I need to make hidden to behave like a global variable and don't use the state!
const Component = React.createClass({
mixins: [React.addons.PureRenderMixin],
getInitialState: function() {
return {hidden: false};
},
renderFixedFields(.....) {
var request_link;
if (!this.state.hidden) {
request_link = <Link to={`/${record.get('id')}/request`}>Send Request </Link>
}
else {
request_link = null
}
return (
<div>
{request_link}
</div>
)
},
renderField(.....) {
var self = this;
//....
if (fieldID == "open_access") {
this.setState({hidden: true});
}
return (
//.....
);
},
renderFieldBlock(........) {
const [maj, min] = getMajMinFields(s);
//....
const minFields = min.entrySeq().map(
([id, f]) => this.renderField(fieldID, .....));
return (
//...
<div>
{ minorFields }
</div>
);
},
}
Setting state inside of render() is always going to be an issue as state changing will issue a render(). While the specific thing you are doing may not cause an issue, you should really be thinking about the lifecycle of your component and where that state really needs to change. If you need to change state based on new props then use componentWillReceiveProps:
componentWillReceiveProps: function(nextProps) {
this.setState({
likesIncreasing: nextProps.likeCount > this.props.likeCount
});
}
Related
I'm looking to render some firebase data to the HomeFeed component. I update the state in the componentDidMount method. You can see what that looks like below. It's an array. Should I just map over that using the map function? How do I access the specific info like "title", "link", "type", etc. to be able to render it?
Thanks a lot!
var React = require('react');
var Rebase = require('re-base');
var base = Rebase.createClass("https://nimbus-8ea70.firebaseio.com/");
// TODO: Render Firebase data to screen.
// Home
// <Home />
var HomeContainer = React.createClass({
render : function() {
return (
<div className="homeContainer">
<HomeFeed />
</div>
);
}
});
// Home Feed
// <HomeFeed />
var HomeFeed = React.createClass({
componentDidMount: function() {
base.fetch('article', {
context: this,
asArray: true,
then(data){
console.log(data);
this.setState({
feed: data
})
}
});
},
getInitialState: function() {
return {
feed: []
}
},
render : function() {
return (
<div className="homeFeed">
{/* Use map function here? */}
</div>
);
}
});
module.exports = HomeContainer;
render will run whenever state has been changed (unless you modify this behavior with, say, shouldComponentUpdate) so as long as you use setState properly your component will automatically update when its state changes.
If you're asking specifically how to turn an array into something that render understands, then yes, map is a very common way to do that. It might look something like this:
render : function() {
return (
<div className="homeFeed">
{this.state.feed.map(function(ea){
return <div>{ea.someProperty}</div>
})}
</div>
);
}
Note that you have to wrap ea.someProperty in curly braces because you're basically inserting JSX inside of a JavaScript expression inside of even more JSX. This kind of nested JSX/Expression/JSX structure is something you'll have to get comfortable with in React I'm afraid.
More about array.map
Given the following, is it possible to access the parent context rather than the containers from a child (non-react component) element?
The example logs container, ideally it would log parent. I would like for Parent to be self contained, not to have it's state managed by its container.
var Container = React.createClass({
getInitialState: function () {
return {
context: 'container'
}
},
render: function () {
return (
<Parent>
<a href="#" onClick={function () {console.log(this.state.context);}.bind(this)}>click me</a>
</Parent>
);
}
});
var Parent= React.createClass({
getInitialState: function () {
return {
context: 'parent'
}
},
render: function () {
return (
<div>
{this.props.children}
</div>
);
}
});
If there is another pattern for handling this, please share as well.
Note: To be clear, I understand how the this keyword works and why the above example works as it does. The example is simply meant to illustrate the problem.
You can import some React helpers for that:
var React = require('react')
...
var children = React.Children.map(this.props.children, child => {
return React.cloneElement(child, {
context: this.state.context
})
})
render() {
return <div>{ children }</div>
}
...
Then your child component will have this.props.context which will be the string 'parent', but this must be a React component, as this needs to refer to the component using the parent prop
var YourComponent = React.createClass({
render() {
return (
<a href="#" onClick={() => console.log(this.props.context)}>
click me
</a>
)
}
})
------
var Parent = require('./Parent')
var YourComponent = require('./YourComponent')
...
render() {
return <Parent><YourComponent /></Parent>
}
I do not know about the first part of your question, but since you commented about dynamically creating components, here's how I do it:
You can set a state variable in the constructor of the class and its parent:
if (typeof this.state == 'undefined') {
this.state = {
componentsToRender: <div></div>
};
}
Then in the parent component, in the componentDidMount() function:
var componentsToRender = [];
if ([conditional]) {
// some logic so you know which component to render
componentsToRender.push(<customChildComponentToRender key={} />);
}
else {
componentsToRender.push(<otherComponentToRender key={} />);
}
this.setState({
componentsToRender: <div>{componentsToRender}</div>
});
Make sure to put a key (lines 4 and 7 of the second code block) or React will scream at you.
In response to your initial question, I would watch this video from the ReactJS Conference 2015 to get more of the heart behind a container. After hearing what the guys at Facebook say (who have radical views on containers!), you might want to rethink the design to make your container more of a data layer.
I would check out THIS article from the react website. I think it might give you some intuition on solving your problem.
As a general rule of thumb, I try and only use this.state to handle internal UI state of a specific component. Everything else is passed via props. If you're needing the full context of a component, I would either pass it as a prop or checkout something like flux or redux which will help you manage state between components.
I have got the beginnings of a clickable list component that will serve to drive a select element. As you can see from the below, onClick of the ListItem, I'm passing the state of a child element (ListItem in this case) to the parents (SelectableList, and CustomSelect component). This is working fine. However, what I would also like to do is change the state of the sibling components (the other ListItems) so that I can toggle their selected states when one of the ListItems is clicked.
At the moment, I'm simply using document.querySelectorAll('ul.cs-select li) to grab the elements and change the class to selected when it doesn't match the index of the clicked ListItem. This works - to an extent. However, after a few clicks, the state of the component has not been updated by React (only by client side JS), and things start to break down. What I would like to do is change the this.state.isSelected of the sibling list items, and use this state to refresh the SelectableList component. Could anyone offer a better alternative to what I've written below?
var React = require('react');
var SelectBox = require('./select-box');
var ListItem = React.createClass({
getInitialState: function() {
return {
isSelected: false
};
},
toggleSelected: function () {
if (this.state.isSelected == true) {
this.setState({
isSelected: false
})
} else {
this.setState({
isSelected: true
})
}
},
handleClick: function(listItem) {
this.toggleSelected();
this.props.onListItemChange(listItem.props.value);
var unboundForEach = Array.prototype.forEach,
forEach = Function.prototype.call.bind(unboundForEach);
forEach(document.querySelectorAll('ul.cs-select li'), function (el) {
// below is trying to
// make sure that when a user clicks on a list
// item in the SelectableList, then all the *other*
// list items get class="selected" removed.
// this works for the first time that you move through the
// list clicking the other items, but then, on the second
// pass through, starts to fail, requiring *two clicks* before the
// list item is selected again.
// maybe there's a better more "reactive" method of doing this?
if (el.dataset.index != listItem.props.index && el.classList.contains('selected') ) {
el.classList.remove('selected');
}
});
},
render: function() {
return (
<li ref={"listSel"+this.props.key}
data-value={this.props.value}
data-index={this.props.index}
className={this.state.isSelected == true ? 'selected' : '' }
onClick={this.handleClick.bind(null, this)}>
{this.props.content}
</li>
);
}
});
var SelectableList = React.createClass({
render: function() {
var listItems = this.props.options.map(function(opt, index) {
return <ListItem key={index} index={index}
value={opt.value} content={opt.label}
onListItemChange={this.props.onListItemChange.bind(null, index)} />;
}, this);
return <ul className="cs-select">{ listItems }</ul>;
}
})
var CustomSelect = React.createClass({
getInitialState: function () {
return {
selectedOption: ''
}
},
handleListItemChange: function(listIndex, listItem) {
this.setState({
selectedOption: listItem.props.value
})
},
render: function () {
var options = [{value:"One", label: "One"},{value:"Two", label: "Two"},{value:"Three", label: "Three"}];
return (
<div className="group">
<div className="cs-select">
<SelectableList options={options}
onListItemChange={this.handleListItemChange} />
<SelectBox className="cs-select"
initialValue={this.state.selectedOption}
fieldName="custom-select" options={options}/>
</div>
</div>
)
}
})
module.exports = CustomSelect;
The parent component should pass a callback to the children, and each child would trigger that callback when its state changes. You could actually hold all of the state in the parent, using it as a single point of truth, and pass the "selected" value down to each child as a prop.
In that case, the child could look like this:
var Child = React.createClass({
onToggle: function() {
this.props.onToggle(this.props.id, !this.props.selected);
},
render: function() {
return <button onClick={this.onToggle}>Toggle {this.props.label} - {this.props.selected ? 'Selected!' : ''}!</button>;
}
});
It has no state, it just fires an onToggle callback when clicked. The parent would look like this:
var Parent = React.createClass({
getInitialState: function() {
return {
selections: []
};
},
onChildToggle: function(id, selected) {
var selections = this.state.selections;
selections[id] = selected;
this.setState({
selections: selections
});
},
buildChildren: function(dataItem) {
return <Child
id={dataItem.id}
label={dataItem.label}
selected={this.state.selections[dataItem.id]}
onToggle={this.onChildToggle} />
},
render: function() {
return <div>{this.props.data.map(this.buildChildren)}</div>
}
});
It holds an array of selections in state and when it handles the callback from a child, it uses setState to re-render the children by passing its state down in the selected prop to each child.
You can see a working example of this here:
https://jsfiddle.net/fth25erj/
Another strategy for sibling-sibling communication is to use observer pattern.
The Observer Pattern is a software design pattern in which an object can send messages to multiple other objects.
No sibling or parent-child relationship is required to use this strategy.
Within the context of React, this would mean some components subscribe to receive particular messages and other components publish messages to those subscribers.
Components would typically subscribe in the componentDidMount method and unsubscribe in the componentWillUnmount method.
Here are 4 libraries that implement the Observer Pattern. The differences between them are subtle - EventEmitter is the most popular.
PubSubJS: "a topic-based publish/subscribe library written in JavaScript."
EventEmitter: "Evented JavaScript for the browser." It's actually an implementation of a library that already exists as part of nodejs core, but for the browser.
MicroEvent.js: "event emitter microlibrary - 20lines - for node and browser"
mobx: "Simple, scalable state management."
Taken from: 8 no-Flux strategies for React component communication which also is a great read in general.
The following code helps me to setup communication between two siblings. The setup is done in their parent during render() and componentDidMount() calls.
class App extends React.Component<IAppProps, IAppState> {
private _navigationPanel: NavigationPanel;
private _mapPanel: MapPanel;
constructor() {
super();
this.state = {};
}
// `componentDidMount()` is called by ReactJS after `render()`
componentDidMount() {
// Pass _mapPanel to _navigationPanel
// It will allow _navigationPanel to call _mapPanel directly
this._navigationPanel.setMapPanel(this._mapPanel);
}
render() {
return (
<div id="appDiv" style={divStyle}>
// `ref=` helps to get reference to a child during rendering
<NavigationPanel ref={(child) => { this._navigationPanel = child; }} />
<MapPanel ref={(child) => { this._mapPanel = child; }} />
</div>
);
}
}
I'm using react-bootstrap's ModalTrigger to show a field-heavy modal (based on react-bootstrap's Modal), which means sending it a bunch of props:
<ModalTrigger modal={<MyModal field1={value1} field2={value2} (more fields...)/>}>
Click here to open
</ModalTrigger>
The parent component that creates the trigger has the fields/values passed in via props, and the parent component of that component has it passed as props as well, by the top-level component that actually holds the data. Both are basically pipes, which is a classic childContext scenario, except that it doesn't work. Here's a simplified version of what I've tried:
var MyModal = React.createClass({
contextTypes : {foo : React.PropTypes.string},
render : function() {
return (
<Modal {...this.props} title="MyTitle">
<div className="modal-body">
The context is {this.context.foo}
</div>
</Modal>
);
}
});
var Content = React.createClass({
childContextTypes : {foo: React.PropTypes.string},
getChildContext : function() {return {foo : "bar"}},
render : function() {
return (
<ModalTrigger modal={<MyModal/>}>
<span>Show modal</span>
</ModalTrigger>
)
}
});
The modal pops up with "The context is", without showing the actual context.
I believe this is happening because the prop sent to ModalTrigger is already rendered/mounted somehow, but I'm not sure why. To the best of my understanding, the owner of MyModal is the Content component, which means that the context should be ok, but this is not the case.
Some more information: I already tried passing {...this.props} and context={this.context} to MyModal with no success. Also, maybe relevant, ModalTrigger uses cloneElement to make sure the modal's onRequestHide prop points to the trigger's hide function.
So what am I missing here? :/
React.cloneElement will change the owner of the element when the ref prop is overriden, which means context will not be passed from the previous owner. However, this does not seem to be the case with ModalTrigger.
Note that the owner-based approach won't work altogether in React 0.14, since context will be passed from parent to child and not from owner to ownee anymore. ModalTrigger renders its modal node prop in another branch of the DOM (See OverlayMixin). Thus, your Modal component is not a child nor a descendant of your Content component and won't be passed child context from Content.
As for solving your problem, you can always create a component whose sole purpose is to pass context to its children.
var PassContext = React.createClass({
childContextTypes: {
foo: React.PropTypes.string
},
getChildContext: function() {
return this.props.context;
},
render: function() {
return <MyModal />;
},
});
To use it:
<ModalTrigger modal={<PassContext context={this.getChildContext()}/>}>
As Matt Smith hinted, it turns out that react-bootstrap already includes a very similar approach to forwarding context via ModalTrigger.withContext. This allows you to create a ModalTrigger component class which will forward its context to its modal node prop, no matter its position in the VDOM tree.
// MyModalTrigger.js
module.exports = ModalTrigger.withContext({
foo: React.PropTypes.String
});
There is a much better way of passing context on to your "portal" type components which render their children into a different container outside the React tree.
Using "renderSubtreeIntoContainer" rather than "render" will pass the context into the subtree as well.
It can be used like so:
import React, {PropTypes} from 'react';
import {
unstable_renderSubtreeIntoContainer as renderSubtreeIntoContainer,
unmountComponentAtNode
} from 'react-dom';
export default class extends React.Component {
static displayName = 'ReactPortal';
static propTypes = {
isRendered: PropTypes.bool,
children: PropTypes.node,
portalContainer: PropTypes.node
};
static defaultProps = {
isRendered: true
};
state = {
mountNode: null
};
componentDidMount() {
if (this.props.isRendered) {
this._renderPortal();
}
}
componentDidUpdate(prevProps) {
if (prevProps.isRendered && !this.props.isRendered ||
(prevProps.portalContainer !== this.props.portalContainer &&
prevProps.isRendered)) {
this._unrenderPortal();
}
if (this.props.isRendered) {
this._renderPortal();
}
}
componentWillUnmount() {
this._unrenderPortal();
}
_getMountNode = () => {
if (!this.state.mountNode) {
const portalContainer = this.props.portalContainer || document.body;
const mountNode = document.createElement('div');
portalContainer.appendChild(mountNode);
this.setState({
mountNode
});
return mountNode;
}
return this.state.mountNode;
};
_renderPortal = () => {
const mountNode = this._getMountNode();
renderSubtreeIntoContainer(
this,
(
<div>
{this.props.children}
</div>
),
mountNode,
);
};
_unrenderPortal = () => {
if (this.state.mountNode) {
unmountComponentAtNode(this.state.mountNode);
this.state.mountNode.parentElement.removeChild(this.state.mountNode);
this.setState({
mountNode: null
});
}
};
render() {
return null;
}
};
This is an example of a portal I use in my production app Casalova that render context properly into their children.
Note: this API is undocumented and is likely to change in the future. For now, though, it's the right way to render context into portal components.
IT IS SOLVED. The code was ok, the problem was with improper import.
It is a long post (due to code samples). I'll appreciate your patience and will be very thankful for your help!
We have a RoR back-end and React on the front-end and we are using alt as an implementation of flux. We also use babel to compile ES6 to ES5. So the problem is that I can not render component due to 2 errors.
First is Uncaught TypeError: Cannot read property 'map' of undefined
It appears on the render function of MapPalette component:
render() {
return (
<div>
{this.state.featureCategories.map(fc => <PaletteItemsList featureCategory={fc} />)}
</div>
);
}
And the second is Uncaught Error: Invariant Violation: receiveComponent(...): Can only update a mounted component.
So here is the whole MapPalette component
"use strict";
import React from 'react';
import PaletteItemsList from './PaletteItemsList';
import FeatureCategoryStore from '../stores/FeatureTypeStore';
function getAppState() {
return {
featureCategories: FeatureCategoryStore.getState().featureCategories
};
}
var MapPalette = React.createClass({
displayName: 'MapPalette',
propTypes: {
featureSetId: React.PropTypes.number.isRequired
},
getInitialState() {
return getAppState();
},
componentDidMount() {
FeatureCategoryStore.listen(this._onChange);
},
componentWillUnmount() {
FeatureCategoryStore.unlisten(this._onChange);
},
render() {
return (
<div>
{this.state.featureCategories.map(fc => <PaletteItemsList featureCategory={fc} />)}
</div>
);
},
_onChange() {
this.setState(getAppState());
}
});
module.exports = MapPalette;
FeatureCategoryStore
var featureCategoryStore = alt.createStore(class FeatureCategoryStore {
constructor() {
this.bindActions(FeatureCategoryActions)
this.featureCategories = [];
}
onReceiveAll(featureCategories) {
this.featureCategories = featureCategories;
}
})
module.exports = featureCategoryStore
FeatureCategoryActions
class FeatureCategoryActions {
receiveAll(featureCategories) {
this.dispatch(featureCategories)
}
getFeatureSetCategories(featureSetId) {
var url = '/feature_categories/nested_feature_types.json';
var actions = this.actions;
this.dispatch();
request.get(url)
.query({ feature_set_id: featureSetId })
.end( function(response) {
actions.receiveAll(response.body);
});
}
}
module.exports = alt.createActions(FeatureCategoryActions);
And the last - how I render React component.
var render = function() {
FeatureCategoryActions.getFeatureSetCategories(#{ #feature_set.id });
React.render(
React.createElement(FeatureSetEditMap, {featureSetId: #{#feature_set.id}}),
document.getElementById('react-app')
)
}
First of all, the first error you get:
Uncaught TypeError: Cannot read property 'map' of undefined
Is because this.state in your component is undefined, which means that you probably haven't implemented getInitialState in your component.
You haven't included the full implementation of your component, which we need to see to be able to help you. But let's walk through their example of a view component:
var LocationComponent = React.createClass({
getInitialState() {
return locationStore.getState()
},
componentDidMount() {
locationStore.listen(this.onChange)
},
componentWillUnmount() {
locationStore.unlisten(this.onChange)
},
onChange() {
this.setState(this.getInitialState())
},
render() {
return (
<div>
<p>
City {this.state.city}
</p>
<p>
Country {this.state.country}
</p>
</div>
)
}
})
They implement getInitialState here to return the current state of the store, which you'll then be able to use in the render method. In componentDidMount, they listen to change events from that store so that any events occuring in that store coming from anywhere in your application will trigger a re-render. componentWillUnmount cleans up the event listener. Very important not to forget this, or your app will leak memory! Next the onChange method (which could be have whatever name, it's not an internal React method), which the store will call when a change event occurs. This just sets the state of the component to whatever the stores state is. Might be a bit confusing that they call getInitialState again here, because you're not getting the initial state, you're getting the current state of the store.
Another important note here is that this example won't work off the bat with ES6/ES2015 classes, because React no longer autobinds methods to the instance of the component. So the example implemented as a class would look something like this:
class LocationComponent extends React.Component {
constructor(props) {
super(props)
this.state = this.getState();
this.onChangeListener = () => this.setState(this.getState());
}
getState() {
return locationStore.getState();
}
componentDidMount() {
locationStore.listen(this.onChangeListener);
}
componentWillUnmount() {
locationStore.unlisten(this.onChangeListener)
}
render() {
return (
<div>
<p>
City {this.state.city}
</p>
<p>
Country {this.state.country}
</p>
</div>
);
}
}
Sorry for your wasted time on reading it, but I figured it out and the reason of trouble was my silly mistake in importing. Essentially, I've imported another Store with the name of needed.
So, instead of import FeatureCategoryStore from '../stores/FeatureTypeStore';
It should be import FeatureCategoryStore from '../stores/FeatureCategoryStore';