0

I am fetching data in parent 'wrapper' component and pass it down to two child components. One child component receives it well, another does not.

In container:

const mapStateToProps = createStructuredSelector({
  visitedCountriesList: getVisitedCountriesList(),
  visitedCountriesPolygons: getVisitedCountriesPolygons()
});

export function mapDispatchToProps(dispatch) {
  return {
    loadVisitedCountries: () => {
      dispatch(loadVisitedCountriesRequest())
    },
  };
}

in redux-saga I fetch data from API and store them:

function mapPageReducer(state = initialState, action) {
   switch (action.type) {
     case FETCH_VISITED_COUNTRIES_SUCCESS:
       return state
         .setIn(['visitedCountriesPolygons', 'features'], action.polygons)
 }

Selectors:

const getVisitedCountriesList = () => createSelector(
  getMapPage,
  (mapState) => {
    let countriesList = mapState.getIn(['visitedCountriesPolygons', 'features']).map(c => {
      return {
        alpha3: c.id,
        name: c.properties.name
      }
    });
    return countriesList;
  }
)

const getVisitedCountriesPolygons = () => createSelector(
  getMapPage,
  (mapState) => mapState.get('visitedCountriesPolygons')
)

in a wrapper component I render two components, triggering data fetch and passing props down to child components (visitedCountriesPolygons and visitedCountriesList):

class MapView extends React.Component {
  constructor(props) {
    super(props)
    this.props.loadVisitedCountries();
  }

  render() {
    return (
      <div>
        <Map visitedCountriesPolygons={this.props.visitedCountriesPolygons} />
        <MapActionsTab visitedCountriesList={this.props.visitedCountriesList} />
      </div>
    );
  }
}

Then, in first child component Map I receive props well and can build a map:

componentDidMount() {
    this.map.on('load', () => {
      this.drawVisitedPolygons(this.props.visitedCountriesPolygons);
    });
};

But in the second component MapActionsTab props are not received at initial render, but only after any update:

class MapActionsTab extends React.Component {
  constructor(props) {
    super(props);
  }
  render() {
    let countriesList = this.props.visitedCountriesList.map(country => {
      return <li key={country.alpha3}>{country.name}</li>;
    }) || '';

    return (
      <Wrapper>
          <div>{countriesList}</div>
      </Wrapper>
    );
  }
}

UPD: Saga to fetch data form API:

export function* fetchVisitedCountries() {
  const countries = yield request
    .get('http://...')
    .query()
    .then((res, err) => {
      return res.body;
  });
  let polygons = [];
  yield countries.map(c => {
    request
      .get(`https://.../${c.toUpperCase()}.geo.json`)
      .then((res, err) => {
        polygons.push(res.body.features[0]);
      })
  });
  yield put(fetchVisitedCountriesSuccess(polygons));
}

and a simple piece of reducer to store data:

case FETCH_VISITED_COUNTRIES_SUCCESS:
       return state
         .setIn(['visitedCountriesPolygons', 'features'], action.polygons)

Why is it different and how to solve it, please?

thanks, Roman

  • Where do you make the API call ? Is it inside `loadVisitedCountriesRequest` ? Can you include code to that too... I think maybe it's a problem with how you handle async flow – Dane Nov 24 '17 at 10:50
  • added. I was thinking the same and played a lot with trying to transform data in saga or reducer to save it normalized, but it failed due to async actions. Ended up with storing it raw and normalizing in selector. That's a different story :) long story short, I am selecting data from the store and in one case (polygons for Map component) it works well, in the second - it is late. – Roman Krayovskyy Nov 24 '17 at 12:02

2 Answers2

1

Apparently, this works correct and it was just a minor issue in another place (not pasted here and not errors reported). After thorough clean up and refactoring it worked as expected.

Conclusion: always keep your code clean, use linter and follow best practices :)

0

I think the problem may be in your selectors, in particular this one, whose component parts being executed immediately (with no fetched data values), and hence values will not change as it is memoized. This means that it will not cause an update to the component should the the underlying data change from the fetched data

const mapStateToProps = createStructuredSelector({
  visitedCountriesList: getVisitedCountriesList, // should not execute ()
  visitedCountriesPolygons: getVisitedCountriesPolygons // should not execute ()
});

By not executing the composed selectors immediately, mapStateToProps will call them each time the state changes and they should select the new values and cause an automatic update of your react component

alechill
  • 4,274
  • 18
  • 23
  • I will try to play with that, probably by requesting it from my component, but still it is not consistent: getVisitedCountriesPolygons executes, passes props to component and re-renders; getVisitedCountriesList executes, but passes no props and does not re-render (or re-render but with no data) – Roman Krayovskyy Nov 25 '17 at 08:03