Relay.js is not resolving composed fragment correctly - javascript

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.

Related

It is possible to import new property, functionality and html from other file to exists component?

I have the package with free and pro components.
I need to add new functionality (pro) for exists free component.
For example, I have a free component
import React from "react";
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
clickCounter: 0
}
}
addClick = () => {
this.setState({
clickCounter: this.state.clickCounter+1
})
}
render() {
return (
<button onClick={this.addClick}>Click {this.state.clickCounter}</button>
);
}
}
export default Example;
and in folder component/pro/ I want new functionality. (This folder is being deleted for the free package build)
And I would like the same component to have more possibilities under the same name for the pro version only.
For example, the limit of clicks.
What is the best way to do it?
Should I remove excess code from the component, or somehow can I load additional functions into an existing component.
If I understood the question correctly, you are distinguishing between a free and a Pro user, and based upon that you want to serve the respective features.
Your user details API should tell you about the user status, (Free or PRO). Based upon that response you can implement like below.
import FreeExample from './FreeExample';
import ProExample from './ProExample';
import AppLoader from './AppLoader';
class App extends React.Component {
isProUser = ({ userData }) => userData.status === 'PRO';
render() {
const { userData, isLoading } = this.props;
if(isLoading) return <AppLoader />
return (
{ isProUser({ userData }) ? <ProExample /> : <FreeExample />}
);
}
}

Onepage with Gatsby JS and Contentful, how to import my first simple string

I am trying to useStatic Query and GraphQL to get a simple title from
Contentful, pass it to state and then show in the render. I cant make it work. I am attaching an image showing my current setup and errors.
Possible problems: 1. the query returns an array, and I need to change it into a string, or access 0 element, the first one, because my content type is just one, as it is a onepage.
Placing of the query in the component, I am not sure if it can be in the constructor of an component
For comparison: in the screen from my file you can see a variable name showing Josh Perez, when I uncomment it and add it to this.state = { dataTest: name}, then in RENDER: this.state.dataTest returns the name Josh Perez well, so passing a variable to state works, but passing a string from graphql query is not possible for me...
I have a limitation which is that I need to create my page component with a class, because of the fact that in the Component did mount I am placing some JQuery, which works well for me.
THIS IS MY TEST CODE
1. In Constructor
class IndexPage extends React.Component {
constructor(props) {
super(props);
// this.state = { data: null };
const name = 'Josh Perez';
this.state = { dataTest: name };
}
In render
{this.state.dataTest}
This works, the variable name is passed to state and shown in render.
However, I want to show in this way a simple text string from Contentful. So I am trying code like this (error message is shown in the screens):
class IndexPage extends React.Component {
constructor(props) {
super(props);
// this.state = { data: null };
//const name = 'Josh Perez';
const data = useStaticQuery(graphql`
query {
allContentfulHomepage (limit: 1) {
edges {
node {
section1Title
}
}
}
}
`)
this.state = { dataTest: data };
It turns out, that the below suggested solution works. I am putting below
my attempt at callingfurther content. It does not work. It displays the following error "Cannot read property 'map' of undefined". I would be very grateful for a suggestion how to improve it, how to make it work.
export default class Test extends Component {
state = {
dataTest: this.props.data.test.edges.map(({ node: test }) =>
test.section1Title),
dataTest2: this.props.data.test.edges.map(({ node: test }) =>
test.section2Lead),
dataTest3: this.props.data.test.edges.map(({ node: test }) =>
test.section1Text.json)
}
render() {
return <div>
<h1>{this.state.dataTest}</h1>
<h1>{this.state.dataTest2}</h1>
{documentToReactComponents(this.state.dataTest3)}
</div>
}
}
export const query = graphql`
{
test:allContentfulHomepage(limit: 1) {
edges {
node {
section1Title
section2Lead
section1Text {
json
}
}
}
}
}
`
If you're writing a page component as a class, you don't need to use the UseStaticQuery, you can use the simple PageQuery for this purpose.
To loop through arrays, the map() method works as well.
UPDATE
import React, { Component } from 'react';
import { graphql } from 'gatsby';
export default class Test extends Component {
render() {
const { edges } = this.props.data.test;
return (
<div>
{edges.map(({ node: itemFromContentful }) => (
<h1>{itemFromContentful.section1Title}</h1>
<h1>{itemFromContentful.section2Lead}</h1>
{documentToReactComponents(section1Text.json)}
))}
</div>
);
}
}
export const query = graphql`
{
test:allContentfulHomePage(limit: 1) {
edges {
node {
section1Title
}
}
}
}
`
Whats happening:
The GraphQL query you're using is bringing the data you want from the Contentful;
The React Stateful Component (class Test) is receiving all the data available from the query as a prop;
We're accessing this data on the render() method using the destructing assignment;
we're accessing the data nodes through the map method (the one I suggested you to take a look;
The curly braces into the JSX allows you to use JS to manipulate what you want - In this case, to render the information.

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
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

React - Apollo Client, How to add query results to the state

I've created a react app driven by Apollo client and graphQL.
My schema is defined so the expected result is an array of objects ([{name:"metric 1", type:"type A"},{name:"metric 2", type:"type B"}])
On my jsx file I have the following query defined:
query metrics($id: String!) {
metrics(id: $id) {
type
name
}
}`;
I've wrapped the component with Apollo HOC like so:
export default graphql(metricsQuery, {
options: (ownProps) => {
return {
variables: {id: ownProps.id}
}
}
})(MetricsComp);
The Apollo client works fine and returns the expected list on the props in the render method.
I want to let the user manipulate the results on the client (edit / remove a metric from the list, no mutation to the actual data on the server is needed). However since the results are on the component props, I have to move them to the state in order to be able to mutate. How can I move the results to the state without causing an infinite loop?
If apollo works anything like relay in this matter, you could try using componentWillReceiveProps:
class ... extends Component {
componentWillReceiveProps({ metrics }) {
if(metrics) {
this.setState({
metrics,
})
}
}
}
something like this.
componentWillReceiveProps will be deprecated soon (reference link)
If you are using React 16 then you can do this:
class DemoClass extends Component {
state = {
demoState: null // This is the state value which is dependent on props
}
render() {
...
}
}
DemoClass.propTypes = {
demoProp: PropTypes.any.isRequired, // This prop will be set as state of the component (demoState)
}
DemoClass.getDerivedStateFromProps = (props, state) => {
if (state.demoState === null && props.demoProp) {
return {
demoState: props.demoProp,
}
}
return null;
}
You can learn more about this by reading these: link1, link2
you can use this:
import {useState} from 'react';
import {useQuery} from '#apollo/client';
const [metrics,setMetrics]=useState();
useQuery(metricsQuery,{
variables:{id: ownProps.id},
onCompleted({metrics}){
setMetrics(metrics);
}
});

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`.

Categories

Resources