Refetch queries in react-native with graphql - javascript

I'm beginner and i try to learn graphql. I want to create an app which update the data(realtime) from react web app to react native mobile app with graphql. In the web app was easy to refetch query when i pressed the OK button. In the mobile app i don't know how to refetch query when i press the button from my web app.
I try to transfer data via websockets(socket.io) and finally i manage to pass the data but it was time-consuming.
Here is WEB APP
and
Here is what i want to do
Web app built with react.js
Mobile app built with react-native.js
Here is my react-native code.I don't know how and where to use refetch query.
App.js
import React, { Component } from "react";
import ApolloClient from "apollo-boost";
import { ApolloProvider } from "react-apollo";
import { View, Text } from "react-native";
import BookList from "./components/BookList";
//apollo client
const client = new ApolloClient({
uri: "http://XXX.XXX.X.X:4000/graphql"
});
class App extends Component {
render() {
return (
<ApolloProvider client={client}>
<View>
<BookList />
</View>
</ApolloProvider>
);
}
}
export default App;
BookList.js
import React, { Component } from "react";
import { graphql } from "react-apollo";
import { Button, View, Text } from "react-native";
import { gql } from "apollo-boost";
import io from "socket.io-client";
const getBooksQuery = gql`
{
books {
name
id
genre
}
}
`;
class BookList extends Component {
displayBooks = () => {
var data = this.props.data;
if (data.loading) {
return <Text>loading books...</Text>;
} else {
return data.books.map(book => {
return <Text>{book.name}</Text>;
});
}
};
render() {
return <View>{this.displayBooks()}</View>;
}
}
export default graphql(getBooksQuery)(BookList);
In the web app it was easy to apply sth that because i put the refetch query inside function which i have when i pressed button like this:
submitForm = e => {
e.preventDefault();
this.props.addBookMutation({
variables: {
name: this.state.name,
genre: this.state.genre,
authorid: this.state.authorid
},
refetchQueries: [{ query: getBooksQuery }]
});
};
*Sorry for my English

So according to the documents of Apollo
You can use Apollo with React Native exactly as you would with React Web.
Here is the corresponding page about implementation with react-native.
https://www.apollographql.com/docs/react/recipes/react-native/
To get the data via sockets in graphQL you need to use subscriptions.
Please follow these steps;
Create a subscription schema and subscription function in your back-end project to be dispatched when a book is updated. I don't know which server lib you use for back-end but I highly encourage you to use apollo-server here.
In your mobile app, you need to subscribeToMore in getBooksQuery which you get all your books.
When a book is updated, your server will send the updated book to the subscribed clients and you will be able to get it from mobile app since you subscribed.
PS: you can use howtographql to learn newer version of apollo. (components which provide render prop functionality)

Related

How to do Expo/react-native Google Authentication in a class based component?

I am trying to implement Google Authentication into my Expo/react native component based class, but the example Expo gives is for functional components.
They use this code for the Google Authentication:
import * as React from 'react';
import * as WebBrowser from 'expo-web-browser';
import * as Google from 'expo-auth-session/providers/google';
import { Button } from 'react-native';
WebBrowser.maybeCompleteAuthSession();
export default function App() {
const [request, response, promptAsync] = Google.useAuthRequest({
expoClientId: 'GOOGLE_GUID.apps.googleusercontent.com',
iosClientId: 'GOOGLE_GUID.apps.googleusercontent.com',
androidClientId: 'GOOGLE_GUID.apps.googleusercontent.com',
webClientId: 'GOOGLE_GUID.apps.googleusercontent.com',
});
React.useEffect(() => {
if (response?.type === 'success') {
const { authentication } = response;
}
}, [response]);
return (
<Button
disabled={!request}
title="Login"
onPress={() => {
promptAsync();
}}
/>
);
}
My question is how would I go about doing something similar to this for a class based component (export default class App extends Component)
This is actually a general subject, how to call React's functional components from within class components.
Specific to Google Sign In, this answer can help: https://stackoverflow.com/a/66974167/1870873

Next.js Example Auth - re-routing based on auth, wrapping around other functions

