Transferring state between two components - javascript

I have a searchForm component that renders a searchResult component. When the searchForm gets the results, it should pass the state to the result's state.
This where I fail.
var SearchForm = React.createClass({
getInitialState: function () {
return {
golden_record: {}
}
},
handleSearchSubmit: function (search_param) {
$.ajax({
url: this.props.url_variation,
type: 'GET',
data: search_param,
success: function (data) {
this.setState(
{
...
}
);
}.bind(this),
});
$.ajax({
url: this.props.url_golden,
type: 'GET',
data: search_param,
success: function (data) {
var golden_record = {
'site': data.sites[0].name,
'country': data.sites[0].country,
};
this.setState({'golden_record': golden_record});
}.bind(this),
})
},
render: function () {
return (
<div className="searchResult">
<SearchResult
golden_record={this.state.golden_record}
/>
</div>
);
}
});
SearchResult:
As you can see I am passing the golden_record as a property to the SearchResult. Inside SearchResult, when I set value of <input /> to the property this.props.golden_record['site'], the input is fixed to that value. But I want to set value rather to this.state.site so that I can change it afterwards, if I wanted to. So I don't know how to copy the read-only value of the prop to the state.
<input type="text" className="form-control" placeholder="Facility name" name="facilityName" onChange={this.onSiteChanged} value={this.props.golden_record['site']} ref="facilityName"></input>
Any suggestions please?

In your SearchResult component, you could set your state in componentWillReceiveProps:
var SearchResult = React.createClass({
...
getInitialState: function(){
return {
site: ''
}
},
componentDidMount: function(){
this.setState({ site: this.props.golden_record.site });
},
componentWillReceiveProps: function(newProps){
this.setState({ site: newProps.golden_record.site });
},
render: function(){
return <input type="text" className="form-control" placeholder="Facility name" name="facilityName" onChange={this.onSiteChanged} value={this.state.site} ref="facilityName"></input>
}
});

Related

Best way to show a loading spinner/gif while my React component is fetching via AJAX?

