DEV Community

JavaScript Joel
JavaScript Joel

Posted on

React: "I really wish this is how I could write components."

Challenge Accepted!

code for useMatchFetch down below.

import React from "react";
import { useMatchFetch } from "./effects/useMatchFetch";

export const Example = () => {
  const render = useMatchFetch("https://swapi.co/api/people/1/?format=json");

  return render({
    pending: () => <div>Loading</div>,
    error: err => <div>{err.toString()}</div>,
    data: data => <pre>{JSON.stringify(data, null, 2)}</pre>
  });
};
Enter fullscreen mode Exit fullscreen mode

Watch my Live Stream

Want to see my process on how I created this? Watch me on Twitch!

Twitch Screenshot

useMatchFetch

I actually really like this. I think I might end up using this in a few places.

import { useState, useEffect } from "react";

const render = data => match =>
  data.pending ? match.pending()
  : data.error ? match.error(data.error)
  : data.data  ? match.data(data.data)
  : null // prettier-ignore

export const useMatchFetch = url => {
  const [data, setData] = useState({ pending: true });

  useEffect(() => {
    fetch(url)
      .then(response => response.json())
      .then(data => setData({ data, pending: false }))
      .catch(error => setData({ error, pending: false }));
  }, [url]);

  return render(data);
};
Enter fullscreen mode Exit fullscreen mode

End

Follow me on Twitter @joelnet

Cheers!

Latest comments (46)

Collapse
 
sebastienlorber profile image
Sebastien Lorber

Your code is subject to race conditions.

See dev.to/sebastienlorber/handling-ap...

You'd rather use a lib that solves it for you with all the edge cases, like github.com/slorber/react-async-hook

Collapse
 
joelnet profile image
JavaScript Joel

Yep this is correct! Any previous fetch needs to be aborted.

Since this code was just for fun, I don't think I'll be spending the time to add these cases into it. But if someone wants to contribute a gist, i'll gladly link it.

Cheers!

Collapse
 
kayis profile image
K

Haha, I wrote the same thing and it was so quick that I didn't bother to post it.

I seldomly use any libraries in React, because it's so easy and quick to build them yourself. Just a hand full of small util compnents and you're good to go.

Collapse
 
joelnet profile image
JavaScript Joel

Agreed. Plus the problem with libraries is they have to cover the use cases of every application in the works. Most times your one liner function is enough for your use case.

Collapse
 
kayis profile image
K

This.

I couldn't write an equivalent to React-Router or Native-Navigation, but I don't have to. A 10line component covers the use-cases I have.

Collapse
 
ericwooley profile image
Eric Wooley • Edited

Glorious! I love the idea.

I'm not a super big fan of hooks, I still prefer HOC, so for those who are interested, here is a similar alternative I am testing out.

Alternatively, a HOC would work just as well, and be decoupled.

// untested
import { ComponentType, useEffect, useState } from 'react'
import defaultPending from './defaultPending'
import defaultError from './defaultError'
import curry from 'lodash/curry'

interface IWithDataOptions<T> {
    pending?: () => ComponentType,
    error?: (e: Error) => ComponentType,
    data: (d: T) => componentType
}
export const withData = curry(<T> (
    url: string,
    options: IWithDataOptions<T> | ComponentType
) => {
    const error = options.error || defaultError
    const pending = options.pending || defaultPending
    const dataRender = options.data || options
    const WithData = (props) => {
        const [data, setData] = useState({ pending: true });

        useEffect(() => {
            fetch(url)
                .then(response => response.json())
                .then(data => setData({ data, pending: false }))
                .catch(error => setData({ error, pending: false }));
        }, [url]);
        if(data.pending) return pending()
        if(data.error) return error(data.error)
        return dataRender(data.data)
    }
    return WithData
});

// example 1
const Example = withData("https://swapi.co/api/people/1/?format=json", {
    pending: () => <div>Loading</div>,
    error: err => <div>{err.toString()}</div>,
    data: data => <pre>{JSON.stringify(data, null, 2)}</pre>
  }
);
<Example />

// example 2
const Example = withData("https://swapi.co/api/people/1/?format=json", (data) => <pre>{JSON.stringify(data, null, 2)}</pre>);
<Example />

