DEV Community

0 seconds of 3 minutes, 24 secondsVolume 90%
Press shift question mark to access a list of keyboard shortcuts
00:00
00:00
03:24
 
Juan Xavier Gomez
Juan Xavier Gomez

Posted on

4 2

Refactor to Refract

Reactive Programming is hard

In 2018, I worked on a project that used RxJS to create some Observables for api calls. The code looked complicated, RxJS itself was complicated, and I had a hard time working with it.
I don't think I really understood what reactive programming was about. Overall, I disliked the concept, or to be more truthful, I found it hard to understand and that lead to frustration.

Practice makes perfect

I started working at Fanduel about a year ago. Since then I've been delving more and more into reactive programming using the library refract originally developed by folks at the company.

Refract describes itself as a way to:

Handle your component effects and side-effects in a clear and declarative fashion by using asynchronous data streams

The library was hard to understand at first, and the benefits of it were not clear to me back then. To me, it simply seemed like an alternative to React.useEffect.

Nowadays I feel more comfortable using reactive programming, streams, and refract. So today I decided to spend one hour refactoring some useEffects into a withEffects aperture just to test my knowledge and experience. After this session (and a year of experience), the benefits of this approach have become more clear to me.

Below I have the original logic that used useEffects. For context, this logic comes from a Profile Page component that displays some user information (other users or your own) and all of the user's posts.

Before:

useEffect(() => {
  if (user.info && !params.userId) setUserData(user.info)
}, [user.info, params.userId])

useEffect(() => {
  if (!params.userId) return
  firebase.doUserInfoGet(params.userId).then((res) => {
    setUserData(res.data())
  })
}, [params.userId, firebase])

useEffect(() => {
  const iife = async () => {
    const postsCollection = await firebase.doUserPostsGet(
      params.userId || user.auth.uid
    )
    const newPosts = []

    postsCollection.forEach((post) => {
      newPosts.push(post.data())
    })
    setPosts(newPosts)
    setLoadingPosts(false)
  }
  iife()
}, [user.auth, params.userId, firebase])
Enter fullscreen mode Exit fullscreen mode
View more on GitHub.

The first effect decides whether or not to use your own information for the profile page according to the params coming from the url.
The second effect makes the api call for other user's information.
The third effect makes the api call for the user's posts.

My useEffects worked fine. They definitely could have used improvements on their own. But nonetheless, they were hard to understand.
My biggest concern was that when I came back to this codebase after 6 months I could not recognize:

  • why the effects needed to execute &
  • when they executed

Using refract I was able to abstract and clean up the logic that was needed by the component.

Observable streams are hard to understand at first, but once you get past that hurdle, you'll soon realize that they allow you to more easily organize effects. This is thanks to the api provided by refract and xstream. Reading the effect streams should make it easier understand why the effects are needed and when each effect should take place.

After:

const aperture = (component, { firebase, user }) => {
  const userIdParam = () =>
    component.observe('match', ({ params }) => params.userId)

  const loadOtherUserInfo$ = userIdParam()
    .filter((param) => param)
    .map((userId) =>
      xs.fromPromise(
        firebase
          .doUserInfoGet(userId)
          .then((res) => res.data())
          .catch(() => ({ error: true }))
      )
    )
    .flatten()
    .compose(
      sampleCombine(component.observe('match', ({ params }) => params.userId))
    )
    .map(([userData, userId]) => ({ userData, isOwnProfile: false, userId }))

  const useOwnUserInfo$ = userIdParam()
    .filter((param) => !param)
    .mapTo({
      userData: user.info,
      isOwnProfile: true,
      userId: user.auth && user.auth.uid,
    })

  const loadPosts$ = userIdParam()
    .map((param) => param || user.auth.uid)
    .map((userId) => xs.fromPromise(firebase.doUserPostsGet(userId)))
    .flatten()
    .map((postsCollection) => {
      const posts = []
      postsCollection.forEach((post) => {
        posts.push(post.data())
      })
      return { loadingPosts: false, posts }
    })

  return xs
    .merge(useOwnUserInfo$, loadOtherUserInfo$, loadPosts$)
    .startWith({ posts: [], loadingPosts: true })
    .map(toProps)
}
Enter fullscreen mode Exit fullscreen mode
View more on GitHub.

The refract api definitely has a learning curve, but boilerplate aside, the effects are much more declarative and easier to come back to after a long hiatus!

TL;DR: Don't be afraid to refractor!

SurveyJS custom survey software

Simplify data collection in your JS app with a fully integrated form management platform. Includes support for custom question types, skip logic, integrated CCS editor, PDF export, real-time analytics & more. Integrates with any backend system, giving you full control over your data and no user limits.

Learn more

Top comments (0)

Image of Docusign

🛠️ Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more