Why are Relay Modern QueryRenderer render props undefined? - javascript

This is my first attempt at using Relay Modern.
Fetching for a specific User from a PostgraphQL GraphQL Server.
It is fetching the data successfully but not passing to render function:
import {createFragmentContainer, QueryRenderer, graphql} from 'react-relay'
import environment from 'environment'
#CSSModules(styles) export default class Profile extends Component {
render() {
var {props: {children}} = this
return (
<QueryRenderer
environment={environment}
query={graphql`
query ProfileQuery {
userById(id: "f0301eaf-55ad-46db-ac90-b52d6138489e") {
firstName
userName
}
}
`}
render={({error, relayProps}) => {
if (error) {
return <div>{error.message}</div>
} else if (relayProps) {
...
}
return <div>Loading...</div>
}}
/>
)
}
}
Only "Loading..." is rendered.
I am guessing because it successfully fetches data that the graphql server and environment are ok.
I am not using React 16 and the project also uses Redux.
Any suggestions please as to why relayProps wouldn't have a value (e.g. relayProps.user)?
One further thing that may help, the environment (file) is in the main application and the QueryRenderer and components are in an imported npm package (to be shared across a number of applications). As mentioned, the query seems to work fine so I did not think this was a problem. I also run the relay compiler on the package but not the main application since there are no relay components there.
Just in case it's needed the environment is setup using:
const {
Environment,
Network,
RecordSource,
Store,
} = require('relay-runtime')
// Instantiate Store for Cached Data
const store = new Store(new RecordSource())
// Create Network for GraphQL Server
const network = Network.create((operation, variables) => {
// GraphQL Endpoint
return fetch(config.gqlapiProtocol + "://" + config.gqlapiHost + config.gqlapiUri + "/a3/graphql" , {
method: 'POST',
headers: {
'Content-Type': "application/json",
'Accept': 'application/json',
},
body: JSON.stringify({
query: operation.text,
variables,
}),
}).then(response => {
return response.json()
})
})
// Instantiate Environment
const environment = new Environment({
network,
store,
})
// Export environment
export default environment

props are not relayprops
render={({ error, props }) => {
if (error) {
return <div>{error.message}</div>;
} else if (props) {
...
}
return <div>Loading...</div>;
}}
and
fetch(GRAPHQL_URL, {
method: 'POST',
get headers() {
return {
'Content-Type': 'application/json',
'Accept': 'application/json',
};
},
body: JSON.stringify({
query: operation.text, // GraphQL text from input
variables
})
})
.then(response => response.json())
.then((json) => {
// https://github.com/facebook/relay/issues/1816
if (operation.query.operation === 'mutation' && json.errors) {
return Promise.reject(json);
}
return Promise.resolve(json);
})
);

Related

How to Render Data from a POST API call in React

