I have made it this far with a FreeCodeCamp project, with a bit of help from some folks who have traveled further down the react.js road.
Before I go on, though, can someone please explain to me exactly what "this.handleData" accomplishes in the following code? (It is someone else's code I was allowed to use, and it helps me get the data I am grabbing from the api to render on the page, but what exactly is handleData doing?)
var LeaderList = React.createClass({
render:function(){
return(
<div >
<h1>Here is the data...</h1>
<AllData />
</div>
);
}
});
var AllData = React.createClass({
dataUrl: 'https://fcctop100.herokuapp.com/api/fccusers/top/recent',
getInitialState: function() {
return {data: []}
},
componentDidMount: function(){
$.getJSON(this.dataUrl, this.handleData)
},
handleData: function(data){
this.setState({data: data});
},
render: function(){
var elems = [];
for(var i=0; i<this.state.data.length; i++){
var subelems = [];
subelems.push(<div><p>{this.state.data[i].img}</p></div>);
subelems.push(<div><p>{this.state.data[i].username}</p></div>);
subelems.push(<div><p>{this.state.data[i].recent}</p></div>);
subelems.push(<div><p>{this.state.data[i].alltime}</p></div>);
elems.push(subelems);
}
return (<div>{elems}</div>);
}
});
ReactDOM.render(
<LeaderList />,
document.getElementById('content')
);
In that code handleData is a custom method that calls setState. setState is part of the React Component API. It causes the Component to re-render, using the data passed to it to set the this.state property available to the component. this.state is accessible in the render method, so the data passed to handleData is being made available to the render method.
As you can see in the render method, this.state is accessed to control what DOM is created.
Firstly, componentDidMount() makes a call to your component's dataUrl, and gets back some data. handleData takes that data and uses it to set the component's state, or internal data store.
That's then used below in the component's render() function, which gets called whenever the state changes.
I think the place to start here is the componentDidMount function. If you take a look at the React Component Lifecycle, you'll see that this function is called after the initial render when the component is placed into the DOM: https://facebook.github.io/react/docs/component-specs.html
The docs advise that:
If you want to integrate with other JavaScript frameworks, set timers
using setTimeout or setInterval, or send AJAX requests, perform those
operations in this method.
It looks like we're making an Ajax call here (Via what I assume is JQuery) to fetch some data from the server. We pass in handleData as a callback which will be called with the results once the Ajax request is complete, which does this:
this.setState({data: data});
To understand what state is in React there are a couple of good links here:
https://facebook.github.io/react/docs/thinking-in-react.html
https://facebook.github.io/react/docs/interactivity-and-dynamic-uis.html
State is reserved only for interactivity, that is, data that changes over time
This is applicable to us as the result of the Ajax request will change this data - only once from nothing to something, but it still changes. When you call setState React triggers a "state transition" which will eventually call render again, where it will populate the elems variable with your data and display it on the screen.
Related
I'm a beginner both in programming and React and I have to create a functioning google Map single page website. I'm using google-map-react.
I have a parent App.js (containing the call to and a HTML sidebar) and a child Map.js containing the map itself and axios request function.
I'm making axios requests to fetch data from foursquare api. It works without side effects. Then I want to pass those data to my app.js and update the parent state so that I can renderthe locations on the sidebar.
This is the function I used (in Map.js). I had to put the call in componentWillReceiveProps as a last resource because componentDidMount didn't work:
https://jsfiddle.net/kd1yuhe5/
I think this may be the issue, but it's also the only way I found to make the list show:
this.props.updateVenues(this.state.venues)
This is the code from App.js
updateVenues(venues) {
this.setState({
venues: venues,
});
}
Then I called the method like this:
<Map updateVenues={this.updateVenues.bind(this)} />
The code works, venues are shown in the sidebar (if you need the code let me know, but I don't think it's relevant), but the I keep making requests until I exceed quota.
Again: I'm a beginner. I just started 3 months ago.
EDIT:
Here are both components:
Map.js
https://jsfiddle.net/kd1yuhe5/5/
App.js
https://jsfiddle.net/xwzrm4bp/2/
When the state of a React component is updated (and without custom implementation of componentShouldUpdate), it triggers a re render of that component (ie call the render function).
If the props of the children of this component have changed since the last render, they will also re render.
They re render because they have received new props, and this will also call their componentWillReceiveProps function.
Since you are fetching data each time Map will receive props, you are fetching data each time something change (state change) on App.
First in Map.js, this.props.query is assigned to this.state.query.
This looks like an error, as in this case what you want are the new props receceived by componentWillReceiveProps, this is the first argument of this function.
So you should assign props.query to this.state.query instead.
Except that actually you should not:
this.state.query is only used in componentWillReceiveProps, therefore there is no need to put props.query into state.query.
Second since you have both this.props.query from the previous props update and props.query which is the new received query, you have the opportunity to fetch only when the query has actually changed:
// Receive the update query from parent and fetch the data
componentWillReceiveProps(nextProps){
if (this.props.query !== nextProps.query) {
this.fetchData(nextProps.query);
}
}
Now you may ask, "ok but why my Map component was always re rendered, even when its props didn't changed".
But they did:
in App.js
<Map
query={this.state.query}
center={this.state.center}
updateVenues={this.updateVenues.bind(this)}
getClickedMarker={this.getClickedMarker.bind(this)}
/>
By calling this.updateVenues.bind(this) and this.getClickedMarker.bind(this) in the render method, you are creating new values (actually new Function references)for the updateVenues and getClickedMarker props, at each render.
Instead, you should bind these method in the contructor of App:
constructor(props) {
super(props);
this.updateVenues = this.updateVenues.bind(this);
this.getClickedMarker = this.getClickedMarker.bind(this);
....
}
....
<Map
query={this.state.query}
center={this.state.center}
updateVenues={this.updateVenues}
getClickedMarker={this.getClickedMarker}
/>
This may limit your API calls a lot, you may also debounce them.
I am using this helper to display a datepicker component
<%= react_component "MyDatePicker", startDate: '', endDate: ''%>
I want to pass javascript values to startDate and endDate props. Any possible way to do this?
I don't really know what you're trying to do here exactly but if you just want to get values of your props from the component to your rails controller, do the following.
You can set state of your props in your react component and send an ajax request whenever the user selects a date.
Fixed this by using Pubsub. What I did is publish the user selected values in the javascript first. Then on react parent component lifecycle method subscribed to the previously published event and use setState to change the state based on that.
Now am on mobile but i will publish the code for clarity once i got access to a pc.
update
Using pubsub is easy. First we need to publish the required from anywhere using javascript
dates={some_value:value, another_value: value }
PubSub.publish(OFFLINE_BOOKING_DURATION, dates)
Here I just pass a dates object. You can pass anything.
Then in react's componentDidMount state I subscribe to this
componentDidMount: function () {
this.token = PubSub.subscribe(OFFLINE_BOOKING_DURATION, this.subscriber)
},
Here the first is object we are expecting and the callback
so here is the call back function here you can do anything like ajax call, set state, or anything
subscriber: function (msg, data) {
#this method gets called on receiving data
#we can access the data we passed like this
data.some_value},
And finally unsubscribe when component unmounts
componentWillUnmount: function () {
PubSub.unsubscribe(this.token)
}
This is how I implemented it. I no longer have the code but I hope you got the point.
I am making a call to an API that allows you to sort the data it returns by different parameters e.g top, latest, popular and so on. What i'm trying to achieve is that when a user clicks a button to sort by a different parameter, the state is changed to the new parameter and the API is called again with that new parameter. Here's my code:
constructor (){
super();
this.state = {
sortType: 'top' //default is to sort by Top
};
this.setSortType = this.setSortType.bind(this);
}
componentWillMount(){
//this retrieves sourceName which is required to make the API call
const parsed = queryString.parse(this.props.location.search);
var sourceName = parsed.sourceId;
//getArticles below is the action that makes the API call with sourcename and sortType as parameters
newsActions.getArticles(sourceName,this.state.sortType);
newsStore.on('articles_change',this.fetchNewsArticles);
}
//Call to setState when user clicks button change sort parameter
setSortType(){
this.setState({
sortType:'latest'
});
}
render() {
return (
<div>
... //logic to display data from the API call
<button onClick={this.setSortType}> Sort By Latest </button>
</div>
);
}
When the button is clicked nothing re-renders. What i'm i missing?
React calls componentWillMount just once on component mount, so the logic it contains won't be updated executed again on state change.
Have a look to React docs, to a better understand of how React licecycle hooks work.
I am new to ES6 and still trying to grasp the concepts of the new specifications, i am currently working on a component in React where i need to make an ajax call and store this response in an object. Then use this object to the map the necessary elements
My component looks like the following
export class App extends Component {
search(){
//make ajax call
response = obj.responseText;
}
getValues(){}
render(){
let result = response.data.map(this.getValues);
return(
<div onKeyDown={this.search.bind(this)}>{result}</div>
)
}
}
How do i declare the "response" variable globally which gets assigned the data from ajax call "obj.responseText"?
It seems like you know what you want to achieve, but are a little confused about how to get there.
I would highly recommend reading the React documentation before you go any further.
Why not global variables?
How do I declare the response variable globally?
In short, don't. Global variables are well-documented as being evil. One instance of this component in a page with a global variable to store its search results would be fine, but imagine if you had two or more instances - they would all share/overwrite each other's search results.
Introducing state
Instead, you want to use React's component state functionality to store your search results.
You can set an initial state by setting a component's this.state in its constructor, (or in ES5, define a getInitialState method on the component).
Then, any time you want to update the component's state, you can call its this.setState(...) method, passing in a new state object. This will also trigger a re-render of the component.
Example
Here is a simple implementation following the above pattern:
export class App extends Component {
// Set the initial state of the component in the constructor
constructor(props) {
super(props);
this.state = {};
}
// This gets called when your component is mounted
componentDidMount() {
// Here we make our AJAX call. I'll leave that up to you
performMyAjaxMethodDefinedSomewhereElse(result => {
// We call this method to update `this.state` and trigger re-rendering
this.setState({ result });
});
}
render() {
// If we haven't received any results yet, display a message
if (!this.state.result) {
return (
<div>No results!</div>
);
}
// Iterate over the results and show them in a list
const result = this.state.result.map(text => (<li>{text}</li>));
// Display the result
return (
<ul>{result}</ul>
);
}
}
Naturally, if you don't want the AJAX call to fire off immediately, you can use a very similar approach, replacing componentDidMount with an event handler which looks almost identical.
Im trying to build an small React.js application and my component structure looks like this:
MainComponent
- CategoryList
-Category
- ItemsList
-Item
My MainContent component does an ajax request for its state data in the componentDidRender: which returns this object:
data:[
Object[0]
-name
-items[]
,
Object[1],
Object[2]
]
Now, I want my CategoryList to write out all the Categories by name, which works just fine, but I also want to print out the items of the selected category. This is my ItemsList component:
var ItemsList = React.createClass({
render:function(){
var itemNodes = this.props.category.items.map(function(item){
return (
<Item name={item.name} />
);
});
return(
<div className="itemList">
{itemNodes}
</div>
);
}
});
And this is how I pass on the "category"-property from my the parent component
<ItemsList category={this.state.data[0]} />
I get an error say "Can´t read property items of undefined" meaning that the category prop never was assigned. I know that this.state.data contains an array of objects so I don´t see the error here.
What do I do wrong?
EDIT: Per request, this is my MainComponent:
var MainComponent = React.createClass({
getInitialState:function(){
return {data: []};
},
componentDidMount:function(){
$.ajax({
type:'get',
url: '/categories',
dataType: 'json',
success:function(data){
this.setState({data: data});
}.bind(this)
});
},
render: function(){
return (
<div className="row">
<div className="col-md-6">
<CategoryList categories={this.state.data} />
</div>
<div className="col-md-6">
<ItemsList category={this.state.data[0]} />
</div>
</div>
);
}
});
Your main component initializes the state with an empty array in data. A render would always fail because there is no this.state.data[0].
One would probably reply that the ajax request will provide the value for this state property data (supposing that your web service is providing a valid array). However, this only happens after the response was received from the server, which will not happen after the first render.
If the information was available immediately, one could either setState on the method componentWillMount or the component constructor, so as to avoid triggering a second render:
componentWillMount() is invoked immediately before mounting occurs. It
is called before render(), therefore setting state synchronously in
this method will not trigger a re-rendering. Avoid introducing any
side-effects or subscriptions in this method.
In this case, since we are waiting for remote information, the React documentation still recommends the use of componentDidMount, as well employed here:
componentDidMount() is invoked immediately after a component is
mounted. Initialization that requires DOM nodes should go here. If you
need to load data from a remote endpoint, this is a good place to
instantiate the network request. Setting state in this method will
trigger a re-rendering.
Therefore, the component's render method must be able to handle the missing state variable. There are multiple ways to approach this, but preventing the nested element from being rendered until we have data is the easiest approach. With some additional logic, the application could inform the user that the particular component is loading.
render() {
return (
<div className="row">
<div className="col-md-6">
<CategoryList categories={this.state.data} />
</div>
<div className="col-md-6">
{this.state.data.length > 0 &&
<ItemsList category={this.state.data[0]} />
}
</div>
</div>
);
}
I can corroborate the previous answer.
Modern (2023) React tells you to use functional components. So, if you want to upload data from a server to render it inside a component you need to use so-called 'hooks' named 'useEffect' and 'useState'.
So first you import them:
import React, { useEffect } from "react";
import { useState } from "react";
Next, you create a functional component and initialize 'state' and 'set_state function' inside a component:
export default function FuctionName() {
let [ value, set_value ] = useState([])
}
This would create the 'value' variable which would preserve its state across renders.
Then, you make a fetch request (before the component's return statement) (but you need to put it inside of the useEffect function for a consistent behavior):
useEffect(() => {
fetch('url')
.then(response => response.json())
.then(server_data => {
set_value(server_data);
})}
, [])
Now, you see why we need state: because we need to store data returned from the server inside a variable and we need this data to preserve across renders.
Also, you think that now you can use your data inside the component's return render like this:
return (
<h1>the data from server: {value.name_of_key}</h1>
)
But you encounter the error of 'reading undefined'.
First, check if the server returns the correct type of object. Put a console.log statement inside the fetch request:
useEffect(() => {
fetch('url')
.then(response => response.json())
.then(server_data => {
set_value(server_data);
console.log(server_data);
})}
, [])
It should be a list with objects inside, like: [{}, {}, {}].
If it is, then the problem is that component renders before it receives the data from the server. The component will first render, then receive the data from the server. If you call the state variable inside the render statement it will call the empty state.
So, in order to mitigate it, your code should be ready to render both the empty state and the state with data. The easy way to do so would be conditional rendering. Render the component with data only if there is data to render:
return (
{value && <h1>the data from server: {value.name_of_key}</h1>}
)
the 'value &&' part lets you implement the logic:
if the value (data from the server) is empty don't render anything (thus do not call any keys from data)
if the value is not empty, render the component which calls to data
Why is that so? The '&&' expression (a and b) evaluates from right to left. If first variable evaluates to 'false', then it does not go further and stops there. The initial state of variable 'value' is an empty object ('let [ value, set_value ] = useState([])' - remember?). So, when the server has not returned data yet on the first render, the expression 'value &&' evaluates to 'false' and does not render the right part of the expression. When the server has returned the data, it calls the 'setState' function with data and forces to render the component the second time. The second time expression 'value &&' evaluates to 'true' and React goes to the right part of the expression and actually renders the component with data. You can check this process by placing console.log statement inside the fetch request and inside the return statement and you will see the order of functions resolutions.
Hope it helps.
Also, check out the article:
https://daveceddia.com/react-before-render/
Happy coding >>