I have problem with getting state for component which is loaded by react-router on path with dynamic segment /item/:id:
var Item = React.createClass({
mixins: [State],
getInitialState: function () {
return {data: {}};
},
loadTrackData: function () {
api.getItemById(this.getParams().id, function (data) {
this.setState({data: data});
}.bind(this));
},
componentDidMount: function () {
this.loadTrackData();
},
componentWillReceiveProps: function () {
this.loadTrackData();
},
render: function () {
console.log(this.state); // empty `data` object
return (
<div>
<h2>{this.state.data.title.prop}</h2> // doesn't work
// but works with
// <h2>{this.state.data.title}</h2>
</div>
);
}
});
Any of these methods componentDidMount and componentWillReceiveProps are not called at all!
And render logs empty data object as it's just initialized..
Router looks like:
var routes = (
<Route path="/" handler={Index}>
<Route name="stream" path="/item/:id" handler={Item} />
<DefaultRoute handler={Dashboard} />
</Route>
);
Related
I'm new to ReactJS and I'm trying to use React Router on a project I'm working on. I'm using a nested route to load a component, but I suspect that because of the structure of my project, the nested route is not using it's handler at all. So when I use the Back button there is no history of the first ajax request, but it keeps showing the results of the second one.
I have this parent component Competitions which performs an ajax request:
var Competitions = React.createClass({
getInitialState: function() {
return {
compData: [],
id: "",
hide: false,
}
},
componentDidMount: function() {
axios.get(this.props.source, {
headers: {'X-Auth-Token': '********************',
'Content-type': 'application/json'}
})
.then(result => {
this.setState({compData: result.data});
})
.catch(error => {
console.log(error);
});
},
changeId: function (newId) {
this.setState({
id: newId
});
},
render: function() {
return (
<CompTable compData={this.state.compData} id={this.state.id} onClick= {this.changeId} hide={this.state.hide}/>
);
}
});
module.exports = Competitions;
The Competitions component passes the results data to it's child CompTable
var CompTable = React.createClass({
propTypes: {
compData: React.PropTypes.array.isRequired
},
getInitialState: function() {
return {
showTeams: this.props.showTeams,
hide: this.props.hide,
};
},
teamsClick: function() {
this.setState({
showTeams: !this.props.showTeams,
hide: !this.props.hide,
});
},
handleClick: function(e) {
this.props.onClick(e);
},
render: function() {
var hide = this.state.hide;
var list = this.props.compData.map(function (comp, i) {
return (
<tr key={i+1}>
<th scope="row">{i+1}</th>
<td className={comp.id} onClick={function() { this.teamsClick(); this.handleClick(comp.id); }.bind(this)}> <Link to={'/competitions/'+comp.league}> {comp.caption} </Link> </td>
<td>{comp.league}</td>
<td>{comp.numberOfTeams}</td>
</tr>
);
}, this);
if (hide) {
return (
<div className="row">
<div > { this.state.showTeams ? <Teams id={this.props.id}/> : null } </div>
</div>
)
}
else {
return (
<tbody>{list}</tbody>
)
}
});
module.exports = CompTable;
Inside the CompTable component I have a conditional render. So the first time the ajax call is made, it loads the Competitions Table and then when a competition is clicked, I grab the clicked team id and load the child component Teams which performs the second ajax request:
var Teams = React.createClass({
getInitialState: function() {
return {
teamData: [],
}
},
componentWillMount: function(){
this.dataSource();
},
componentWillReceiveProps: function(nextProps){
this.dataSource(nextProps);
},
dataSource: function(props) {
props = props || this.props;
var url = 'http://api.football-data.org/v1/competitions/x/teams';
var source = url.replace(url.split('/')[5], this.props.id);
axios.get(source, {
headers: {'X-Auth-Token': '********************',
'Content-type': 'application/json'}
})
.then(result => {
this.setState({teamData: result.data.teams});
})
.catch(error => {
console.log(error);
});
},
render: function() {
return (
<TeamsTable teamData={this.state.teamData}/>
);
}
});
module.exports = Teams;
I'm posting the Index file with the Router:
var app = document.getElementById('app');
var CompetitionsWrapper = React.createClass({
render: function () {
return (
<Competitions source="http://api.football-data.org/v1/competitions/?season=2016"/>
);
}
});
ReactDOM.render(
<Router history={browserHistory}>
<Route path="/" component={Layout}>
<IndexRoute component={Home}></IndexRoute>
<Route path="/competitions" component={CompetitionsWrapper}>
<Route path="/competitions/:teamLeague" component={Teams} ></Route>
</Route>
</Route>
</Router>,
app);
Every time the competitions table loads and I'm able to load a Teams Table by clicking on a competition, when I'm using the back button there isn't a new ajax request, but I still see the loaded Teams Table and I can see from Chrome developer tools that the same team is reloaded. Is there any way to recall the Competitions ajax request when I use the back button or is there another way to make browser history work correctly?
I have 2 React Classes:
const PostList = React.createClass({
getInitialState: function () {
return {
status: "loading",
posts: []
}
},
render: function() {
return (
<div id="container">
<ReactRouter.Link to="upload"></ReactRouter.Link>
{this.state.status}
{
this.state.posts.map(function (post) {
return (
<Post post={post} />
);
})
}
</div>
);
}
});
const Upload = React.createClass({
render: function() {
return (
<div className="upload">Upload</div>
);
}
})
const MainContent = React.createClass({
render: function() {
return (
<div>
<Header />
<div id="inner-content">
{this.props.children}
</div>
<Footer />
</div>
);
}
})
const routes = (
<Route path="/" component={MainContent}>
<ReactRouter.IndexRoute component={PostList} />
<Route path="upload" component={Upload} />
</Route>
)
var render = ReactDOM.render(<Router history={History}>{routes}</Router>, document.getElementById("content"));
The question is how can i access the setState function on PostList to render new posts?
Posts will be refreshed through socket.io conenction so when a new posts arrives, i just want to set the state of the PostList to rerender the full html code with he new post(s)
Sorry for bad english...
Add componentDidMount function and set up a socket listener inside it such that when a new message arrives, add it to the current list of posts
const PostList = React.createClass({
getInitialState: function () {
return {
status: "loading",
posts: []
}
},
componentDidMount: function () {
var that = this;
// Be sure to set up data fetching code here
// Set up listener for posts updates here
socket.on('update', function (data) {
// Assuming data contains the post content
that.setState({
posts: that.state.posts.concat(data)
});
});
},
render: function() {
return (
<div id="container">
<ReactRouter.Link to="upload"></ReactRouter.Link>
{this.state.status}
{
this.state.posts.map(function (post) {
return (
<Post post={post} />
);
})
}
</div>
);
}
});
I have a NavBarRouteMapper object that I pass to my navbar. However, in the onpress function of one of the buttons I need to access the state, but I'm not sure how to bind 'this' to the object, since it is a non-function. Relevant code as follows
class app extends Component {
state: {
sideMenuIsOpen: boolean,
};
constructor(props: Object) {
super(props);
this.state = {
sideMenuIsOpen: false,
};
};
static NavigationBarRouteMapper = {
LeftButton(route, navigator, index, navState) {
if (index > 0) {
return (
<SimpleButton
// TODO: Make this work, Menu button needs to go to this
// The problem is here. this.state is undefined
onPress={console.log(this.state)}
customText="Back"
style={styles.navBarLeftButton}
textStyle={styles.navBarButtonText}
/>
);
}
},
RightButton(route, navigator, index, navState) {
// TODO change add button for admins
switch (route.title) {
case "Login":
return null;
default:
return (
<SimpleButton
onPress={() => {
navigator.push({
title: "Profile",
component: Profile,
});
}}
customText="Add"
style={styles.navBarRightButton}
textStyle={styles.navBarButtonText}
/>
);
}
},
Title(route, navigator, index, navState) {
return (
<Text style={styles.navBarTitleText}>{route.title}</Text>
);
},
};
render() {
return (
<SideMenu
menu={<Menu navigate={this.navigate} />}
isOpen={this.state.sideMenuIsOpen}
>
<Navigator
ref="rootNavigator"
initialRoute={{
title: "Login",
component: LoginScene,
navigator: this.refs.rootNavigator,
}}
renderScene = {this.renderScene}
navigationBar={
<Navigator.NavigationBar
// Since this is an object, I can't bind 'this' to it,
// and the documentation calls for it to be passed an object
routeMapper={app.NavigationBarRouteMapper}
style={styles.navBar}
/>
}
/>
</SideMenu>
);
};
}
So you're trying to return the parent's state from the child onClick? If so you can add an onClick which calls the parent onClick that you can pass to the child via props.
var Hello = React.createClass({
getInitialState: function() {
return {test: "testing 1 2 3"};
},
clickMethod: function () {
alert(this.state.test);
},
render: function() {
return <div onClick={this.clickMethod}>Hello {this.props.name}</div>;
}
});
var Child = React.createClass({
render: function() {
return <div>Hello {this.props.name}</div>;
}
});
ReactDOM.render(
<Hello name="World" />,
document.getElementById('container')
);
https://jsfiddle.net/reactjs/69z2wepo/
Also if you had a list of components where each component needs to pass a unique piece of data to the parent you can do so using bind.
var Hello = React.createClass({
getInitialState: function() {
return {test: "testing 1 2 3"};
},
clickMethod: function (argument) {
alert(argument);
},
render: function() {
return <div onClick={this.clickMethod.bind(this, "custom argument")}>Hello {this.props.name}</div>;
}
});
var Child = React.createClass({
render: function() {
return <div>Hello {this.props.name}</div>;
}
});
ReactDOM.render(
<Hello name="World" />,
document.getElementById('container')
);
https://jsfiddle.net/chrshawkes/64eef3xm/1/
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 });
}
});
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
});