I keep getting undefined for this.props.data when I have passed it from by setting this.state this.props.data from last component.
Here is the jobOffer component which properly passes data and render:
const JobOffer = React.createClass({
mixins: [Navigation],
getInitialState: function() {
console.log(this.props.data);
return {
listing: this.props.data
};
},
componentDidMount: function() {
AppStore.addChangeListener(this._onChange);
},
_onChange : function(){
this.setState({listingDetail:this.state.listing});
},
handleClick: function () {
this.transitionTo('/listing/' + this.state.listing.id );
},
render: function () {
var data = this.state.listing;
var employmentType;
switch(data.employment_type) {
case 'F':
employmentType = 'Full Time';
break;
case 'P':
employmentType = 'Part Time';
break;
case 'H':
employmentType = 'Hourly';
break;
default:
employmentType = 'Unknown';
}
return (
<a onClick={this.handleClick}>
<img style={{width: 50+'px', height: 50+'px'}} src="images/job1.jpg" alt="" className="img-circle" />
<div className="title">
<h5>{data.job_title}</h5>
<p>{data.Business.business_name}</p>
</div>
<div className="data">
<div ><i>Posted 1 Day Ago</i></div>
<div className="city"><i className="fa fa-map-marker"></i>{data.Business.business_city}</div>
<div className="type full-time"><i className="fa fa-clock-o"></i>{employmentType}</div>
<div className="sallary"><i className="fa fa-dollar"></i>{data.job_compensation}</div>
</div>
</a>
);
},
});
module.exports = JobOffer;
You can see that I am passing listingDetail when _onChange. Here is the jobDetails component that is is giving the undefined error.
var React = require('react');
var ReactBootstrap = require('react-bootstrap');
var Button = ReactBootstrap.Button;
var Modal = ReactBootstrap.Modal;
var AppActions = require('../actions/app-actions');
var AppStore = require('../stores/app-store');
var Navigation = require('react-router').Navigation;
var _ = require('lodash');
var JobOffer = require('./JobOffer');
// Our custom component is managing whether the Modal is visible
const ListingDetail = React.createClass({
mixins: [Navigation],
getInitialState: function() {
console.log(this.props.data);
return {
ListingDetail: this.props.data
};
},
componentDidMount: function() {
AppStore.addChangeListener(this._onChange);
},
_onChange : function(){
},
handleClick: function () {
this.transitionTo('/listing/apply/' + this.state.ListingDetail.id );
},
render: function () {
var data = this.state.ListingDetail
return (
<img style={{width: 200+'px', height: 200+'px'}} src="images/job1.jpg" alt="" className="img-circle" />
<div className="title">
<h5>{data.job_title}</h5>
<p>{data.Business.business_name}</p>
</div>
<div className="city"><i className="fa fa-map-marker"></i>{data.Business.business_city}</div>
<div className="type full-time"><i className="fa fa-clock-o"></i>{employmentType}</div>
<div className="sallary"><i className="fa fa-dollar"></i>{data.job_compensation}</div>
<div className="container">
<div className="row">
<div className="col-sm-8">
<h3></h3>
<article>
<h2>Job Details</h2>
<p>{data.job_description}</p>
</article>
</div>
);
},
});
module.exports = ListingDetail;
Any idea?
I don't see that neither JobOffer nor ListingDetail is being rendered anywhere. Which component is going to render which?
The this.props property of any component is going to contain any properties that were passed to the component at the point it was rendered (or rather "mounted", in React's component lifecycle terminology). It's not meant to be mutated during the component's lifespan, so you can view them as "initialization" values. The this.state property of a component on the other hand should contain properties that do change throughout the component's lifecycle, and each time you change it by using this.setState() the component will be re-rendered (unless you prevent a re-render in the this.shouldComponentUpdate method) to reflect the new state.
So, this.props contains any values that you passed as "attributes" when mounting the component. If you use JSX it could look something like this:
var Parent = React.createClass({
render: function() {
return <DetailsListing id={123} job_title="React Dev" />;
}
});
In this case, DetailsListing's this.props.id is 123 and this.props.job_title is "React Dev" as expected.
You seem to confuse the difference between state and props by returning props as an initial state. Props should be props and state should be state, not intermixed (this might be opinionated, but I think it's definitely mandated by the React principles). Either way, this.props will only contain the values that you passed it when rendering/mounting.
Related
I'm trying to do unit testing to a component using enzyme shallow rendering. Trying to test state activeTab of the component and it throws TypeError: Cannot read property state. my component Accordion. Accordion component jsx code
class Accordion extends Component {
constructor(props) {
super(props)
this.state = {
activeTab: 0
}
}
static defaultProps = {
tabs: [{title: 'Status'}, {title: 'Movement'}]
}
render() {
const { tabs } = this.props
, { activeTab } = this.state
return (
<div className={`accordion`}>
{tabs.map((t, i) => {
const activeClass = activeTab === i ? `accordion--tab__active` : ''
return(
<section key={i} className={`accordion--tab ${activeClass}`}>
<header className={`accordion--header`}>
<h4 className={`accordion--title`}>
<button onClick={() => {this._selectAccordion(i)}}>{t.title}</button>
</h4>
</header>
<div className="accordion--content">
{t.title}
Content
</div>
</section>
)
})}
</div>
)
}
_selectAccordion = activeTab => {this.setState({activeTab})}
}
export default Accordion
and Accordion.react.test.js
import { shallow } from 'enzyme'
import Accordion from './components/Accordion'
test('Accordion component', () => {
const component = shallow(<Accordion name={`Main`}/>)
expect(component.state('activeTab')).equals(0)
})
This could be a this scoping issue. With event handlers in React, you have to bind the event handler in the constructor to "this". Here is some info from React's docs about it:
You have to be careful about the meaning of this in JSX callbacks. In
JavaScript, class methods are not bound by default. If you forget to
bind this.handleClick and pass it to onClick, this will be undefined
when the function is actually called.
This is not React-specific behavior; it is a part of how functions
work in JavaScript. Generally, if you refer to a method without ()
after it, such as onClick={this.handleClick}, you should bind that
method.
class Accordion extends Component {
constructor(props) {
super(props)
this.state = {
activeTab: 0
}
// This binding is necessary to make `this` work in the callback
this._selectAccordion = this._selectAccordion.bind(this);
}
static defaultProps = {
tabs: [{title: 'Status'}, {title: 'Movement'}]
}
_selectAccordion(activeTab){
this.setState({activeTab : activeTab})
}
render() {
const { tabs } = this.props,
{ activeTab } = this.state
return (
<div className={`accordion`}>
{tabs.map((t, i) => {
const activeClass = activeTab === i ? `accordion--tab__active` : ''
return(
<section key={i} className={`accordion--tab ${activeClass}`}>
<header className={`accordion--header`}>
<h4 className={`accordion--title`}>
<button onClick={() => {this._selectAccordion(i)}}>{t.title}</button>
</h4>
</header>
<div className="accordion--content">
{t.title}
Content
</div>
</section>
)
})}
</div>
)
}
}
Your tests should verify how the component works but not "how to change a state". You need to throw new props into your component and get a result, and the result is expected.
I've tested my components with snapshots
This is an example of my current project
describe('<Component />', () => {
it('Page rendered', () => {
const rendered = renderComponent({
...testProps,
loadDataList,
loading: true,
});
expect(rendered).toMatchSnapshot();
});
});
I am somewhat new to ReactJS
I have a react class that is rendering a number of items: (Sample)
var app = app || {};
app.Results = React.createClass({
componentDidMount: function () {
},
handleUpdateEvent: function(id){
var _self = this;
var handler = function()
{
var query = _self.props.results.query;
_self.props.onSearch(query); // re-does the search to re-render items ...
// obviously this is wrong since I have to click the button twice to see the results
//
}
var optionsURL = {dataType: 'json'};
optionsURL.type= 'POST';
optionsURL.url = 'http://localhost:8983/solr/jcg/dataimport?command=delta-import&clean=false&commit=true&json.nl=map&wt=json&json.wrf=?&id='+id;
// updates index for specific item.
jQuery.ajax(optionsURL).done(handler);
},
render: function () {
var tdLabelStyle = {
width: '150px'
}
return (
<div id="results-list">
{this.props.results.documents.map(function (item) {
return (
<div id={item.id} key={item.id} className="container-fluid result-item">
<div className="row">
<div className="col-md-6">
<table>
<tr><td colspan="2">{item.name}</td></tr>
<tr style={{marginTop:'5px'}}><td style={tdLabelStyle}><b>Amount:</b></td><td>{item.amount}
<button type="Submit" onClick={() => {this.handleUpdateEvent(item.id)}} title="Refresh Amount" >Refresh</button>
</td></tr>
</table>
</div>
</div>
</div>
)
},this)}
</div>
);
}
});
I have a button within the table that makes a call out to SOLR to perform a delta import, then re-calls the select function in order to grab the new data.
I'm obviously doing the handleUpdateEvent function incorrectly, however, I'm not 100% sure how to go about getting either the entire thing to re-render, or just the individual item to re-render.
(Hopefully I've made sense...)
Any help is appreciated.
(onSearch Function)
handleSearchEvent: function (query) {
if (this.state.query != null)
{
if (this.state.query.filters != null)
{
query.filters = this.state.query.filters;
}
}
$("#load-spinner-page").show();
if (app.cache.firstLoad) {
$("body").css("background","#F8F8F8");
app.cache.firstLoad = false;
}
var _self = this;
app.cache.query = query;
docSolrSvc.querySolr(query, function(solrResults) {
_self.setState({query: query, results: solrResults});
$("#load-spinner-page").hide();
});
},
The first thing to change is the use of React.createClass. This has been depracated in favour ES6 syntax. Also, I dont't suggest using jQuery along side React. It's not impossible to do, but there are other things to consider. Read this for more. I'll use it here, but consider something like fetch or axios (or one of the many other libraries) for fetching the data.
I think you're on the right track, but a few things to update. Because the available options are changing, I would put them into the components state, then having the handleUpdateEvent function update the state, which will trigger a re-render.
Your class would look something like this:
class Results extends React.Component {
constructor(props) {
super(props);
// this sets the initial state to the passed in results
this.state = {
results: props.results
}
}
handleUpdateEvent(id) {
const optionsURL = {
dataType: 'json',
type: 'POST',
url: `http://localhost:8983/solr/jcg/dataimport?command=delta-import&clean=false&commit=true&json.nl=map&wt=json&json.wrf=?&id=${ id }`
};
// Instead of calling another function, we can do this right here.
// This assumes the `results` from the ajax call are the same format as what was initially passed in
jQuery.ajax(optionsURL).done((results) => {
// Set the component state to the new results, call `this.props.onSearch` in the callback of `setState`
// I don't know what `docSolrSvc` is, so I'm not getting into the `onSearch` function
this.setState({ results }, () => {
this.props.onSearch(results.query);
});
});
}
render() {
const tdLabelStyle = {
width: '150px'
};
// use this.state.results, not this.props.results
return (
<div id="results-list">
{
this.state.results.documents.map((item) => (
<div>
<div id={ item.id } key={ item.id } className="container-fluid result-item">
<div className="row">
<div className="col-md-6">
<table>
<tr><td colspan="2">{item.name}</td></tr>
<tr style={{marginTop:'5px'}}>
<td style={ tdLabelStyle }><b>Amount:</b></td>
<td>{item.amount}
<button type="button" onClick={ () => { this.handleUpdateEvent(item.id) } } title="Refresh Amount" >Refresh</button>
</td>
</tr>
</table>
</div>
</div>
</div>
</div>
))
}
</div>
);
}
}
new to React JS here. I have a setup where I an App parent component that has a initialFormInput state holding a recipe object. A function is passed from the parent component to a child component called EditRecipeButton that can change this state to a specific recipe that is called from an edit button.
This state in the parent is mapped to a state in a child AddRecipe component with a via this.props and my reasoning is that whenever the parent component state changes this state in AddRecipe changes as well. But that doesn't happen, what am I doing wrong here?
Here is my code:
var App = React.createClass({
getInitialState(){
return{
showModal:false,
recipeKeys: [ ],
recipes: [ ],
initialFormInput: {name: "", ingredients: []}
}
},
open: function(){
this.setState({showModal:true});
},
close: function(){
this.setState({showModal:false});
},
editRecipe: function(recipe){
console.log(recipe);
this.setState({initialFormInput: recipe}, function(){
this.open();
});
},
render: function(){
return(
<div className="container">
<h1>Recipe Box</h1>
<RecipeList recipes = {this.state.recipes} deleteRecipe = {this.deleteRecipe} editRecipe={this.editRecipe} />
<AddRecipeButton openModal = {this.open}/>
<AddRecipe closeModal = {this.close} showModal={this.state.showModal} addRecipeKey = {this.addRecipeKey} initialFormInput = {this.state.initialFormInput}/>
</div>
)
}
var RecipeList = function (props) {
return (
<ul className="list-group">
{
props.recipes.map( (item,index) => <RecipeItem recipe={item} deleteRecipe = {props.deleteRecipe} editRecipe={props.editRecipe}/> )
}
</ul>
);
};
var RecipeItem = React.createClass({
getInitialState: function(){
return {displayIngredients: false}
},
toggleRecipe: function() {
this.setState({displayIngredients: !this.state.displayIngredients})
},
render: function() {
return(
<li className="list-group-item" >
<h4 onClick={this.toggleRecipe}>{this.props.recipe.name}</h4>
<div style={{display: this.state.displayIngredients ? 'block' : 'none'}}>
<h5 className="text-center">Ingredients</h5>
<hr/>
<ul className="list-group" >
{this.props.recipe.ingredients.map((item) => <IngredientItem ingredient={item} />)}
</ul>
<ButtonToolbar>
<DeleteRecipeButton deleteRecipe = {this.props.deleteRecipe} recipeName={this.props.recipe.name}/>
<EditRecipeButton editRecipe = {this.props.editRecipe} recipe={this.props.recipe}/>
</ButtonToolbar>
</div>
</li>
)
}
});
var IngredientItem = function(props){
return (
<li className="list-group-item">
<p>{props.ingredient}</p>
</li>
)
};
var EditRecipeButton = React.createClass({
render: function(){
return (
<Button bsStyle="default" bsSize="small" onClick={() => this.props.editRecipe(this.props.recipe)}>Edit</Button>
)
}
});
var AddRecipe = React.createClass({
//Form in modal to add recipe
getInitialState(){
return {
name: this.props.initialFormInput.name,
ingredients: this.props.initialFormInput.ingredients
};
},
getValidationStateName(){
var length = this.state.name.length;
if(length > 0) {
return "success";
} else {
return "error";
}
},
getValidationStateIngredients(){
var length = this.state.ingredients.length;
if(length > 0){
return "success";
} else {
return "error";
}
},
handleInput: function(key,e){
var input = e.target.value;
if(key === "ingredients"){
input = e.target.value.split(",");
}
var update = {};
update[key] = input;
this.setState(update, function(){
console.log(this.state);
});
},
handleSubmit(){
var recipe = JSON.stringify({name: this.state.name, ingredients: this.state.ingredients});
localStorage.setItem(this.state.name, recipe);
var recipeObject= JSON.parse(recipe);
this.props.addRecipeKey(recipeObject);
this.props.closeModal();
this.setState({name: "", ingredients: []});
},
render: function(){
return (
<div>
<Modal show={this.props.showModal} onHide={this.props.closeModal}>
<Modal.Header closeButton>
<Modal.Title>Add a Recipe Here</Modal.Title>
</Modal.Header>
<Modal.Body>
<form>
<FormGroup controlId="formNameText" validationState = {this.getValidationStateName()}>
<ControlLabel>Recipe</ControlLabel>
<FormControl
type="text"
placeholder="Give your recipe a name"
value={this.state.name}
onInput={this.handleInput.bind(this,'name')}
/>
<FormControl.Feedback />
</FormGroup>
<br/>
<FormGroup controlId="formIngredientsTextarea" validationState = {this.getValidationStateIngredients()}>
<ControlLabel>Ingredients</ControlLabel>
<FormControl
componentClass="textarea"
placeholder="Insert your ingredients, separated by a comma"
value={this.state.ingredients}
onInput={this.handleInput.bind(this,'ingredients')}
/>
<FormControl.Feedback />
<hr/>
</FormGroup>
<Button bsStyle="primary" onClick={this.handleSubmit}>Submit</Button>
</form>
</Modal.Body>
</Modal>
</div>
)
}
});
ReactDOM.render(<App />, document.getElementById('app'));
});
So this does not change when the parent state changes:
getInitialState(){
return {
name: this.props.initialFormInput.name,
ingredients: this.props.initialFormInput.ingredients
};
},
As the name suggests, getInitialState only provides the initial state of a component. Subsequent update won't trigger that function.
You need to to implement componentWillReceiveProps to update the state in when props change. From the docs:
componentWillReceiveProps() is invoked before a mounted component receives new props. If you need to update the state in response to prop changes (for example, to reset it), you may compare this.props and nextProps and perform state transitions using this.setState() in this method.
Note that React may call this method even if the props have not changed, so make sure to compare the current and next values if you only want to handle changes. This may occur when the parent component causes your component to re-render.
If you want to share state across components use redux isntead. Also maintain a separate file for each component.
This link might help you
Step by Step Guide To Building React Redux Apps
I have the following code (simplified for, well, simplicity). I want my Submission components to reveal the author either when individually clicked (works) or when a button is pressed to reveal all of them (does not work).
I've confirmed that the SubmissionList state is being changed when I click the button, and that the initial state of the Submissions is also being correctly set, so why does the setState change not filter down to the Submissions?
I'm sure I'm missing something, so any help would be appreciated. Thanks!
let Submission = React.createClass({
getInitialState: function() {
return {
showAuthor: this.props.revealed
};
},
reveal: function() {
this.setState({
showAuthor: !this.state.showAuthor
});
},
render: function() {
let authorText;
if (this.state.showAuthor) {
authorText = " - " + this.props.author;
} else {
authorText = "";
}
return (
<li className="submission" onClick={this.reveal}>
<span className="submissionText">
{this.props.text}
</span>
<span className="submissionAuthor">
{authorText}
</span>
</li>
);
}
});
let SubmissionList = React.createClass({
getInitialState: function() {
return {
revealAll: true
};
},
revealAllSubmissions: function() {
this.setState({
revealAll: !this.state.revealAll
});
},
render: function() {
let revealed = this.state.revealAll;
let submissionNodes = this.props.data.map(function(submission) {
return (
<Submission author={submission.author} key={submission.id} text={submission.text} revealed={revealed} />
);
});
return (
<div className="allSubmissions">
<button onClick={this.revealAllSubmissions}>Reveal All</button>
<ul className="submissionList">
{submissionNodes}
</ul>
</div>
);
}
});
EDIT: Couldn't actually see my tags written out in the paragraphs. Updated for clarity.
If you want both components to have internal state you need to add componentWillReceiveProps handler to Submission. And sync props to state there as well.
let Submission = React.createClass({
getInitialState: function() {
return {
showAuthor: this.props.revealed
};
},
componentWillReceiveProps: function(nextProps) {
if(this.props.revealed !== nextProps.revealed) {
this.setState({
showAuthor: nextProps.revealed
});
}
},
...
But as you can see this results in code duplication. Since now you have 2 sources of state (props and internal component state). A better idea would be to move state to parent component and make Submission to be a representational component (w/o internal state)
let Submission = ({showAuthor, author, toggle, text}) => (
<li className="submission" onClick={toggle}>
<span className="submissionText">
{text}
</span>
<span className="submissionAuthor">
{showAuthor ? `-${author}` : null}
</span>
</li>
)
Where toggle is a function that switch showAuthor for current submission stored somewhere in SubmissionList state.
UPD you can pass specific toggle that toggles only current submission. For example
let submissionNodes = this.props.data.map(function(submission) {
return (
<Submission
author={submission.author}
key={submission.id}
text={submission.text}
toggle={() => this.toggleSubmissionById(submission.id)} />
);
})
I have following React.js application structure:
<App />
<BreadcrumbList>
<BreadcrumbItem />
<BreadcrumbList/>
<App />
The problem is, when I click on <BreadcrumbItem /> , I want to change a state in <App />
I used callback to pass props to <BreadcrumbList/> but that`s how far I got.
Is there any pattaren how to easily pass props up to compenent tree ?
How can I pass prop to <App />, without doing any callback chaining ?
If you are doing something simple then its often just better to pass the change in state up through the component hierarchy rather than create a store specifically for that purpose (whatever it may be). I would do the following:
BreadcrumbItem
var React = require('react/addons');
var BreadcrumbItem = React.createClass({
embiggenMenu: function() {
this.props.embiggenToggle();
},
render: function() {
return (
<div id="embiggen-sidemenu" onClick={this.embiggenMenu} />
);
}
});
module.exports = BreadcrumbItem ;
THEN pass it up to the parent through the BreadcrumbList component.....
<BreadcrumbItem embiggenToggle={this.props.embiggenToggle}>
... and UP to App, then use it to set the state....
var React = require('react/addons');
var App = React.createClass({
embiggenMenu: function() {
this.setState({
menuBig: !this.state.menuBig
});
},
render: function() {
return (
<div>
<BreadcrumbList embiggenToggle={this.embiggenMenu} />
</div>
)
}
});
module.exports = BreadcrumbItem;
This example toggles a simple boolean however you can pass up anything you like. I hope this helps.
I have not tested this but it was (quickly) ripped from a live working example.
EDIT:
As it was requested i'll expand upon the vague: "you can pass up anything".
If you were making a navigation menu based on an array and needed to pass up the selected item to a parent then you would do the following
var React = require('react/addons');
var ChildMenu = React.createClass({
getDefaultProps: function () {
return {
widgets : [
["MenuItem1"],
["MenuItem2"],
["MenuItem3"],
["MenuItem4"],
["MenuItem5"],
["MenuItem6"],
["MenuItem7"]
]
}
},
handleClick: function(i) {
console.log('You clicked: ' + this.props.widgets[i]);
this.props.onClick(this.props.widgets[i]);
},
render: function() {
return (
<nav>
<ul>
{this.props.widgets.map(function(item, i) {
var Label = item[0];
return (
<li
onClick={this.handleClick.bind(this, i)}
key={i}>
{Label}
</li>
);
}, this)}
</ul>
</nav>
);
}
});
module.exports = ChildMenu;
You would then do the following in the parent:
var React = require('react/addons');
var ChildMenuBar = require('./app/top-bar.jsx');
var ParentApp = React.createClass({
widgetSelectedClick: function(selection) {
//LOGGING
//console.log('THE APP LOGS: ' + selection);
//VARIABLE SETTING
var widgetName = selection[0];
//YOU CAN THEN USE THIS "selection"
//THIS SETS THE APP STATE
this.setState({
currentWidget: widgetName
});
},
render: function() {
return (
<ChildMenu onClick={this.widgetSelectedClick} />
);
}
});
module.exports = ParentApp;
I hope this helps. Thanks for the upvote.
If you use Flux pattern, you can have a AppStore which listen a BREADCRUMB_CLICK event. So when you click on a BreadCrumbItem, you can execute an action which dispatch BREADCRUMB_CLICK event. When AppStore handle the event, he inform App component which update your state.
For more informations:
Flux architecture