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

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
Go to details
... 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:
Go to details
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

Related

Console.Log Not Being Called Inside React Constructor

I'm trying to add a component to a default .NET Core MVC with React project. I believe I have everything wired up to mirror the existing "Fetch Data" component, but it doesn't seem like it's actually being called (but the link to the component in my navbar does move to a new page).
The component itself...
import React, { Component } from 'react';
export class TestComponent extends Component {
static displayName = TestComponent.name;
constructor (props) {
super(props);
console.log("WHO NOW?");
this.state = { message: '', loading: true, promise: null };
this.state.promise = fetch('api/SampleData/ManyHotDogs');
console.log(this.state.promise);
}
static renderForecastsTable (message) {
return (
<h1>
Current Message: {message}
</h1>
);
}
render () {
let contents = this.state.loading
? <p><em>Loading...</em></p>
: TestComponent.renderForecastsTable(this.state.message);
return (
<div>
<h1>Weather forecast</h1>
<p>This component demonstrates fetching data from the server.</p>
{contents}
</div>
);
}
}
The App.js
import React, { Component } from 'react';
import { Route } from 'react-router';
import { Layout } from './components/Layout';
import { Home } from './components/Home';
import { FetchData } from './components/FetchData';
import { Counter } from './components/Counter';
import { TestComponent } from './components/TestComponent';
export default class App extends Component {
static displayName = App.name;
render () {
return (
<Layout>
<Route exact path='/' component={Home} />
<Route path='/counter' component={Counter} />
<Route path='/fetch-data' component={FetchData} />
<Route path='/test-controller' component={TestComponent} />
</Layout>
);
}
}
That console.log("Who now") is never called when I inspect, and the page remains totally blank. I can't find a key difference between this and the functioning components, and google has not been much help either. Any ideas what is missing?
Edit
While troubleshooting this, I ended up creating a dependency nightmare that broke the app. Since I'm only using the app to explore React, I nuked it and started over--and on the second attempt I have not been able to reproduce the not-rendering issue.
It is advisable to use componentDidMount to make the call to the REST API with the fetch or axios.
class TestComponent extends Component{
constructor(props){
state = {promise: ''}
}
async componentDidMount () {
let promise = await fetch ('api / SampleData / ManyHotDogs');
this.setState ({promise});
console.log (promise);
}
render(){
return(
<div>{this.state.promise}</div>
);
}
}

"User is not Defined" trouble filtering and rendering search results with React.Js/Material-Ui

I am trying to build a search bar that filters through an array of users that are placed in a grid using Material-Ui styling. I have tried multiple times to map through and display the users but I am unable to render the page with the users displayed in a grid.
I keep getting an error saying "User is not Defined"
Here is the UserList.js code
// implemented a class based component
import React, { Component } from 'react';
import './UserList.css'
// we will be using this for our cards
import Grid from '#material-ui/core/Grid';
import TextField from '#material-ui/core/TextField';
import * as contentful from 'contentful';
class UserList extends Component {
// state object contains two properties
state = {
users: [
{
"id": 1,
"name": "Mary Ann",
"socialmedia": "Instagram",
"following": "401k"
},
{
"id": 2,
"name": "Jessica P",
"socialmedia": "Instagram",
"following": "600k"
}
],
searchString: ''
}
constructor() {
// super is the parent consturctor
super()
// we are making sure the list is loaded at the beginning of the lifecycle
this.getUsers()
}
// here we are implementing that method after we retreived it above
getUsers = () => {
// client.getEntries
({
// what data are we returning
content_type: 'user',
// we use query because we will have a search filter activated
// with this we will also need to make sure whent he user searches, the search barupdates accordingly
query: this.state.searchString
})
// returns a promise
// once there is a response, we use that callback function to make sure we ae setting the state again
.then((response) => {
this.setState({users: response.elements})
})
// in case there is an error we want to catch it
.catch((error) => {
console.log("Error occured while fetching data")
console.log(error)
})
}
// we are passing an event as a parameter
onSearchInputChange = (event) => {
// if the value entered by the user in the textfield, make sure the state is updated
if (event.target.value) {
this.setState({searchString: event.target.value})
// if that is not the case,we set the state to an empty string
} else {
this.setState({searchString: ''})
}
// we are making sure so the query is executed again and we are making sure only the users are returned, matching to our search stirng
this.getUsers()
}
// The View
// now we are making sure it is rendered in the browser
render() {
return (
<div>
{this.state.users ? (
<div>
<TextField style={{padding: 24}}
id="searchInput"
placeholder="Search for Influencers"
margin="normal"
onChange = {this.onSearchInputChange} />
<Grid container spacing={24} style={{padding: 24}}>
{ this.state.users.map(currentUser => (
<Grid item xs={12} sm={6} lg={4} xl={3}>
<User user={currentUser} />
</Grid>
))}
</Grid>
</div>
) : "No courses found" }
</div>
)
}
}
export default UserList;
Here is the User.js Code
import React from 'react';
import Card from '#material-ui/core/Card';
import CardActions from '#material-ui/core/CardActions';
import CardContent from '#material-ui/core/CardContent';
import CardMedia from '#material-ui/core/CardMedia '
import Button from '#material-ui/core/Button';
import Typography from '#material-ui/core/button';
const User = (props) => {
return (
<div>
<h1 className='profile'> #HASHTAG HOUND </h1>
{this.state.users.map((element, index) => {
return <div>
<h1 className='profile'>{element.name}</h1>
<h2 className='profile'>{element.socialmedia}</h2>
<h2 className='profile'>{element.following}</h2>
</div>
})}
</div>
)
}
}
export default User;
It seems like the problem is the User Component is not being imported inside the UserList.js file. So simply import it: import User from './User'; (set it to the correct path).
Issue resolved. Click here to watch the => WORKING DEMO
You were trying to get users data from UsersList file which is not possible by importing that file, you need to create another file where you put the data and use the data in both the files by exporting the data from the file and just import the const user from that file usersListData.jsx as shown on the working demo and apply map on that const.

