I currently have two contexts - GalleryContext & AdminContext
In AdminContext, whenever I fire off handleSendImage(), I am trying to call a function (getGallery()) from GalleryContext which updates the gallery state in GalleryContext.
However, I am receiving this error:
Unhandled Rejection (TypeError): this.setState is not a function
Could anyone please advise me on how to fix this? Much appreciated in advance!
Here's my code:
GalleryContext:
class GalleryProvider extends Component {
state = {
gallery: []
};
getGallery() {
let database = firebase
.database()
.ref("/gallery/")
.once("value")
.then(images => {
console.log(images.val());
this.setState(
{
gallery: images.val()
},
() => {}
);
});
}
componentDidMount() {
this.getGallery();
}
render() {
return (
<GalleryContext.Provider
value={{
...this.state,
getGallery: this.getGallery
}}>
{this.props.children}
</GalleryContext.Provider>
);
}
}
AdminContext.js
class AdminProvider extends Component {
static contextType = GalleryContext;
state = {
upload_image: null,
gallery_title: null,
gallery_description: null,
gallery_filename: null,
progress: null
};
handleSendImage = (event, gallery, user) => {
event.preventDefault();
const { upload_image } = this.state;
firebase
.storage()
.ref(`images/${upload_image.name}`)
.put(upload_image)
.on(
"state_changed",
snapshot => {
// progrss function ....
const progress = Math.round(
(snapshot.bytesTransferred / snapshot.totalBytes) * 100
);
this.setState({
progress
});
},
error => {
// error function ....
console.log(error);
},
() => {
// complete function ....
firebase
.storage()
.ref("images")
.child(upload_image.name)
.getDownloadURL()
.then(url => {
const newImage = {
description: this.state.gallery_description,
download_url: url,
file_name: this.state.gallery_filename,
id: gallery.length,
uploader_uid: user.uid
};
firebase
.database()
.ref("/gallery/")
.child(gallery.length)
.set(newImage)
.then(() => {
this.context.getGallery();
})
.catch(error => {
console.log(error);
});
});
}
);
};
render() {
return (
<AdminContext.Provider
value={{
...this.state
handleSendImage: this.handleSendImage
}}>
{this.props.children}
</AdminContext.Provider>
);
}
}
Since you're using states, you would need a constructor for your class component and need to bind your methods within the constructor. I suggest you to try the following code:
class AdminProvider extends Component {
static contextType = GalleryContext;
constructor(props) {
super(props)
this.state = {
upload_image: null,
gallery_title: null,
gallery_description: null,
gallery_filename: null,
progress: null
};
this.handleSendImage = this.handleSendImage.bind(this);
}
handleSendImage = (event, gallery, user) => {
event.preventDefault();
const { upload_image } = this.state;
firebase
.storage()
.ref(`images/${upload_image.name}`)
.put(upload_image)
.on(
"state_changed",
snapshot => {
// progrss function ....
const progress = Math.round(
(snapshot.bytesTransferred / snapshot.totalBytes) * 100
);
this.setState({
progress
});
},
error => {
// error function ....
console.log(error);
},
() => {
// complete function ....
firebase
.storage()
.ref("images")
.child(upload_image.name)
.getDownloadURL()
.then(url => {
const newImage = {
description: this.state.gallery_description,
download_url: url,
file_name: this.state.gallery_filename,
id: gallery.length,
uploader_uid: user.uid
};
firebase
.database()
.ref("/gallery/")
.child(gallery.length)
.set(newImage)
.then(() => {
this.context.getGallery();
})
.catch(error => {
console.log(error);
});
});
}
);
};
render() {
return (
<AdminContext.Provider
value={{
...this.state
handleSendImage: this.handleSendImage
}}>
{this.props.children}
</AdminContext.Provider>
);
}
}
Similarly:
class GalleryProvider extends Component {
constructor(props) {
super(props);
this.state = {
gallery: []
};
this.getGallery = this.getGallery.bind(this);
}
getGallery() {
let database = firebase
.database()
.ref("/gallery/")
.once("value")
.then(images => {
console.log(images.val());
this.setState(
{
gallery: images.val()
},
() => {}
);
});
}
componentDidMount() {
this.getGallery();
}
render() {
return (
<GalleryContext.Provider
value={{
...this.state,
getGallery: this.getGallery
}}>
{this.props.children}
</GalleryContext.Provider>
);
}
}
Alternatively, you could use React Hooks inside functional components. Hope this solves your problem.
I would suggest you to read React's documentation on Constructor for more information.
Related
class CardList extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
};
}
componentDidMount() {
firestore
.collection('users')
.get()
.then((querySnapshot) => {
querySnapshot.forEach((doc) => {
this.setState({ data: doc.data() });
});
});
}
render() {
return (
<div className="cardlist">
{this.state.data.email
? this.state.data.map((data) => {
return <div>{this.state.data.email}</div>;
})
: console.log('error')}
</div>
);
}
}
TypeError: this.state.data.map is not a function
I want to take out the emails in the Firestore and print them out, but I can't print them because of typeerror. Why is there an error?
console.log(this.state.data) result is
{ createdAt: t, name: 'good', email: 'good#gmail.com', isTutor: 'off' };
{ name: 'joe', isTutor: 'on', email: 'joe#gmail.com', createdAt: t };
You are not assigning value to your array properly, rather you should do like the code below. I've also refactored the code in render function.
class CardList extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
};
}
componentDidMount() {
firestore
.collection('users')
.get()
.then((querySnapshot) => {
querySnapshot.forEach((doc) => {
this.setState({ data: [...this.state.data, doc.data()] });
});
});
}
render() {
return (
<div className="cardlist">
{this.state.data &&
this.state.data.map((item) => {
return <div>{item.email}</div>;
})}
</div>
);
}
}
this.setState({ data: doc.data() }); - you are not adding to the state, but replacing it with an object for each doc. And you cannot .map an object, thus the error.
I have get my data from firebase , loop through them and display them to dom.
then I added a delete button and send a delete request using axios and it's delete from firebase but the dom doesn't rerender. I set a deleting state to change it in 'then' block but even when I change the state it dosn't rerender!
what can I do?
class Orders extends Component {
state = {
orders: [],
loading: true,
deleting: false,
};
componentDidMount() {
axios
.get('/order.json')
.then((res) => {
// console.log(res.data);
const fetchedOrders = [];
for (let key in res.data) {
fetchedOrders.push({ ...res.data[key], id: key });
}
this.setState({ loading: false, orders: fetchedOrders });
})
.catch((err) => {
this.setState({ loading: false });
});
}
deleteHandler = (id) => {
axios.delete(`/order/${id}.json`).then((res) => {
this.setState({ deleting: true });
console.log(res, this.state.deleting);
});
};
render() {
return (
<div>
{this.state.orders.map((order) => (
<Order
key={order.id}
ingredients={order.ingredient}
price={order.price}
id={order.id}
delete={() => this.deleteHandler(order.id)}
/>
))}
</div>
);
}
}
You have to update the orders state while calling deleteHandler! Try this code!
import React from 'react';
import axios from 'axios';
// YOUR OTHER IMPORT GOES HERE
class Orders extends Component {
constructor(props) {
this.state = {
orders: [],
loading: true,
deleting: false,
}
}
componentDidMount() {
axios
.get('/order.json')
.then((res) => {
// console.log(res.data);
const fetchedOrders = [];
for (let key in res.data) {
fetchedOrders.push({ ...res.data[key], id: key });
}
this.setState({ loading: false, orders: fetchedOrders });
})
.catch((err) => {
this.setState({ loading: false });
});
}
deleteHandler = (id) => {
this.setState({
orders: this.state.orders.filter(orderValue => orderValue.id !== id)
})
axios.delete(`/order/${id}.json`).then((res) => {
this.setState({ deleting: true });
console.log(res, this.state.deleting);
});
};
render() {
return (
<div>
{this.state.orders.map((order) => (
<Order
key={order.id}
ingredients={order.ingredient}
price={order.price}
id={order.id}
delete={() => this.deleteHandler(order.id)}
/>
))}
</div>
);
}
}
I have a reusable component for Sign in with Apple Button
After user success, i navigate hem to Home screen
But i notes when i log navigation it's log undefined,
and when i log this.props i just got the two actions i made in redux!
So how can i access to navigation in this component and why it's not accessed by default!
Log
props => {"isLogin": [Function isLogin], "storeToken": [Function storeToken]}
navigation => undefined
Code
import appleAuth, {
AppleAuthCredentialState,
AppleAuthError,
AppleAuthRealUserStatus,
AppleAuthRequestOperation,
AppleAuthRequestScope,
AppleButton,
} from '#invertase/react-native-apple-authentication';
import React from 'react';
import {ActivityIndicator, StyleSheet, View} from 'react-native';
import {connect} from 'react-redux';
import API from '../../api/API';
import {isLoginFunc} from '../../redux/actions/isLoginAction';
import {saveToken} from '../../redux/actions/saveTokenAction';
class AppleAuth extends React.Component {
constructor(props) {
super(props);
this.authCredentialListener = null;
this.user = null;
this.state = {
credentialStateForUser: -1,
loading: false,
};
}
componentDidMount() {
const {navigation} = this.props;
console.log('did-navigation', navigation);
console.log('did- this.props', this.props);
/**
* subscribe to credential updates.This returns a function which can be used to remove the event listener
* when the component unmounts.
*/
this.authCredentialListener = appleAuth.onCredentialRevoked(async () => {
// console.warn('Credential Revoked');
this.fetchAndUpdateCredentialState().catch(error =>
this.setState({credentialStateForUser: `Error: ${error.code}`}),
);
});
this.fetchAndUpdateCredentialState()
.then(res => this.setState({credentialStateForUser: res}))
.catch(error =>
this.setState({credentialStateForUser: `Error: ${error.code}`}),
);
}
componentWillUnmount() {
/**
* cleans up event listener
*/
this.authCredentialListener();
}
signIn = async () => {
// start a login request
try {
const appleAuthRequestResponse = await appleAuth.performRequest({
requestedOperation: AppleAuthRequestOperation.LOGIN,
requestedScopes: [
AppleAuthRequestScope.EMAIL,
AppleAuthRequestScope.FULL_NAME,
],
});
this.setState({loading: true});
const {
user: newUser,
email,
nonce,
fullName: {familyName, givenName},
identityToken,
realUserStatus /* etc */,
} = appleAuthRequestResponse;
let username = `${givenName} ${familyName}`;
this.user = newUser;
this.fetchAndUpdateCredentialState()
.then(res => {
this.setState({credentialStateForUser: res});
console.log('res:::', res);
})
.catch(error => {
console.log(`Error: ${error.code}`);
this.setState({credentialStateForUser: `Error: ${error.code}`});
});
if (identityToken) {
console.log('email', email);
console.log('username', username);
console.log('nonce', nonce);
this.sendData(email, username, nonce);
// e.g. sign in with Firebase Auth using `nonce` & `identityToken`
} else {
// no token - failed sign-in?
}
if (realUserStatus === AppleAuthRealUserStatus.LIKELY_REAL) {
console.log("I'm a real person!");
}
// console.warn(`Apple Authentication Completed, ${this.user}, ${email}`);
} catch (error) {
if (error.code === AppleAuthError.CANCELED) {
alert('User canceled Apple Sign in');
// console.warn('User canceled Apple Sign in.');
} else {
console.error(error);
}
}
};
fetchAndUpdateCredentialState = async () => {
if (this.user === null) {
this.setState({credentialStateForUser: 'N/A'});
} else {
const credentialState = await appleAuth.getCredentialStateForUser(
this.user,
);
if (credentialState === AppleAuthCredentialState.AUTHORIZED) {
this.setState({credentialStateForUser: 'AUTHORIZED'});
} else {
this.setState({credentialStateForUser: credentialState});
}
}
};
// Send data "name,image,email" to API
sendData = async (Email, Name, Id) => {
try {
let response = await API.post('/apple', {
email: Email,
name: Name,
id: Id,
});
let {
data: {
data: {
response: {token},
},
},
} = response;
console.log('token:?>:', token);
console.log('props', this.props);
console.log('navigation', this.props.navigation);
this.setState({loading: false});
this.props.storeToken(token);
this.props.isLogin(true);
// this.props.navigation.push('BottomTabNavigator');
} catch (err) {
console.log(err);
alert('Unexpected Error, try again later.');
this.setState({loading: false});
}
};
render() {
return (
<View style={styles.container}>
{this.state.loading ? (
<ActivityIndicator />
) : (
<AppleButton
style={styles.appleButton}
cornerRadius={5}
buttonStyle={AppleButton.Style.WHITE}
buttonType={AppleButton.Type.SIGN_IN}
onPress={() => this.signIn()}
/>
)}
</View>
);
}
}
const styles = StyleSheet.create({
appleButton: {
width: 200,
height: 50,
// margin: 10,
},
container: {
flex: 1,
justifyContent: 'center',
},
});
const mapDispatchToProps = dispatch => {
// to excute the actions we want to invok
return {
isLogin: isLogin => {
dispatch(isLoginFunc(isLogin));
},
storeToken: token => {
dispatch(saveToken(token));
},
};
};
export default connect(
null,
mapDispatchToProps,
)(AppleAuth);
-
singin.js
<AppleAuth /> in the render method
if you render your component as component, not as a navigation screen, it will not receive navigation prop. It was like this in all versions of react-navigation
Access the navigation prop from any component
I'm trying to render the data from my database get this instead Failed to compile.
./src/components/list-pets.component.js
Line 38:5: Expected an assignment or function call and instead saw an expression no-unused-expressions
Search for the keywords to learn more about each error.enter code here
Here is my code from the trouble component
import React, { Component } from 'react';
import axios from 'axios';
export default class ListPets extends Component {
constructor(props) {
super(props);
this.state = {
pets: []
};
}
componentDidMount = () => {
this.getPets();
};
getPets = () => {
axios.get('http://localhost:5000/pets')
.then((response) => {
const data = response.data;
this.setState({ pets: data });
console.log('Data has been received!');
})
.catch((err) => {
console.log(err);
});
}
displayPet = (pets) => {
if (!pets.length) return null;
return pets.map((pet, index) => {
<div key={index}>
<h3>{pet.name}</h3>
<p>{pet.species}</p>
</div>
});
};
render() {
console.log('State: ', this.state);
return (
<div className='adopt'>
{this.displayPet(this.state.pets)}
</div>
)
}
}
You need to return a value at each pets.map iteration, currently you’re returning undefined.
return pets.map((pet, index) => {
return (
<div key={index}>
<h3>{pet.name}</h3>
<p>{pet.species}</p>
</div>
)
});
You have to wait until fetching data is completed.
You should have to define the loading bar while fetching.
class App extends Component {
constructor() {
super();
this.state = {
pageData: {},
loading: true
}
this.getData();
}
async getData(){
const res = await fetch('/pageData.json');
const data = await res.json();
return this.setState({
pageData: data,
loading: false
});
}
componentDidMount() {
this.getData();
}
render() {
const { loading, pageData } = this.state;
if (loading){
return <LoadingBar />
}
return (
<div className="App">
<Navbar />
</div>
);
}
}
I'd like to see in this component users with their photos.
I can see id's of each user, so database.getUserList function works! But there is something wrong with getAdditionalUserInfoById function.
import { database } from "../firebase";
class UsersInfo extends Component {
render() {
let users = database.getUserList(this.props.id);
return (
<div>
<List>
{Object.keys(users).map(key => (
<ListItem>
<Avatar>
<ImageAvatars
photoUrl={database.getAdditionalUserInfoById(
users[key]["userId"],
"photoUrl"
)}
/>
</Avatar>
<ListItemText
primary={users[key]["userId"]}
secondary="Jan 9, 2014"
/>
</ListItem>
))}
</List>
</div>
);
}
}
I have getAdditionalUserInfoById in another file that queries firebase. With this console.log command, I can see the correct photoUrl value of each user in console, but in the component above it is undefined. How can I get it to UsersInfo component, what should be fixed here?
export const getAdditionalUserInfoById = (userId, query) => {
var userDbRef = database.ref("users/" + userId);
userDbRef
.child("additionalInfo")
.child(query)
.on("value", function(snapshot) {
console.log("userInfoById: ", snapshot.val());
return snapshot.val();
});
};
Firebase DB Structure:
"users" : {
"h06c4wAxn0eeN3yQ4Qw9DfEVww03" : {
"additionalInfo" : {
"photoUrl" : "https://thumb.ibb.co/iVW1y9/Screen_Shot_2018_05_10_at_12_23_59_PM.jpg"
}
}
}
***** UPDATE *****
class InfoWindowContent extends Component {
constructor(props) {
super(props);
this.state = {
users: [],
userPhotos: []
};
}
componentDidMount() {
this.setState({ users: database.showUserListbyGeoId(this.props.id) });
var userPhotos = [];
Object.keys(this.state.users).map(function(key) {
userPhotos[
this.state.users[key]["userId"]
] = database.getAdditionalUserInfoById(
this.state.users[key]["userId"],
"photoUrl"
);
});
this.setState({ userPhotos });
}
render() {
const { users, userPhotos } = this.state;
return (
<div>
<List>
{Object.keys(users).map(key => (
<ListItem>
<Avatar>
<ImageAvatars
photoUrl={this.state.userPhotos[users[key]["userId"]]}
/>
</Avatar>
<ListItemText
primary={users[key]["userId"]}
secondary={"31 Dec, 2035"}
/>
</ListItem>
))}
</List>
</div>
);
}
}
My Firebase Database Functions
// ../firebase.js
export const showUserListbyGeoId = cityId => {
var userList = [];
var cityUserListRef = database.ref("cities/" + cityId + "/users");
cityUserListRef.orderByChild("userId").on("value", function(snapshot) {
if (snapshot.exists()) {
snapshot.forEach(function(childSnapshot) {
var key = childSnapshot.key;
userList[key] = childSnapshot.val();
});
}
});
return userList;
};
export const getAdditionalUserInfoById = (userId, query, callback) => {
var userDbRef = database.ref("users/" + userId);
userDbRef
.child("additionalInfo")
.child(query)
.on("value", snapshot => {
return callback(snapshot.val());
});
};
database.getUserList is async function. You must wait for data returning.
The solution is, define a state in component. After the data returned, you would setState, and the view would be updated.
I don't know about your database.getUserList use callback or Promise. If it return the Promise, the example like this:
class UsersInfo extends Component {
constructor(props) {
super(props);
this.state = { users: [] };
}
componentDidMount() {
database.getUserList(this.props.id)
.then((results) => {
this.setState({ users: results });
});
}
public render() {
const users = this.state.users;
...
}
}
For getAdditionalUserInfoById, because it is event listener, you can use callback
export const getAdditionalUserInfoById = (userId, query, callback) => {
var userDbRef = database.ref('users/' + userId);
userDbRef.child("additionalInfo").child(query).on("value", (snapshot) => {
callback(snapshot.val());
});
})
}
componentDidMount() {
const self = this;
getAdditionalUserInfoById(userId, query, (value) => {
// set state or do something
self.setState({ user: value })
})
}
UPDATE:
// ../firebase.js
export const showUserListbyGeoId = (cityId, callback) => {
const userList = [];
const cityUserListRef = database.ref("cities/" + cityId + "/users");
cityUserListRef.orderByChild("userId").on("value", (snapshot) => {
if (snapshot.exists()) {
snapshot.forEach((childSnapshot) => {
userList.push(childSnapshot.val());
});
}
return callback(userList);
});
};
export const getAdditionalUserInfoById = (userId, query) => {
var userDbRef = database.ref("users/" + userId);
return userDbRef
.child("additionalInfo")
.child(query)
.once("value")
.then((snapshot) => ({
[query]: snapshot.val(),
userId,
}));
};
class InfoWindowContent extends Component {
constructor(props) {
super(props);
this.state = {
users: [],
userPhotos: []
};
}
componentDidMount() {
const self = this;
database.showUserListbyGeoId(this.props.id, (userList) => {
self.setState(userList);
return Promise.all(
userList.map(({ userId }) => database.getAdditionalUserInfoById(
userId,
"photoUrl"
))
).then((userPhotos) => {
self.setState({ userPhotos })
});
});
}
render() {
const { users, userPhotos } = this.state;
return (
<div>
<List>
{userPhotos.map((photo) => (
<ListItem>
<Avatar>
<ImageAvatars
photoUrl={photo.photoUrl}
/>
</Avatar>
<ListItemText
primary={photo.userId}
secondary={"31 Dec, 2035"}
/>
</ListItem>
))}
</List>
</div>
);
}
}