// example 3
const withExampleData = withData("https://swapi.co/api/people/1/?format=json")
const Example =  withExampleData((data) => <pre>{JSON.stringify(data, null, 2)}</pre>)
<Example />

const CustomLoaderExample = withExampleData({
    peding: () => <div>Just a second...</div>,
    data: (data) => <pre>{JSON.stringify(data, null, 2)}</pre>
})

<CustomLoaderExample />

Collapse
 
joelnet profile image
JavaScript Joel

HOCs are another great way to solve this. Great examples!

Collapse
 
patroza profile image
Patrick Roza • Edited

I think it would be nice to have a refetch action so that the request can be remade even to the same url.
Also I think it would be nice to be able to distinguish then between the initial fetch, and future updates.

Also im debating with myself if I like the separate callbacks for each case, or if I would prefer being given the loading, data and error props together, and then do a more imperative approach with conditions. It seems more flexible.
In that case I would make the render function separately available, instead of wrapping it around the data

Something along the lines of the react-apollo graphql HOC and Query component etc, but then for REST.

Collapse
 
joelnet profile image
JavaScript Joel

All great features! Submit a pull request ;)

Collapse
 
patroza profile image
Patrick Roza

Anyway, here it is; written in the train, so apologies for any typos ;-)

gist.github.com/patroza/4488f8fa2b...

Collapse
 
patroza profile image
Patrick Roza

Would love to, but where to? Where the codes at? :)
(Maybe im blind)

Thread Thread
 
joelnet profile image
JavaScript Joel

There's no repo for it. It was just a live stream demo :)

Collapse
 
davidchase profile image
David Chase

Nice write up! :)

Reminds me of these posts using daggy from fantasy-land
medium.com/javascript-inside/slayi...
datarockets.com/blog/javascript-pa...

nice little library that wraps it up
github.com/devex-web-frontend/remo...

Collapse
 
joelnet profile image
JavaScript Joel

One of the reasons, I decided not to make this an npm package. I figured something was already out there. I thought it would be fun to show the process.

Looks like those other libs have the same idea. I always love seeing an article with daggy!

daggy is actually a perfect use case for this.

Cheers!

🍻

Collapse
 
davidchase profile image
David Chase

Yeah indeed a lot times there’s a solution but not a how to reach it write up or something like that so def keep coming with the process writes up for sure !! :)

Thread Thread
 
joelnet profile image
JavaScript Joel

That was one thing I found interesting about this process. Most of the time the ideal solution would be created ahead of time and then you would teach that process.

But it's interesting to see how you would think to get to that conclusion on your own.

I think that's the difference between live streams and tutorials. You get to see the thought process, which I really enjoy watching!

Cheers!

🍻

Thread Thread
 
davidchase profile image
David Chase

But it's interesting to see how you would think to get to that conclusion on your own.

yah that process is fun, we used to do FP lunch sessions at an old job and refactor some current/legacy code into something we just learned or found interesting right on a big screen tv so everyone could watch and we talk thru the process.

Collapse
 
buinauskas profile image
Evaldas Buinauskas • Edited

Very functional and very Elm'ish.

I've watched this talk and it covered exactly that.

Collapse
 
joelnet profile image
JavaScript Joel

Thanks for the tip! I'm saving this video to my Watch Later.

Collapse
 
buinauskas profile image
Evaldas Buinauskas

Definitely worth watching. You might find some inspiration on how to improve your React code even more :)

Thread Thread
 
joelnet profile image
JavaScript Joel

There's always room for improvement!

Collapse
 
netojose profile image
José Neto

Wow!

Collapse
 
joelnet profile image
JavaScript Joel

Cheers!

🍻

Collapse
 
nikolicstjepan profile image
Stjepan

First time that I "unicorned" article! Well played Sir!

Collapse
 
joelnet profile image
JavaScript Joel

I'm so honored 😁

Collapse
 
eliseumds profile image
Eliseu Monar dos Santos • Edited

What if you want to render a loader while new data is loading? That means you'd render a loader + the current data at the same time. Same with errors.

Collapse
 
joelnet profile image
JavaScript Joel

Submit a pull request ;)

Collapse
 
bilal_fazlani profile image
Bilal

Scala-js allows writing react components this way

Collapse
 
joelnet profile image
JavaScript Joel

This sounds pretty cool. I haven't seen scalajs but if it has pattern matching I'm down!