简体   繁体   中英

React: How to pass data between 'pages' and also handle page reloads?

I have a React + Redux application that gets a feed per user in a feed listing page with the ability to view more details of a specific feed item in a new page (I will explain with a code example below). There is an API to get the feed listing but there is no API to get details of a specific feed item. How should I pass data from the feed listing page to the feed details page? How will I handle reload of the feed details page or the scenarios where a user can bookmark the feed details page and visit it at a later date?

My code is as follows:

app.js

import React from 'react';
import {
  Route,
  Switch
} from 'react-router-dom';
import { ConnectedRouter } from 'connected-react-router'
import Home from './homeComponent';
import Login from './loginComponent';
import FeedListing from './feedListingComponent';
import FeedDetails from './feedDetailsComponent';
import NoMatch from './NoMatchComponent';

const App = ({ history }) => {
  return (
    <ConnectedRouter history={history}>
        <Switch>
          <Route exact={true} path="/" component={Home} />
          <Route path="/login" component={Login} />
          <Route path="/feed/:profileId" component={FeedListing} />
          <Route path="/feed_details/:feedId" component={FeedDetails} />
          ... more routes
          <Route component={NoMatch} />
        </Switch>
    </ConnectedRouter>
  );
};

export default App;

feedListingComponent.js

import React, {Component} from 'react'
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as feedActions from '../actions/feedActions';

class FeedListingComponent extends Component {
    constructor(props) {
        super();
    }

    componentDidMount() {
        const { profileId } = this.props.match.params;
        this.props.actions.getFeed(profileId); // Calls API to get feed for feed listing and contains all the data that would be required for the feed details in each feed item
    }

    render() {
        return (
            <div>
                ... code that loops through the getFeed response starts here
                    <a href="/feed_details/{feedId}">Go to details</a>
                ... code that loops through the getFeed response ends here
            </div>
        )
    }
}

function mapStateToProps(state) {
  return {
    feed: state.feed.get('feed')
  }
}

function mapDispatchToProps(dispatch) {
  return {
    actions: bindActionCreators(feedActions, dispatch)
  };
}

export default connect(mapStateToProps, mapDispatchToProps)(FeedListingComponent);

feedDetailsComponent.js

import React, {Component} from 'react'
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as feedActions from '../actions/feedActions';

class FeedDetailsComponent extends Component {
    constructor(props) {
        super();
    }

    componentDidMount() {
        ... some code here
    }

    render() {
        return (
            <div>
                ... need to show the feed details here
            </div>
        )
    }
}

function mapStateToProps(state) {
  return {
    ... some code here
  }
}

function mapDispatchToProps(dispatch) {
  return {
    actions: bindActionCreators(feedActions, dispatch)
  };
}

export default connect(mapStateToProps, mapDispatchToProps)(FeedDetailsComponent);

I was considering calling the API to get feed for feed listing in the details page as well which means I would need to pass the :profileId to the feed details route as well for the feed listing API to work:

<a href="/feed_details/{feedId}/{profileId}">Go to details</a>

Then I could filter the response by feedId like so:

const feedItem = feed.filter(function(feedItem){
    return feedItem.feedId == feedId;
});

But this not an optimal solution at all, considering that the feed may contain thousands or million of feed items.

Can anyone suggest a better more optimal way to handle all the scenarios I have mentioned.

UPDATE: Adding my actions and reducer code as well.

feedActions.js

import * as actionTypes from './actionTypes';
import feedApi from '../api/feedApi';

export function getFeed(profileId) {
  return function(dispatch) {
    return feedApi.getFeed(profileId).then(response => {
      dispatch(getFeedSuccess(response.data));
    }).catch(error => {
      throw(error);
    });
  };
}

export function getFeedSuccess(response) {
  return { type: actionTypes.FEED, feed : response }
}

feedReducer.js

import Immutable from 'immutable';
import * as actionTypes from '../actions/actionTypes';

const initialState = Immutable.fromJS({
    feed: {}
});

export default function feedReducer(state = initialState, action) {
  switch(action.type) {
    case actionTypes.FEED:
      return state.setIn(['feed'], action.feed)
    default:
      return state;
  }
}

getFeed response

[
    {
        feedId: 1,
        feedTitle: "Lorem ipsum",
        feedDescription: "Lorem ipsum"
    },
    {
        feedId: 2,
        feedTitle: "Lorem ipsum",
        feedDescription: "Lorem ipsum"
    },
    {
        feedId: 3,
        feedTitle: "Lorem ipsum",
        feedDescription: "Lorem ipsum"
    },
    ... and so on
]

Unfortunately, there's no way to find a specific element in an array like that without looking through each item, it's just a limitation of the data structure.

However, I'm assuming that the feedListing component isn't going to be displaying a list of millions of items. You're probably going to want some kind of pagination happening there, right?

If that's the case, you could keep track of what page is being displayed with a smaller array sliced from the main array that's also kept in the store. When the user goes to the next page, an action slices the right section from the main array and updates the store. Then when the user selects one of those items and heads to the detail page, you'd only have to filter that smaller array to find the correct item, since you know the selected item was on the correct page.

Edit: Looking at this again, the way I would personally structure it would be to put it all on one page, and conditionally render the details component when the user selects a visible item.

class FeedListingComponent extends Component {
    constructor(props) {
        super();
        this.state = {
            selectedItem: null,
        }
    }

    componentDidMount() {
        const { profileId } = this.props.match.params;
        this.props.actions.getFeed(profileId); // Calls API to get 
feed for feed listing and contains all the data that would be required 
for the feed details in each feed item
    }

    displayDetails(item) {
        this.setState({
            selectedItem: item
        })
    }

    render() {
        if(this.state.selectedItem) {
            return (
                <FeedDetailsComponent
                    selectedItem={this.state.selectedItem}
                />
            );
        return (
            {this.props.feed.map(feedItem => (
               <ListItemComponent
                   onClick={() => this.displayDetails(feedItem)}
               />
            }
        );
    }
}

Hopefully that makes sense. You'll need some kind of 'back' button on the details page that sets selectedItem back to null so the list display returns

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM