FormidableLabs / freactal

Clean and robust state management for React and React-like libs.
MIT License
1.65k stars 46 forks source link

Accessing component props in effects #43

Open artemkochnev opened 7 years ago

artemkochnev commented 7 years ago

I'm trying out freactal for an internal admin tool, and I'm running into an issue with handling history changes as a response to effects.

I'm using react-router@v4 for routing, which no longer exposes a history singleton, meaning that I have to wrap my components in withRouter to get access to the history object on my component's props and be able to make changes to it. However, I'd like to redirect the user after making an API call, but there isn't a way to get at the props in an effect, so I end up writing component methods like these:

addAsset = async params => {
  const { effects: { addAsset }, history } = this.props;
  await addAsset(params);
  history.push('/marketing/assets');
};

I've thought about wrapping my root component in withRouter and then setting the history in my state, but then I still can't access the state within an effect to be able to make changes to it.

Do you have any thoughts on how to best get around this issue without having to write a bunch of boilerplate component methods?

acjay commented 7 years ago

This struck me as perhaps a limitation of Freactal's current API. Generally speaking, the need to couple to external systems, whether that's a router, a web socket, etc.

I almost wish there were a separate key under provideState as a holding place for other references that should be available to initialize and effects throughout the stack.

markacola commented 7 years ago

Just a note that you can access component props and context from initialState. One way around this issue is storing reference to the required props in state:

const withState = provideState({
  initialState: (props, context) => ({
    router: props.router
  }),
  effects: {
    doSomethingThenPush: (effects) => state => {
      doSomething().then(() => {
        state.router.push('/somewhere')
      })
      return state
    }
  }
})

export default withRouter(withState(SomeComponent))

Feels like a bit of an anti-pattern, and would love feedback on this, but this is a work around that I am using. Somehow having access to component props in effect directly would be great.

acjay commented 7 years ago

While that seems like a totally pragmatic solution, it just feels like both props and state are meant to be fairly primitive data, not directly exposing complex APIs. It seems like the latter should be encapsulated within effects. You do this, to be fair, but it would be great to have a way to route these references to effects in a way that leaves props and state out of the equation..

divmain commented 7 years ago

Please see the discussion in #36. I'm looking at a more powerful middleware approach that would allow you access to the state provider instance.

ericclemmons commented 7 years ago

Came here surprised as well that effects don't have access to props.

In particular, I have an effect that would call a slightly different GraphQL query based on props like "limit" or "offset".

As it is now, I'm trying to create a HoC that generates effects at runtime with each render to access these, but it strikes me as expensive.

bingomanatee commented 6 years ago

I'm against it. The whole moswen flow is you feed set up the state, and it feeds information into the view; you interact, and you feed deltas into the state. Giving state a special window into props feels like a "backwash" -- because props is very transient to WHERE in the dom tree you are. If you want to pull props, do it in the simple function or components - the sate machine should operate without being welded to any particular part of the view.