Cannot read property 'getRequest' of undefined - Relay - javascript

I am trying to make my 1st Relay query. I did npm run relay and npm run build. Everything works well, but in console I am getting error:
Does anybody know what may cause this error?
Update.
Table.js (component where I want make query)
import React, { Component } from 'react';
import { graphql, QueryRenderer } from 'react-relay';
const environment = import('../../environment.js');
class Table extends Component {
render() {
return (
<QueryRenderer
environment={environment}
query={graphql`
query TableQuery {
users {
data {
name
}
}
}
`}
render={({error, props}) => {
return <div>User: 1</div>;
}}
/>
);
}
}
export default Table;
environment.js (relay config)
import {
Environment,
Network,
RecordSource,
Store,
} from 'relay-runtime';
function fetchQuery(
operation,
variables,
) {
return fetch('/graphql', {
method: 'POST',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify({
query: operation.text,
variables,
}),
}).then(response => {
return response.json();
});
}
const network = Network.create(fetchQuery);
const store = new Store(new RecordSource());
const environment = new Environment({
network,
store
});
export default environment;
Everything is from docs setup page.

In Table.js, it seems you mixed up the syntax for importing:
const environment = import('../../environment.js'); // Wrong
const environment = require('../../environment.js'); // OK
import environment from '../../environment.js'; // OK
Using import('../../environment.js') makes it a dynamic import which returns a Promise (depending on your bundler config) and is unlikely what you want.

Related

Component keeps making api calls with custom axios hook

I have a dashboard component which renders some widget. It fetches widget data from four different APIs.
Dashbaord.jsx
import { Box, Stack } from '#mui/material';
import { useAxios } from '../../api/use-axios';
import { NewWidget } from '../../components/widget/NewWidget';
import ApiConfig from '../../api/api-config';
const Dashboard = () => {
const { response: studentResponse } = useAxios({
url: ApiConfig.STUDENTS.base,
});
const { response: courseResponse } = useAxios({
url: ApiConfig.COURSES.base,
});
const { response: feesResponse } = useAxios({
url: ApiConfig.FEES.total,
});
return (
<Box padding={2} width="100%">
<Stack direction={'row'} justifyContent="space-between" gap={2} mb={10}>
<NewWidget type={'student'} counter={studentResponse?.data?.length} />
<NewWidget type={'course'} counter={courseResponse?.data?.length} />
<NewWidget type={'earning'} counter={feesResponse?.data} />
<NewWidget type={'teacher'} counter={studentResponse?.data?.length} />
</Stack>
</Box>
);
};
export default Dashboard;
It uses a custom hook useAxios to make API calls.
use-axios.jsx
import { useState, useEffect } from 'react';
import axios from 'axios';
axios.defaults.baseURL = 'http://localhost:3000';
export const useAxios = (axiosParams) => {
const [response, setResponse] = useState(undefined);
const [error, setError] = useState('');
const [loading, setLoading] = useState(true);
const fetchData = async (params) => {
try {
const result = await axios.request({
...params,
method: params.method || 'GET',
baseURL: 'http://localhost:3000',
headers: {
accept: 'application/json',
},
});
setResponse(result.data);
} catch (error) {
setError(error);
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchData(axiosParams);
}, [axiosParams]); // execute once only
return { response, error, loading };
};
api-config.js
export default {
COURSES: {
base: '/courses',
},
FEES: {
base: '/fees',
total: '/fees/total',
},
STUDENTS: {
base: '/students',
},
};
But somehow, It keeps rendering and also all the responses form APIs, it logs to undefiend.
I tried removing dependency axiosPamras from useEffect in useAxios, It stops making multiple requests but still it shows dependency warning and also response is still undefined.
Update:
undefined error is fixed, I wasn't passing authorization token. :(
But still when axiosParams added to dependency it keeps calling apis in loop
This is happening because of the way you're calling useAxios. You're passing an object literal each time, eg
const { response: studentResponse } = useAxios({
url: ApiConfig.STUDENTS.base,
});
Because you're calling with an object, equality of this is determined by reference - and passing an object literal is a new reference on each render, even though it's "the same" object as far as you're concerned. So the useEffect with axiosParams as its dependency will rerun each time, hence the repeated sending of requests.
The easiest solution in this case is probably to extract these objects to constants which are stored outside the component - they come from an ApiConfig object so it seems unlikely this will change while the application is running. And doing this will mean the reference will always be the same and thus not trigger your useEffect to rerun.
That is, put this outside the component:
const STUDENT_AXIOS_CONFIG = { url: ApiConfig.STUDENTS.base };
and the same for the other 2 sets of axios Params. Then inside the component do:
const { response: studentResponse } = useAxios(STUDENT_AXIOS_CONFIG);
and of course do the same for the other 2 calls.

