FormidableLabs / freactal

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

Question: calling an effect with updated state #55

Closed jhsu closed 7 years ago

jhsu commented 7 years ago

so i'm trying to have two different provideStates for pagination, one that handles the pagination state, and one that does the fetching.

const withFetching = provideState({
  initialState: () => {
    return {
      entities: [],
    };
  },
  effects: {
    fetchItems: (effects, params) =>
      state => {
        // TODO: use { page , pageSize } from params
        return fetch('/things')
          .then(res => res.json())
          .then(({things}) => state => ({...state, entities: things}));
      },
  },
});

const justPagination = provideState({
  initialState: () => {
    return {
      page: 1,
      pageSize: 10,
    };
  },
  effects: {
    nextPage: effects => {
      return state => {
        const nextState = {...state, page: page + 1};
        return effects.fetchItems(nextState).then(() => nextState);
      };
    },
  },
});

const Container = withFetching(justPagination(YourComponent));

This doesn't work, but I'd like to do something like that. Any suggestions?

TIMNHE commented 7 years ago

Same issue here, when try to compose 2 providers with the same component:

const counterStore = provideState({
  initialState: () => ({counter: 0}),
  effects: {
    addCount: (effects, val) => state => Object.assign({}, state, {counter: state.counter + val})
  }
});

const colorStore = provideState({
  initialState: () => ({color: 'black'}),
  effects: {
    setColor: (effects, col) => state => Object.assign({}, state, {color: col})
  }
});

const CounterChildComponent = injectState(({state: {color, counter}, effects}) => {
  const handleClick = () => effects.addCount(1);
  return (
    <div className="childComponent" onClick={handleClick}>
      <span>CounterChild ...</span>
      <span>color: {color}, {counter}</span>
    </div>
  );
});

const ColourChildComponent = injectState(({state, effects}) => {
  const handleClick = () => effects.setColor('blue');
  return (
    <div className="childComponent" onClick={handleClick}>
      <span>ColorChild ...</span>
      <span>{state.color}</span>
    </div>
  );
});

const ParentComponent = (props) =>
  <div className="parent">
    <CounterChildComponent/>
    <ColourChildComponent/>
  </div>;

export default counterStore(colorStore((ParentComponent)));
// ramda compose does not work either -> 
// R.compose(counterStore, colorStore)(ParentComponent)

It's strange because when apply the colorStore in first place, you can update the colourChild but not the counterChild ...

... if you click on counterChild many times and then click on the color component, both states are updated

divmain commented 7 years ago

@jhsu, there's one problem with the code you posted, and that's probably the source of the problems you're encountering. The state update function is a synchronous function - it returns new state. However, your nextPage state update function returns a Promise. That's probably not what you intended. Something like this will work:

const serverData = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const getData = page => {
  page = page || 0;
  return Promise.resolve(serverData[page]);
};

const withFetching = provideState({
  initialState: () => ({ entities: [0] }),
  effects: {
    fetchPage: async (effects, page) => {
      const pagedData = await getData(page);
      return state => {
        const entities = [].concat(state.entities);
        entities[page] = pagedData;
        return Object.assign({}, state, { entities });
      };
    }
  }
});

const justPagination = provideState({
  initialState: () => ({ page: 0 }),
  effects: {
    nextPage: async (effects, currentPage) => {
      const nextPage = currentPage + 1;
      await effects.fetchPage(nextPage);
      return state => Object.assign({}, state, { page: nextPage });
    }
  }
});

const App = injectState(({ state, effects }) => {
  const onClick = () => effects.nextPage(state.page);
  return (
    <div>
      <div>entities: { JSON.stringify(state.entities) }</div>
      <div>page: { state.page }</div>
      <button onClick={onClick}>click</button>
    </div>
  );
});
divmain commented 7 years ago

@TIMNHE, your issue is somewhat different. I believe it is fixed here and was the same bug as #42. This will be released on npm shortly.