I'm trying to figure out how to code my current API call so that I can access each field from the API call and render it, then be able to use it across multiple components. I'm using the QuickBase API call that only allows POST to pull field values. I've been out of the game for a couple of years and can't figure out how to accurately render these to be able to be used in other components by importing the api.js file. The project is a React within Electron to pull QuickBase data, and be able to create Line Charts (7 on one page) to show a job cost/hours and the jobs included departments cost/hours. All of my data is in quickbase, I just can't figure out how to get it over to react and able to actually use it!
Here is my API call:
let headers = {
'QB-Realm-Hostname': 'XXXXXXXXX.quickbase.com',
'User-Agent': 'FileService_Integration_V2.1',
'Authorization': 'QB-USER-TOKEN XXXXXX_XXXXX_XXXXXXXXXXXXXXX',
'Content-Type': 'application/json'
}
let body = {"from":"bpz99ram7","select":[3,6,80,81,82,83,86,84,88,89,90,91,92,93,94,95,96,97,98,99,101,103,104,105,106,107,109,111,113,115,120,123,224,225,226,227,228,229,230,231,477,479,480,481],"sortBy":[{"fieldId":6,"order":"ASC"}],"groupBy":[{"fieldId":40,"grouping":"equal-values"}],"options":{"skip":0,"top":0,"compareWithAppLocalTime":false}}
fetch('https://api.quickbase.com/v1/records/query',
{
method: 'POST',
headers: headers,
body: JSON.stringify(body)
})
.then(res => {
if (res.ok) {
return res.json().then(res => console.log(res));
}
return res.json().then(resBody => Promise.reject({status: res.status, ...resBody}));
})
.catch(err => console.log(err))
Any help would be greatly appreciated as I've been struggling on this for awhile! Right now I'm able to get all the correct data in the Console. But don't know how to go about rendering it on my application for actual use.
Thanks!
I think you should put your code inside a function and call that function from the component where you need the data, something like
import React, { Component } from 'react'
let headers = {
'QB-Realm-Hostname': 'XXXXXXXXX.quickbase.com',
'User-Agent': 'FileService_Integration_V2.1',
'Authorization': 'QB-USER-TOKEN XXXXXX_XXXXX_XXXXXXXXXXXXXXX',
'Content-Type': 'application/json'
};
class App extends Component {
state = {
data: null,
}
componentDidMount() {
this.fetchData();
}
fetchData = () => {
let body = {"from":"bpz99ram7","select":[3,6,80,81,82,83,86,84,88,89,90,91,92,93,94,95,96,97,98,99,101,103,104,105,106,107,109,111,113,115,120,123,224,225,226,227,228,229,230,231,477,479,480,481],"sortBy":[{"fieldId":6,"order":"ASC"}],"groupBy":[{"fieldId":40,"grouping":"equal-values"}],"options":{"skip":0,"top":0,"compareWithAppLocalTime":false}}
fetch('https://api.quickbase.com/v1/records/query', {
method: 'POST',
headers: headers,
body: JSON.stringify(body)
}).then(response => {
if (response.ok) {
return response.json().then(res => {
this.setState({
data: res,
})
});
}
return response.json().then(resBody => Promise.reject({status: response.status, ...resBody}));
}).catch(err => console.log(err))
}
render() {
const { data } = this.state;
if (data === null) return 'Loading...';
return (
<div>
{/* Do something with data */}
</div>
);
}
}
export default App;
Check the Docs, you can send the JSON in the props of the component to render it.
You can modify your code following this example.
sandbox
import { useEffect, useState } from "react";
async function apiCall() {
return await new Promise((resolve, reject) => {
// Api Call
fetch("https://jsonplaceholder.typicode.com/todos/1")
.then((response) => response.json())
.then((json) => resolve(json));
});
}
const TestApp = () => {
let [data, setData] = useState({ Text: "Before api call." });
useEffect(() => {
(async () => {
let res = await apiCall();
res.Text = "After api call.";
setData(res);
})();
}, []);
return (
<div>
UserId: {data.userId} id: {data.id} title: {data.title}{" "}
completed: {data.completed}
</div>
);
};
module.exports = TestApp;

ReactJS: Update values without reload the page

I have this problem, when I do a insert or a change about some data, to see the new data I need to reload the page while I would to update automatically the value without the need to reload the page. How can I do?
This is the part where the user click on submit and the post
_onSubmit(Document)
{
const self = this
if ( !_.isEmpty(Document) )
{
//..
if (Document && !_.isEmpty(Document.Anagraphics))
{
alertify.confirm(
utility.t('sureYouWanna_SAVE'),
() => {
const now = new Date();
Document._id = `PRODUCT:${new Date().getTime()}-${utility.CUID()}`
Document.CreationDate = now.toISOString()
Document.CategoryCode
Document.Status = 'New';
Document.Type = 'PRODUCT';
self._POST(Document)
},
function(){}
).set('labels', {ok: utility.t('YES_SAVE'), cancel: utility.t('CANCEL')})
}
else
{
$methods.WarnMissingValues()
}
}
else {
$methods.WarnMissingValues()
}
}
_POST(Document)
{
console.log("DOCUMENT POST", Document)
const self = this
const auth = this.props.db.auth
fetch(`${this.props.db.couch_db_host_url}requests`,{
method: 'POST',
credentials: 'include',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': 'Basic ' + btoa(`${auth.username}:${auth.password}`)
},
body: JSON.stringify(Document)
})
.then(response => {
alertify.dismissAll()
if(response.status > 299 || response.status < 200){
alertify.error(utility.t('AN_ERROR_OCCURRED'))
self._updateState({ submitSucceeded: false })
}
else{
alertify.alert(utility.t('ITEM_EDITED_OK'), function(){})
self.props.history.push({
pathname: RoutesIT.products_details
})
}
})
.catch((err, warning) => {
if (err)
{
alertify.dismissAll()
alertify.error(utility.t('AN_ERROR_OCCURRED'))
console.log('_POST', err);
self._updateState({ submitSucceeded: false })
}
else
{
console.log(warning)
alertify.dismissAll()
alertify.warning(utility.t(warning))
}
})
}
How can I do to not reload the page to see the result of the post? Thank you
UPDATE:
In the page I have also:
function mapStateToProps(state) {
const { app: { login, p, c, l, c_timestamp, p_timestamp, l_timestamp }, form } = state;
return {
db: login ? login.db : null,
Sender: login ? login.Location : null,
timestamp: login ? login.timestamp : null,
[ FORM_NAME ]: form[FORM_NAME],
products: p,
locations: l,
categories: c,
categories_timestamp: c_timestamp,
products_timestamp: p_timestamp,
locations_timestamp: l_timestamp,
utente: login,
};
}
while the reducers
case actions.CATE_UPDATE:
{
return {
...state,
c: action.payload,
c_timestamp: new Date().getTime()
}
}
For what I can see in your code, the problem may lie in the fact that you're not dispatching any action when you submit the data.
Redux store can only be modified via actions, and since you're not triggering any, its contents are never being updated. This explains why your component is not updated in real time: your local data is never changing, so React is not aware of any updates. Things works when you reload the page because you're probably fetching the data from server, where the data did change during your POST request.
In order to fix this issue, you first need to pass a mapDispatchToProp to the your component, same as what you did with mapStateToProps:
connect(mapStateToProps, mapDispatchToProps)(YourComponent);
Inside of mapDispatchToProps, you have to return a property containing a function that will dispatch the CATE_UPDATE action you want to run:
const mapDispatchToProps = (dispatch) => ({
cateUpdateAction: (payload) => dispatch({
type: CATE_UPDATE,
payload
}),
});
Once you've done that, you'll be able to access this function from your component's props and call it inside of your _POST method.
if (response.status > 299 || response.status < 200){
alertify.error(utility.t('AN_ERROR_OCCURRED'))
self._updateState({ submitSucceeded: false })
} else {
alertify.alert(utility.t('ITEM_EDITED_OK'), function(){})
// Dispatch action to update data in Redux store
self.props.cateUpdateAction(data_to_save);
self.props.history.push({
pathname: RoutesIT.products_details
})
}

