0

Hello this few days I am trying to render list of products using redux-saga. I am using react-boilerplate as my structure. I have two components ProductsList and ProductsItem:

ProductsList.js

function ProductsList({ loading, error, products }) {
  if (loading) {
    return <List component={LoadingIndicator} />;
  }

  if (error !== false) {
    const ErrorComponent = () => (
      <ListItem item="Something went wrong, please try again!" />
    );
    return <List component={ErrorComponent} />;
  }

  if (products !== false) {
    return <List items={products} component={Products} />;
  }

  return null;
}

ProductsList.propTypes = {
  loading: PropTypes.bool,
  error: PropTypes.any,
  products: PropTypes.any,
};

export default ProductsList;

Products.js:

function Products(props) {
  return (
    <div className="contact">
      <span>{props.title}</span>
    </div>
  );
}

Products.propTypes = {
  title: PropTypes.string.isRequired
};

List.js

function List(props) {
      const ComponentToRender = props.component;
      let content = <div />;

      // If we have items, render them
      if (props.items) {
        content = props.items.map(item => (
          <ComponentToRender key={`item-${item.id}`} item={item} />
        ));
      } else {
        // Otherwise render a single component
        content = <ComponentToRender />;
      }

      return (
        <Wrapper>
          <Ul>{content}</Ul>
        </Wrapper>
      );
    }

    List.propTypes = {
      component: PropTypes.func.isRequired,
      items: PropTypes.array,
    };

My main page container calls an action with ComponentDidMount function (Everyting works there, I debugged it). But maybe something is wrong with prototype ant rendering.

MainPage.js

class MainPage extends React.Component {
  componentDidMount() {
    this.props.onFetch();
  }

  render() {
    const { error, loading, products } = this.props;

        const reposListProps = {
          loading,
          error,
          products,
        };

    return (
         <article>
            <Helmet>
              <title>Products</title>
              <meta
                name="description"
                content="A React.js Boilerplate application products"
              />
            </Helmet>
            <div>
                <ProductsList {...reposListProps} />
            </div>
          </article>
        );
      }
    }

PostedCasesClient.propTypes = {
  loading: PropTypes.bool,
  error: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]),
  products: PropTypes.oneOfType([PropTypes.array, PropTypes.bool]),
  onFetch: PropTypes.func
};


export function mapDispatchToProps(dispatch) {
  return {
    onFetch: evt => {
      dispatch(fetchProducts());
    },
  };
}

const mapStateToProps = createStructuredSelector({
  mainPage: makeSelectPostedCasesClient
});

const withConnect = connect(
  mapStateToProps,
  mapDispatchToProps,
);

const withReducer = injectReducer({ key: 'main', reducer }); const withSaga = injectSaga({ key: 'main', saga });

export default compose( withReducer, withSaga, withConnect, )(MainPage);

Later after dispaching my fetchProduct action I use sagas. This part also working, because I get my products array to reducer.

Saga.js

export function* getProducts() {
  try {

  let requestURL = 'http://localhost:8080/produts';

  const products = yield call(request, requestURL, { method: 'GET' });

      yield put(fetchProductSuccess(products));

  } catch (error) {
    yield put(type: 'FETCH_PRODUCTS_FAILURE', error)
    console.log(error);
  }
}
export default function* actionWatcher() {
     yield takeLatest(FETCH_PRODUCTS_BEGIN, getProducts)
}

reducer.js

const initialState = fromJS({
  loading: false,
  error: false,
  items: false
});

function ProductsReducer(state = initialState, action) {
  switch(action.type) {
    case FETCH_PRODUCTS_BEGIN:
      return state
        .set('loading', true)
        .set('error', false)
        .setIn('items', false);
    case FETCH_PRODUCTS_SUCCESS:
      return state
        .setIn('items', action.products)
        .set('loading', false)
    case FETCH_PRODUCTS_FAILURE:
      return state.set('error', action.error).set('loading', false);
    default:
      return state;
  }
}

Maybe someone could tell me what I am doing wrong? If you need more code please tell me, I will edit it.

