react container not being removed on re-render - javascript

I have a form that displays an error container if any errors are added to the state. There is a handleSubmit function that calls a validateForm function before sending through the ajax request.
getInitialState: function () {
return {
formErrors: []
};
},
render: function () {
var errors = this.state.formErrors.map(function(error) {
return (
<li>
{error}
</li>
);
});
var errorContainer = (
<ul>
{errors}
</ul>
);
In the return block of the render function, there is this line to display the error container:
{ this.state.formErrors.length == 0 ? '' : errorContainer }
Inside of the validateForm function, I have something like this:
this.setState({ formErrors: [] });
var name = this.refs.name.getDOMNode().value.trim();
if (!name) {
newErrors = this.state.formErrors;
newErrors.push('Please add a name');
this.setState({
formErrors: newErrors
});
}
This works in that it will add the errors to the formErrors key in the state, but if I then input the correct things, any "new" errors will be appended in the view to the li as opposed to painting a fresh error container with N-1 errors, as in that scenario I would have filled in the name field.
Here's a jsfiddle: http://jsfiddle.net/ke4bmqny/7/

Your parent form component controls the error state, when you pass this down to the error display child component the errors are props of that component. See Communicate Between Components.
Change this line to camel-case :P
<GroupFormErrorComponent formErrors={this.state.formErrors} />
And you had it almost right but your error display component doesn't and shouldn't need to copy the error into another state variable.
var GroupFormErrorComponent = React.createClass({
render: function () {
var errors = this.props.formErrors.map(function(error, index) {
return (
<li key={index}>
{error}
</li>
);
});
var errorContainer = (
<div>
<div>
Sorry but there are errors:
</div>
{errors}
</div>
);
return (
<div>
{ this.props.formErrors.length == 0 ? '' : errorContainer }
</div>
);
}
});
PS Just a little tip on the DOMReady stuff you added to js fiddle - you didn't need it, instead just put the <script> part below the html (below <div id='form'></div>) and it will be ready.
Updated fiddle: http://jsfiddle.net/dL0uj9oc/3/

As far as I can see there is no case where 'this.state.formErrors' is emptied. So I would change the lines to:
var newErrors = [];
if (!name) {
newErrors.push('Please add a name');
this.setState({
formErrors: newErrors
});
}
But it would be better if you shared a jsfiddle where people can reproduce the problem.
On a side note, may be you should get your name value from state, instead of reading from DOM (where you get the name input value).

Related

Why does clicking on a tab that changes state and has nothing to do with ajax call creates that ajax call?

I have a simple layout.
There is a <StateSelector> in <Navbar> clicking on that executes a method.
The value of innerHTML of that button in <StateSelector> is passed as an argument to a function that was passed to it as a prop. And the method present in parent changes the activeOption State to All, Offline and online depending on the button clicked.
Now, There is one more child to this parent called <TwitchList>. This <TwitchList>'s render method contains an array of 8 users names and 8 calls are made to twitch.tv to get data for those channels.
Note that I have not linked <StateSelector> just yet. It has no interaction to $.ajax(); in <TwitchList> except the fact that <TwitchList> and <StateSelector> belong to same parent.
Why does clicking on a element inside <StateSelector> generating ajax calls?
And clicking on it one time generates 8 calls which is equal to number of users in usersList[].
I have tried searching for this issue and I have tried to work my way around for about 4 days now and I just don't understand why it is happenning.
var Navbar = React.createClass({
render: function () {
return (
<div className="navbar">
<h1 className="header">TWITCH STREAMERS</h1>
<StateSelector changeActiveOption={this.props.changeActiveOption}/>
</div>
);
}
});
var StateSelector = React.createClass({
changeOption: function (e) {
this.props.changeActiveOption(e.target.innerHTML.toLowerCase());
},
render: function () {
return (
<div className="selectorList">
<div className="selector" onClick={this.changeOption}>ALL</div>
<div className="selector" onClick={this.changeOption}>ONLINE</div>
<div className="selector" onClick={this.changeOption}>OFFLINE</div>
</div>
);
}
});
var TwitchList = React.createClass({
render: function () {
var userslist = ["ESL_SC2", "OgamingSC2", "cretetion", "freecodecamp", "storbeck", "habathcx", "RobotCaleb", "noobs2ninjas"];
var finalList = [];
function makeURL(user, type) {
return "https://api.twitch.tv/kraken/" + type + "/" + user;
}
userslist.forEach(function (user) {
$.ajax({
url: makeURL(user, "streams"),
dataType: 'jsonp',
success: function (data) {
function getID(data) {
if (data.stream) {
return data.stream._id;
} else {
return Math.random();
}
}
function getImage(data) {
if (!data.stream) {
return "https://dummyimage.com/50x50/ecf0e7/5c5457.jpg&text=0x3F";
}
else {
return data.stream.preview.medium;
}
}
console.log(data);
var id = getID(data);
var preview = getImage(data);
console.log(preview);
finalList.push(
<li className="twitchUser" key={id}>
<img src={preview} alt="preview"/>
</li>
)
},
fail: function (xhr, error, url) {
console.error(error + " " + xhr + " " + url);
}
});
});
return (
<div className= "twitchListWraper" >
<ul className="twitchList">
{finalList}
</ul>
</div>
)
}
});
var App = React.createClass({
getInitialState: function () {
return {
activeOption: "all"
};
},
changeActiveOption: function (option) {
this.setState({
activeOption: option
});
},
render: function () {
return (
<div className="app-root">
<Navbar changeActiveOption={this.changeActiveOption}/>
<TwitchList activeOption={this.state.activeOption}/>
</div>
);
}
});
ReactDOM.render(<App />, document.getElementById('root'));
JSBin: http://jsbin.com/sulihogegi/edit?html,css,js,console,output
Every time state changes on <App>, it re-renders. This includes re-rendering its children (on of which is <TwitchList>). Your ajax call is made in <TwitchList>'s render function, so every time state changes, it's going to be hitting that ajax code.
If you're wondering why the state's changing, it's because you have a function, changeActiveOption, that updates the <App>'s state being passed to <Navbar> which is then passes down to <StateSelector>.
The appropriate thing to do here is find a life cycle event in which to make the ajax call. I'd recommend componentWillMount and componentWillUpdate.
Take a look at lifecycle functions here.
you shouldn't write your ajax call to render function of TwitchList component instead you can write it to componentDidMount function

Call multiple functions in React, one inside and another outside a component?

I have a trigger that is supposed to change a state of its child component. The results of both render statements are inconsequential. My only concern here is that I am unsure how to use the trigger trigger to call the leftbehind function without putting leftbehind inside its parent render Another.
My code is below. The goal is to have leftbehind run without having to put it inside the render.
var Another = React.createClass({
leftbehind: function() {
if (this.props.status === "dare") {
alert('Winning!');
}
},
render: function() {
if (this.props.status === "truth") {
return (<p>Yes</p>);
} else {
return (<p>Nope</p>);
}
}
});
var App = React.createClass({
getInitialState:function() {
return {deesfault: "truth"};
},
trigger: function() {
this.setState({deesfault: "dare"});
},
render: function() {
return (
<div>
<p onClick={this.trigger}>{this.state.deesfault}</p>
<Another status={this.state.deesfault}/>
</div>
);
}
});
The reason I do not want to place leftbehind inside the render, is because it is technically supposed to take the place of an API call. And I do not want that to be called inside the render function.
Your implementation executes leftbehind each time <Another> is rendering with its status prop being true. That said, once status is flipped to true, leftbehind will be executed over and over in every following rendering until it is flipped back to false. This will seriously cause problems.
Since the intention is to trigger leftbehind with a click event, I would restructure the components in different ways.
Move leftbehind into the parent component and have it executed along with the click event. If <Another> needs the results, passed them on through props.
var Another = React.createClass({
render() {
return <div>{this.props.params}</div>;
}
});
var App = React.createClass({
getInitialState() {
return {apiRes: null};
},
onClick() {
const res = someAPICall();
this.setState({apiRes: res});
},
render() {
return (
<div>
<p onClick={this.onClick}>Fire</p>
<Another params={this.state.apiRes} />
</div>
);
}
});
Or, move the <p> element into <Another> along with the click event.
var Another = React.createClass({
getInitialState() {
return {apiRes: null};
},
onClick() {
var res = someAPICall();
this.setState({apiRes: res});
},
render() {
return (
<div>
<p onClick={this.onClick}>Fire</p>
<div>{this.state.apiRes}</div>
</div>
);
}
});
var App = function() { return <Another />; }
In the latter, the key logic is handled in the inner component. The outer one is just a container. In the former one, the outer component handles the logic and pass on the results if any. It depends on how the components relate with the API call to decide which suits better. Most importantly, in both cases the API will not execute unless the click event is triggered.

TypeError: undefined is not an object (evaluating 'this.props') when trying to use callback between child and parent in React.js

I'm currently having a problem when trying to send an action from a child element to its parent element. I want to change the state of the MessagingContainer when a thread is clicked to mark that thread as the active thread. So when the thread (ThreadElement) is clicked, it needs to send to its parent (ThreadList), which then sends to its parent (MessagingContainer) in order for the Messaging Container to update the state.
Firstly, is this the right approach, RE: state and modifying state?
Secondly, I can't seem to get this working. I'm getting a persistent error of TypeError: undefined is not an object (evaluating 'this.props').
I've omitted the messages part of the code below so only the threads is visible.
var ThreadElement = React.createClass({
render: function() {
console.log('ThreadElement');
console.log(this.props);
var threadParticipantsNames = this.props.thread.participants.map(function(participant) {
var participantName;
if (participant.user) {
participantName = participant.user.metadata.first_name;
} else {
participantName = 'Anonymous';
}
return (
<span key={participant.id}>
{participantName}
</span>
);
});
return (
<div key={this.props.thread.id} onClick={this.props.handleActiveThreadChange}>
{threadParticipantsNames}
</div>
);
}
});
var ThreadList = React.createClass({
render: function() {
console.log('ThreadList');
console.log(this.props);
var threadNodes = this.props.threads.map(function(thread) {
return (
<ThreadElement thread={thread} key={thread.id} handleActiveThreadChange={this.props.handleActiveThreadChange} />
);
});
return (
<div className="threadList">
{threadNodes}
</div>
);
}
});
var MessagingContainer = React.createClass({
handleActiveThreadChange: function() {
console.log('MessagingContainer handleActiveThreadChange called.');
},
getInitialState: function() {
return {
activeThread: 0,
data: threadsJSON
};
},
render: function() {
console.log('MessagingContainer');
console.log(this.props);
return (
<div>
<ThreadList threads={this.state.data.threads} handleActiveThreadChange={this.handleActiveThreadChange} />
<MessageList thread={this.state.data.threads[this.state.activeThread]} />
</div>
);
}
});
The console gives the below output, if that helps. The MessagingContainer and ThreadList console.log()s seem to work, so does this suggest the issue is in the ThreadElement class?
[Log] MessagingContainer
[Log] {}
[Log] ThreadList
[Log] {threads: Array, handleActiveThreadChange: function}
[Error] TypeError: undefined is not an object (evaluating 'this.props')
Thanks!
From what I can see, I think the problem is your .map function in the ThreadList component. Your context has changed for this since you're inside the map function. You should use .bind or a local that = this variable to save the right context:
var threadNodes = this.props.threads.map(function(thread) {
return (
<ThreadElement thread={thread} key={thread.id} handleActiveThreadChange={this.props.handleActiveThreadChange} />
);
}).bind(this);
or
var that = this;
var threadNodes = this.props.threads.map(function(thread) {
return (
<ThreadElement thread={thread} key={thread.id} handleActiveThreadChange={that.props.handleActiveThreadChange} />
);
});

React child component is not firing on click event

I'm trying to make a tag-suggest input field. I'm trying to understand why this code doesn't work, as it can be applied to any number of cases.
FYI: ReactComponent is just a helper class I implemented that contains few methods like _bind etc.
class TagSuggestInput extends ReactComponent {
constructor(){
super();
this._bind('handleSelectionClick', 'handleRemoveTag', 'addNewTag', 'render');
this.state = {
suggestedOptions: [],
tagListTo: []
};
}
addNewTag(selectedIndex){
var _this = this,
tag= _this.state.suggestedOptions[selectedIndex].tag,
tagList = _this.state.tagListTo;
if($.inArray(email, tagList) == -1){
_this.setState({tagListTO: tagList.push(tag)});
}
}
handleRemoveTag(tag){
var _this = this;
// Remove tag code goes here. This is not the problem part
}
handleSelectionClick(selectedIndex, e){
var _this = this;
_this.addNewTag(selectedIndex);
// other stuff here
}
render() {
var _this = this;
return (
<div className="tagitos">
{_this.state.tagListTo.map(function(item, index){
return (
<span key={index} >
<Tag data={item} onRemove={_this.handleRemoveTag.bind(_this)} />
</span>
);
})}
<input className="tag-input" ref="input"></input>
<ul>
{_this.state.suggestedOptions.map(function(item, index){
return (
<li key={index}
onClick={_this.handleSelectionClick.bind(_this, index)}
>
<OptionComponent data={item} index={index}/>
</li>
);
})}
</ul>
</div>
);
}
}
Child component
class Tag extends ReactComponent{
constructor(){
super();
this._bind('render', 'removeFromList');
}
removeFromList(tag){
var _this = this;
_this.props.onRemove(tag);
}
render(){
var _this = this;
return(
<span className="tag-element">
<div>{_this.props.data}</div>
<div onClick={_this.removeFromList(_this.props.data)} className="tag-closeButton">×</div>
</span>
);
}
}
I want to remove the tag by clicking on the tag X button, not on the tag itself, otherwise I could have just made the entire code in the parent scope like I did with options.
Workflow: Options are generated from back-end, as autocomplete, listed below the input field in the parent. When option is selected it generates a tag. So far so good!
BUT: the code for removing the tag is automatically called and tries to remove it. Since I've removed it the tag stays, but nothing happens on 'X' click. As if onCLick event is not bound.
Function removeFromList is not called, but it is called when component is added to the view. Why? How to prevent this? My guess is that by solving this I would solve the onClick problem also.
It doesn't work because you do not bind function to onclick. You only run it once on each render
You may write something like this
removeFromList(){
var _this = this;
var tag = _this.props.data;
_this.props.onRemove(tag);
}
...
<div onClick={_this.removeFromList}></div>

Dynamically generating components in React-Bootstrap

I'm trying to dynamically generate alert components in React-Bootstrap at runtime by instantiating the components from Javascript classes. I'm doing this because there are lots of alerts to show and because Javascript classes are more succinct to create.
My attempts to do this are not working. I'm not sure whether the problem generally applies to React or just to React-Bootstrap. However, the error occurs in react.js, which throws the following:
TypeError: undefined is not a function
The throw occurs in the alert.getComponent() call in the following JSX file:
/** #jsx React.DOM */
var Alert = ReactBootstrap.Alert;
var AlertDismissible = React.createClass({
getInitialState: function() {
return {
isVisible: true
};
},
render: function() {
if(!this.state.isVisible)
return null;
var message = this.props.message;
if(this.props.code !== null)
message = message +'(Code '+ this.props.code +')';
return (
<Alert bsStyle={this.props.level} onDismiss={this.dismissAlert}>
<p>{message}</p>
</Alert>
);
},
dismissAlert: function() {
this.setState({isVisible: false});
}
});
function AlertNotice(level, message, code) {
this.level = level;
this.message = message;
this.code = code || null;
}
AlertNotice.prototype.getComponent = function() {
// What should go here? Using React.createClass() doesn't help
return (
<AlertDismissible level={this.level} message={this.message}
code={this.code} />
);
};
function SuccessAlert(message) {
AlertNotice.call(this, 'success', message);
}
SuccessAlert.prototype = Object.create(AlertNotice);
SuccessAlert.prototype.constructor = SuccessAlert;
/* ...more kinds of alerts... */
function ErrorAlert(message, code) {
AlertNotice.call(this, 'danger', message, code);
}
ErrorAlert.prototype = Object.create(AlertNotice);
ErrorAlert.prototype.constructor = ErrorAlert;
var SomethingWithAlerts = React.createClass({
render: function() {
var alerts = [
new ErrorAlert("Goof #1", 123),
new ErrorAlert("Goof #2", 321)
].map(function(alert) {
// react.js throws "TypeError: undefined is not a function"
return alert.getComponent();
});
return (
<div>{alerts}</div>
);
}
});
var TestComponent = (
<div>
<SomethingWithAlerts />
</div>
);
React.renderComponent(
TestComponent,
document.getElementById('content')
);
The Alert component comes from the React-Bootstrap library. The div components seem extraneous but I found them necessary to satisfy the react framework. In reality, I'll be storing the AlertNotice instances in react state and then generating react nodes from them.
What is the proper way to go about this?
Here's a hint. If I replace return alert.getComponent(); with the following hardcoded alert, the AlertDismissible components render without error (in duplicate) but I get a warning:
return (
<AlertDismissible level="danger" message="Goof" code="777" />
);
The following is the warning message I get with the above replacement, including a link that explains I should set key= to a unique for each alert:
Each child in an array should have a unique "key" prop. Check the render method
of SpecimenSetManager. See http://fb.me/react-warning-keys for more information.
However, if I simply replace the code inside of AlertNotice.prototype.getComponent with the above hardcoded alert, I get the same TypeError message as before.
For completeness, here is my HTML source. This is react and react-boostrap v0.11.1
<html>
<head>
<script src="lib/react.js"></script>
<script src="lib/react-bootstrap.js"></script>
<script src="lib/JSXTransformer.js"></script>
<link rel="stylesheet" href="css/bootstrap-theme.min.css">
<link rel="stylesheet" href="css/bootstrap.min.css">
</head>
<body>
<div id="content"></div>
<script src="components.js" type="text/jsx"></script>
</body>
</html>
I solved the problem. The solution was to create a special react component that represents a set of alerts. Apparently it is only possible to reference automatic or object variables in component parameters from within the React.createClass() definition. Perhaps this is a syntactic constraint of JSX rather than a logical constraint of react.
I don't understand why this solution works. I would like to understand so that I don't have to deal with similar problems again in the future. If you can explain the general principle I'm violating and the general principle that should be followed instead -- something more insightful than what I've stated here -- then I'll mark your response as the "answer" to this question. I'd like to know how much flexibility I really have.
Here's the code that works, including a new AlertSet component:
/** #jsx React.DOM */
function AlertNotice(level, message, code) {
this.level = level;
this.message = message;
this.code = code || null;
}
function SuccessAlert(message) {
AlertNotice.call(this, 'success', message);
}
SuccessAlert.prototype = Object.create(AlertNotice);
SuccessAlert.prototype.constructor = SuccessAlert;
/* ...more kinds of alerts... */
function ErrorAlert(message, code) {
AlertNotice.call(this, 'danger', message, code);
}
ErrorAlert.prototype = Object.create(AlertNotice);
ErrorAlert.prototype.constructor = ErrorAlert;
var Alert = ReactBootstrap.Alert;
var AlertDismissible = React.createClass({
getInitialState: function() {
return {
isVisible: true
};
},
render: function() {
if(!this.state.isVisible)
return null;
var message = this.props.message;
if(this.props.code !== null)
message = message +'(Code '+ this.props.code +')';
return (
<Alert bsStyle={this.props.level} onDismiss={this.dismissAlert}>
<p>{message}</p>
</Alert>
);
},
dismissAlert: function() {
this.setState({isVisible: false});
}
});
var AlertSet = React.createClass({
render: function() {
var alerts = this.props.alerts.map(function(alert, i) {
return (
<AlertDismissible key={"alert-"+i} level={alert.level}
message={alert.message} code={alert.code} />
);
});
// component must be a single node, so wrap in a div
return (
<div>{alerts}</div>
);
}
});
var SomethingWithAlerts = React.createClass({
render: function() {
var alerts = [
new ErrorAlert("Goof #1", 123),
new ErrorAlert("Goof #2", 321)
];
return (
<AlertSet alerts={alerts} />
);
}
});
// TestComponent returns a single node, so doesn't need a div
var TestComponent = (
<SomethingWithAlerts />
);
React.renderComponent(
TestComponent,
document.getElementById('content')
);

Categories