Reuse same view and logic just change the endpoints in VUE

I came to an point where i have a bunch of endpoints that behave the same like:
http:://api.development/projects/status/types
http:://api.development/projects/errors/types
http:://api.development/projects/priority/types
They all have the same verbs: GET, POST (add), PUT (edit) and DELETE and they share the same data structure:
{
name: "",
description: ""
}
Therefore the view and the logic to manage that on my client will be the same.
I am using VUE for the client. I thought of creating a component to reuse the view and create three other components that includes that component. Therefore the logic will be written in all of this three parent components.
But how can I implement the logic to reuse it across them? The only piece of code will change will be the endpoints.
You can create a service file, and there you declare your API calls, and then just export that file as a component and reuse the calls anywhere in your app.
It would look something like this:
import axios from 'axios'
const api = 'API'
export default {
data() {
return {
user: `${api}/some/route`,
hotels: `${api}/other/route/`
}
},
methods: {
getHeaders() {
return {
headers: {
Authorization: 'Bearer ' + 'TOKEN',
'Content-Type': 'application/json'
}
}
},
getModule(route, cb) {
axios
.get(route, this.getHeaders())
.then(response => {
cb(response.data)
})
.catch(err => {
cb(err)
})
},
postModule(route, data, cb) {
axios
.post(route, data, this.getHeaders())
.then(response => {
cb(response.data)
})
.catch(e => {
cb(e)
})
},
putModule(route, data, cb) {
axios
.put(route, data, this.getHeaders())
.then(response => {
cb(response.data)
})
.catch(e => {
console.log(e)
})
},
deleteModule(route, id, cb) {
axios
.delete(route + id, this.getHeaders())
.then(response => {
cb(response.data)
})
.catch(e => {
console.log(e)
})
}
}
}
In the component where you want to execute the call you do this:
import ServiceFileName from '#/services/YourServiceFileName'
methods:{
getData () {
Main.methods.getModule(Main.data().hotels, data => {
console.log(data)
})
}
}
You mean something like mixins?

Vue, fetch returns empty array

