Learning React & chrome extensions the hard way, so my this.url has one of 2 values:
null or www.sample.com
I'm getting the url value from an async call using chrome.storage.local.get(...) so once I get the response from the storage I set the value to this.url and want to use such value to display a component using a ternary operator like so:
export default React.createClass({
url: '',
componentDidMount: function(){
this.observeResource();
},
observeResource(){
var self = this
function getValue(callback){
chrome.storage.local.get('xxxx', callback);
}
getValue(function (url) {
this.url = url.xxxx;
return this.url;
});
},
/* RENDER */
render: function(){
return (
<div className="app">
<AppHeader />
{this.url != null ?
<VideoFoundOverlay />
: null}
{this.url == null ?
<VideoNotFoundOverlay />
: null }
</div>
)
}
});
I can not get the value of this.url outside of the observeResource function. Where am I going wrong?
You need to use this.setState()
constructor(props) {
super(props)
this.state = {
url: ''
}
}
componentDidMount() {
setTimeout(() => {
this.setState({ url: 'www.sample.com' })
}, 1000)
}
render() {
return <div>this.state.url</div>
}
Look at working example in JSFiddle
The this inside the following call points to window rather than the component itself, since the callback is invocated as a function, the window is actually the "owner" of the function.
getValue(function (url) {
this.url = url.newswireStoryUrl;
return this.url;
});
To avoid this error, you could use self.url instead, since you have explicitly assign this to self. Or you could use Arrow Function instead of function(...).
To render the component, you should declare url as a state, because only state changes will cause the render function to be called.
export default React.createClass({
getInitialState: function() {
return { url: '' };
},
componentDidMount: function(){
this.observeResource();
},
observeResource(){
var self = this;
function getValue(callback){
chrome.storage.local.get('newswireStoryUrl', callback);
}
getValue(function (url) {
self.setState({ url: url.newswireStoryUrl });
return self.state.url;
});
},
/* RENDER */
render: function(){
return (
<div className="app">
<AppHeader />
{this.state.url !== null ?
<VideoFoundOverlay />
: null}
{this.state.url === null ?
<VideoNotFoundOverlay />
: null }
</div>
);
}
});
I would go by and do it like this:
export default React.createClass({
getInitialState: function(){
return {
url: null
}
},
componentDidMount: function(){
this.observeResource();
},
observeResource(){
var self = this
function getValue(callback){
chrome.storage.local.get('newswireStoryUrl', callback);
}
getValue(function (url) {
//here you are seting the state of the react component using the 'self' object
self.setState({url: url.newswireStoryUrl})
//not sure if the 'return this.url' is needed here.
//here the 'this' object is not refering to the react component but
//the window object.
return this.url;
});
},
videoNotFound: function(){
return (
<div className="app">
<AppHeader />
<VideoNotFoundOverlay />
</div
)
},
/* RENDER */
render: function(){
if(this.state.url == null){
return this.videoNotFound()
}
return (
<div className="app">
<AppHeader />
<VideoFoundOverlay />
</div>
)
}
});
Related
I'm trying to pass the 'handleWordClick' function to a component but I keep getting the error that the proptype was not specified. Also when I try to pass the prop with .bind
'this.handleWordClick.bind(this)'
...I get the error that cannot read property 'bind'.
This error maybe due to my lack of knowledge but would appreciate some help.
Thanks.
Here's the part of the code of the container:
handleWordClick: function () {
this.setState({ isModalOpen: true });
},
handleCloseModalClick: function () {
this.setState({ isModalOpen: false });
},
renderBookPage: function(which) {
var book = this.state.book;
return book[which].map(function (page, index) {
return <WordWrapper
key={index}
page={page}
onClick={this.handleWordClick} />
})
},
Here is the component that I'm trying to pass the 'onClick' prop:
function WordWrapper (props) {
return (
<span style={styles.cursorPointer} onClick={props.onClick}>{props.page.word}</span>
)
}
WordWrapper.propTypes = {
page: PropTypes.shape({
word: PropTypes.string.isRequired
}),
onClick: PropTypes.func.isRequired
}
module.exports = WordWrapper;
Try this
var _this = this;
return book[which].map(function (page, index) {
return <WordWrapper
key={index}
page={page}
onClick={_this.handleWordClick} />
})
The reason for the error is, the this inside .map() refers to the map() not the React.createClass()
I know you can pass down states and props in React from a parent component to a child component, but is there any way to do this the opposite way?
For example:
Given some child component:
var Child = React.createClass({
getInitialState: function(){
return {
data: ''
};
},
componentDidMount: function(){
this.setState({data: 'something'});
},
render: function() {
return (
<div>
...
</div>
);
}
});
and given some parent component:
var Parent = React.createClass({
render: function() {
return (
<div>
<Child />
...
</div>
);
}
});
Is there any way for me to give Parent the value of the state data from Child?
No.
But yes. But really no.
You cannot "pass" anything from a child to a parent in React. However, there are two solutions you can use to simulate such a passing.
1) pass a callback from the parent to the child
var Parent = React.createClass({
getInitialState: function() {
return {
names: []
};
},
addName: function(name) {
this.setState({
names: this.state.names.push(name)
});
},
render: function() {
return (
<Child
addName={this.addName}
/>
);
}
});
var Child = React.createClass({
props: {
addName: React.PropTypes.func.isRequired
},
handleAddName: function(event) {
// This is a mock
event.preventDefault();
var name = event.target.value;
this.props.addName(name);
},
render: function() {
return (
...
onClick={this.handleAddName}
...
);
}
});
The second option is to have a top-level state by using a Flux-style action/store system, such as Reflux or Redux. These basically do the same thing as the above, but are more abstract and make doing so on much larger applications very easy.
One way to do this is through a 'render props' pattern I was recently introduced to. Remember, this.props.children is really just a way for React to pass things down to a component.
For your example:
var Parent = React.createClass({
render: function() {
return (
<div>
<Child>
{(childState) => {
// render other 'grandchildren' here
}}
</Child>
</div>
);
}
});
And then in <Child> render method:
var Child = React.createClass({
propTypes: {
children: React.PropTypes.func.isRequired
},
// etc
render () {
return this.props.children(this.state);
}
});
This is probably best suited for cases where the <Child /> is responsible for doing something but doesn't really care much at all about the children that would be rendered in its place. The example the react training guys used was for a component that would fetch from Github APIs, but allow the parent to really control what / if anything was rendered with those results.
I have a react component that gets a prop from another parent component. I checked in react developer tools, and the prop is for sure getting passed.
Here is my code:
var Post = React.createClass({
getInitialState: function () {
return { content: this.props.content };
},
rawMarkup: function() {
var rawMarkup = marked(this.state.content, {sanitize: true});
return { __html: rawMarkup };
},
render: function() {
return (
<div>
{this.props.content }
<div dangerouslySetInnerHTML={ this.rawMarkup() } />
</div>
);
}
});
This results in the error: Uncaught TypeError: Cannot read property 'replace' of undefined for marked.js. However, when I setInitialState to return { content: "Blah" }; it works fine. So it looks like the prop is not set there?
But when I do the {this.props.content} in the render, it works fine?
It's just that your state is out of date. Try adding this:
getInitialState: function () {
return { content: this.props.content || '' };
},
componentWillReceiveProps: function(nextProps) {
if (this.props.content !== nextProps.content) {
this.setState({
content: nextProps.content || '',
});
}
},
Read more about components' lifecycle here.
Edit: This will solve your problem, but generally using state this way is an anti-pattern (unless content is an input or something, you haven't mentioned that in your question). What you should do instead is create a new component that will only accept content prop and render marked output. I suggest you use a stateless functional component here.
var MarkedContent = (props) => {
return <div dangerouslySetInnerHTML={{__html: marked(props.content || '', {sanitize: true})}}></div>
}
Drop this component inside your Post component like this:
var Post = React.createClass({
render: function() {
return (
<div>
<MarkedContent content={this.props.content} />
</div>
);
}
});
Thanks David Walsh!
You don't have to synchronize props with state, even more using props in state is anti-pattern. render() is called each time when props or state changed
However, it's not an anti-pattern if you make it clear that
synchronization's not the goal here
var Post = React.createClass({
rawMarkup: function() {
var rawMarkup = marked(this.props.content, {sanitize: true});
return { __html: rawMarkup };
},
render: function() {
return (
<div>
{this.props.content }
<div dangerouslySetInnerHTML={ this.rawMarkup() } />
</div>
);
}
});
Do all your Post's have content?
I guess you are getting the list of posts from somewhere (a database) and for some of them the content is undefined, hence the:
Uncaught TypeError: Cannot read property 'replace' of undefined
Probably this.props.content has undefined as value. Then, this.state.content is initialized to undefined and when you call marked(this.state.content, {sanitize: true}) you get this error because you are passing an undefined to marked.
I'm creating a survey-type app in React. The questions are arranged as items in a carousel.
When the user selects an answer - I AM able to change the state of the question (setting a button as active). However, I would also like to advance the carousel to the next item.
var Question = React.createClass({
getInitialState() {
return {
selectedIndex: -1
};
},
handleClick(index) {
this.setState({selectedIndex: index});
this.props.onQuestionAnswered();
},
render() {
var answerItems = answerChoices.map(function (answer) {
return (
<ReactBootstrap.ListGroupItem
key={answer.text}
text={answer.text}
active={answer.index == this.state.selectedIndex}
onClick={this.handleClick.bind(this, answer.index)}>
{answer.text}
</ReactBootstrap.ListGroupItem>
);
}.bind(this));
return (
<div>
<h3>{this.props.qText.text}</h3>
<ReactBootstrap.ListGroup>
{answerItems}
</ReactBootstrap.ListGroup>
</div>
);
}
});
var Carousel = React.createClass({
getInitialState() {
return {
index: 0,
};
},
handleSelect() {
this.setState({
index: 1
});
},
render() {
var questionItems = questionContent.map(function (question) {
return (
<ReactBootstrap.CarouselItem key={question.text}>
<Question qText={question}/>
</ReactBootstrap.CarouselItem>
);
});
return (
<ReactBootstrap.Carousel interval={false} activeIndex={this.state.index} onQuestionAnswered={this.handleSelect}>
{questionItems}
</ReactBootstrap.Carousel>
);
}
});
var App = React.createClass({
render() {
return (
<div>
<h4>Survey</h4>
<Carousel/>
</div>
);
}
});
React.render(<App/>, document.getElementById('content'));
I have a full JSFiddle available: http://jsfiddle.net/adamfinley/d3hmw2dn/
Console says the following when I try to call the function prop:
Uncaught TypeError: this.props.onQuestionAnswered is not a function
What do I have to do to call the parent function? Alternatively - is there a better pattern I should be using? (first time with React).
It looks like the error is coming from the Question component, which doesn't have the onQuestionAnswered prop. So you simply need to pass it in your questionItems map iteration.
var self = this;
var questionItems = questionContent.map(function (question) {
return (
<ReactBootstrap.CarouselItem key={question.text}>
<Question onQuestionAnswered={self.handleSelect} qText={question}/>
</ReactBootstrap.CarouselItem>
);
});
I have the next component 'Father' that contains a 'Children' component in React js.
var Father = React.createClass({
render: function () {
return (
<div>
<Children/>
</div>
);
},
onUpdate: function(state) {
this.setState(state);
} });
I want to call the onUpdate function on the father from the children BUT without calling the 'Children' method 'componentDidUpdate' because I'm using that method for some other thing that breaks my application.
How can I do that?
Pass it down in properties. If you need to update only specific parts and prevent your children from updating, use the method shouldComponentUpdate
var Father = React.createClass({
render: function () {
return (
<div>
<Children onUpdateCallback={this.onUpdate}/>
</div>
);
},
onUpdate: function(state) {
this.setState(state);
}
});
var Child = React.createClass({
render: function () { ... },
shouldComponentUpdate: function (prevProps, prevState) { ... return false}
});
If your Children can't/shouldn't update while the Parent does, I think you are probably doing something wrong, but good luck.