EDIT:

Here is my selector:

const selectGlobal = state => state.get('global');

const makeSelectMainClient = () =>
  createSelector(selectMainPageDomain, substate => substate.toJS());

  const makeSelectLoading = () =>
    createSelector(selectGlobal, globalState => globalState.get('loading'));

  const makeSelectError = () =>
    createSelector(selectGlobal, globalState => globalState.get('error'));

  const makeSelectProducts = () =>
    createSelector(selectGlobal, globalState =>
      globalState.getIn(['products']),
    );

export default makeSelectPostedCasesClient;
export {
selectMainPageDomain,
  selectGlobal,
  makeSelectLoading,
  makeSelectError,
  makeSelectProducts,
};
PrEto
  • 395
  • 2
  • 7
  • 23
  • Whats the error you're getting? – tpdietz Aug 16 '18 at 15:25
  • Right now my my list renders faster that Saga with ComponentDidMount does request. So I could not get items properties. Maybe You know how to render component only after Request are finished successfully? @tpdietz – PrEto Aug 16 '18 at 15:44
  • We need to see how you are hooking up the `ProductsReducer` to your store. This is important because it defines the `name` of the state. Can you post the contents of `createReducer` from the `app/reducer.js` file? Are you importing your reducer and mapping it to `global`? Because `global` is the part of state your selectors are looking. – tpdietz Aug 16 '18 at 16:35

1 Answers1

0

You need update the mapStateToProps function connecting the MainPage component to receive the data in your new reducers. Currently you have:

const mapStateToProps = createStructuredSelector({
  mainPage: makeSelectPostedCasesClient
});

However, your component expect to receive loading, error, and products. You will need to create a mapStateToProps function that provides these variables to your component. Something like:

const mapStateToProps = createStructuredSelector({
  products: makeSelectProducts(),
  loading: makeSelectLoading(),
  error: makeSelectError(),
});

You may have to write your own selectors for getting the data out of your ProductsReducer. Once you do this, when your reducers gets new data, the selectors will automatically get the new data and update your component.

tpdietz
  • 1,358
  • 9
  • 17
  • Yes, I did it right now. But my '' renders faster than my ComponentDidMount or ComponentWillMount returns repsonse with all the arrays. Maybe you know how to solve it? – PrEto Aug 16 '18 at 16:17
  • Yes, `` will render before the `onFetch` method finishes getting all the data. But this is exactly what you want. Notice the `ProductList` component is designed to handle this situation. `ProductList` has a `loading` state, which is rendered while `onFetch` is still waiting for the data. After `onFetch` completes and updates the `ProductsReducer ` reducer, your selectors will read the new data and pass the new data to `MainPage`. When `MainPage` gets the new data, it passes the new data into `ProductsList`. – tpdietz Aug 16 '18 at 16:23
  • If the data is good, `ProductsList` should re-render with all the data. If this is not happened, you should console log the props in the render methods of `MainPage` and `ProductsList` to make sure they get new data after `onFetch` completes. If you are not getting the data you expect, you should verify the reducer does have the data and that the selectors are looking at the right slice of redux state. – tpdietz Aug 16 '18 at 16:23
  • Can you post your `makeSelectProducts` selector and how you are hooking up your `ProductsReducer` reducer to the `combineReducers` method in `app/reducers.js` . This is almost certainly a problem with getting the data out of the reducer and into your component. – tpdietz Aug 16 '18 at 16:26
  • so somehow it not going to loading phase in ProductsList. And I have added selectors.js – PrEto Aug 16 '18 at 16:30
  • Sounds like your selectors are not getting the correct data out of your reducer. Notice how `ProductsList` will simply return `null` if it gets no data. – tpdietz Aug 16 '18 at 16:36
  • it instantly goes to 3 part where product is true, in `ProductsList`. Somehow it skips loading part. And it returns, that item.id -> id is missing. If I use `ComponentWillMount`, errors occur and after them it gets list of objects. If I use `ComponentWillMount` list of object is undefined. – PrEto Aug 16 '18 at 16:43