Imagine I have a very simple React component that shows a list of elements that are stored in this.state.myList (see example below)
Hitting a "Refresh" button at the bottom causes React to query the backend server and retrieve an updated list that it will then display. The actual contents or implementation of this list are irrelevant.
var Foo = React.createClass({
handleButtonClick: function() {
return $.ajax({
type: "POST",
url: "/some/refresh/url",
data: JSON.stringify({}),
dataType: "json",
contentType: "application/json",
success: (function(response){
self.setState({ myList: response.list });
})
});
},
render: function() {
return (
<div className="container">
<ul>
{
this.state.myList.map(function(item) {
return <li id="{item.id}">{item.name}</li>
});
}
</ul>
<input type="submit" value="REFRESH LIST" onClick={this.handleButtonClick} />
</div>
);
}
});
Let's say the AJAX call (for whatever reason) takes a few seconds. In that meantime, I'd love to show a standard "loading" or "spinner" gif to let the user know it's working.
What's the best approach to doing that here?
Right before the AJAX call I could manually update the DOM and insert a spinner gif but that doesn't seem like the "React way to do it". And plus I don't know what impact that would have on the ReactDOM that react maintains.
I could track a state for isLoading and show the spinner instead of the list if it is loading. But then I would need it to render() something and then immediately kick off another call to an AJAX action.
Any help appreciated.
Thanks!
The way I always solve this is for the component to track fetchInProgress in its state.
Before you make your fetch, you set this value to true; when the fetch completes (either success or fail), you set the value back to false.
The component's render method then honors this flag; if the flag is true, it renders a spinner instead of a dataset.
var Foo = React.createClass({
handleButtonClick: function() {
// before making call, set fetch flag
self.setState({ fetchInProgress: true });
return $.ajax({
type: "POST",
url: "/some/refresh/url",
data: JSON.stringify({}),
dataType: "json",
contentType: "application/json",
success: (function(response) {
// when updating with dataset, also reset fetch flag
self.setState({
fetchInProgress: false,
myList: response.list
});
}),
failure: ((function(reason) {
// make sure to reset even if fetch fails!
self.setState({
fetchInProgress: false
});
})
});
},
render: function() {
return (
<div className="container">
<ul>
{
this.state.fetchInProgress
: <Spinner />
: this.state.myList.map(function(item) {
return <li id="{item.id}">{item.name}</li>
})
}
</ul>
<input type="submit" value="REFRESH LIST" onClick={this.handleButtonClick} />
</div>
);
}
});
The React model is built around having UI as a representation of your state. That means you should model your state as "what is the necessary data" and the return value of render() is just how you display that data.
In your case, you should keep track of isLoading and in render() you conditionally display the spinner based on the value in your state.
var Foo = React.createClass({
getInitialState: function() {
return {isLoading: false};
},
handleButtonClick: function() {
this.setState({ isLoading: true });
return $.ajax({
type: "POST",
url: "/some/refresh/url",
data: JSON.stringify({}),
dataType: "json",
contentType: "application/json",
success: (function(response){
self.setState({ myList: response.list, isLoading: false });
})
});
},
render: function() {
return (
<div className="container">
<ul>
{
this.state.myList.map(function(item) {
return <li id="{item.id}">{item.name}</li>
});
}
</ul>
{this.state.isLoading && <Spinner />}
<input type="submit" value="REFRESH LIST" onClick={this.handleButtonClick} />
</div>
);
}
});
small edit from #Tom's answer above.
render: function() {
return (
<div className="container">
<ul>
{
this.state.fetchInProgress ?
<Spinner />
:
this.state.myList.map(function(item) {
return <li id="{item.id}">{item.name}</li>
})
}
</ul>
<input type="submit" value="REFRESH LIST" onClick={this.handleButtonClick} />
</div>
);
}

select box in reactjs not working

I have a form with a select box that gets data from the server and post it to the same server. I'm using select box from ant design.
But that part that handles changes handleChange() is not working and give me this error :
Cannot read property 'value' of undefined
This is my code:
let optionData = [];
class ContentCountries extends React.Component {
constructor(props){
super(props);
this.state = {
value: 'Select',
jsonData: 0
};
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
event.preventDefault();
var data = {
name: this.state.value
}
console.log(data);
/*$.ajax({
type: 'POST',
url: 'https://google.com',
data: data
})*/
}
componentDidMount(){
//ajax request
/*$.ajax({
type: 'GET',
url: 'https://google.com',
succes: function(data){
optionData = data;
}
})*/
//static example
optionData.push('Bangalore');
optionData.push('Pune');
this.setState({jsonData: 1});//change the status so the select tag will get the new values(render will happen)
}
render() {
var optionsTag = <Option value="">Select City</Option>
if(optionData.length){
optionsTag = optionData.map((data,index) => {
return <Option value={data} key={index}>{data}</Option>
})
}
return (
<form onSubmit={false}>
<label>
Please select city:
<Select value={this.state.value} onChange={this.handleChange.bind(this)}>
{ optionsTag}
</Select>
</label>
<input type="button" onClick={this.handleSubmit.bind(this)} value="Submit" />
</form>
)
}
}
EDITED: Sorry, as you said, it is Ant Design. However, it is almost the same with your handleChange function.
Because it takes the value as the argument, not the event. So that there is no target property inside passed argument.
handleChange(val) {
this.setState({value: val});
}

React DOM not re-rendering

My React JS file is below:
The logic behind this:
1.) CreateTable renders CreateColumns, CreateRows, & ChangeResults
On the first render, CreateRows is empty, but once the component mounts, a fetch() call is made to update the rows, page re-renders and I have my table
2.) ChangeResultscomponent is loaded which creates an input box. State for num_of_rows to an empty string (placeholder, not sure if I even need to do this).
When I input some number into the input field and hit click, onClick runs updatePage, which calls the function updateRows (in CreateTable), that then changes the state of people_per_page. I have the console.log() to verify that this is actually happening, and it prints out as expected.
I thought that since CreateRows inherits people_per_page, and I'm changing the state of people_per_page, it would cause a re-render...but nothing is happening.
Can anyone see why that might be?
var CreateTable = React.createClass({
getInitialState: function(){
console.log('initial state loaded')
return {
'table_columns' : ['id','email', 'first', 'last', 'title', 'company', 'linkedin', 'role'],
people_per_page: 34
}
},
updateRows: function(rows) {
console.log(rows)
this.setState(
{people_per_page: rows},
function() {
console.log(this.state.people_per_page)
}
)
},
render: function(){
return (
<div>
<ChangeResults updateRows = {this.updateRows} />
<table>
<CreateColumns columns={this.state.table_columns} />
<CreateRows num_of_rows = {this.state.people_per_page} />
</table>
</div>
)
}
});
var ChangeResults = React.createClass({
getInitialState: function(){
return {
num_of_rows : ''
}
},
handleChange: function(e) {
this.setState({
'num_of_rows' : e.target.value
});
},
updatePage: function(){
this.props.updateRows(this.state.num_of_rows);
},
render: function(){
return (
<div>
Number of people per page: <br />
<input type="text" onChange = {this.handleChange} />
<button onClick={this.updatePage}> Update Page </button>
</div>
)
}
})
var CreateColumns = React.createClass({
render: function(){
var columns = this.props.columns.map(function(column, i){
return (
<th key={i}>
{column}
</th>
)
});
return (
<thead>
<tr>
{columns}
</tr>
</thead>
)
}
});
var CreateRows = React.createClass({
getInitialState: function() {
return {
'people':[],
}
},
componentDidMount: function(){
console.log('componentDidMount running')
this.createRow();
},
createRow : function(){
console.log('starting fetch')
fetch('http://localhost:5000/search', {
method: 'POST',
body: JSON.stringify({
people_per_page: this.props.num_of_rows
})
})
.then(function(response) {
return response.json()
})
.then((responseJson) => {
return this.setState({'people' : responseJson.people })
});
},
render: function(){
var rows = this.state.people.map(function(row, i){
return (
<tr key={i}>
<td>{row['id']}</td>
<td>{row['email']}</td>
<td>{row['first']}</td>
<td>{row['last']}</td>
<td>{row['title']}</td>
<td>{row['company']}</td>
<td>{row['linkedin_url']}</td>
<td>{row['role']}</td>
</tr>
)
})
return (
<tbody>
{rows}
</tbody>
)
}
});
ReactDOM.render(<CreateTable />, document.getElementById('content'));
In <CreateRows />, componentDidMount is only called for the first render, when the component is 'mounted' on the page. After that, you need to fetch new data in componentDidUpdate or somewhere else in the application.

