Alternative ways to pass react component into higher order function - reactjs

I'm using ReactJS higher order function to enhance the existing component with API fetching capability together with loading , error views.In order to become more reusable , I wish another programmer who use my HOC to be able to add customize loading , error views like this.
var FetchTest = fetchableContainer({
url:"https://jsonplaceholder.typicode.com/posts",
loadingView: <div>..Custom Loading..</div>,
noConnectionView: <div>.. Custom no connection view .. </div>,
errorView: <div>Custom Error View</div>
})(TestComponent);
Unfortunately , it shows error Message .
Element type is invalid: expected a string (for built-in components)
or a class/function (for composite components) but got: object.
Can someone tell me another solutions with clean and elegant code.Here is my fetchableContainer.
import React, {Component} from 'react';
import axios from 'axios';
import loadingView from './loadingView.js';
import errorView from './errorView.js';
import noDataView from './noDataView.js';
import noConnectionView from './noConnectionView.js';
//redux imports
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import { Link } from 'react-router';
const fetchableContainer = (json) => (BaseComponent) => {
let url = json.url || "";
let loadingView = json.loadingView || loadingView;
let errorView = json.errorView || errorView;
let noConnectionView = json.noConnectionView || noConnectionView;
let noDataView = json.noDataView || noDataView;
class FetchableContainer extends React.Component{
constructor(props){
super(props);
this.state = {
fetchData: null,
loading: false,
fetchError: null,
interntConnection: navigator.onLine?true:false,
};
}
componentDidMount(){
this.setState({loading: true});
axios.get(this.url,{
headers: {
'Access-Control-Allow-Origin' : '*',
'Access-Control-Allow-Methods' : 'GET,PUT,POST,DELETE,PATCH,OPTIONS',
}
}).then((response)=>{
this.setState({fetchData: response.data});
this.setState({loading: false});
}).catch((error)=>{
console.log("Erorr happen");
console.log(error);
this.setState({fetchError: error});
});
}
render(){
if(!this.state.interntConnection){
return <this.noConnectionView/>;
}
if(this.state.loading){
return <this.loadingView/>;
}
if(this.state.fetchError){
return <this.errorView/>;
}
return (
<BaseComponent {...this.props}{...this.state}/>
);
}
}
}
export default fetchableContainer;