I'm trying to use the next.js with authentication for a small project. The authentication currently works but doesn't allow me to show the data in my navbar.
I was using it with firebase originally BUT NOT ANYMORE!! Now have the authentication set up separately below.
This is the example repo, it has my API in it for auth and the next.js, which i'm trying to integrate together to have login and logout working with header's set for api calls.
https://github.com/Hewlbern/example
Just getting the basic login and logout functionality, so I can control user access to my website. I know this is really simple - just quite confused how to do it with next.js with how document page an app works :S
I am trying to show a table of output from this API, and give the ability to download the outputed json (into a CSV or whatever). So having that available after a search with the query params, and only on a page after the user is logged in, is the point :)
Here's an example of the login functionality I'm using.
import { useRef, useState } from 'react';
import React from 'react'
import PropTypes from 'prop-types'
import Layout from "../components/Layout";
export default function Login() {
const emailRef = useRef<HTMLInputElement>(null);
const passRef = useRef<HTMLInputElement>(null);
const [message, setMessage] = useState<any>(null);
async function handleLogin() {
const resp = await fetch('http://localhost:3001/auth/login', {
method: 'POST',
headers: {
'Content-Type': "application/x-www-form-urlencoded"
},
body: JSON.stringify({
email: emailRef.current?.value,
password: passRef.current?.value
})
});
const json = await resp.json();
setMessage(json);
}
return (
<Layout>
{JSON.stringify(message)}
<input type="text" placeholder="email" ref={emailRef} />
<input type="password" placeholder="password" ref={passRef} />
<button onClick={handleLogin}>Login</button>
</Layout>
);
}
This is posting to this api request
router.post('/login', (req, res) => {
// console.log(req.body)
let email = req.body.email;
let password = req.body.password;
console.log(email,password)
DatabaseService.GetUser(email).then(user => {
if(user===null){
res.sendStatus(404);
}
else{
if(bcrypt.compareSync(password, user[0].password)) {
jwt.sign({user}, 'secretkey', { expiresIn: '30d' }, (err, token) => {
DatabaseService.SetSession(token,JSON.stringify(user[0].user_id)).then(inserted=>{
res.json({
token
});
});
});
} else {
res.sendStatus(500);
}
}
});
});
So just with this small example, hat's wrong with how I'm sending the requests currently? (thinking it's the format the login takes requests in?)
If someone has done something similar or knows how to solve these issues, I'd really appreciate it :)
Cheers!
What I'd recommend here is to create a custom hook which makes use of React's Context API in order to "monitor" the auth state changing. Then wrap you app in that provider and you'll have the flexibility do anything you want with that auth state using your new custom hook.
Here's an example of how that custom hook would look using a authentication with Firebase:
import React, { createContext, useContext, useState } from 'react'
import { auth } from './services' // this is just firebase.auth()
const UserContext = createContext()
export const UserProvider = ({ children }) => {
const [user, setUser] = useState(undefined)
auth.onAuthStateChanged(setUser)
return <UserContext.Provider value={user}>{children}</UserContext.Provider>
}
export const useUser = () => useContext(UserContext)
Now you just need to wrap your app in the UserProvider.
Like this:
import React, { StrictMode } from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter } from 'react-router-dom'
import App from './app'
import { UserProvider } from './hooks'
const rootElement = document.getElementById('root')
ReactDOM.render(
<StrictMode>
<BrowserRouter>
<UserProvider>
<App />
</UserProvider>
</BrowserRouter>
</StrictMode>,
rootElement
)
Then as an example, let's say you wanted to automatically direct away from your Login page if the use is logged it. You could make use of the useEffect hook, and useHistory hooks to navigate to / if the user is logged in.
Something like this will do:
import React, { useEffect } from 'react'
import { useHistory } from 'react-router-dom'
import { useUser } from './hooks'
const LoginPage = () => {
const history = useHistory()
const user = useUser() // user is undefined if not logged in
useEffect(() => {
if (user) { // will run the condition if user exists
history.push('/')
}
}, [user])
...
}
You could go on to actually use the user data in your navigation bar using something like this:
import React from 'react'
import { Link } from 'react-router-dom'
import { useUser } from './hooks'
const NavBar = () => {
const user = useUser()
return (
<div>
{user ?
<Link to="/profile">Welcome, {user.displayName}</Link> :
<Link to="/login">Login</Link>
}
</div>
)
}
Obviously you can change this for us according to your own needs, but all this should get you going with how work with authentication state in a clean robust manner.