I'm fetching some data in my vue-cli project.
I'm using Vuex to store the data.
It all runs successfully apart from the fact that I get an empty array, I have checked in Postman, and it works perfectly.
As you can see in my actions i had my commit in the if statement, currently commented out and moved. But when run in there I get a Promise returned. And as the current edition of my code I get an empty array.
I really cant see what my error is, so my best bet is you guys are able to see what I'm missing.
First I have my actions:
export default {
async getProLanguages({ commit }) {
commit(C.PROLANGAUGE_DATA_PENDING);
try {
const res = await fetch('https://dev-webapp-kimga5xexrm3o.azurewebsites.net/api/ProLang', {
method: 'GET',
mode: 'cors',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': 'Bearer xxx'
}
});
if (res.status === 200) {
console.log(res);
// commit(C.PROLANGAUGE_DATA_SUCCESS, JSON.stringify(res.json()));
}
else {
commit(C.PROLANGAUGE_DATA_NO_CONTENT);
}
console.log(res)
return commit(C.PROLANGAUGE_DATA_SUCCESS, JSON.stringify(res.json()));
}
catch (e) {
commit(C.PROLANGAUGE_DATA_FAILURE);
}
}
And my mutations:
/**
* Indicates that programming language has succeded
*
* #param state
* #param payload
*/
[C.PROLANGAUGE_DATA_SUCCESS](state, payload) {
state.programmingLanguages = { ...state.programmingLanguages, loading: false, error: false, noContent: false, items: payload }
},
And I have my default state, which is imported into state.js:
const getDefaultState = () => ({
programmingLanguages: {
loading: false,
error: false,
noContent: false,
items: [
{
id: undefined,
name: undefined
}
]
}
});
I call my action with a beforeRouteEnter:
beforeRouteEnter(to, from, next) {
store.dispatch('programmingLanguages/getProLanguages').then(() => {
next();
});
}
and finally in my component I import mapState from Vuex:
computed: {
...mapState({
prolangs: state => state.programmingLanguages.programmingLanguages.items
})
}
I think something like items = await res.json(), then committing items could be a way forward (make sure all promises are resolved).

React call stateless component

So i am trying to get some data from the backend just before my home component renders:
This is the home component:
class App extends Component {
constructor(props) {
super(props);
//asta e o alternativa la componentwillmount dar nu pot sa alterez
//starea in constructor cica
}
componentWillMount(){
console.log('componentWillMount is executing')
console.log(store.getState())
//GetInitialData.bind(this)()
GetInitialData();
console.log('component should have executed')
}
This is my stateless component responsible for making the request and dispatching the response to the redux store:
import axios from 'axios';
import { get_library } from '../../Redux/actions/Actions'
import store from "../../Redux/store/Store";
import { connect } from "react-redux";
const mapDispatchToProps = dispatch => {
return {
get_library : context => dispatch(get_library(context))
}
}
const GetInitialData2 = () => {
console.log('This should work')
}
const GetInitialData = (props) => {
console.log('Making the GET request')
//axios.get('http://localhost:8080/try')
axios({
method : 'get',
url : 'http://localhost:8080/getLibrary',
headers : {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization' : 'Bearer ' + getToken(),
},
})
.then(response => {
console.log('Store in Initial Data')
console.log(store.getState())
//store.dispatch(get_library(response.data))
//this.props.store.dispatch(get_library(response.data))
props.get_library(response.data)
//this.setState({ data: response.data });
console.log('New state now')
//console.log(this.state)
})
};
function getToken() {
// Retrieves the user token from localStorage
return localStorage.getItem('id_token')
}
//function GetInitialData (){ connect(null, mapDispatchToProps)(Connected_GetInitialData)}
//export default GetInitialData;
export default connect(null, mapDispatchToProps)(GetInitialData)
export default GetInitialData2;
My problem is that i keep getting props is not defined error which is pointing to the home component, no matter what. Even if i call GetInitialData2(which only prints something) or GetInitialData.
There is something i'm not understanding. I don't need to pass anything to my GetInitialData function since all it does is alter the redux store.
EDIT - With middleware:
class App extends Component {
componentDidMount() {
console.log('In DidMount, calling function');
GetInitialData();
console.log('DidMount should have executed');
}
And:
const GetInitialData = () => {
console.log('Making the GET request')
//axios.get('http://localhost:8080/try')
axios({
method : 'get',
url : 'http://localhost:8080/getLibrary',
headers : {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization' : 'Bearer ' + getToken(),
},
})
.then(response => {
console.log('Store in Initial Data')
console.log(store.getState())
console.log(response.data)
//store.dispatch(get_library(response.data))
store.dispatch(get_library(response.data))
//this.setState({ data: response.data });
console.log('New store state now')
console.log(store.getState())
})
};
function getToken() {
// Retrieves the user token from localStorage
return localStorage.getItem('id_token')
}
export default GetInitialData;
The result is that it just stops at store.dispatch and does nothing. All the console.logs work and i get a valid response.
Result... after too many hours: Seems that it was failing silently at
reducer level where i had:
const initialState = {
library: {},
playlist_names: [],
playlists : [],
songs : [],
articles : [],
};
And instead it should have been : library : [];, the error was: TypeError: Invalid attempt to spread non-iterable instance. I only got to this by printing the response.data in the console and manually running store.dispatch from browser console... I still do not understand why that error didn't surface.

Categories

Resources