How to access collection and documents within it in React.js? - reactjs

So, basically I have started bulding new app with React, Redux and Firebase.
I am still learning and am not able to solve some things but this is the first moment, when I am really stuck.
I have created a collection in the Firestore called 'posts' and created one post manually just to see if it is working.
Unfortunately, after implementing my code I keep receiving an error:
TypeError: Cannot read property 'posts' of undefined
AdminpanelAuth.componentDidMount
/src/components/Admin/Adminpanel.js:23
20 | componentDidMount() {
21 | this.setState({ loading: true });
22 |
> 23 | this.props.firebase.posts().on('value', snapshot => { <- HERE IS THE PROBLEM
I have tried different variations and different approaches but none of them seemed to be working. Could anyone advise? Below I am attaching a part of my code of Adminpanel.js and Firebase.js
Firebase.js
import app from 'firebase/app';
import 'firebase/auth';
import 'firebase/firestore';
const firebaseConfig = {
my config here
};
class Firebase {
constructor() {
app.initializeApp(firebaseConfig);
// initialize Firebase Authenticator
this.auth = app.auth();
// initialize Firebase Realtime Database
this.db = app.firestore();
}
// Initialize two functions that connect to Firebase : Log In and Log Out
doSignInWithEmailAndPassword = (email, password) =>
this.auth.signInWithEmailAndPassword(email, password);
doSignOut = () => this.auth.signOut();
// Initialize functions for posts
post = pid => this.db.doc('posts'+pid);
posts = () => this.db.collection('posts');
}
export default Firebase;
Adminpanel.js
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { compose } from 'recompose'
import { withFirebase } from '../Firebase'
import { withAuthorisation } from '../Session'
import SignOutBtn from '../SignOutBtn'
const Adminpanel = ({authUser}) => (
<div>{authUser ? <AdminpanelAuth /> : <AdminpanelNonAuth />}</div>
)
class AdminpanelAuth extends Component {
constructor(props) {
super(props);
this.state = {
loading:false,
posts:[]
}
}
componentDidMount() {
this.setState({ loading: true });
this.props.firebase.posts().on('value', snapshot => {
const postsObject = snapshot.val();
const postsList = Object.keys(postsObject).map(key => ({
...postsObject[key],
pid: key,
}));
this.setState({
posts: postsList,
loading: false,
});
});
}
./Firebase/index.js
import FirebaseContext, { withFirebase } from './context';
import Firebase from './Firebase';
export default Firebase;
export { FirebaseContext, withFirebase };

It looks as though your problem might be that you are trying to access your Firebase class through props when it needs to be instantiated in your Adminpanel component.
In your imports you have:
import { withFirebase } from '../Firebase'
Which looks as though you are trying to use an HOC but looking at Firebase.js, there is no indication of you passing a component in, to then pass props to.
Instead, in your Adminpanel component, try changing your import to:
import Firebase from '../Firebase'
Then, in the constructor of Adminpanel:
constructor(props) {
super(props);
this.state = {
loading:false,
posts:[]
}
this.firebase = new Firebase()
}
Then, try to call the class method by doing:
this.firebase.posts().on('value', snapshot => {

Related

react-redux, thunk middleware installed, class component - error "Actions must be plain objects. Instead, the actual type was: 'Promise'" dispatch

mern stack, using a class component I call Landing I use the componentDidMount method.
on form submit axios is using the get method to return my user object. I am then dispacting my user object with an exported function to my store. Recieving this error:
Actions must be plain objects. Instead, the actual type was: 'Promise'. You may need to add middleware to your store setup to handle dispatching other values, such as 'redux-thunk' to handle dispatching functions.
I export default the Landing component by executing connect and passing the action I had exported followed by the execution of Landing.
The index.js file is currently utilizing redux-thunk middleware.
My goal is to update the state of user so all components immediately display the content that is changing on the form submit.
App.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import { reducers } from './reducers';
import App from './components/App';
const store = createStore(reducers, {}, compose(applyMiddleware(thunk)));
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root'),
);
Landing.js
import React from 'react';
import { io } from 'socket.io-client';
import _ from 'underscore';
import { connect } from 'react-redux'
import './styles.css';
import { bid } from '../../actions/auction';
import { bidPlaced, loggedUser } from '../../api/index';
import { CONNECTION_PORT } from '../../config';
class Landing extends React.Component {
constructor(props) {
super(props);
this.state = {
user: ''
}
componentDidMount() {
this.setState({user: JSON.parse(localStorage.getItem('profile))}) //sets the logged in user to state
}
handleFormSubmit = async (e) => { //This function is wrapped in tryCatch in my app
e.preventDefault()
//Storing user data in an object
const userData = {email: this.state.user.result.email, id: this.state.user.result._id}
const response = await loggedUser(userData)
//which returns a promise, which does currently hold the updated userModel
this.props.bid(response)
}
render (
<div>
<form onSubmit={handleFormSubmit}>
<input value={'all the user information'}>
<button type="submit">Click Me</button>
<form/>
)
}
export default connect(null, { bid })(Landing);
in my actions directory:
auction.js
export const bid = async (user) => ({
type: EDIT,
data: user
});
reducers
bid.js
import * as actionType from '../constants/actionTypes';
let payload = null
if (localStorage.getItem('profile')) payload = localStorage.getItem('profile')
const bidReducer = (state = { authData: payload }, action) => {
switch (action.type) {
case actionType.EDIT:
localStorage.setItem('profile', JSON.stringify({ ...action?.data }));
return { ...state, authData: action.data, loading: false, errors: null };
default:
return state;
}
};
export default bidReducer;
Just remove async in the bid
export const bid = (user) => ({
type: EDIT,
data: user
});

I wonder if this really is the correct way to use onAuthStateChanged

Following this react-firestore-tutorial
and the GitHub code. I wonder if the following is correct way to use the onAuthStateChanged or if I have understod this incorrect I'm just confused if this is the right way.
CodeSandBox fully connect with a test-account with apikey to Firebase!! so you can try it what I mean and I can learn this.
(NOTE: Firebase is blocking Codesandbox url even it's in Authorised domains, sorry about that but you can still see the code)
t {code: "auth/too-many-requests", message: "We have blocked all
requests from this device due to unusual activity. Try again later.",
a: null}a:
Note this is a Reactjs-Vanilla fully fledge advanced website using only;
React 16.6
React Router 5
Firebase 7
Here in the code the Firebase.js have this onAuthStateChanged and its called from two different components and also multiple times and what I understand one should only set it up once and then listen for it's callback. Calling it multiple times will that not create many listeners?
Can someone have a look at this code is this normal in Reactjs to handle onAuthStateChanged?
(src\components\Firebase\firebase.js)
import app from 'firebase/app';
import 'firebase/auth';
import 'firebase/firestore';
class Firebase {
constructor() {
app.initializeApp(config);
.......
}
.....
onAuthUserListener = (next, fallback) =>
this.auth.onAuthStateChanged(authUser => {
if (authUser) {
this.user(authUser.uid)
.get()
.then(snapshot => {
const dbUser = snapshot.data();
// default empty roles
if (!dbUser.roles) {
dbUser.roles = {};
}
// merge auth and db user
authUser = {
uid: authUser.uid,
email: authUser.email,
emailVerified: authUser.emailVerified,
providerData: authUser.providerData,
...dbUser,
};
next(authUser);
});
} else {
fallback();
}
});
user = uid => this.db.doc(`users/${uid}`);
}
export default Firebase;
This two rect-higher-order Components:
First withAuthentication:
(src\components\Session\withAuthentication.js)
import React from 'react';
import AuthUserContext from './context';
import { withFirebase } from '../Firebase';
const withAuthentication = Component => {
class WithAuthentication extends React.Component {
constructor(props) {
super(props);
this.state = {
authUser: JSON.parse(localStorage.getItem('authUser')),
};
}
componentDidMount() {
this.listener = this.props.firebase.onAuthUserListener(
authUser => {
localStorage.setItem('authUser', JSON.stringify(authUser));
this.setState({ authUser });
},
() => {
localStorage.removeItem('authUser');
this.setState({ authUser: null });
},
);
}
componentWillUnmount() {
this.listener();
}
render() {
return (
<AuthUserContext.Provider value={this.state.authUser}>
<Component {...this.props} />
</AuthUserContext.Provider>
);
}
}
return withFirebase(WithAuthentication);
};
export default withAuthentication;
And withAuthorization:
(src\components\Session\withAuthorization.js)
import React from 'react';
import { withRouter } from 'react-router-dom';
import { compose } from 'recompose';
import AuthUserContext from './context';
import { withFirebase } from '../Firebase';
import * as ROUTES from '../../constants/routes';
const withAuthorization = condition => Component => {
class WithAuthorization extends React.Component {
componentDidMount() {
this.listener = this.props.firebase.onAuthUserListener(
authUser => {
if (!condition(authUser)) {
this.props.history.push(ROUTES.SIGN_IN);
}
},
() => this.props.history.push(ROUTES.SIGN_IN),
);
}
componentWillUnmount() {
this.listener();
}
render() {
return (
<AuthUserContext.Consumer>
{authUser =>
condition(authUser) ? <Component {...this.props} /> : null
}
</AuthUserContext.Consumer>
);
}
}
return compose(
withRouter,
withFirebase,
)(WithAuthorization);
};
export default withAuthorization;
This is normal. onAuthStateChanged receives an observer function to which a user object is passed if sign-in is successful, else not.
Author has wrapped onAuthStateChanged with a higher order function – onAuthUserListener. The HOF receives two parameters as functions, next and fallback. These two parameters are the sole difference when creating HOC's withAuthentication and withAuthorization.
The former's next parameter is a function which stores user data on localStorage
localStorage.setItem('authUser', JSON.stringify(authUser));
this.setState({ authUser });
while the latter's next parameter redirects to a new route based on condition.
if (!condition(authUser)) {
this.props.history.push(ROUTES.SIGN_IN);
}
So, we are just passing different observer function based on different requirements. The component's we will be wrapping our HOC with will get their respective observer function on instantiation. The observer function are serving different functionality based on the auth state change event. Hence, to answer your question, it's completely valid.
Reference:
https://firebase.google.com/docs/reference/js/firebase.auth.Auth#onauthstatechanged
https://reactjs.org/docs/higher-order-components.html

Why does the observer not react on updates in my store?

I'm trying to get mobx to work with my react hooks and I am starting off with a very simple example but it still does not work. I must have missed something but I have been trying for hours to find what that might be.
Here is my store:
import { observable, decorate } from 'mobx';
import { createContext } from 'react';
import { IUser } from '../models/IUser';
export class UserStore {
public user: IUser;
public getUser() {
myApi
.get(myUrl)
.then(response => {
this.user = response;
});
}
}
decorate(UserStore, {
user: observable,
});
export default createContext(new UserStore());
And here is the component printing the username of the user:
import React, { useContext } from 'react';
import { observer } from 'mobx-react-lite';
import UserStore from '../../stores/UserStore';
const MyComponent = observer(() => {
const userStore = useContext(UserStore);
return (
<div>
{userStore.user && userStore.user.userName}
</div>
);
});
export default MyComponent;
And to fire the api call, App does the following:
const App: React.FC = () => {
const userStore = useContext(UserStore);
useEffect(() => {
userStore.getUser();
}, []);
return(...);
};
export default App;
I can see that
1: App performs the call
2: The user is set to the response of the call
3: If I console log the userStore.user.userName after it has been set, it looks just fine.
The quirk is that the label in MyComponent never gets updated. Why?
I believe the bug is in decorate.
Changing the behavior from using the decorate syntax, to wrapping the FC with observable works just fine, like this:
const UserStore = observable({
user: {}
});
Another thing that also works is to have your stores as classes and using the old decorator syntax like this:
export class UserStore {
#observable public user: IUser;
}
This requires you to add the following to .babelrc:
["#babel/plugin-proposal-decorators", { "legacy": true }]

React - Provide Global Singleton From Composition Root

To start off, I have been working with React now for three months and the application I am building is testable, performant, etc... Nothing wrong. My experience pre-React is from the Angular world and what is considered a best practice there is not normally in react and vice-a-versa... I don't think what I am doing is wrong for the application I am building also don't want to miss anything big.
To make a long story short, inside of my App.tsx (using TypeScript) file I am creating a new instance of a singleton service and exporting it as a named export. For example, my app component looks something like:
import * as React from 'react'
... axios import here
import { HttpService } from './core/http.service';
import { Spinner } from './shared/spinner';
const axiosInstance = Axios.create({config here});
const httpService = new HttpService(axiosInstance);
class App extends React.Component {
props: any;
state: any;
constructor(props: any) {
super(props);
}
render() {
return(<App Root Component Here...>)
}
}
export { httpService };
export default App;
Imagine a component somewhere in the app that needs to use my singleton service. For the purposes of my question, I will call the component Home (home/Home.tsx).
import * as React from 'react'
import { httpService } from '../App';
class Home extends React.Component {
props: HomeProps;
state: HomeState;
constructor(props: HomeProps) {
super(props);
this.state = {
isLoading: false,
myData: []
}
this.loadData = this.loadData.bind(this);
}
componentDidMount() {
this.loadData();
}
// Using httpService here.
loadData() {
this.setState({isLoading: true});
httpService.get('/api/somedataurl').then((response) => {
const { data } = response;
this.setState({myData: data});
}).then(() => {
this.setState({isLoading: false});
});
}
myDataList() {
return (<ul>...{map of this.state.myData}</ul>)
}
render() {
return this.state.isLoading ? (<Spinner>) : this.myDataList();
}
}
export default Home;
I decided to use this implementation because I know that I can always rely on the App component to be available and there are no plans for server-side rendering.
As a simple solution, is there anything seriously flawed with providing my singleton service as a named export and importing into other components as needed?

How do you update a component in React when new data arrives on a stream?

I'm using the electron-boilerplate and Kurt Weiberth's tutorials to create my first node.js native app. I was able to create the app in the tutorial and now I want to add a component that gets updated when new tweets are streamed in given a query.
To do this, I created Tweet, TweetStream, and TweetFeed components, below. This kind of works, but I keep getting an error
Warning: flattenChildren(...): Encountered two children with the same key, ###############. Child keys must be unique; when two children share a key, only the first child will be used.
There are no duplicates when I look at the state for tweets, so I'm not sure why React is encountering them. Have I put something in the wrong place? Putting the Twit stream in a Component doesn't feel right, but I'm not sure where else it could go. I'd like to be able to update the query at some point so it seems like it needs to respond to an event when the query is updated.
Tweet
import React, { Component } from 'react';
class Tweet extends Component {
render() {
return (<li>
{this.props.tweet}
</li>);
}
}
export default Tweet;
TweetStream
import React, { Component } from 'react';
import Tweet from './Tweet';
class TweetStream extends Component {
render() {
return (
<ul>
{
this.props.tweets.map((tweet) => {
return <Tweet key={tweet.id} tweet={tweet.text} />;
})
}
</ul>
);
}
}
export default TweetStream;
TweetFeed
import React, { Component } from 'react';
const express = require('express');
const Twit = require('twit');
const app = express();
const server = require('http').createServer(app);
const io = require('socket.io')(server);
class TweetFeed extends Component {
handleTweet(tweet) {
this.state = {
id: tweet.id,
text: tweet.text
};
this.props.actions.addTweet(tweet);
}
render() {
const ts = this;
io.on('connection', function (socket) {
console.log('User connected. Socket id %s', socket.id);
socket.on('disconnect', function () {
console.log('User disconnected. %s. Socket id %s', socket.id);
});
});
const T = new Twit({
consumer_key: 'KEY',
consumer_secret: 'SECRET',
access_token: 'TOKEN',
access_token_secret: 'TOKEN_SECRET',
timeout_ms: 60 * 1000, // optional HTTP request timeout to apply to all requests.
});
const stream = T.stream('statuses/filter', { track: this.props.query });
stream.on('tweet', function (tweet) {
io.sockets.emit('tweet', tweet);
ts.handleTweet(tweet);
});
return (<div />);
}
}
export default TweetFeed;
Tweets Reducer
const initialTwitterState = [];
export default function reducer(state = initialTwitterState, action) {
switch (action.type) {
case 'ADD_TWEET':
return [{id: action.text.id, text: action.text.text}, ...state];
default:
return state;
}
}
These are called from a Home component
// #flow
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import React, {Component} from 'react';
import styles from './Home.css';
import TodoInput from './TodoInput';
import TweetStream from './TweetStream'
import TweetFeed from './TweetFeed'
import * as TodoActions from '../actions/todo';
import * as TwitterActions from '../actions/twitter';
class Home extends Component {
render() {
console.log(this.props)
return (
<div>
<TweetStream tweets={this.props.tweets} actions={this.props.tweet_actions}/>
<TweetFeed query={this.props.todos.query} tweets={this.props.tweets} todos={this.props.todos} actions={this.props.tweet_actions}/>
</div>
);
}
}
function mapStateToProps(state) {
return state;
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(TodoActions, dispatch),
tweet_actions: bindActionCreators(TwitterActions, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Home);

Resources