Error using React Native hooks, facebook, firebase

I have a problem with react-native, I am trying to login with facebook using the expo-facebook and firebase libraries, it loads everything well and it starts session well, but when it loads the following screen the following comes up:
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:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See https://facebook.com/react-invalid-hook-call for tips about how to debug and fix this problem.
Code:
import React, { Component } from 'react'
import { View, Button, Text, StatusBar, LayoutAnimation, Image, StyleSheet } from 'react-native'
import { Toast } from 'native-base'
import * as Facebook from 'expo-facebook'
import * as firebase from 'firebase'
const app = {
id: APP_ID,
name: APP_NAME,
}
export default class LoginScreen extends Component {
static navigationOptions = {
header: null,
}
facebook = async () => {
Facebook.initializeAsync(app.id, app.name)
const { type, token } = await Facebook.logInWithReadPermissionsAsync(app.id, {
permission: 'public_profile',
})
if ( type == "success" ) {
const credential = firebase.auth.FacebookAuthProvider.credential( token )
firebase.auth().signInWithCredential(credential)
.catch(e => {
console.log(e)
})
}
this.props.navigation.navigate('App')
}
render() {
LayoutAnimation.easeInEaseOut()
return <View>
<Button title="Log in with facebook" style={styles.button} onPress={this.facebook}>
</Button>
</View>
}
}
Here is an image of error:

How to integrate azure ad into a react web app that consumes a REST API in azure too