React multipages, component data passing

I'm doing a react app and id I'd like to know how to think multipages websites. Actually i'im I'm doing a course searcher,i searcher; I use routie to render the different components that renders render the page. The problem is that they arent aren't related by hierarchy, so the ajax data isn't accessible to the component that renders the result.I've I've tried vainly to use a js var data but doesnt var data but that doesn't work too either.Ive read https://facebook.github.io/react/tips/communicate-between-components.html
but i don't see what to do with own event system. If someone could illustrate the last paragraph of this doc it is great for all the people that are in this case.
var data = {};
var CourseSearcher = React.createClass({
getInitialState: function(){
return {
return { places: '',
branch: 0,
dayOfMonth: '',
timeStart: '',
timeEnd: '',
data: []};
},;
},
handlePlacesChange: function(e){
this.setState({places: e.target.value});
},
handleBranchChange: function(e){
this.setState({branch: e.target.value});
},
handleDayOfMonthChange: function(e){
this.setState({dayOfMonth: e.target.value});
},
handleTimeStartChange: function(e){
this.setState({timeStart: e.target.value});
},
handleTimeEndChange: function(e){
this.setState({timeEnd: e.target.value});
},
handleSubmit: function(e){
// stop the default browser action
e.preventDefault();
// Do an ajax post
$.ajax({
url:'php/results.php',
dataType: 'json',
type: 'GET',
data: {
data: {places: this.state.places,
branch:this.state.branch,
dayOfMonth:this.state.dayOfMonth,
timeStart:this.state.timeStart,
timeEnd:this.state.timeEnd},
},
success: function(data){
this.setState({data: data});
data = this.state.data;
routie('results');
}.bind(this),
error: function (xhr,status,err){
console.error('php/results.php',status,err.toString());
}.bind(this)
});
},
render: function(){
return(
<div>
<form method="get" onSubmit={this.handleSubmit}>
<label>Où?</label>
<input
type="text"
placeholder="Lieux"
value={this.state.places}
onChange={this.handlePlacesChange}
/>
<label>Quoi?</label>
<select value={this.state.branch} onChange={this.handleBranchChange}>
<option>Matière</option>
<option>Français</option>
<option>Anglais</option>
</select>
<label>Quand ?</label>
<input
type="date"
value={this.state.dayOfMonth}
onChange={this.handleDayOfMonthChange}
/>
<input
type="time"
value={this.state.timeStart}
onChange={this.handleTimeStartChange}
/> -
<input
type="time"
value={this.state.timeEnd}
onChange={this.handleTimeEndChange}/>
<button type="submit">Go!</button>
</form>
</div>
);
}
});
console.log(data);
var ResultList = React.createClass({
render: function() {
console.log(data);
return(
<h1>Hello</h1>);
}
);
}
});
var ResultBox = React.createClass({
render: function() {
return (
<div>
<h4>{}</h4>
</div>
);
}
});
routie({
'':function() {
React.render(<CourseSearcher />,
document.getElementById('content'));
},
'results': function() {
React.render(
React.render(<ResultList results={data} />,
document.getElementById('content'));
}
});
Done well with react router ;)
I've done it with react router where components are related to some dedicated urls

