react.js don't render until ajax request finish - javascript

I have a pretty simple React.js component which I need to make an isomorphic (rendered on the server). The problem is that component rendered with helpful information only after ajax request completes, like that:
export default React.createClass({
getInitialState() {
return {}
},
componentDidMount() {
fetch("/users/").then(response => {
this.setState(users: response.data)
})
},
render() {
if (this.state.users == undefined) {
return <div />
}
return <div>{this.state.users.map(some_function)}</div>
}
})
The problem is that it's pointless to return empty div to search engines. I want ajax request to be finished (even on the server) and render only after that. How can I achieve that?

As touched on by #Dencker, you want to make the parent component decide when to render the child component, something like this would work:
// Parent
export default React.createClass({
getInitialState() {
return {
usersLoaded: false
}
},
componentDidMount() {
fetch("/users/").then(response => {
this._users = response.users;
this.setState({
usersLoaded: true
});
})
},
render() {
if ( this.state.usersLoaded ) {
return (
<ChildComponent users={this._users} />
)
} else {
return null;
}
}
});
// Child
export default React.createClass({
render() {
return <div>{this.props.users.map(some_function)}</div>
}
});
What I'm doing there is:
Setting an initial state on the parent component which is usersLoaded: false.
In the render function for that component, making sure I only render the child component when the parent's usersLoaded state is true.
parent component's componentDidMount method is where the AJAX call takes place, and note I use a variable on the component to store the users, not the state object (states generally should only be used to store very simple values).
This is then passed down to the child component as a prop.
All of the above makes the child component far simpler as it will only now need a render method and no if/else check.

Related

How to pass data in two way data binding in class base component in ReactJS?

My problem that is in a child component, I have a state that updated via post method and I must show this state in my parent component both these component are the class base component
The ReactJS is a library with One directional data binding. So that is not possible to pass data like Angular or VueJS. you should pass a handler function to the child-component and then after the Axios answer update the local and also the parent component.
And there is a little hint here, there is no different for your situation between class components and functional components. pay attention to the sample code:
class ParentComponent extends React.Component {
state = {
data: undefined,
};
handleGetData = data => {
this.setState({
data,
});
};
render() {
return (
<ChildComponent onGetData={this.handleGetData} />
);
}
}
And now inside the ChildComponent you can access to the handler function:
class ChildComponent extends React.Component {
componentDidMound() {
const { onGetData } = this.props;
Axios
.get('someSampleUrl')
.then( (data) => {
onGetData(data); // running this update your parent state
});
}
render() {
return (
<div />
);
}
}
React's practice is one-directional. For best practice, you must 'Raise' the states into the parent component.
But if you were looking specifically to pass data up, you need to pass a callback down as a prop. use that callback function to pass the data up:
Parent:
<Parent>
<Child callback={console.log} />
</Parent>
Child:
const Child = (props) => {
const { callback } = props;
const postData = (...) => {
...
callback(result);
}
...
}

Why am I getting "return _this.state.data" instead of JSON object

I have a parent component doing an AJAX call to get a JSON object. I've done a few console.log's to make sure that the data is correct in the parent component, but then when I pass through props, I get a value of:
ƒ data() {
return _this.state.data;
}
What I've done to this point seems simple enough so I can't find what the issue is.
Parent Component:
class InfoBox extends Component {
state = {
data: []
};
componentDidMount = () => {
this.loadDonationsFromServer();
setInterval(this.loadDonationsFromServer, this.props.pollInterval);
};
loadDonationsFromServer = () => {
$.ajax({
url: "https://jsonplaceholder.typicode.com/comments",
dataType: "json",
cache: false,
success: data => {
this.setState({ data });
},
error: (xhr, status, err) => {
console.error(status, err.toString());
}
});
};
render = () => {
return (
<React.Fragment>
<h1>Information</h1>
<InfoList
data={() => this.state.data}
/>
</React.Fragment>
);
};
}
export default DonationBox;
Child Component:
class InfoList extends Component {
constructor(props) {
super(props);
this.state = {
data: this.props.data
};
}
componentDidMount() {
console.log(this.state.data);
//logs: ƒ data() {
// return _this.state.data;
// }
}
render() {
return <div> Placeholder </div>;
}
}
export default InfoList;
I tried using bind in the child component but still got the same thing:
constructor(props) {
super(props);
this.state = {
data: this.props.data
};
this.checkData = this.checkData.bind(this);
}
componentDidMount() {
this.checkData();
}
checkData = () => {
console.log(this.state.data);
};
First, yes, you should change the data prop that you send to InfoList to be this.state.data rather than an anonymous function. So: <InfoList data={this.state.data} />
But, the main issue is in using componentDidMount in the child component, when really you should be using componentWillReceiveProps instead.
componentDidMount is only called once, and it doesn't wait for your AJAX
The componentDidMount lifecycle hook is invoked one time, before the initial render.
In your child component, at componentDidMount you are trying to log this.state.data - but this state is based on what was set in the constructor which was what was passed in as the data prop when you first mounted InfoList. That was [], because InfoBox had yet to receive back data from its Ajax call. To put it another way:
InfoList.componentDidMount() fired before InfoBox.loadDonationsFromServer() got back its response. And InfoList.componentDidMount() does not get fired again.
componentWillReceiveProps is called whenever props change
Instead, your child component should be using the componentWillReceiveProps lifecycle hook. This is invoked every time a component receives new props. Once the parent's state changes (after load donations returns), then it passes new props to the child. In componentWillReceiveProps, the child can take these new props and updates his state.
I have created a code sandbox that shows you through a bunch of log statements what happens when, along with what your props and state look like at various points in the lifecycle. Instead of actually doing an ajax fetch, I'm just doing a 2-second wait to simulate the fetch. In InfoList.js, the code for componentWillReceiveProps is currently commented out; this way, you can see how things work as they stand. Once you remove the comments and start using componentWillReceiveProps, you'll see how they get fixed.
Additional resources
This is a helpful article that pretty much describes the exact same issue you're facing.
An excellent quick reference for React lifecycle hooks is the React Cheat Sheet)
That is because the data prop that is being passed in is a function.
Change
<InfoList data={() => this.state.data} />
to
<InfoList data={this.state.data} />
Just a nit, you don't really need the constructor in your child component to define the state. Just define it the way you have in your parent component.