I have one web app which is React, and I already configured Azure AD Authentication for the web app itself. Its 100% Client site app, no server side components.
I used this component:
https://github.com/salvoravida/react-adal
My code is as follows:
adalconfig.js
import { AuthenticationContext, adalFetch, withAdalLogin } from 'react-adal';
export const adalConfig = {
tenant: 'mytenantguid',
clientId: 'myappguid',
endpoints: {
api: '14d71d65-f596-4eae-be30-27f079bf8d4b',
},
cacheLocation: 'localStorage',
};
export const authContext = new AuthenticationContext(adalConfig);
export const adalApiFetch = (fetch, url, options) =>
adalFetch(authContext, adalConfig.endpoints.api, fetch, url, options);
export const withAdalLoginApi = withAdalLogin(authContext, adalConfig.endpoints.api);
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import DashApp from './dashApp';
import registerServiceWorker from './registerServiceWorker';
import 'antd/dist/antd.css';
import { runWithAdal } from 'react-adal';
import { authContext } from './adalConfig';
const DO_NOT_LOGIN = false;
runWithAdal(authContext, () => {
ReactDOM.render(<DashApp />, document.getElementById('root'));
// Hot Module Replacement API
if (module.hot) {
module.hot.accept('./dashApp.js', () => {
const NextApp = require('./dashApp').default;
ReactDOM.render(<NextApp />, document.getElementById('root'));
});
}
},DO_NOT_LOGIN);
registerServiceWorker();
dashapp.js
import React from "react";
import { Provider } from "react-redux";
import { store, history } from "./redux/store";
import PublicRoutes from "./router";
import { ThemeProvider } from "styled-components";
import { LocaleProvider } from "antd";
import { IntlProvider } from "react-intl";
import themes from "./settings/themes";
import AppLocale from "./languageProvider";
import config, {
getCurrentLanguage
} from "./containers/LanguageSwitcher/config";
import { themeConfig } from "./settings";
import DashAppHolder from "./dashAppStyle";
import Boot from "./redux/boot";
const currentAppLocale =
AppLocale[getCurrentLanguage(config.defaultLanguage || "english").locale];
const DashApp = () => (
<LocaleProvider locale={currentAppLocale.antd}>
<IntlProvider
locale={currentAppLocale.locale}
messages={currentAppLocale.messages}
>
<ThemeProvider theme={themes[themeConfig.theme]}>
<DashAppHolder>
<Provider store={store}>
<PublicRoutes history={history} />
</Provider>
</DashAppHolder>
</ThemeProvider>
</IntlProvider>
</LocaleProvider>
);
Boot()
.then(() => DashApp())
.catch(error => console.error(error));
export default DashApp;
export { AppLocale };
Until that point everything works fine, when the user is not authenticated its redirected to login.live.com for authentication and then its redirected back.
However I also created another azure webapp for hosting a REST API, that REST API is already configured in Azure AD, so that users that try to use the rest will need to be authenticated.
Now the question is: How do I setup my client side APP to consume REST API which is protected by Azure AD.?
I found this and looks what I am looking for, but I am not sure how to integrate this into my existing code above
https://github.com/AzureAD/azure-activedirectory-library-for-js/issues/481
Update:
For potential readers
This answer plus the instructions on this url to configure App registrations helped me to solve the problem: https://blog.ithinksharepoint.com/2016/05/16/dev-diary-s01e06-azure-mvc-web-api-angular-and-adal-js-and-401s/
The key here is adalApiFetch, defined in adalConfig.js. As you can see, it's a simple wrapper around adalFetch. This method (defined in react-adal) receives an ADAL instance (authContext), a resource identifier (resourceGuiId), a method (fetch), a URL (url) and an object (options). The method does the following:
Use the ADAL instance (authContext) to obtain an access token for the resource identified by resourceGuiId.
Add this access token to the headers field of the options object (or create one if it wasn't provided).
Call the given "fetch" method passing in url and the options object as parameters.
The adalApiFetch method (which you have defined in adalConfig.js) simply calls adalFetch with the resource identified in adalConfig.endpoints.api.
Ok, so how do you use all of this to make a REST request, and consume the response in your React app? Let's use an example. In the following example, we will be using the Microsoft Graph API as the Azure AD-protected REST API. We will be identifying it by it's friendly identifier URI ("https://graph.microsoft.com"), but just keep in mind that that could just as well be the Guid app ID.
adalConfig.js defines the ADAL configuration, and exports a couple helper methods:
import { AuthenticationContext, adalFetch, withAdalLogin } from 'react-adal';
export const adalConfig = {
tenant: '{tenant-id-or-domain-name}',
clientId: '{app-id-of-native-client-app}',
endpoints: {
api: 'https://graph.microsoft.com' // <-- The Azure AD-protected API
},
cacheLocation: 'localStorage',
};
export const authContext = new AuthenticationContext(adalConfig);
export const adalApiFetch = (fetch, url, options) =>
adalFetch(authContext, adalConfig.endpoints.api, fetch, url, options);
export const withAdalLoginApi = withAdalLogin(authContext, adalConfig.endpoints.api);
index.js wraps indexApp.js with the runWithAdal method from react-adal, which ensures the user is signed with Azure AD before loading indexApp.js:
import { runWithAdal } from 'react-adal';
import { authContext } from './adalConfig';
const DO_NOT_LOGIN = false;
runWithAdal(authContext, () => {
// eslint-disable-next-line
require('./indexApp.js');
},DO_NOT_LOGIN);
indexApp.js simply loads and renders an instance of App, nothing fancy here:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
ReactDOM.render(<App />, document.getElementById('root'));
registerServiceWorker();
App.js is a simple component where the magic happens:
We define a state value. In this case, it's called apiResponse since we're just displaying the raw API response, but of course you could name this state whatever you wanted (or have multiple state values).
During componentDidMount (which is run after the element is available in the DOM), we make a call to the adalApiFetch. We pass in fetch (from the Fetch API as the fetch parameter, and the endpoint for the REST request we want to make (the /me endpoint in Microsoft Graph, in this case):
In the render method, we simply display this state value in a <pre> element.
import React, { Component } from 'react';
import { adalApiFetch } from './adalConfig';
class App extends Component {
state = {
apiResponse: ''
};
componentDidMount() {
// We're using Fetch as the method to be called, and the /me endpoint
// from Microsoft Graph as the REST API request to make.
adalApiFetch(fetch, 'https://graph.microsoft.com/v1.0/me', {})
.then((response) => {
// This is where you deal with your API response. In this case, we
// interpret the response as JSON, and then call `setState` with the
// pretty-printed JSON-stringified object.
response.json()
.then((responseJson) => {
this.setState({ apiResponse: JSON.stringify(responseJson, null, 2) })
});
})
.catch((error) => {
// Don't forget to handle errors!
console.error(error);
})
}
render() {
return (
<div>
<p>API response:</p>
<pre>{ this.state.apiResponse }</pre>
</div>
);
}
}
export default App;
I still had the issue with the config given above. I added on more config to the above and it worked. Hope it helps.
import { AuthenticationContext, adalFetch, withAdalLogin } from 'react-adal';
export const adalConfig = {
tenant: '{tenant-id-or-domain-name}',
clientId: '{app-id-of-native-client-app}',
endpoints: {
api: 'https://graph.microsoft.com'
},
cacheLocation: 'localStorage',
extraQueryParameter: 'prompt=admin_consent'
};
export const authContext = new AuthenticationContext(adalConfig);
Phillipe's response put me down the right path, but I was still running into an issue with my token not being accepted.
aadsTS700051: response_type 'token' is not enabled for the application.
To resolve I needed to go into my app's registration > manifest & set oauth2AllowImplicitFlow to true:
"oauth2AllowImplicitFlow": true,
Log out of your Azure account, sign back in & you should receive your user's details.

MobX + React Native : way to inject stores

I'm trying to work with MobX for a new project.
I started it on May 2017, and everything was working well. I had to stop, and now I go on developing it. I had to make an npm install to manage making it working, but now, I have some problem with stores...
I rely on this tutorial for the structure : https://blog.callstack.io/write-react-native-apps-in-2017-style-with-mobx-e2dffc209fcb
This is my structure :
Main index.js
import { Provider } from 'mobx-react';
import Stack from './router';
import stores from './stores';
export default class App extends Component {
render() {
return (
<Provider {...stores}>
<Stack />
</Provider>
);
}
}
Index.js of my stores in ./stores/index.js
import ChatStore from './ChatStore';
import UserStore from './UserStore';
export default {
UserStore: new UserStore(),
ChatStore: new ChatStore(),
};
./stores/UserStore.js (important parts)
import { observer, inject } from 'mobx-react';
import {autobind} from 'core-decorators';
...
#inject(['ChatStore'])
#observer
#autobind
export default class UserStore {
#observable isAuthenticated = false;
#observable isConnecting = false;
#observable user = null;
#observable messages = [];
#observable hasMoreMessages = false;
#observable skip = 0;
...
login() {
const payload = {
strategy: 'local',
material_id: DeviceInfo.getManufacturer(),
password: DeviceInfo.getManufacturer()
};
return this.authenticate(payload);
}
...
Now, for components part :
Router.js
import { StackNavigator } from 'react-navigation';
import Home from './containers/Home';
const stackNavigatorConfig = {
initialRouteName: 'Home',
};
export default StackNavigator({
Home: {
screen: Home,
},
}, stackNavigatorConfig);
./containers/Home.js
import React, { Component } from 'react';
import { AsyncStorage } from 'react-native';
import { observable } from 'mobx';
import { observer, inject } from 'mobx-react';
#inject('UserStore')
#observer
export default class Home extends Component {
props: Props;
...
render() {
this.props.UserStore.login().catch(error => {
console.log('LOGIN', 'ERROR', JSON.stringify(error), error.message);
});
return {
...
}
}
And then, I get an error :
So, I sum up :
I use <Provider> from MobX, to give all my stores to my app
Then, I get the Store I want in my component with #inject
I use it as a props, using this.props.UserStore...
But it does not work. I rely on this tutorial for the structure : https://blog.callstack.io/write-react-native-apps-in-2017-style-with-mobx-e2dffc209fcb
Maybe there was an update between May 2017 and today, that makes things different... It was working well on May 2017.
I think this is a dummy error, but I can't find which one...
Everything looks good except the decorators on your UserStore class: #inject(['ChatStore']) #observer #autobind. #inject(['ChatStore']) #observer is used on React components, #autobind might still work as intended.
It should work if you remove those.
maybe worth using #action from mobx

Categories