how to get data from graphql server using react and react-apollo?

After about 6-8 hours trying, I'm resorting to help.
All I want is to query my graphql server and the response data to be entered into my react component as props (See ParameterBox.js). Please let me know what I'm doing wrong.
For Reference: INDEX.JS FILE (Likely correct)
import React from 'react';
import ReactDOM from 'react-dom';
import {
ApolloClient,
createNetworkInterface,
ApolloProvider,
} from 'react-apollo';
import App from './App';
const networkInterface = createNetworkInterface({
uri: 'http://localhost:3001/graphql'
});
const client = new ApolloClient({
networkInterface: networkInterface
});
ReactDOM.render(
<ApolloProvider client={client}>
<App />
</ApolloProvider>,
document.getElementById('root')
);
For Reference: APP.JS FILE (I think it's correct)
//App.js
import React, { Component } from 'react';
import ParameterBox from './ParameterBox';
class App extends Component {
render(){
return(
<div>
<ParameterBox />
</div>
)
}
}
export default App;
PARAMETERBOX.JS FILE (Here is where the issue is, somewhere...)
//ParameterBox.js
import React, { Component } from 'react';
import axios from 'axios';
import { gql, graphql } from 'react-apollo';
import ParameterList from './ParameterList';
class ParameterBox extends Component {
constructor (props) {
super(props);
this.state = { data: [] };
this.loadParamsFromServer = this.loadParamsFromServer.bind(this);
}
//*** Old Method ***/
// I still set my data using the old methods of API urls. I want to switch to GraphQL
loadParamsFromServer(){
axios.get('http://localhost:3001/api/params')
.then (res => {
this.setState({ data: res.data });
})
}
//**** end old method ****/
componentDidMount() {
this.loadParamsFromServer();
}
render(){
return(
<div >
<h2> Parameters: </h2>
<p> HOW DO I GET IT HERE? {this.props.AllParamsQuery } </p>
<ParameterList
data={ this.state.data }/>
</div>
)
}
}
const AllParams = gql`
query AllParamsQuery {
params {
id,
param,
input
}
}`;
export default graphql(AllParams, {name: 'AllParamsQuery'})(ParameterBox);
You may find it helpful to review the Apollo documentation for basic queries here.
When you wrap your component with the graphql HOC, it will send your query to the server and then make the result available to your component as this.props.data. So, the result for your particular query would be found at this.props.data.params (the operation name, AllParamsQuery is not referenced inside the returned data).
The other thing to bear in mind is that the GraphQL is asynchronous, so while props.data will always be available, initially it will be empty. Your render logic will need to account for that fact by verifying that this.props.data.params is truthy before tyring to render it. You can also check whether the query is still in flight or has completed.
Edit: because you define a name property (AllParamsQuery) inside the config object you pass to graphql(), your query results will be available as that prop instead of data -- i.e. 'this.props.AllParamsQuery.params`.

Relay.js is not resolving composed fragment correctly

I'm working on converting a Flux app to Relay.js and I'm running into some issues. I can't seem to get component fragment composition to work properly. The correct data comes back from the server, but the composed data is never passed back into the props of the parent component for some reason.
here's my code so far:
LibraryLongDescription.js
import React, {Component, PropTypes} from 'react';
import Relay from 'react-relay';
import DocumentTitle from 'react-document-title';
import Address from '../components/Address';
import Orders from '../pages/Orders';
export default class LibraryLongDescription extends Component {
constructor(props)
{
super(props);
this.state = {
library: {}
};
console.log(props);
if(props.library){
this.state.library = props.library;
}
}
render()
{
return (
<DocumentTitle title="Libraries">
<div>
{this.state.library.name}
<div className="row">
<Address type={"Address"} address={this.state.library.address} />
</div>
<div className="top-space-60">
<h3>Orders</h3>
<Orders />
</div>
</div>
</DocumentTitle>
);
}
}
export default Relay.createContainer(LibraryLongDescription, {
fragments: {
library: () => Relay.QL`fragment on Library {
id,
name,
address{
id
sanNumber,
addressLine1,
addressLine2,
city,
state,
zip
},
${Orders.getFragment('orders')}
}`,
}
});
Orders.js
import React, {Component, PropTypes} from 'react';
import Relay from 'react-relay';
class Orders extends Component {
constructor(props)
{
super(props);
console.log(props);
}
render()
{
return (<h1>This is where the order goes</h1>);
}
}
export default Relay.createContainer(Orders, {
fragments: {
orders: () => Relay.QL`fragment on Library {
orders(first: 10){
edges{
node{
id,
message_number,
order_total
}
}
pageInfo{
hasPreviousPage,
hasNextPage
}
}
}`
}
});
This does not resolve correctly. When I console log props in LibraryLongDescription.js I get all the values from that query, but I don't get anything from the Orders fragment. When I look to see what came over the network I get data in this form:
{
"data":{
"library":{
"id":"valid",
"name":"valid",
"address":{
correct data
},
"_orders1mpmPX":{
"edges":[
{
"node":{
correct data
},
"cursor":"correct data"
},
],
"pageInfo":{
correct data
}
}
}
}
}
but when I console log props from library Long description I don't see anything for orders. I also get this property: __fragment__ which seems to not really have anything useful on it. Any help with this would be greatly appreciated. I've been searching the internet for solutions for hours. If there's any info I did not provide that would be of use let me know.
After spending a stupid amount of time trying to solve this issue I have discovered relay does not like you defining a type field in a fragment query. here's what I mean... the library query changed to this:
export default Relay.createContainer(LibraryLongDescription, {
fragments: {
library: () => Relay.QL`
fragment on Library {
id,
name,
address{
id
sanNumber,
addressLine1,
addressLine2,
city,
state,
zip
},
orders(first: 500){
${Orders.getFragment('orders')}
}
}
`,
}
});
and the orders query changed to this:
export default Relay.createContainer(Orders, {
fragments: {
orders: () => Relay.QL`fragment on OrderConnection {
edges{
node{
id
purchaseDate
}
}
pageInfo{
hasPreviousPage
hasNextPage
}
}`
},
});
if you don't have some sort of root field like orders defined on the parent, relay won't know how to resolve that field back to the parent to be passed back into your child component. by doing this: orders(first: 500) you are declaring that name as a dependency for relay to pass it into that component. Without that name relay does not see your component requiring that name and it won't pass it. I hope this helps someone else out someday. I spent more than a full day trying to figure this one out.

React Redux Loading bar for react router navigation

So I'd like to implement a loading bar just like github has. It should start loading on a click to another page and finish when it arrived.
I'm using material-ui and for the loader react-progress-bar-plus.
I tried to use react-router's lifecycle hooks, namely componentDidUpdate and componentWillReceiveProps to set the state to be finished.
For start, I attached an onTouchTap function to the menu items but it just does not want to work properly.
What is the best way to implement this feature?
You can use router-resolver with react-progress-bar-plus.
See this example:
http://minhtranite.github.io/router-resolver/ex-4
The usage example:
// app.js
//...
import {RouterResolver} from 'router-resolver';
//...
const routes = {
path: '/',
component: App,
indexRoute: {
component: require('components/pages/PageHome')
},
childRoutes: [
require('./routes/Example1Route'),
require('./routes/Example2Route'),
require('./routes/Example3Route')
]
};
const renderInitial = () => {
return <div>Loading...</div>;
};
const onError = (error) => {
console.log('Error: ', error);
};
ReactDOM.render(
<Router routes={routes}
history={history}
render={props => (
<RouterResolver {...props} renderInitial={renderInitial} onError={onError}/>
)}/>,
document.getElementById('app')
);
And:
// components/pages/PageExample1.js
import React from 'react';
import Document from 'components/common/Document';
class PageExample1 extends React.Component {
static resolve() {
return new Promise((resolve) => {
setTimeout(() => {
resolve('simple data');
}, 2000);
});
};
static propTypes = {
response: React.PropTypes.string.isRequired
};
render() {
return (
<Document title='Example1 | Router resolver' className='page-ex-1'>
<h1>Example 1: {this.props.response}</h1>
</Document>
);
}
}
export default PageExample1;
I made a small package react-router-loading that allows you to show loading indicator and fetch some data before switching the screen.
Just use Switch and Route from this package instead of react-router-dom:
import { Switch, Route } from "react-router-loading";
Add loading props to the Route where you want to wait something:
<Route path="/my-component" component={MyComponent} loading/>
And then somewhere at the end of fetch logic in MyComponent add loadingContext.done();:
import { LoadingContext } from "react-router-loading";
const loadingContext = useContext(LoadingContext);
const loading = async () => {
//fetching some data
//call method to indicate that fetching is done and we are ready to switch
loadingContext.done();
};

Categories

Resources