Here I try to set state.autocomplete to 'hello' and then print it, but state seems to be null. How can that be when I just updated the state using setState? data is set as a global variable.
var data = {
populate_at: ['web_start', 'web_end'],
autocomplete_from: ['customer_name', 'customer_address']
};
var AutocompleteFromCheckboxes = React.createClass({
handleChange: function(e) {
this.setState( { autocomplete_from: 'event.target.value' } );
console.log('autocompleteFrom state: ', this.state.autocomplete_from);
// ^ => Uncaught TypeError: Cannot read property 'autocomplete_from' of null
return 1;
},
render: function() {
var autocompleteFrom = this.props.autocomplete_from.map(function(value) {
return (
<label for={value}>
<input type="checkbox" name={value} value="{value}"
onChange={this.handleChange.bind(this)}
ref="autocomplete-from"/>
{value}
</label>
);
}, this);
return (
<div className="autocomplete-from">
{autocompleteFrom}
</div>
);
}
});
var DynamicForm = React.createClass({
getInitialState: function() {
return {
name : null,
populate_at : null,
same_as : null,
autocomplete_from : "not set",
title : null
};
},
saveAndContinue: function(e) {
e.preventDefault();
var data = {
name : this.refs.name.getDOMNode().value,
};
console.log('data: ' + data.name);
},
render: function() {
return (
<AutocompleteFromCheckboxes
autocomplete_from={this.props.data.autocomplete_from} />
);
}
});
var mountpoint = document.getElementById('dynamic-form');
if ( mountpoint ) {
React.render(<DynamicForm data={data} />, mountpoint);
}
});
From the reactjs docs:
setState() does not immediately mutate this.state but creates a pending state transition. Accessing this.state after calling this method can potentially return the existing value.
https://facebook.github.io/react/docs/component-api.html
What you can do is pass a callback function to setState which is triggered once the state has been updated:
this.setState(
{autocomplete_from: ...},
function () {
... at this point the state of the component is set ...
}
)
You need to set the initial state of your component, try adding the following to the top of your component.
getInitialState: function() {
return {
autocomplete_from: ''
};
}
EDIT:
In your DynamicFrom component you have:
render: function() {
return (
<AutocompleteFromCheckboxes
autocomplete_from={this.props.data.autocomplete_from} />
);
}
Since you are trying to reference the state you should write
autocomplete_form={this.state.autocomplete_from}
Also you are trying to set the state from a child component and it should not directly modify state. The best way to approach this is to pass down a function from DynamicFrom(holds the state) to AutocompleteFromCheckboxes. Like so.
var DynamicForm = React.createClass({
handleChange: function(value) {
this.setState({autocompelete_from: value});
},
render: function() {
return(
<AutocompleteFromCheckboxes
autocomplete_from={this.state.autocomplete_from}
handleChange={this.handleChange}
/>
);
},
....
});
Then call that function in your child component
AutocompleteFromCheckboxes = React.createClass({
....
onChange={this.handleChange}
....
handleChange: function(e) {
this.props.handleChange(e.target.value);
}
});
To see updated state value after doing setState you should do something like below
this.setState( { autocomplete_from: 'event.target.value' }, () => {
console.log(this.state.autocomplete_from);//this will print the updated state value
});
Related
I have a react.js component where I want to pass down a bunch of different methods to child components from the parent component, the methods modify the state of the parent component.
class Page extends Component {
constructor(props) {
super(props);
this.state = {
items: this.props.items,
currentItemID: 1
};
this.actions = this.actions.bind(this);
}
render() {
return (
<div className="page">
{
this.state.items.map(item =>
<Item key={item._id} item={item} actions={this.actions} />
)
}
</div>
);
}
actions() {
return {
insertItem: function (itemID) {
const currentItems = this.state.items;
const itemPosition = this.state.items.map((item) => item._id).indexOf(itemID);
const blankItem = {
_id: (new Date().getTime()),
content: ''
};
currentItems.splice(itemPosition + 1, 0, blankItem)
this.setState({
items: currentItems,
lastAddedItemID: blankItem._id
});
},
setCurrentItem: function (itemID) {
this.setState({ currentItemID: itemID });
},
focus: function(itemID) {
return (itemID === this.state.currentItemID);
}
}
}
In my child component, I am trying to use the focus method in the componentDidMount lifecyle method as shown below:
componentDidMount() {
if (this.props.actions().focus(this.props.item._id)) {
this.nameInput.focus();
}
}
However, I am getting the error
Uncaught TypeError: Cannot read property 'currentItemID' of undefined
in the definition of the focus method, within the actions methods. Can anyone point me in the right direction as to why I'm getting the error or an alternative way to pass down multiple actions to child components?
the context is not passed to the function, then the 'this' in the function is that of the function itself and not the component.. you can solve it that way (put the functions in the components):
actions() {
return {
insertItem: this.insertItem.bind(this),
setCurrentItem: this.setCurrentItem.bind(this),
focus: this.focus.bind(this),
}
}
insertItem(itemID) {
const currentItems = this.state.items;
const itemPosition = this.state.items.map((item) => item._id).indexOf(itemID);
const blankItem = {
_id: (new Date().getTime()),
content: ''
};
currentItems.splice(itemPosition + 1, 0, blankItem)
this.setState({
items: currentItems,
lastAddedItemID: blankItem._id
});
},
setCurrentItem(itemID) {
this.setState({ currentItemID: itemID });
},
focus(itemID) {
return (itemID === this.state.currentItemID);
}
but yet, the recomended way is to put the functions in the components like above and remove the actions method and do this:
<Item key={item._id} item={item} actions={{
insertItem: this.insertItem.bind(this),
setCurrentItem: this.setCurrentItem.bind(this),
focus: this.focus.bind(this)
}} />
or
<Item key={item._id} item={item} actions={{
insertItem: () => this.insertItem(),
setCurrentItem: () => this.setCurrentItem(),
focus: () => this.focus()
}} />
This is my code:
var Item = React.createClass({
render: function () {
return (
React.createElement('div', {},
React.createElement('span', {}, this.props.a),
React.createElement('span', {}, this.props.b)
)
);
}
});
var RParent = React.createClass({
getInitialState: function () {
return {messages: []};
},
addMess: function (data) {
var self = this;
self.state.messages.push({
a: data.a,
b: data.b
});
this.setState({messages: self.state.messages});
},
render: function () {
var messages = this.state.messages;
return (
React.createElement('div', {},
React.createElement(Item, messages))
);
}
});
var box = ReactDOM.render(React.createElement(RParent), document.getElementById('parentDiv'));
function render(a, b) {
box.addMess({
a: a,
b: b
});
}
Data is sent to the render function. It appears that somehow the properties are not reaching the render function of the Item as they appear to be undefined when I try print them to console.
Any ideas why this might be happening?
The problem is that nothing is rendered into the "parentDiv". It just remains blank. However, on inspecting the elements, I can still see two "span" children under it (which were not created in html or anywhere else except here). But those span elements have no content.
I think there's a fundamental problem to your code. You are trying to pass 'a' and 'b' to your react component through an external function 'render' that seems to be invoking one of the component's functions. This is wrong.
You should pass 'a' and 'b' to your React component RParent as props. Whenever the value of 'a' and 'b' change, your parent component will automatically rerender.
var Item = React.createClass({
render: function () {
return (
React.createElement('div', {},
React.createElement('span', {}, this.props.a),
React.createElement('span', {}, this.props.b)
)
);
}
});
var RParent = React.createClass({
getInitialState: function () {
return {messages: []};
},
,
render: function () {
var messages = this.props.messages;
return (
React.createElement('div', {},
React.createElement(Item, messages))
);
}
});
ReactDOM.render(
(<RParent messages= {'a': a, 'b': b} />)
, document.getElementById('parentDiv'));
I'm using react-router which forces me to use React.cloneElement to pass down properties to my Children. I can pass down objects and functions, but my issue is where one of my functions has a return object back up to the parent, which is always undefined. The function triggers in the parent, but it doesn't receive the object I'm passing it from the child.
Here is a jsFiddle of the below example code if anyone wants to edit it https://jsfiddle.net/conor909/gqdfwg6p/
import React from "react";
import ReactDom from "react-dom";
const App = React.createClass({
render() {
return (
<div>
{this.getChildrenWithProps()}
</div>
)
},
getChildrenWithProps() {
return React.Children.map(this.props.children, (child) => {
return React.cloneElement(child, {
myFunction: this.myFunction
});
});
},
// NOTE:
// the idea is that the variable 'newForm' should be sent back up to App, I can log out 'newForm' in the Child, but here in App, it is undefined.
myFunction(newForm) {
console.log(newForm); // => undefined object
}
});
const Child = React.createClass({
propTypes: {
myFunction: React.PropTypes.func,
myForm: React.PropTypes.object
},
render() {
return (
<form className="col-sm-12">
<MyForm
changeForm={this.onChangeForm}
form={this.props.myForm} />
</form>
)
},
onChangeForm(formChanges) {
let newForm = {
...this.props.myForm,
...formChanges
}
// console.log(newForm); => here my newForm object looks fine
this.props.myFunction(newForm);
}
});
const MyForm = React.createClass({
propTypes: {
changeForm: React.PropTypes.func.isRequired
},
render() {
return (
<div>
<Input onChange={this.onChangeForm}>
</div>
)
},
onChangeForm(value) {
this.props.changeForm({ something: value });
}
});
So, I believe this is a formatting issue OR I'm not clear about how the return works when dynamically building.
The render function in Results works, if I replace the code with anythign else it renders where I want. Similarly, the console.log's in the Results function outputs the data correctly. There's no error, it just doesn't render the html and it doesn't hit the debugger in SynonymElement.
What am I missing in here / what core concept am I misconstruing?
(This is just an input form that takes a word, user hits submit, it returns an object with the word as a key and the value an array of synonynms. that get rendered in the ul)
'use strict'
const Smithy = React.createClass({
dsiplayName: "Smithy",
getInitialState: function() {
return { data: []};
},
handleSubmit: function(data) {
$.get('/get-synonyms', { data: data.data }).done(function(data) {
this.setState({ data: data})
}.bind(this));
},
render: function() {
return (
<div className="smithy">
<h1>Craft Tweet</h1>
<SmithyForm onSubmit={this.handleSubmit} />
<Results data={this.state.data} />
</div>
)
}
})
const SmithyForm = React.createClass({
displayName: "SmithyForm",
getInitialState: function() {
return { placeholder: "tweet", value: "" };
},
handleChange: function(event) {
this.setState({value: event.target.value});
},
handleSubmit: function(event) {
event.preventDefault();
var tweet = this.state.value.trim();
this.props.onSubmit({ data: tweet });
this.setState({value: ''});
},
render: function() {
var placeholder = this.state.placeholder;
var value = this.state.value;
return (
<form className="smithyForm" onSubmit={this.handleSubmit}>
<input type="text" placeholder={placeholder} value={value} onChange={this.handleChange} />
<button>smithy</button>
</form>
);
}
})
const SynonymElement = React.createClass({
render: function() {
debugger
return (
<li>{this.props.data}</li>
)
}
})
const Results = React.createClass({
render: function() {
var words = this.props.data;
return (
<div className="results">
{
Object.keys(words).map(function(value) {
{ console.log(value) }
<div className={value}>
<ul>
{
words[value].map(function(syn) {
{ console.log(syn) }
return <SynonymElement data={syn} />
})
}
</ul>
</div>
})
}
</div>
)
}
})
ReactDOM.render(<Smithy />, document.getElementsByClassName('container')[0])
Might have some other complicating issues but assuming everything else is wired up correctly, you need to return the result of the function you pass into the first map (over the collection Object.keys(words)) just as you have for the later map otherwise the function is executed and nothing useful is returned.
Possibly just a dupe of loop inside React JSX
return (
<div className="results">
{
Object.keys(words).map(function(value) {
return ( // <-- this
<div className={value}>
I am waiting the props to come up from a store named GetDealersStore, and the way I am fetching that data is with an action where I am doing this:
componentWillMount () { GetDealersActions.getDealers(); }
I already test the app and componentWillMount() is running before the initial render where I have this
let dealerInfo;
if (this.state.dealerData) {
dealerInfo = this.state.dealerData.dealersData.map((dealer) => {
return (<div>CONTENT</div>);
})
} else {
dealerInfo = <p>Loading . . .</p>
}
but for the first second you can see <p>Loading . . .</p> in the screen which is the else in the conditional above, and then the rest of the render comes up with return (<div>CONTENT</div>); which is the if in the conditional. So, I guess, this means that the render method has been trigger twice because it keeps waiting for the data coming from the database.
The data from the database is not available at the time of the 1st render, so, how can I fetch that data before the 1st initial render occurs?
You can't do this with a single component. You should follow the Container Component pattern to separate data from rendering.
let DealersContainer = React.createClass({
getInitialState() {
return {dealersData: []};
},
componentWillMount() {
GetDealersActions.getDealers();
},
render() {
let {dealersData} = this.state;
return (<div>
{dealersData.map((dealer) => {
let props = dealer;
return (<Dealer ...props />); // pass in dealerData as PROPS here
})}
</div>);
}
});
Then update your Dealer component to receive props and render the actual content.
My answer is similar to Mathletics', just in more detail.
In this example I've included initialising state of dealerData to null; this is the check that's made to determine whether the data has been returned from the store by the container.
It's verbose, but declarative, and does what you want, in the order that you want, and it will work each time.
const DealerStore = MyDataPersistenceLibrary.createStore({
getInitialState() {
return {
dealerData: null
};
},
getDealers() {
// some action that sets the dealerData to an array
}
});
const DealerInfoContainer = React.createClass({
componentWillMount() {
DealerStoreActions.getDealers();
},
_renderDealerInfo() {
return (
<DealerInfo {...this.state} />
);
},
_renderLoader() {
return (
<p>Loading...</p>
);
},
render() {
const { dealerData } = this.state;
return (
dealerData
? this._renderDealerInfo()
: this._renderLoader()
);
}
});
const DealerInfo = React.createClass({
getDefaultProps() {
return {
dealerData: []
};
},
_renderDealers() {
const { dealerData } = this.props;
return dealerData.map(({ name }, index) => <div key={index}>{name}</div>);
},
_renderNoneFound() {
return (
<p>No results to show!</p>
);
},
render() {
const { dealerData } = this.props;
return (
dealerData.length
? this._renderDealers()
: this._renderNoneFound()
);
}
});