axois react-keycloak/web token in inteceptor

I'm trying to use a request inteceptor with react-keycloak/web - however i get an array of errors when doing so.
import axios from 'axios';
import { useKeycloak } from '#react-keycloak/web';
const { keycloak } = useKeycloak();
const instance = axios.create({
baseURL: 'https://example.com/api/v1/',
timeout: 30000,
});
instance.interceptors.request.use(
(config) => {
config.headers.Authorization = `${keycloak.token}`;
return config;
},
(error) => {
return Promise.reject(error);
}
);
but i get:
React Hook "useKeycloak" cannot be called at the top level. React Hooks must be called in a React function component or a custom React Hook function react-hooks/rules-of-hooks
Now i have of course tried to create a function with for example GetToken():
function GetToken() {
const { keycloak } = useKeycloak();
return keycloak.token
}
but doing so leaves me with:
Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app
What am i doing wrong here?
You can't use react hooks out of react tree.
You can export axoios instance and import it elsewhere inside react (eg: App.js) and set interceptors there.
for example:
import axiosIns from 'api';
import { useKeycloak } from '#react-keycloak/web';
import { useEffect } from "react";
import WholeApp from "WholeApp";
const App = () => {
const { keycloak } = useKeycloak();
useEffect(() => {
axiosIns.interceptors.request.use(
(config) => {
config.headers.Authorization = `${keycloak.token}`;
return config;
},
(error) => {
return Promise.reject(error);
}
);
}, []);
return <WholeApp />;
}

How to get an RTK Query API endpoint state (isLoading, error, etc) in a React Class component?

