I have using this component to push data out, however i can not grab the document name i.e. "sampleCloudFunction" under CloudFunctionMonitor(please see picture) from the database and display it
docRef.id does not work, i am not looking for the ID but the actual name of the sub document?
<Card.Content>
<Card.Header>{this.props.cloudFunction.docRef.id}</Card.Header>
<Card.Description>{}</Card.Description>
</Card.Content>
Full code index.js:
import "../../App";
import "semantic-ui-css/semantic.min.css";
import { Icon, Card, Button } from "semantic-ui-react";
import "semantic-ui-css/semantic.min.css";
import React, { Component } from "react";
import "semantic-ui-css/semantic.min.css";
export default class Cards extends Component {
render() {
return (
<Card color="green">
<Card.Content>
<Card.Header>{this.props.cloudFunction.docRef.id}</Card.Header>
<Card.Description>{}</Card.Description>
</Card.Content>
<Card.Content extra>
<Icon name="cog" /> Records: {this.props.cloudFunction.lastRunLoad}
<br />
<Icon name="cog" />
{this.props.cloudFunction.lastRunMessage}
<br />
<Icon name="clock" /> Last Run:{" "}
{this.props.cloudFunction.lastRunTime.toDate().toString()}
<br />
<Icon name="angle double down" />
Schedule: {this.props.cloudFunction.schedule}
</Card.Content>
<Card.Content extra>
<div className="ui two buttons">
<Button basic color="green">
Cloud Function
</Button>
</div>
</Card.Content>
</Card>
);
}
}
Firestore Query
useEffect(() => {
const unsubscribe = FirestoreService.getCloudFunctionItems(
(querySnapshot) => {
const updatedCloundFunctions = querySnapshot.docs.map(
(docSnapshot) => ({
guid: uuidV4(),
...docSnapshot.data(),
})
);
console.log(updatedCloundFunctions);
setCloudFunctions(updatedCloundFunctions);
},
(error) => setError("list-item-get-fail")
);
return unsubscribe;
}, []);
The document data does not contain it's ID. You'll have to explicitly add it as shown below:
const updatedCloundFunctions = querySnapshot.docs.map((docSnapshot) => ({
guid: uuidV4(),
// Add Document ID
id: docSnapshot.id
...docSnapshot.data(),
})
);
Then you can access it by this.props.cloudFunction.id
Related
I am creating a simple app that displays a list of user cards from a JSON file and their location on a leaflet map.
That part has been done but now I need to implement functionality to link to each user's page with more info about the user as well as their zoomed in location on the map.
This is my App.js file:
import React from "react";
import { BrowserRouter as Router } from "react-router-dom";
import { Routes, Route } from "react-router-dom";
import UserList from "./pages/UserList";
import UserProfile from "./pages/UserProfile";
import "./App.css";
function App() {
return (
<Router>
<Routes>
<Route path="/" element={<UserList/>} />
<Route path="/pages/UserList" element={<UserList/>} />
<Route path="/pages/UserProfile" element={<UserProfile/>}/>
</Routes>
</Router>
);
}
export default App;
This is my UserList page where I fetch the data and list it in user cards as well as in the leaflet map with markers related to each user location:
import { useState, useEffect } from "react";
import UserProvider, { useAppContext } from "../components/UserProvider";
import UserCard from "../components/UserCard";
import Markers from "../components/Markers";
import { MapContainer, TileLayer, useMap } from "react-leaflet";
import "../App.css";
function SetMapView() {
const { user } = useAppContext();
const map = useMap();
useEffect(() => {
if (!user) return;
const { lat, lng } = user.address.geo;
map.flyTo([lat, lng], 5);
}, [user]);
return null;
}
function UserList() {
const [users, setUsers] = useState([]);
const [data, setData] = useState([]);
useEffect(() => {
(async () => {
let userData;
try {
const response = await fetch(
"https://jsonplaceholder.typicode.com/users/"
);
userData = await response.json();
} catch (error) {
console.log(error);
userData = [];
}
setUsers(userData);
setData(userData);
// Map
})();
}, []);
return (
<UserProvider>
<div className="App">
<div className="cards-container">
{users.map((user, index) => (
<UserCard userData={user} key={index} />
))}
</div>
<div className="map-container">
<MapContainer
center={[47.217324, 13.097555]}
zoom={0}
style={{ height: "100vh" }}
>
<TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
attribution='© OpenStreetMap contributors'
/>
<Markers data={data} />
<SetMapView />
</MapContainer>
</div>
</div>
</UserProvider>
);
}
export default UserList;
This is my UserCar component:
import './UserCard.css';
import { Link } from "react-router-dom";
const UserCard = ({ userData }) => {
return (
<Link to="./pages/UserProfile">
<div className="card">
<div className="card-name">{userData.name}</div>
<div className="card-body">
<div className="card-email"><i className="fa fa-envelope" /> {userData.email}</div>
<div className="card-phone"><i className="fa fa-phone" /> {userData.phone}</div>
<div className="card-website"><i className="fa fa-globe" /> {userData.website}</div>
<div className="card-company"><i className="fa fa-briefcase" /> {userData.company.name}</div>
</div>
</div>
</Link>
);
};
export default UserCard;
And then when clicking on these user cards they would lead to individual user profile pages containing full user information and their zoomed in location on the leaflet map.
I already have a user details component to display the full list of user info and this is the code:
const UserDetails = ({ userData }) => {
return (
<div className="card">
<div className="card-name">{userData.name}</div>
<div className="card-body">
<div className="card-username"><i className="fa fa-envelope" /> {userData.username}</div>
<div className="card-email"><i className="fa fa-envelope" /> {userData.email}</div>
<div className="card-address"><i className="fa fa-envelope" /> {userData.address.street}, {userData.address.suite}, {userData.address.city}, {userData.address.zipcode}, {userData.address.geo.lat}, {userData.address.geo.lng}</div>
<div className="card-phone"><i className="fa fa-phone" /> {userData.phone}</div>
<div className="card-website"><i className="fa fa-globe" /> {userData.website}</div>
<div className="card-company"><i className="fa fa-briefcase" /> {userData.company.name}, {userData.company.catchPhrase}, {userData.company.bs}</div>
</div>
</div>
);
};
export default UserDetails;
And then this is the page for displaying the full user data:
import UserDetails from "../components/UserDetails";
function UserProfile({ userData }) {
return (
<UserDetails userData={userData} />
);
}
export default UserProfile;
And that's where I'm stuck. I am not sure how to pull and sort and display specific user data here so this page can display full user data and map with user zoomed in location.
Any help would be appreciated.
You have to follow a different approach to achieve this. UserProvider will no longer help you. You need to do several changes:
UserModal is gone. You do not need it.
You need to pass the user id when clicking on the User details to fetch each user specific data when navigating to UserProfile route. Using useParams you get the id, then fetch the specific user data and then building the card with that data. Now for the map you need to make the map a separate comp where you will have two use cases. One render all the markers with all users. Second you render only one marker with single user data.
function UserProfile() {
const { id } = useParams();
const { user } = useGetUser(id);
const navigate = useNavigate();
const handleClick = () => {
navigate("/");
};
return user && id ? (
<>
<UserDetails userData={user} />
<Button onClick={handleClick}>Go back</Button>
<Map user={user} />
</>
) : null;
}
function Map({ users, user }) {
return (
<div className="map-container">
<MapContainer
center={[47.217324, 13.097555]}
zoom={0}
style={{ height: "100vh" }}
>
<TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
attribution='© OpenStreetMap contributors'
/>
<Markers users={users} user={user} />
{user && <SetMapView user={user} />}
</MapContainer>
</div>
);
}
I customized UserMarker by creating one case to loop over all markers and another to use a single marker. There is many more. You can check the demo
Hey I am trying to covert a class component into a functional component and for some reason the props I'm passing into the form component are being passed as object values. The original class component works fine but I am not sure what is causing this issue with props. what it looks like with
what it should look like with class component:
what I currently have with functional component:
function component home page
import React from "react";
// import Jumbotron from "react-bootstrap/Jumbotron";
import Row from "react-bootstrap/Row";
import Card from "../components/Card";
import Form from "../components/Form";
import Col from "react-bootstrap/Col";
import Container from "react-bootstrap/Container";
import Jumbotron from "react-bootstrap/Jumbotron";
import { useState } from "react";
import API from "../utils/API";
import Book from "../components/Book";
import Button from "react-bootstrap/Button";
import { List } from "../components/List";
import Footer from "../components/Footer";
import "./style.css";
export default function Home() {
let [books, setBooks] = useState([]);
let [q, setQ] = useState("");
let [message, setMessage] = useState("Search For A Book to Begin");
// const handleInputChange = (event) => {
// let { name, value } = event.target;
// setQ(([name] = value));
// };
const handleInputChange = (event) => {
setQ(event.target.value)
};
let getBooks = () => {
API.getBooks(q)
.then((res) => setBooks(res.data))
.catch(() => setBooks([]));
setMessage("No New Books Found, Try a Different Query");
};
const handleFormSubmit = (event) => {
event.preventDefault();
getBooks();
};
let handleBookSave = (id) => {
const book = books.find((book) => book.id === id);
API.saveBook({
googleId: book.id,
title: book.volumeInfo.title,
subtitle: book.volumeInfo.subtitle,
link: book.volumeInfo.infoLink,
authors: book.volumeInfo.authors,
description: book.volumeInfo.description,
image: book.volumeInfo.imageLinks.thumbnail,
}).then(() => getBooks());
};
return (
<div>
<Container>
<Row>
<Col md={12}>
<Jumbotron className="rounded-3 mt-4">
<h1 className="text-center ">
<strong>(React) Google Books Search</strong>
</h1>
<h2 className="text-center">
Search for and Save Books of Interest.
</h2>
</Jumbotron>
</Col>
<Col md={12}>
<Card title="Book Search" icon=" fa-book">
<Form
handleInputChange={handleInputChange}
handleFormSubmit={handleFormSubmit}
q={q}
/>
</Card>
</Col>
</Row>
<Row>
<Col md={12}>
<Card title="Results">
{books.length ? (
<List>
{books.map((book) => (
<Book
key={book.id}
title={book.volumeInfo.title}
subtitle={book.volumeInfo.subtitle}
link={book.volumeInfo.infolink}
authors={book.volumeInfo.authors.join(", ")}
description={book.volumeInfo.description}
image={book.volumeInfo.imageLinks.thumbnail}
Btn={() => (
<Button
onClick={() => handleBookSave(book.id)}
variant="primary"
className="ml-2"
>
Save
</Button>
)}
/>
))}
</List>
) : (
<h2 className="text-center">{message}</h2>
)}
</Card>
</Col>
</Row>
<Footer />
</Container>
</div>
);
}
Form component
import React from "react";
function Formmy({q, handleInputChange, handleFormSubmit }) {
return (
<form>
<div className="form-group">
<label htmlFor="Query">
<strong>Book</strong>
</label>
<input
className="form-control"
id="Title"
type="text"
value={q}
placeholder="Ready Player One"
name="q"
onChange={handleInputChange}
required
/>
</div>
<div className="float-end">
<button
onClick={handleFormSubmit}
type="submit"
className="btn btn-lg btn-danger float-right"
>
Search
</button>
</div>
</form>
);
}
export default Formmy;
I saw dozens of examples but they are not working in my case, I want to update the page variable in "bookitem" component and rerender it. using gives an error ' Expected an assignment or function call and instead saw an expression no-unused-expressions'
import React from 'react'
import { Pagination, Container } from 'semantic-ui-react'
import bookitem from './Book_item'
const PaginationI = () => (
<Container style={{textAlign: "center", padding:'4rem'}}>
<Pagination defaultActivePage={5} totalPages={10} onPageChange={PageChange}/>
</Container>
)
function PageChange(event,data){
console.log(data.activePage);
<bookitem page={data.activePage}/>
};
export default PaginationI
//////////////////////////////////////////////////////////////////////////////////////////////////////
class bookitem extends Component{
constructor(props){
super (props);
this.state={
counter:0,
page:0,
data2:[]
};
}
componentWillMount(){
console.log(this.props.page)
axios.get('/books/'+this.state.page).then(res=>{console.log(res.data);this.setState({data2:res.data});})
console.log('aa')
console.log(this.state.data2)
}
genurl(isbn){
console.log(isbn)
let url='http://covers.openlibrary.org/b/isbn/'+ isbn + '-L.jpg'
return url;
}
render(){return(
<div>
<div>{this.state.page}</div>
<Container>
<div style={{padding:"1em 1em", textAlign: "right"}}>
<Card.Group itemsPerRow={3} stackable={true} doubling={true}>
{this.state.data2.map(card=>(
<Card href="#">
<Image src={this.genurl(card.isbn)} wrapped ui={false} />
<Card.Content>
<Card.Header>{card.title}</Card.Header>
<Card.Meta>
<span className='date'>Author:{card.author}</span>
</Card.Meta>
<Card.Content >
<Rating icon='star' defaultRating={card.avgrating} maxRating={5} />
</Card.Content>
<Card.Description>
{card.avgrating} Avg rating, {card.totalratings} total ratings.
</Card.Description>
</Card.Content>
<Card.Content >
<a>
<Icon name='pencil alternate' />
{card.reviews} Reviews
</a>
</Card.Content>
</Card>
))}
</Card.Group>
</div>
</Container>
</div>
)
}
}
export default bookitem
The problem is that you are not rendering the bookitem component at all. You have to manage the state of your activePage, pass it to the bookitem and actually render this component.
import React, { useState } from "react";
import { Pagination, Container } from "semantic-ui-react";
import BookItem from "./Book_item";
const PaginationI = () => {
const [activePage, setActivePage] = useState(0); // manage the state of activePage
function PageChange(event, data) {
setActivePage(data.activePage); // update the state in event handler
}
return (
<Container style={{ textAlign: "center", padding: "4rem" }}>
<BookItem page={activePage} /> {/* render your component */}
<Pagination
defaultActivePage={5}
totalPages={10}
onPageChange={PageChange} /> {/* pass event handler */}
</Container>
);
};
export default PaginationI;
Also you would have to rename the bookitem component due to collision with HTML tags like this
import React from "react";
class BookItem extends Component {
constructor(props) {
super(props);
this.state = {
counter: 0,
page: 0,
data2: [],
};
}
componentWillMount() {
console.log(this.props.page);
axios.get("/books/" + this.state.page).then((res) => {
console.log(res.data);
this.setState({ data2: res.data });
});
console.log("aa");
console.log(this.state.data2);
}
genurl(isbn) {
console.log(isbn);
let url = "http://covers.openlibrary.org/b/isbn/" + isbn + "-L.jpg";
return url;
}
render() {
return (
<div>
<div>{this.state.page}</div>
<Container>
<div style={{ padding: "1em 1em", textAlign: "right" }}>
<Card.Group itemsPerRow={3} stackable={true} doubling={true}>
{this.state.data2.map((card) => (
<Card href="#">
<Image src={this.genurl(card.isbn)} wrapped ui={false} />
<Card.Content>
<Card.Header>{card.title}</Card.Header>
<Card.Meta>
<span className="date">Author:{card.author}</span>
</Card.Meta>
<Card.Content>
<Rating
icon="star"
defaultRating={card.avgrating}
maxRating={5}
/>
</Card.Content>
<Card.Description>
{card.avgrating} Avg rating, {card.totalratings} total
ratings.
</Card.Description>
</Card.Content>
<Card.Content>
<a>
<Icon name="pencil alternate" />
{card.reviews} Reviews
</a>
</Card.Content>
</Card>
))}
</Card.Group>
</div>
</Container>
</div>
);
}
}
export default BookItem;
First of all Bookitem must starts with capitalized letter. So instead of <bookitem /> you must have <Bookitem/>.
Now if you want to change state of a react component from another component, you have to pass a function from parent to child which will be called when you want to change the state. For example
const Compoent1 = () => {
const [state, setState] = useState(value)
.....
return <Component2 changeState={setState} />
}
The PhotosPage component below is rendered using
<Route path="/settings/photos" component={PhotosPage} />
The signature of the component is:
const PhotosPage = ({
uploadProfileImage,
photos,
profile,
deletePhoto,
setMainPhoto
})=>{}
However, there are two types of parameters that are used:
photos and profile are part of redux state
uploadProfileImage, deletePhoto, and setMainPhoto are imports
That are never passed in. Are these parameters passed in by react, or is this a javascript feature that I don't understand. Thanks.
import React, { useState, useEffect, Fragment } from "react";
import { connect } from "react-redux";
import { compose } from "redux";
import { firestoreConnect } from "react-redux-firebase";
import {
Segment,
Header,
Divider,
Grid,
Button
} from "semantic-ui-react";
import { toastr } from "react-redux-toastr";
import DropzoneInput from "./DropzoneInput";
import CropperInput from "./CropperInput";
import {
uploadProfileImage,
deletePhoto,
setMainPhoto
} from "../../userActions";
import UserPhotos from "./UserPhotos";
const query = ({ auth }) => {
return [
{
collection: "users",
doc: auth.uid,
subcollections: [{ collection: "photos" }],
storeAs: "photos"
}
];
};
const actions = {
uploadProfileImage,
deletePhoto
};
const mapState = state => ({
auth: state.firebase.auth,
profile: state.firebase.profile,
photos: state.firestore.ordered.photos
});
const PhotosPage = ({
uploadProfileImage,
photos,
profile,
deletePhoto,
setMainPhoto
}) => {
const [files, setFiles] = useState([]);
const [image, setImage] = useState(null);
useEffect(() => {
return () => {
files.forEach(file => URL.revokeObjectURL(file.preview));
};
}, [files]);
const handleUploadImage = async () => {
try {
await uploadProfileImage(image, files[0].name);
handleCancelCrop();
toastr.success("Success", "photo has been uploaded.");
} catch (error) {
toastr.error("Ooops", "something whent wrong");
console.log(error);
}
};
const handleCancelCrop = () => {
setFiles([]);
setImage(null);
};
const handleDeletePhoto = async photo => {
//try {
await deletePhoto(photo);
// } catch (error) {
// toastr.error("OOps", error.message);
// }
};
return (
<Segment>
<Header dividing size="large" content="Your Photos" />
<Grid>
<Grid.Row />
<Grid.Column width={4}>
<Header color="teal" sub content="Step 1 - Add Photo" />
<DropzoneInput setFiles={setFiles} />
</Grid.Column>
<Grid.Column width={1} />
<Grid.Column width={4}>
<Header sub color="teal" content="Step 2 - Resize image" />
{files.length > 0 && (
<CropperInput setImage={setImage} imagePreview={files[0].preview} />
)}
</Grid.Column>
<Grid.Column width={1} />
<Grid.Column width={4}>
<Header sub color="teal" content="Step 3 - Preview & Upload" />
{files.length > 0 && (
<>
<div
className="img-preview"
style={{
minHeight: "200px",
minWidth: "200px",
overflow: "hidden"
}}
/>
<Button.Group>
<Button
onClick={handleUploadImage}
style={{ width: "100px" }}
positive
icon="check"
/>
<Button
onClick={handleCancelCrop}
style={{ width: "100px" }}
icon="close"
/>
</Button.Group>
</>
)}
</Grid.Column>
</Grid>
<Divider />
<UserPhotos
photos={photos}
profile={profile}
deletePhoto={handleDeletePhoto}
/>
</Segment>
);
};
export default compose(
connect(
mapState,
actions
),
firestoreConnect(auth => query(auth))
)(PhotosPage);
When we wrap a component with 'Connect' this is then connected to our Redux store. We can then give it 2 functions - mapStateToProps (which maps our store state to the component props) and mapDispatchToProps - which we call actions - (that maps our store actions to our component). The actions become part of the props that we can call from the component.
So I keep getting a weird error when running my app. I have done some research and none of the solutions seem to be working for my situation.
First things first, here is my code that is throwing issues:
import React, { Component } from "react";
import { View, Text, TextInput, ActivityIndicator } from "react-native";
import SvgUri from "react-native-svg-uri";
import tokenStore from "../../stores/token";
import authApi from "../../api/authApi";
import { toast } from "../../helpers";
import Button from "../../components/Button";
import HeaderButton from "../../components/Button";
import ScrollWrapper from "../../components/ScrollWrapper";
import Container from "../../components/Container";
import Input from "../../components/Input";
import ContentGroup from "../../components/Content/Group";
import Label from "../../components/Label";
import Logo from "../../components/Logo";
import * as apps from "../../themes/apps";
import LoginPageStyles from "./LoginPage.styles";
import catalyst_logo_white from "../../styles/images/catalyst_logo_white.svg";
type Props = {};
export default class LoginPage extends Component<Props> {
state = { loading: false, email: "", password: "" };
setLoading(loading: boolean) {
this.state.loading = true;
}
handleSubmit = async () => {
this.setLoading(true);
try {
const token = await authApi.login(this.state.email, this.state.password);
tokenStore.set(token);
} catch (e) {
toast.error("Invalid email or password");
} finally {
this.setLoading(false);
}
};
handleForgotPassword = () => {};
render() {
const style = LoginPageStyles.get();
return (
<ScrollWrapper
contentContainerStyle={{ flexGrow: 1, justifyContent: "center" }}
>
<View style={style.container}>
<View style={style.logoContainer}>
<SvgUri width={186} height={24} source={catalyst_logo_white} />
</View>
<View style={style.loginContainer}>
<Container type="bottom">
<Label>Login</Label>
<Input
size="medium"
icon="user"
placeholder="you#domain.com"
onChangeText={value => this.setState({ email: value })}
value={this.state.email}
autoCapitalize="none"
/>
</Container>
<Container type="bottom">
<Label>Password</Label>
<Input
size="medium"
icon="lock"
placeholder="Your Password"
secureTextEntry={true}
onChangeText={value => this.setState({ password: value })}
value={this.state.password}
autoCapitalize="none"
/>
</Container>
<Container type="bottom" alignment="full">
<Button
size="medium"
style={style.submitButton}
onPress={() => this.props.onSelectApp(apps.contentFuel)}
>
Login
</Button>
</Container>
</View>
</View>
<View style={style.forgotPasswordContainer}>
<Button
linkTo="/forgotPassword"
type="basic"
size="medium"
variant="secondary"
>
Forgot Password
</Button>
</View>
</ScrollWrapper>
);
}
}
The troublesome code is the Buttons. I don't know why it is throwing this error:
Warning: React.createElement: type is invalid -- expected a string
(for built-in components) or a class/function (for composite
components) but got: object.
import React from "react";
import { Text } from "react-native";
import { Link } from "react-router-native";
import FontAwesome, { Icons } from "react-native-fontawesome";
import AppContext from "../../../../AppContext";
import ButtonStyles from "./Button.styles";
export default class Button extends React.Component {
render() {
const { size, type, icon, children, onPress, variant, linkTo } = this.props;
return (
<AppContext.Consumer>
{app => {
const style = ButtonStyles.get({ app, size, type, variant });
return (
<Link
to={linkTo}
onPress={onPress}
style={[style.body, this.props.style]}
>
<React.Fragment>
{icon && (
<FontAwesome style={style.icon}>{Icons[icon]}</FontAwesome>
)}
<Text style={style.text}>{children}</Text>
</React.Fragment>
</Link>
);
}}
</AppContext.Consumer>
);
}
}
Some of the solutions I have found is adding brackets to the Button import but that did not work. I know it is the buttons causing problems because when I remove them entirely, the app runs with no issues.
Does anyone have any idea whats wrong?