i'm aware i cant use useState in class components, but there's this tutorial i'm trying to relpicate in my App. They used a functional component unlike me.
Their App.js in the tutorial is like this:
const App = () => {
useEffect(()=>{
fcmService.registerAppWithFCM();
fcmService.register(onRegister, onNotification, onOpenNotification);
localNotificationService.configure(onOpenNotification)
},[])
const onRegister = (token) => {
console.log("[App] Token", token);
}
const onNotification = (notify) => {
// console.log("[App] onNotification", notify);
const options = {
soundName: 'default',
playSound: true,
}
localNotificationService.showNotification(
0,
notify.notification.title,
notify.notification.body,
notify,
options,
)
}
const onOpenNotification = async (notify) => {
console.log('notify', notify);
}
So basically, const onOpenNotification, const onRegister, const onNotification are called in another file imported into App.js. I tried adding them in my App.js like :
class App extends Component {
componentDidMount() {
fcmService.registerAppWithFCM();
fcmService.register(onRegister, onNotification, onOpenNotification);
localNotificationService.configure(onOpenNotification)
const onRegister = (token) => {
console.log("[App] Token", token);
}
const onNotification = (notify) => {
// console.log("[App] onNotification", notify);
const options = {
soundName: 'default',
playSound: true,
}
localNotificationService.showNotification(
0,
notify.notification.title,
notify.notification.body,
notify,
options,
)
}
const onOpenNotification = async (notify) => {
console.log('notify', notify);
}
}
}
My console.log shows these errors:
LOG [FCMService] getInitialNotification getInitialNotification null
LOG [LocalNotificationService] onRegister: {"os": "android", "token": "emA0hq4KCMq0j:APA91bEWbOUXjxdIs_s2ksSbjwxhdMVfr35y9sZBUIYX72Q9obU7daQw4zI-a0qn6KsvxWvGtQoEdPlTq5l98trb-yhmtARDcliqAayi_r0K8f_"}
LOG [FCMService] getToken Rejected [TypeError: onRegister is not a function. (In 'onRegister(fcmToken)', 'onRegister' is undefined)]
I'm guessing that's because the const functions() are not properly structured, Whats the best way to replicate this tutorial, preferably if I don't have to change to a functional component
I don't think I would be able to put all the information you might need to help here, but I would appreciate if you could take some time to see how the 2 imported files I talked about are structured and how they call the const onRegister in App.js
Thank you!
EDIT: Updated Code
Try moving the methods to class level.
class App extends Component {
componentDidMount() {
fcmService.registerAppWithFCM();
fcmService.register(this.onRegister, this.onNotification, this.onOpenNotification);
localNotificationService.configure(this.onOpenNotification)
}
onRegister = (token) => {
console.log("[App] Token", token);
}
onNotification = (notify) => {
// console.log("[App] onNotification", notify);
const options = {
soundName: 'default',
playSound: true,
}
localNotificationService.showNotification(0,notify.notification.title,
notify.notification.body, notify, options,)
}
onOpenNotification = async (notify) => {
console.log('notify', notify);
}
}
Related
I'm relatively new to testing and I can happily test UI components that use renders, but I'm struggling to figure out the correct way to test logic components in my current preferred architecture method.
I'm using the BLoC architecture method with RxJS as I much prefer using this to other state management libraries.
I've tried to simply import the functions or access them like I would do in the components and mock them but I can't seem to actually properly simulate the functions.
Here is the an example of the BLoC:
import { IHomeScreenService } from 'actions/interface'
import delivery from 'delivery'
import { ProtoBloc } from '../ProtoBloc'
import { IHomeScreenState } from './stateModel'
const defaultHomeScreenState: IHomeScreenState = {
categoriesData: [],
mealsData: null,
isLoaded: false,
mealsLoading: false,
currentCategory: 'Beef'
}
class HomeScreenBloc extends ProtoBloc<IHomeScreenState> implements IHomeScreenService {
constructor() {
super(defaultHomeScreenState)
}
initialLoad = async () => {
await this.getCategories()
// Set a default category for initial load
await this.getRecipesByCategory(this.state.currentCategory)
this.setLoader()
}
getCategories = async () => {
const { value, error } = await delivery.MealAPI.getCategories()
const newState = { ...this.state }
if (error) {
console.warn('Request Failed')
return
}
if (value) {
newState.categoriesData = value.categories
this.pushState(newState)
}
}
getRecipesByCategory = async (category: string) => {
const { value, error } = await delivery.MealAPI.getRecipesByCategory(category)
const newState = { ...this.state }
if (error) {
console.warn('Request Failed')
return
}
if (value) {
newState.mealsData = value.meals
newState.currentCategory = category
this.pushState(newState)
}
}
private setLoader = () => {
this.pushState({
...this.state,
isLoaded: true,
})
}
}
export default new HomeScreenBloc()
And here is my test file for this BLoC:
import React from 'react'
import HomeScreenBloc from './index'
let defaultState
HomeScreenBloc.getCategories = jest.fn()
beforeEach(() => {
defaultState = {
categoriesData: [],
mealsData: null,
isLoaded: false,
mealsLoading: false,
currentCategory: 'Beef'
}
})
test('should get default state', () => {
expect(HomeScreenBloc.defaultState).toEqual(defaultState)
})
test('it should set the categories state', () => {
HomeScreenBloc.getCategories()
const newState = HomeScreenBloc.getSubject()
expect(HomeScreenBloc.getCategories).toBeCalled()
console.log(newState)
})
Currently all tests are passing, but what I wan't to be able to check is too see if the state is changing as expected. I also don't wan't to test the API calls in this function as I'll be testing those in the delivery layer.
Any help is very much appreciated, I've been banging my head against this for a while and can't find any resources online that relate to this architecture method.
I have a variable that I want to keep track of and update its value between two classes. In one of my classes, I started using props like this with the variable isLoading in my Post class:
class Post extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoading: false
};
}
post = () => {
this.props.uploadPost()
this.props.updatePhoto()
this.props.updateDescription('')
this.props.navigation.navigate('Home')
}
openLibrary = async () => {
const { status } = await Permissions.askAsync(Permissions.CAMERA_ROLL)
if (status === 'granted') {
const image = await ImagePicker.launchImageLibraryAsync()
if(!image.cancelled ){
this.setState({ isLoading: true });
const resize = await ImageManipulator.manipulateAsync(image.uri, [], { format: 'jpeg', compress: 0.1 })
const url = await this.props.uploadPhoto(resize.uri)
this.props.updatePhoto(url)
this.setState({ isLoading: false });
}
}
}
...
Now, I also have another class called Camera that I want to update this same variable. However, I'm not implementing a child like function where I call Post or Camera class in each other.
This is my code for Camera.
class CameraUpload extends React.Component {
state = {
type: Camera.Constants.Type.back,
};
snapPhoto = async () => {
const { status } = await Camera.requestPermissionsAsync();
if (status === 'granted') {
const image = await this.camera.takePictureAsync()
global.config.loading = true;
image ? this.props.navigation.navigate('Post') : null
if( !image.cancelled ){
const resize = await ImageManipulator.manipulateAsync(image.uri, [], { format: 'jpeg', compress: 0.1 })
const url = await this.props.uploadPhoto(resize.uri)
this.props.updatePhoto(url)
loading = false;
// url ? this.props.navigation.navigate('Post') : null
}
}
}
I tried using a global config variable but the variable's value was not getting updated between classes. Please let me know what the best way to go about solving this problem is. Thanks!
React Context
You can use the concept of "Context" in react. You may read about it here
https://reactjs.org/docs/context.html
Async Storage
Also you can use async storage if it suits your design
You can make one utility class:
import AsyncStorage from '#react-native-community/async-storage';
export async function storeData(key, value) {
try {
await AsyncStorage.setItem(key, value)
} catch (e) {
console.log("Error Storing in AsyncStorage stroing " + key + " in async Storage")
}
}
export async function getData(key) {
try {
const value = await AsyncStorage.getItem(key)
return (value == null || value == undefined) ? undefined : value
} catch (e) {
console.log("Error Reading in AsyncStorage stroing " + key + " in async Storage")
}
}
You can store and get data through key-value pairs.
// Firstly import it in your js
import * as asyncStorage from './AsyncStorage'
//For storingthe data:
await asyncStorage.storeData("key1", "value1");
// For getting the data:
await asyncStorage.getData("key1")
You can try global variable:
class post {
onpost(){
global.isLoading = true;
}
}
class cameraupload {
componentDidMount(){
global.isLoading = false;
}
}
Context is the way to go for small pieces of shared state, in my opinion.
Combined with hooks, it's very easy to access state and call functions from any child component.
Define your provider with any shared state and functions
import React, { createContext, useState } from 'react'
const Context = createContext()
const MySharedStateProvider = ({ children }) => {
const [myState, setMyState] = useState('hello')
const updateMyState = value => setMyState(value)
return (
<Context.Provider value={{ myState, updateMyState }}>
{children}
</Context.Provider>
)
}
export { MySharedStateProvider, Context as MySharedStateContext }
Create a hook for your Context, that can be used in any child component
import { useContext } from 'react'
import { MySharedStateContext } from './MySharedStateProvider.js'
export const useMySharedState = () => useContext(MySharedStateContext)
Wrap your components with the provider (I know it wouldn't look like this, it's just an example)
<MySharedStateProvider>
<Posts/>
<CameraUpload/>
</MySharedStateProvider>
Now in your Post or CameraUpload component, you use your hook to get the values
import { useMySharedState } from './MySharedStateHook.js'
const Post = () => {
const { myState, setMyState } = useMySharedState()
}
I currently have a firebase file setup like so:
class Firebase {
constructor() {
app.initializeApp(firebaseConfig);
this.auth = app.auth();
this.db = app.database();
this.storage = app.storage();
}
// *** Auth API ***
doCreateUserWithEmailAndPassword = (email, password) =>
this.auth.createUserWithEmailAndPassword(email, password);
This is just part of the file. To access it I setup a context
export const withFirebase = Component => props => (
<FirebaseContext.Consumer>
{firebase => <Component {...props} firebase={firebase} />}
</FirebaseContext.Consumer>
)
I then can wrap any component with withFirebase bada bing bada boom, it works.
However I'm trying out a redux type of library (react sweet state) which is pretty much a few js consts. In example:
const initialState = {
orgID: null,
memberID: 'blah here',
data: [],
}
const actions = {
fetchOrg: () => async ({ setState }) => {
const callFirebase = await Firebase.auth.onAuthStateChanged(authUser => {
if(authUser) {
//do some stuff
} else {
return false;
}
})
},
}
How can I use the firebase class instance in this setting?
I tried const firebaseAuth = new Firebase();
I get this error: Firebase: Firebase App named '[DEFAULT]' already exists (app/duplicate-app).
My solution to this ended up being just passing firebase as a prop to the action, which I believe is the "reacty" way of doing it:
const actions = {
fetchOrg: firebase => async ({ setState }) => {
const callFirebase = await Firebase.auth.onAuthStateChanged(authUser => {
if(authUser) {
//do some stuff
} else {
return false;
}
})
},
}
I have a React component using hooks like this:
const myComponent = (props) => {
useEffect(() => {
FetchData()
.then(data => {
setState({data: data});
}
// some other code
}, []);
//some other code and render method...
}
fetchData is in charge to use axios and get the data from an API:
const FetchData = async () => {
try {
res = await myApiClient.get('/myEndpoint);
} catch (err) {
console.log('error in FetchData');
res = err.response
}
}
and finally myApiClient is defined externally. I had to use this setup in order to be able to use different APIs...
import axios from "axios";
axios.defaults.headers.post["Content-Type"] = "application/json";
const myApiClient = axios.create({
baseURL: process.env.REACT_APP_API1_BASEURL
});
const anotherApiClient = axios.create({
baseURL: process.env.REACT_APP_API2_BASEURL
});
export {
myApiClient,
anotherApiClient
};
with this setup I am getting the warning
Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
I googled a bit and I saw some suggestions on how to clean up requests from useEffect, like this, but my axios is defined externally. So how can I send the cancellation using this setup?
Also, the application is using redux, not sure if it is in some way involved.
Any other suggestion to avoid the error is welcome.
You can use defer from rxjs for this:
const FetchData = () => {
try {
return myApiClient.get("/myEndpoint");
} catch (err) {
console.log("error in FetchData");
return err.response;
}
};
const myComponent = (props) => {
useEffect(() => {
const subscription = defer(FetchData()).subscribe({
next: ({
data
}) => {
setState({
data: data
});
},
error: () => {
// error handling
},
complete: () => {
// cancel loading state etc
}
});
return () => subscription.unsubscribe();
}, []);
}
Alway check if you are dealing with fetch or any long operations.
let _isMounted = false;
const HooksFunction = props => {
const [data, setData] = useState({}); // data supposed to be object
const fetchData = async ()=> {
const res = await myApiClient.get('/myEndpoint');
if(_isMounted) setData(res.data); // res.data supposed to return an object
}
useEffect(()=> {
_isMounted = true;
return ()=> {
_isMounted = false;
}
},[]);
return (
<div>
{/*....*/}
<div/>
);
}
I have this class based component using the gapi (Google Auth) API that renders a button and it works:
import React from 'react';
class GoogleAuth extends React.Component {
state = { isSignedIn: null };
componentDidMount() {
window.gapi.load('client:auth2', () => {
window.gapi.client
.init({
clientId: process.env.REACT_APP_CLIENT_ID,
scope: 'email',
})
.then(() => {
this.auth = window.gapi.auth2.getAuthInstance();
this.handleAuthChange();
this.auth.isSignedIn.listen(this.handleAuthChange);
});
});
}
handleAuthChange = () => {
this.setState({ isSignedIn: this.auth.isSignedIn.get() });
};
handleSignIn = () => {
this.auth.signIn();
};
handleSignOut = () => {
this.auth.signOut();
};
renderAuthButton() {
if (this.state.isSignedIn === null) {
return null;
} else if (this.state.isSignedIn) {
return <button onClick={this.handleSignOut}>Sign Out</button>;
} else {
return <button onClick={this.handleSignIn}>Sign in with Google</button>;
}
}
render() {
return <div>{this.renderAuthButton()}</div>;
}
}
export default GoogleAuth;
I'm having a tough time trying to convert this to use hooks. The main issue is this.auth... That's how the class has a reference to window.gapi.auth2.getAuthInstance()
I have tried many different ways including keeping auth in state like:
export default function GoogleAuth() {
const [isSignedIn, setIsSignedIn] = useState(null);
const [auth, setAuth] = useState(null);
useEffect(() => {
window.gapi.load('client:auth2', () => {
window.gapi.client
.init({
clientId: process.env.REACT_APP_CLIENT_ID,
scope: 'email',
})
.then(() => {
setAuth(window.gapi.auth2.getAuthInstance());
setIsSignedIn(auth.isSignedIn.get());
auth.isSignedIn.listen(() => setIsSignedIn(auth.isSignedIn.get()));
});
});
}, [auth]);
It's only 8 months later but try useRef with auth like below. It works for me.
const GoogleAuth = () => {
const [isSignedIn, setSignedIn] = useState(null)
const auth = useRef(null);
useEffect(() => {
window.gapi.load('client:auth2', () => {
window.gapi.client.init({
clientId:
'jcu.apps.googleusercontent.com',
scope: 'email'
}).then(() => {
auth.current = window.gapi.auth2.getAuthInstance();
setSignedIn(auth.current.isSignedIn.get());
auth.current.isSignedIn.listen(onAuthChange)
});
});
}, [isSignedIn]);
const onAuthChange = () => {
setSignedIn(auth.current.isSignedIn.get())
}
if (isSignedIn === null) {
return (
<div>I don't know if we are signed in!</div>
);
} else if ( isSignedIn ){
return (
<div>I am signed in!</div>
);
} else {
return ( <div>I am not signed in. :(</div>);
}
}
Couple issues - you're referencing auth immediately after you're setting the state - auth won't be set until it re-renders with its new state.
I'm playing with similar code, and I had to resort to utilizing window.gapi in the initial setup to properly access the returned auth instance.
I imagine it may throw an error if a user clicks quickly they could catch it before auth is set, but I found the sign in/out functions to be able to handle this.
I also found it easiest to test in Incognito, as cookies and cacheing of the api seemed to create an unpredictable local testing environment.
My current component state
Just line in useEffect after auth.current = ...
setSignedIn(auth.current.isSignedIn.get()); is de facto function onAuthChange so call it like that:
.then(() => {
auth.current = window.gapi.auth2.getAuthInstance();
onAuthChange();
auth.current.isSignedIn.listen(onAuthChange);
});