Converting ES5 Mixins to Higher Order Components

In my project I'm trying to get rid of all the mixins and replace them with HOCs. I am stuck using ES5 at the moment.
export default React.createClass({
mixins: [SomeAsyncMixin],
data: {
item1: {
params: ({params, query}) => {
params: ({params, query}) => {
if (!query.p) {
return null;
}
const status = someTernaryResult
return {
groups: query.groups,
status,
subject: params.subject,
};
},
promise: query => query && query.subject && api(makeUrl(`/some/endpoint`, query))
},
item2: {
params: ({params, query}) => {
//same as before
},
promise: ({subject, query}) =>
// same as before
}
render() {
// some stuff
return(
// some jsx
);
}
}
Inside of the mixin, it has a componentWillMount and a componentWillUpdate that runs an update function that will loop through each key on data and update the props/state.
In React's docs about removing mixins, their mixins hold the data, not the component.
There are MANY components in my project that have a data object and use this mixin to update their props/state. How do I make a reusable component to handle this data object?
Also, how do I even access this data object from within the component? In the component this.data is null. Inside of the mixin this.data is the data object from inside the component.. why?
Your higher order component and mixin will look very similar. The main difference will be how data, props, and state are shared/passed. In the mixin case, you are altering your component's definition with the mixin's behavior, so the state and props are all in the one resulting component.
In the higher order component case, you are creating a new component that wraps around your other component. Thus, the shared state/behavior is entirely contained within wrapping component, and any data that needs to be used within the wrapped component can be passed via props.
So from what you have in your example, your higher order component would be something like
const someAsync = (data) => (WrappedComponent) => {
class SomeAsyncComponent extends React.Component {
constructor(...args) {
super(...args)
this.state = {
...
}
}
componentWillMount() {
// Make use of data, props, state, etc
...
}
componentWillUpdate() {
...
}
render() {
// May filter out some props/state, depending on what is needed
// Can also pass data through if the WrappedComponent needs it.
return (
<WrappedComponent
{ ...this.props }
{ ...this.state }
/>
)
}
}
return SomeAsyncComponent
}
And then your usage of it
export default someAsync(dataConfig)(WrappedComponent)

How can I set initial React state from API data?

I have a rest API back end set up. I want to set values inside the getInitialState function for a component, but I can't figure out how to populate the object I need to return because I am using an asynchronous http request. The object I return has undefined values, as expected. How do I get around this?
I'm using fetch (can switch to any other library, honestly) as of now. I can't figure out how to call getInitialState after the asynchronous call has returned some value instead of before it happens.
import React from 'react';
import 'whatwg-fetch';
export default class IndexPage extends React.Component {
render() {
// I basically need to call getInitialState after the last promise has been resolved
fetch('https://localhost:3000/api/aye')
.then(function(response) {
return response.json();
})
.then(function(json) {
// Need to return some values from this.
});
return (
<div>
<h1>{this.state.jsonReturnedValue}</h1>
</div>
);
}
}
Thanks in advance!
You should call this.setState in order to change state values
export default class IndexPage extends React.Component {
constructor() {
super();
this.state = {
jsonReturnedValue: null
}
}
componentDidMount() {
fetch('https://localhost:3000/api/aye')
.then(response => response.json())
.then(json => {
this.setState({ jsonReturnedValue: json });
});
}
render() {
return (
<div>
<h1>{ this.state.jsonReturnedValue }</h1>
</div>
);
}
}
In your situation -
It's better to get the rendering done for the first time with empty state data lets say
constructor(props){
super(props);
this.state = {
data : []
};
}
and make ajax call in componentDidMount, this is the place where you can perform dom manipulation and send ajax request to fetch data via REST.
After new data is fetched from server set the state with new data
this.setState({data:newDataFromServer});
e.g In componentDidMount
componentDidMount() {
sendAjaxRequest()
.then(
(newDataFromServer) => {
this.setState({data : newDataFromServer });
});
}
This will cause the re-rendering to happen with latest data fetched from server and new state changes will be reflected.
You should do something along the following. Firstly, create the state in the constructor method. This will then allow you to use this value in your render method by referencing {this.state.jsonReturnedValue}. The constructor() method is the ES6 version of getInitialState() in case you are not aware of this. This is where you should set the state of the component.
Then in the componentDidMount which will run after the render method you can make the API call api and use React's setState method to update the value of this.state.jsonReturnedValue.
When this.setState() runs and sets a new value it will then run the render method again and update the view accordingly.
export default class IndexPage extends React.Component {
constructor(){
super();
this.state = {
jsonReturnedValue: null
}
}
componentDidMount() {
this.jsonList();
}
jsonList() {
fetch('https://localhost:3000/api/aye')
.then(function(response) {
this.setState({
jsonReturnedValue: response.json()
})
})
}
render() {
return (
<div>
<h1>{this.state.jsonReturnedValue}</h1>
</div>
);
}
}

React: componentDidMount + setState not re-rendering the component

I'm fairly new to react and struggle to update a custom component using componentDidMount and setState, which seems to be the recommended way of doing it. Below an example (includes an axios API call to get the data):
import React from 'react';
import {MyComponent} from 'my_component';
import axios from 'axios';
export default class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
data: []
};
}
GetData() {
return axios.get('http://localhost:5000/<route>');
}
componentDidMount() {
this.GetData().then(
(resp) => {
this.setState(
{data: resp.data}
)
}
)
}
render() {
return (
<MyComponent data={this.state.data} />
);
}
}
Doing console.log(this.state.data) just below render() shows that this.state.data does indeed get updated (from [] to whatever the API returns). However, the problem appears to be that MyComponent isn't rendered afresh by componentDidMount. From the Facebook react docs:
Setting state in this method will trigger a re-rendering.
This does not seem to be the case here: The constructor of MyComponent only gets called once (where this.props.data = []) and the component does not get rendered again. I'd be great if someone could explain why this is and whether there's a solution or a different way altogether to get the updating done.
UPDATE
I've added the code for MyComponent (minus some irrelevant features, as indicated by ...). console.log(data_array) prints an empty array.
import React from 'react';
class DataWrapper {
constructor(data) {
this._data = data;
}
getSize() {
return this._data.length;
}
...
}
export class MyComponent extends React.Component {
constructor(props) {
super(props);
this._dataWrapper = new DataWrapper(this.props.data);
this.state = {
data_array: this._dataWrapper,
};
}
render() {
var {data_array} = this.state;
console.log(data_array);
return (
...
);
}
}
You are falling victim to this antipattern.
In MyComponent constructor, which only gets called the first time it mounts, passed your empty array through new DataWrapper and now you have some local state which will never be updated no matter what your parent does.
It's always better to have one source of truth, just one state object anywhere (especially for things like ajax responses), and pass those around via props. In fact this way, you can even write MyComponent as a simple function, instead of a class.
class Example extends Component {
state = { data: [] }
GetData() { .. }
componentDidMount() {
this.GetData().then(res =>
this.setState({data: new DataWrapper(res.data)})
)
}
render() { return <MyComponent data={this.state.data} /> }
}
...
function MyComponent (props) {
// props.data will update when your parent calls setState
// you can also call DataWrapper here if you need MyComponent specific wrapper
return (
<div>..</div>
)
}
In other words what azium is saying, is that you need to turn your receiving component into a controlled one. Meaning, it shouldn't have state at all. Use the props directly.
Yes, even turn it into a functional component. This helps you maintain in your mind that functional components generally don't have state (it's possible to put state in them but ... seperation of concerns).
If you need to edit state from that controlled component, provide the functions through props and define the functions in the "master" component. So the master component simply lends control to the children. They want anything they talk to the parent.
I'm not posting code here since the ammendment you need to make is negligible. Where you have this.state in the controlled component, change to this.props.

Categories

Resources