Firstly, your FetchTest is undefined because the fetchableContainer doesn't return anything! Technically, it returns a function which returns nothing. You should return the class if you actually want to use it.
Also, this seems a strange way to create a component. Currently, it would be equivalent to do this:
var FetchTest = fetchableContainer({
url:"https://jsonplaceholder.typicode.com/posts",
loadingView: <div>..Custom Loading..</div>,
noConnectionView: <div>.. Custom no connection view .. </div>,
errorView: <div>Custom Error View</div>
}, TestComponent);
Container:
//...
const fetchableContainer = (json, BaseComponent) =>
class FetchableContainer extends React.Component {
//...
}
}
export default fetchableContainer;
The error message is probably displayed because you are trying to use the undefined FetchTest in some code that you haven't posted.
I would recommend creating a React Container in the standard way (https://redux.js.org/basics/usage-with-react#implementing-container-components) and passing in the parameters you need as props.
For example, it might look a little like this:
// Imports etc...
class FetchableContainer extends React.Component {
// ...
render() {
//...
return this.props.BaseComponent(/* Props */);
}
}
export default FetchableContainer;

I just found the error.This is because I was passing the JSX component and trying to fetch that JSX component.The error was solved by passing the function that return JSX component and fetch that function on render method of FetchableContainer like this.
var FetchTest = fetchableContainer({
url: 'https://jsonplaceholder.typicode.com/posts/',
errorView: ()=> <div>Custom Error View</div>,
LoadingView:()=><div>Custom loading</div> ,
NoConnectionView: ()=> <div>Custom no Connection</div>
})(TestComponent);
My Final code for FetchableContainer is as follow.
import React, {Component} from 'react';
import axios from 'axios';
import LoadingView from './LoadingView';
import ErrorView from './ErrorView';
import NoDataView from './NoDataView';
import NoConnectionView from './NoConnectionView';
//redux imports
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import { Link } from 'react-router';
const fetchableContainer = (json) => (BaseComponent) =>{
let LoadingFetchView = json.LoadingView || LoadingView;
let fetchUrl = json.url || "https://jsonplaceholder.typicode.com/posts/";
let ErrorFetchView = json.ErrorView || ErrorView;
let NoConnectionFetchView = json.NoConnectionView || NoConnectionView;
let NoDataFetchView = json.NoDataView || NoDataView;
class FetchableContainer extends React.Component{
constructor(props){
console.log("inside constructure");
console.log(LoadingView);
super(props);
this.state = {
fetchData: null,
loading: false,
fetchError: null,
interntConnection: navigator.onLine?true:false,
};
}
componentDidMount(){
this.setState({loading: true});
axios.get(fetchUrl,{
headers: {
'Access-Control-Allow-Origin' : '*',
'Access-Control-Allow-Methods' : 'GET,PUT,POST,DELETE,PATCH,OPTIONS',
}
}).then((response)=>{
this.setState({fetchData: response.data});
this.setState({loading: false});
}).catch((error)=>{
console.log("Erorr happen");
console.log(error);
this.setState({fetchError: error});
});
}
render(){
if(!this.state.interntConnection){
return <NoConnectionFetchView/>;
}
if(this.state.loading){
return <LoadingFetchView/>;
}
if(this.state.fetchError){
return <ErrorFetchView/>;
}
return (
<BaseComponent {...this.props}{...this.state}/>
);
}
}
return FetchableContainer;
}
export default fetchableContainer;

Related

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

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

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 => {

Cannot read property 'map' of undefined in ReactJS

I've got a problem with my ReactJS App with getting data from api. I still have an error: 'Cannot read property 'map' of undefined', and I have no idea why it's happening.
My code:
UsersList.js
import React from 'react';
import ReactDOM from 'react-dom';
import fetch from 'isomorphic-fetch';
import { Card, Container, Icon } from 'semantic-ui-react'
import User from './User'
class ProfilesList extends React.Component {
constructor(props) {
super(props);
this.state = {
users: [],
fetched: false,
loading: false,
};
}
componentWillMount(){
this.setState({
loading : true
});
fetch('http://58be98154389c312007f403f.mockapi.io/users/users').then(res => res.json())
.then(res =>{
this.setState({
users : res.results,
loading : true,
fetched : true
});
});
}
render() {
const {fetched, loading, users} = this.state;
let content;
if(fetched){
content = <div>{this.state.users.map((user,index) =>
<User key={user.username} id={index+1} user={user}/>)}</div>;
}
else if(loading && !fetched){
content = <p> Loading ...</p>;
}
else{
content = (<div></div>);
}
return (
<div>
{content}
</div>
);
}
}
export default ProfilesList;
User.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Card, Container, Icon } from 'semantic-ui-react'
class User extends React.Component {
render() {
const {user, id} = this.props;
return (
<Card
image='http://semantic-ui.com/images/avatar/large/elliot.jpg'
header={user.username}
meta='Friend'
description='Elliot is a sound engineer living in Nashville who enjoys playing guitar and hanging with his cat.'
extra={(
<a>
<Icon name='user' />
16 Friends
</a>
)}
/>
);
}
}
export default User;
Thanks for your help!
Your state.users is undefined when you try to do this.state.users.map() in your render function. So task #1 is to figure out why your fetch() is returning undefined and fix that. It's a good idea to build in some handling for cases when you get undefined results or other errors and set your state appropriately. Additionally, I tend to check that an expected array is not undefined before I try to map it, like this:
{
expectedArray
?
expectedArray.map(someMappingFunction)
:
<div>expectedArray was 'undefined' or otherwise 'falsy'</div>
}
Such a conditional statement is called a "ternary" and is very useful because it can be embedded in JSX as an "if/else" statement. It has the form (condition) ? (expression if true) : (expression if false). For example:
var foo = 7;
var bar = (foo % 2 == 0) ? "Even" : "Odd";
console.log(bar); // "Odd"

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);

Can't find an internal method in a React container component

I'm trying to get AJAX-retrieved data into a parent React component so it can be fed down to a child component. I'm using the popular pattern for this defined here where a comment list is used as the example:
components/CommentList.js
import React from 'React';
export class CommentList extends React.Component {
constructor(props) {
super(props);
}
render() {
return <ul> {this.props.comments.map(renderComment)} </ul>;
}
renderComment({body, author}) {
return <li>{body}—{author}</li>;
}
}
components/CommentListContainer.js
import React from 'React';
import { CommentList } from './CommentList';
export class CommentListContainer extends React.Component {
constructor() {
super();
this.state = { comments: [] }
}
componentDidMount() {
$.ajax({
url: "http://get/some/api",
dataType: 'json',
success: function(comments) {
this.setState({comments: comments});
}.bind(this)
});
}
render() {
return <CommentList comments={this.state.comments} />;
}
}
index.js: the entry point for webpack
import React from 'react'
import { render } from 'react-dom'
import { CommentListContainer } from './components/CommentListContainer';
window.React = React;
render(
<CommentListContainer />,
document.getElementById('nav__react-target')
)
When doing all this, I get the following error:
Uncaught ReferenceError: renderComment is not defined
I've move the methods around as well as tweaked the importing of dependencies in various spots with no luck. Any ideas?
Thanks in advance.
You don't have unguarded references to sibling methods with ES2015 classes (as you do in Java / C#, etc.) - instead you need to explicitly reference this to get at the methods of the class:
render() {
// I changed map(renderComment) to map(this.renderComment)
return <ul>{this.props.comments.map(this.renderComment)}</ul>;
}

Resources