Reactjs Async Rendering of Components

Reactjs async rendering of components

There are two ways to handle this, and which you choose depends on which component should own the data and the loading state.

  1. Move the Ajax request into the parent and conditionally render the component:

    var Parent = React.createClass({
    getInitialState: function() {
    return { data: null };
    },

    componentDidMount: function() {
    $.get('http://foobar.io/api/v1/listings/categories/').done(function(data) {
    this.setState({data: data});
    }.bind(this));
    },

    render: function() {
    if (this.state.data) {
    return <CategoriesSetup data={this.state.data} />;
    }

    return <div>Loading...</div>;
    }
    });
  2. Keep the Ajax request in the component and render something else conditionally while it's loading:

    var CategoriesSetup = React.createClass({
    getInitialState: function() {
    return { data: null };
    },

    componentDidMount: function() {
    $.get('http://foobar.io/api/v1/listings/categories/').done(function(data) {
    this.setState({data: data});
    }.bind(this));
    },

    render: function() {
    if (this.state.data) {
    return <Input type="select">{this.state.data.map(this.renderRow)}</Input>;
    }

    return <div>Loading...</div>;
    },

    renderRow: function(row) {
    return <OptionRow obj={row} />;
    }
    });

How to async await in react render function?

You should always separate concerns like fetching data from concerns like displaying it. Here there's a parent component that fetches the data via AJAX and then conditionally renders a pure functional child component when the data comes in.

class ParentThatFetches extends React.Component {
constructor () {
this.state = {};
}

componentDidMount () {
fetch('/some/async/data')
.then(resp => resp.json())
.then(data => this.setState({data}));
}

render () {
{this.state.data && (
<Child data={this.state.data} />
)}
}
}

const Child = ({data}) => (
<tr>
{data.map((x, i) => (<td key={i}>{x}</td>))}
</tr>
);

I didn't actually run it so their may be some minor errors, and if your data records have unique ids you should use those for the key attribute instead of the array index, but you get the jist.

UPDATE

Same thing but simpler and shorter using hooks:

const ParentThatFetches = () => {
const [data, updateData] = useState();
useEffect(() => {
const getData = async () => {
const resp = await fetch('some/url');
const json = await resp.json()
updateData(json);
}
getData();
}, []);

return data && <Child data={data} />
}

How to render something that is async in React?

I don't fully understand what you are trying to output but how you would usually handle this is with both the useState hook and the useEffect hook see example below.

  //function that stores the data in the result array, 
//but result array will only be available after the
//server response, and after the page is rendered
const pin = () => {
const [result, setResults] = useState([]);
var url = "http://warm-hamlet-63390.herokuapp.com/pin/list"

useEffect(() => {
//Attempt to retreive data
try {
const res = transformData();

if (res) {
// Add any data transformation
setResults(transformData(res))
}
else {
throw (error)
}
}
catch (error) {
//Handle error
}
}, [])

// Handle data transformation
const transformData = async () => {
const res = await axios.get(url)

const txt = JSON.stringify(res.data.data)
const result = JSON.parse(txt)

return result
}

if (!result) {
// Return something until the data is loaded (usually a loader)
return null
}

// Return whatever you would like to return after response succeeded
return <></>;
}

This is all assuming that Pin is a component like you have shown in your code, alternatively, the call can be moved up to the parent component and you can add an inline check like below to render the pin and pass some data to it.

{result && <Pin property={someData} />}

Just a bit of background the useEffect hook has an empty dependency array shown at the end "[]" this means it will only run once, then once the data has updated the state this will cause a rerender and the change should be visible in your component

Understanding async React rendering

Your code prints 0 because it is the value of the variable number at render time.

You use the following code:

fetch("some.url")
.then(res => res.json())
.then(list => {
for (let n of list) {
numbers.push(n);
}
});

to get a new value asynchronously, but it won't have any effect: the component is already rendered.

If you want to refresh it, you must put your variable number in the state and use setState() to pass the new value.

If you want to keep with function components, you should use the brand new hooks feature, which should give you the equivalent of setState.

Load async data before rendering the DOM

You're close. It's normal for the first render to have undefined for the state, since that's what you've set the initial state to, and it takes time to get the data. So you just need to render something different for the case where you don't have data yet. You can return null if you want to render nothing, or perhaps a loading placeholder:

if (productDetails) {
return (
<>
{productDetails.handle}
</>
)
} else {
return <div>Loading...</div>
// or
//return null;
}

For your case, that should be enough. But if you ever find yourself working with a really complicated component, where always having to check for undefined is combersome, then you could split it into two components. The outer component does the loading and handles the possibility of undefined, and the inner component only gets rendered once undefined is no longer possible:

const Parent = (props) => {
const [productDetails, setProductDetails] = useState();

useEffect(() => {
const fetchData = async () => {
const data = await getProduct(handle);
//console.log(data)
setProductDetails(data);
};
fetchData();
}, []);

if (productDetails) {
return <Child {...props} productDetails={productDetails} />;
} else {
return null;
}
};

const Child = ({ product, productDetails }) => {
// Everything in here can assume productDetails exists

return (
<>
{productDetails.handle}
</>
)
};

How to render async content on React?

You could fetch the images the same way as you fetched the posts:

Include them in your state

this.state = {
posts: [],
image: null
};

Call getImage in componentWillMount

componentWillMount() {
this.getPosts();
this.getImage();
}

setState when the promise resolves:

.then(response => {
this.setState(state => ({
...state,
image: {
url: response.data.source_url,
alt: response.data.alt_text
}
}));
});

Display a loading screen or a spinner until the image loads

render() {

const { posts, image } = this.state;

if (!image) {
return "Loading";
}

// ...
}

I would also advise using componentDidMount instead of componentWillMount, because componentWillMount is deprecated and is considered unsafe.

Here's a codesandbox example.



Related Topics



Leave a reply



Submit