DEV Community

Mike Talbot
Mike Talbot

Posted on

Async components in React - Open Source library

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>
    }
})
Enter fullscreen mode Exit fullscreen mode

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))
        }
    }
)
Enter fullscreen mode Exit fullscreen mode

MIT licensed - available on npm

npm install --save react-async-component-hoc
Enter fullscreen mode Exit fullscreen mode

Discussion (3)

Collapse
mbrtn profile image
Ruslan Shashkov

What is createAsyncComponent here?

Collapse
miketalbot profile image
Mike Talbot Author

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

Collapse
mbrtn profile image
Ruslan Shashkov

Will check it out, thanx!