How would one do two day data binding in React, having an input value A update when B is and vice versa, while keeping history of previous transforms?

The following code for data binding:
var Text = React.createClass({
getInitialState: function(){
return {
pageName: this.props.pageName,
url: this.props.pageName,
}
},
handleChange: function(event){
var pageName = event.target.value;
var url = event.target.value.toLowerCase();
this.setState({
pageName: pageName,
url: url,
});
},
render: function() {
return (
<div>
pageName: <input ref="pageName" onChange={this.handleChange} value={this.state.pageName}/><br/>
url: <input ref="url" onChange={this.handleChange} value={this.state.url}/><br/>
</div>);
}
});
React.render(<Text pageName="name" />, document.body);
A live demo can be see here. However, being that React re-renders after every input, the toLowerCase() function only picks up on the last letter. How would I make it so that it remembers the previous state(not looking for a slice()/splice() workaround, if possible). Thanks in advance!
You could add an id to the inputs to be able to determine where the input came from when handleChange is run. And then make sure that all input into url is always translated to Lowercase when you update pageName. E.g.
var Text = React.createClass({
getInitialState: function(){
return {
pageName: this.props.pageName,
url: this.props.pageName,
}
},
handleChange: function(event){
var pageName = event.target.value;
// ADDED EXTRA LINE HERE
pageName = (event.target.id == "url") ? pageName.toLowerCase() : pageName;
var url = event.target.value.toLowerCase();
this.setState({
pageName: pageName,
url: url,
});
},
render: function() {
return (
<div>
pageName: <input ref="pageName" onChange={this.handleChange} value={this.state.pageName}/><br/>
url: <input ref="url" id="url"onChange={this.handleChange} value={this.state.url}/><br/>
</div>);
}
});
React.render(<Text pageName="name" />, document.body);
You CANNOT however do two-way binding between both fields AND maintain all CAPS typed in name AND transform url to lowercase.

Categories