loading...

Async components in React - Open Source library

miketalbot profile image Mike Talbot ・2 min read

Open Source project on GitHub

I've been working with Hooks in React for a while, but still kept stumbling over writing good async code that would render what it could, when it could. This is especially tricky if you have lots of "non-fetch" based api calls.

We have an async API that uses local IndexedDb and online resources if they are available to be optimal offline. This pretty much rules out Suspense for now.

We've built our own component library that might be useful to others. It lets you compose components the way you'd expect. The simplest example being to just call a few async calls and then render the result:

const useStyles = makeStyles(theme=>{
    return {
        icon: {
            backgroundColor: theme.palette.primary.main
        }
    }
})

export const ExampleComponent1 = createAsyncComponent(function Weather({
    lat,
    lon
}) {
    const classes = useStyles()
    return async ()=> {
        const response = await fetch(
            `https://api.openweathermap.org/data/2.5/onecall?lat=${lat}&lon=${lon}&units=metric&appid=${API_KEY}`
        )
        const data = await response.json()
        return <List>
            <ListItem>
                <ListItemText primary={data.current.weather[0].main} secondary={data.timezone}/>
            </ListItem>
            <ListItem>
                <ListItemAvatar>
                    <Avatar className={classes.icon}>
                        <FaThermometerFull/>
                    </Avatar>
                </ListItemAvatar>
                <ListItemText primary={`${data.current.temp} C`} secondary={"Temperature"}/>
            </ListItem>
            <ListItem>
                <ListItemAvatar>
                    <Avatar className={classes.icon}>
                        <GiWaterDrop/>
                    </Avatar>
                </ListItemAvatar>
                <ListItemText primary={`${data.current.humidity}%`} secondary={"Humidity"}/>
            </ListItem>
        </List>
    }
})

There are some good working examples in the example project on the Github repo and a demo of them here.

It composes like a regular React component but allows for async and the usual kind of fallbacks for loaders etc. It also ensures you can call useful hooks like styles and contexts before you get into the async guts.

It goes further though, allowing for progress reporting and out of sequence rendering:

export const ExampleComponent3 = createAsyncComponent(
    async function MyComponent({ resolve }) {
        const order = [10, 7, 4, 1, 2, 8, 6, 9, 3, 5]
        for (let i = 0; i < 10; i++) {
            let item = order[i]
            resolve(
                item,
                <Box p={1}>
                    I am item index {item} - rendered in sequence {i + 1}
                </Box>
            )

            if (i < 9) {
                resolve(order[i + 1], <Box ml={1}><CircularProgress color={"secondary"} size={20}/></Box>)
            }
            await new Promise((resolve) => setTimeout(resolve, 1500))
        }
    }
)

MIT licensed - available on npm

npm install --save react-async-component-hoc

Posted on May 18 by:

Discussion

markdown guide
 

What is createAsyncComponent here?

 

The library we have written. Available on MIT license from the link.