kentcdodds / ama

Ask me anything!
https://github.com/kentcdodds/ama/issues?q=is%3Aissue+is%3Aclosed
685 stars 75 forks source link

Could you create a video on integrating non react specific packages into react? #551

Open hrastnik opened 5 years ago

hrastnik commented 5 years ago

A lot of times I need to use non react specific packages in my react apps. I'd love to see some common patterns when doing this.

Different libraries have different APIs and I often find myself struggling when I have to convert some of it to work well with React. There's a lot of different approaches so I'm not sure what to go with most of the time (hoc, render props, context, redux-thunks, redux middleware).

I'd love it if you could take an existing library (with promises, callbacks, internal state and all that) and make it work with React.

hedgerh commented 5 years ago

heyo, do you have some examples of libraries that you struggle with getting to play well with React? if you can get me some various examples, I may be able to give you some advice.

hrastnik commented 5 years ago

For example, I have a httpClient that is an instance of the HttpClient class. It has methods like getUsers, getPosts, updatePost... It also has a token member that has to be set in order to access protected HTTP endpoints.

I want to know what's the best way to use this class in React. I could directly import the instance and use it in my components but this feels bad as I'm coupling the UI and the service.

I'd ideally want to keep my token in my Redux store and keep it synced with the HttpClient. I can do this by creating a Component that checks if the token in the store has changed via the cDU lifecycle method and updates the HttpClient.token accordingly. Alternatively I could create a middleware that listens for the actions that update the token and handle the syncing in the middleware.

Then there's the problem of injecting/connecting the service with the components. I could use Context or a HOC or listen for actions in some middleware...

The possible options are overwhelming for me, and I don't know where to find best practices on the subject.

hedgerh commented 5 years ago

Ahhh, I see. Okay, so here's what I'd do:

I personally wouldn't bother keeping the token in my Redux state if it's only ever going to be used inside of the httpClient instance. If you prefer to also keep it in there, it's no biggie.

Your thunks/sagas/wherever you're doing async stuff is a great place to update the httpClient.token. Update the token in the same place that you'd dispatch an action after an http request:

import httpClient from './httpClient'

const login = (email, password) => (dispatch) => {
  return httpClient.login(email, password)
    .then(response => {
      httpClient.token = response.token
      dispatch(loginSuccess(response))
    })
}

const refreshToken = () => (dispatch) => {
  httpClient.refreshToken()
    .then(token => {
      httpClient.token = token
      dispatch(refreshTokenSuccess(token)
    })
}

Rule of thumb: When using Redux with a library like this, that logic is most likely going to go in a thunk. To give you a sense of whether you'll ever need to write a middleware to do something practical: I've been using Redux for ~3 years and have never written a middleware that I've actually used in an app. Not that you won't ever need to, you just most likely won't for standard stuff like where you're at right now. If you do need a middleware (maybe dealing with websockets or something?), someone most likely wrote one already.

Hope that helps. Feel free to ask any more questions you have.

hrastnik commented 5 years ago

Thanks a lot for your response. That is similar to what I ended up doing.

In my case when I update the token in the store I want to do a lot of things so I benefit a lot if the token is stored in the Redux store. I have to update the token in AsyncStorage, HttpClient, WebSocket service and the token also servers as the single source of truth for isUserLoggedIn prop (isUserLoggedIn is a computed property). Updating the token in so many places quickly proves impractical. I ended up dispatching an action that updates the token in the store, and using a HOC to keep the token from store in sync with my services. The solution feels a bit rough around the edges at the moment so I'd feel comfortable changing it to something better.

Also, ever since I saw this talk I've started thinking that middleware is a really powerful tool that rarely gets used to it's potential. Middleware could enable us to write code that is truly decoupled. For example, your UI could dispatch "REGISTER_BUTTON_CLICKED" or "BACK_BUTTON_PRESSED" actions, and your navigation library could handle those actions and the only "glue" between them would be a middleware that listens to UI actions and dispatches navigation actions. I haven't yet embraced this middleware-y way of writting apps as I'm worried I could run into some troubles I'm not yet aware of, but it could be worth trying out.

hedgerh commented 5 years ago

For your services, you could always just interface directly with your store instance to get what you need. You could use .subscribe to listen for token state changes and re-sync all of your services. Or, you could have your services call store.getState() to get the token on demand when they need it, rather than all of them storing it. Maybe create a token service that handles subscribing to the store and the other services grab the token from it?

I tend to avoid using cDU type of lifecycle hooks unless I need to use them (animation, accessibility, etc.)

Interesting talk. You can basically implement a similar pattern with redux-saga, if you aren't familiar with it: tell Redux Saga to listen for specific or actions that match a pattern (or predicate function), then Redux Saga will execute a saga that can dispatch other actions that update state, trigger other sagas, or just dispatch to provide clarity when debugging. It can also do some other more complex control flows, since sagas are generators, so they can be paused.