Ok, I think I've gone through almost all of RTK Query's docs, and read up on RTK Query's caching. Seems like it's a pretty big part of it, even if its not something I need at the moment.
So, I'm trying to do a simple query using RKT Query in a class-based component, and then select from the Redux Store the isLoading state for the endpoint call. However, currently in the render() {} of my LoginPage.jsx, the endpoint.<name>.select()(state) call on mapStateToProps within LoginPageContainer.jsx doesn't seem to be working. (See code below).
From looking at the examples from the docs on using RTK Query on classes, it looks like I'm missing a "cache key" in the .select(<cache_key>)(state) call. However, I haven't incorporated tags in my endpoint yet (I believe I don't have a need for them yet).
My question:
Can someone shed some light on what's the proper use on RTK Query generated endpoint's select() method used outside of React Hooks? I understand the idea behind cache tags for automatic re-fetching (but that's not likely what's wrong here), but I'm not sure how or what cache key I'm missing here to just get the running endpoint query state in a class component. Thanks, everyone!
The Code:
// LoginPage.jsx
import React, { Component } from 'react'
import PT from 'prop-types'
import LoginForm from './components/LoginForm'
export default class LoginPage extends Component {
static propTypes = {
loginWithUsername: PT.func.isRequired,
loginWithUsernameState: PT.object.isRequired
}
render() {
// This value never updates
const { isLoading } = this.props.loginWithUsernameState
// always outputs "{"status":"uninitialized","isUninitialized":true,"isLoading":false,"isSuccess":false,"isError":false}"
// Even during and after running the `loginWithUsername` endpoint query
console.log(this.props.loginWithUsernameState)
return (
<div>
{isLoading && 'Loading ...'}
<LoginForm
onSubmit={(values) => this.props.loginWithUsername(values)} />
</div>
)
}
}
// LoginPageContainer.jsx
import { connect } from 'react-redux'
import { teacherApi } from './api'
import LoginPage from './LoginPage'
const { loginWithUsername } = teacherApi.endpoints
const mapStateToProps = (state) => ({
loginWithUsernameState: loginWithUsername.select()(state)
})
const mapDispatchToProps = (dispatch) => ({
loginWithUsername: (payload) => dispatch(loginWithUsername.initiate(payload))
})
export default connect(mapStateToProps, mapDispatchToProps)(LoginPage)
// api.js
import { createApi, fetchBaseQuery } from '#reduxjs/toolkit/query/react'
export const teacherApi = createApi({
reducerPath: 'teacherApi',
baseQuery: fetchBaseQuery({ baseUrl: '/teacher/' }),
endpoints: (builder) => ({
loginWithUsername: builder.query({
query: (data) => ({
url: 'login',
method: 'post',
body: data,
headers: { 'Content-Type': 'application/json' }
}),
}),
}),
})
The "cache key" passed to endpoint.select() is the same variable you're passing to your hook:
useGetSomeItemQuery("a");
useGetSomeItemQuery("b");
const selectSomeItemA = endpoint.select("a");
const selectSomeItemB = endpoint.select("b");
const itemAREsults = selectSomeItemA(state);
const itemBResults = selectSomeItemB(state);
This results in looking up state => state[apiSlice.reducerPath].queries["getSomeItem('a')"], or whatever the exact cached data field is for that item.
const result = api.endpoints.getPosts.select()(state)
const { data, status, error } = result
Note that unlike the auto-generated query hooks, derived booleans such
as isLoading, isFetching, isSuccess are not available here. The raw
status enum is provided instead.
https://redux-toolkit.js.org/rtk-query/usage/usage-without-react-hooks

Global http response error handling in vue/axios with vuex

I'm trying to fix a behavior in my VueJS SPA wherein a limbo state arises. The app doesn't know the JWT has already expired and therefore presents itself as if the user is still logged in. This can happen after hibernation, for example.
These users can keep on making any request to the API, but end up with a 401 response (and correctly so).
I'd like to have a global handler for 401 responses. (This would be: "clear everything user-related from vuex and present the page as if the user was a guest, with login form popup, etc.") Otherwise, I would have to write a 401 handler for EVERY request.
I can add response interceptors to axios, and they work fine. These interceptors don't have access to Vuex (or Vue), though.
Whenever I try to import Vuex or Vue into my Axios, I get circular dependencies (of course) and everything breaks.
If I just throw/return the error, I still have to handle it separately on every request. How can I dispatch methods on this.$store from within an axios interceptor?
The Axios file contains an export default class API that is added to Vue globally in main.js:
import api from 'Api/api'
// ...
Vue.prototype.$http = api
I had thought there has to be a way to access Vue from $http, since it's a global instance method. But I appear to be mistaken?
Code
main.js
// ...
import api from 'Api/api'
// ...
Vue.prototype.$http = api
new Vue({
el: '#app',
router,
store,
template: '<App/>',
components: { App },
vuetify: new Vuetify(opts),
});
api.js
import Client from './ApiClient'
const apiClient = new Client({ basePath: process.env.VUE_APP_API_URL })
const api = {
get(url) {
return apiClient._get(`${basePath}/${url}`)
},
post(url, data) {
return apiClient._post(`${basePath}/${url}`, data)
},
// ...
}
export default api
ApiClient.js
const axios = require('axios')
const errorHandler = (error) => {
if (error.response.status === 401) {
store.dispatch('user/logout') // here is the problem
}
return Promise.reject({ ...error })
}
export default class API {
constructor(options) {
this.options = Object.assign({ basePath: '' }, options)
this.axios = axios.create({ timeout: 60000 })
this.axios.interceptors.response.use(
response => response,
error => errorHandler(error)
)
}
// ...
}
Importing store in ApiClient.js results in a dependency cycle: I assume because I'm importing Vue in it?
store.js
import Vue from 'vue'
import Vuex from 'vuex'
import PersistedState from 'vuex-persistedstate'
import CreateMutationsSharer from 'vuex-shared-mutations';
import SecureLS from 'secure-ls';
// import modules
Vue.use(Vuex);
const ls = new SecureLS({ encodingType: 'aes' });
export default new Vuex.Store({
// options
})
conf
import Axios from 'axios'
import IdentityProxy from './IdentityProxy.js'
import UsuariosProxi from './UsuariosProxi'
import ZonasProxi from './ZonasProxi'
//axios
Axios.defaults.headers.common.Accept='application/json'
//Axios.defaults.headers.common['Access-Control-Allow-Origin'] = '*';
Axios.interceptors.request.use(
config => {
let token = localStorage.getItem('access_token');
if(token){
config.headers= {
'x-access-token': `${token}`
}
}
return config;
},
error => Promise.reject(error)
);
Axios.interceptors.response.use(
response => response,
error => {
if (error.response.status===403||error.response.status===401) {
localStorage.removeItem('access_token');
window.location.reload(true);
}
return Promise.reject(error);
}
);
let url=null
if(localStorage.getItem("config")!==null){
let config = JSON.parse(localStorage.getItem("config"))
url = config
}
console.log(url)
export default{
identityProxy: new IdentityProxy(Axios, url),
_usuarioProxi: new UsuariosProxi(Axios, url),
_zonasProxi: new ZonasProxi(Axios, url),
}
//
export default class IdentityProxy{
constructor(axios,url){
this.axios = axios;
this.url =url;
}
register(params){
return this.axios.post(this.url+'/identity/register',params)
}
login(params){
return this.axios.post(this.url+'/auth/signin',params)
}
}
//
export default class UsuariosProxi{
constructor(axios,url){
this.axios = axios;
this.url =url;
}
/* getAll(){
return this.axios.get(this.url+'/users')
} */
getAll(page, take) {
return this.axios.get(this.url + `/users?page=${page}&take=${take}`);
}
create(params) {
return this.axios.post(this.url + '/auth/signup', params);
}
get(id) {
return this.axios.get(this.url + `/users/${id}`);
}
update(id, params) {
return this.axios.put(this.url + `/users/${id}`, params);
}
remove(id) {
return this.axios.delete(this.url + `/users/${id}`);
}
//-----APARTE SOLO TRAE LISTA DE ROLES--------
getRoles() {
return this.axios.get(this.url + '/users/newrol');
}
}
//st
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
const state = {
user:null
}
export default new Vuex.Store({
state
});
main.js:
import store from './store';
const Instance = new Vue({
store,
...
})
export const { $store } = Instance;
Now you can import { $store } from '#/main.js' anywhere you want. And it's going to be the same instance you have mounted in your app, not a new Vuex.Store({}) (which is what ./store exports, each time you import it somewhere else).
You can export the same way anything else you might want to use in services, tests, helpers, etc... I.e:
export const { $store, $http, $bus, $t } = Instance;
What about direct import your store to ApiClient.js? Something like
const axios = require('axios')
import store from 'path/to/store'
const errorHandler = (error) => {
if (error.response.status === 401) {
store.dispatch('user/logout') // now store should be accessible
}
return Promise.reject({ ...error })
}
export default class API {
constructor(options) {
this.options = Object.assign({ basePath: '' }, options)
this.axios = axios.create({ timeout: 60000 })
this.axios.interceptors.response.use(
response => response,
error => errorHandler(error)
)
}
// ...
}
Base on these thread I was able to manage a solution for my needs:
main.js
import api, {apiConfig} from 'Api/api'
apiConfig({ store: $store });
ApiClient.js
let configs = {
store: undefined,
};
const apiConfig = ({ store }) => {
configs = { ...configs, store };
};
export default api;
export { apiConfig };
This way the api.js file will require a configuration that can later be expanded.

Can't display data from REST call, even though the server is sending data

I am trying to perform a rest call with a token in the header to display information. There is a required header token so my code looks like this for my restClient.js, app.js, and users.js.
//restClient.js
import { jsonServerRestClient, fetchUtils } from 'admin-on-rest';
const httpClient = (url, options = {}) => {
if (!options.headers) {
options.headers = new Headers({ Accept: 'application/json' });
}
options.headers.set('token', 'admin');
return fetchUtils.fetchJson(url, options);
}
const restClient = jsonServerRestClient('http://localhost:8080/api/v2', httpClient);
export default (type, resource, params) => new Promise(resolve => setTimeout(() => resolve(restClient(type, resource, params)), 500));
//App.js
import React, {Component} from 'react';
import { Admin, Resource } from 'admin-on-rest';
import { UserList } from './users';
import restClient from './restClient';
class App extends Component {
render() {
return(
<Admin restClient={restClient}>
<Resource name="admin/user" list={UserList}/>
</Admin>
);
}
}
export default App;
//Users.js
// in src/users.js
import React from 'react';
import { List, Datagrid, EmailField, TextField, SimpleList } from 'admin-on-rest';
export const UserList = (props) => (
<List {...props}>
<Datagrid >
<TextField source="email"/>
<TextField source="info"/>
</Datagrid>
</List>
);
Example of JSON
I've tested my rest call with postman and it is definitely returning data. Also is there anyway to check what data is being sent back in the call? The server is running express.js and I've set up the route to include the required headers.I've also attached an example of what my JSON looks like that I am returning.
Since aor fetchUtils returns a promise. you can intercept the promise and perform any kind of inspection you want (and do a lot more too)
Below is how my code is handling something similar. I am also intercepting the API call and displaying custom notifications.
function handleRequestAndResponse(url, options={}) {
return fetchUtils.fetchJson(url, options)
.then((response) => {
const {headers, json} = response;
//admin on rest needs the {data} key
const data = {data: json}
if (headers.get('x-total-count')) {
data.total = parseInt(headers.get('x-total-count').split('/').pop(), 10)
}
// handle get_list responses
if (!isNaN(parseInt(headers.get('x-total-count'), 10))) {
return {data: json,
total: parseInt(headers.get('x-total-count').split('/').pop(), 10)}
} else {
return data
}
})
}
You can simply do something like below
return fetchUtils.fetchJson(url, options)
.then(res => {
console.log(res)
return res
})
I dont have the reputation to comment yet but Fiddler 4 is a good way to see what the UI is sending to the server.

Categories