React Hook useEffect multiple problems - javascript

This is my first time using a useEffect hook. what its doing is reading the database to see if a value returns and if a value returns thats the userToken which is sign of an account so it signs you in. However, I have 2 problems. The Link name button doesnt automatically sign you in, instead you have to type another letter in the input box after to sign in. I tried fixing this by adding connectUser under writeUserData on line 49 but that just makes the site crash. My second problem is I cant display {userToken} on the page after bewing signed in. I recieve Error: Objects are not valid as a React child (found: object with keys {username}). If you meant to render a collection of children, use an array instead.
import React, { useState, useEffect } from "react";
import {initializeApp} from 'firebase/app';
import { getDatabase, ref, set, child, get} from 'firebase/database';
export default function Username(props) {
const [userName, setUsername] = useState('');
const [userToken, setuserToken] = useState('')
const clientCredentials = {
apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
databaseURL: process.env.NEXT_PUBLIC_FIREBASE_DATABASE_URL,
projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
}
const app = initializeApp(clientCredentials);
const db = getDatabase();
const address = props.address;
function writeUserData(userId, userN, ) {
const reference = ref(db, 'users/' + userId);
set(reference, {
username: userN,
});
}
const dbRef = ref(getDatabase());
function connectUser() {
get(child(dbRef, `users/${address}`)).then((snapshot) => {
if (snapshot.exists()) {
setuserToken(snapshot.val());
} else {
console.log("No data available");
}
}).catch((error) => {
console.error(error);
});
}
const handleChange = (event) => setUsername(event.target.value);
function handleClick(e) {
writeUserData( address, userName);
}
useEffect(() => {
connectUser()
});
while (userToken == '')
return (
<>
<div>
<p className = "account-Info" >address: {address}</p>
</div>
<div id="form">
<h2 className='user-Create' > Username </h2>
<form id='set-User'>
<input id='username' className="user-Create" type='text' value={userName} onChange={handleChange}
required minLength='3' maxLength='30' pattern="[a-zA-Z0-9_]+" title='only letters, numbers, and underscores.'/>
<button className='user-Create' type="button" onClick={handleClick}>Link Name</button>
</form>
</div>
</>
);
while (userToken)
return (
<p className = "account-Info" >hi user</p>
);
}

First, please do not use while. Instead, use if.
if (!Boolean(userToken)) return <something />;
// No need to give condition here
// If userToken is not false, this will return
return <main />;
Second, useEffect's dependency, according to what you wrote, which is
useEffect(() => {
connectUser();
});
It means you execute connectUser() everytime any component update. Pass a dependency. If want no dependency, use [], which means execute connectUser() only once when component mounts.
useEffect(() => {
connectUser();
},[]);
Third, for Error: Objects are not valid as a React child (found: object with keys {username}), if you get similar error in future, use console.log() to debug. In your case, console.log(userToken) and go see in console of your browser.
Fourth, if you are handling about authentication, did you consider using firebase/auth?

For useEffect running multiple times, please pass in a dependency array. Check the react documentation on useEffect. You can start by giving an empty array as the second parameter to useEffect

Related

Uncaught ReferenceError: getAuth is not defined

Self taught coder here. Hopefully I'm explaining the issue adequately.
I'm trying to create some user authentication using firebase. I keep getting an error saying "Line 18:16: 'getAuth' is not defined". I'm confused because I was following a tutorial. I have tried reordering my imports as I read online that might be the reason for the error. For some reason I think my problem is in the config file and how I've initialized everything. I'm new to firebase. Any potential solves would be appreciated.
Here is my firebase.js config
import { initializeApp } from "firebase/app";
import { getFirestore } from 'firebase/firestore'
import { getAuth } from "firebase/auth";
import "firebase/storage"
const firebaseConfig = {
apiKey: "process.env.REACT_APP_FIREBASE_KEY",
authDomain: "uploadimg.firebaseapp.com",
projectId: "uploadimgofficial",
storageBucket: "uploadimg.appspot.com",
messagingSenderId: "MESSENGER_ID",
appId: "APP_ID",
measurementId: "MESAUREMENT_ID"
};
const app = initializeApp(firebaseConfig);
export const auth = getAuth()
// Init firestore
const db = getFirestore()
export { db }
And this is where I'm trying to set up the login page
import React from "react";
import "./Login.scss";
import { useState } from "react";
import { signInWithEmailAndPassword } from "firebase/auth";
import { auth } from "../../firebase";
const Login = () => {
const [error, setError] = useState(false);
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const handleLogin = (e) => {
e.preventDefault();
};
const auth = getAuth(); // <------ THIS IS ERROR LINE
signInWithEmailAndPassword(auth, email, password)
.then((userCredential) => {
// Signed in
const user = userCredential.user;
console.log(user)
})
.catch((error) => {
setError(true)
});
return (
<div className="login">
<form onSubmit={handleLogin} className="login__form">
<input
className="login__email"
onChange={e => setNewEmail(e.target.value)}
type="email"
placeholder="email" />
<input
The issue with your code is that you have imported the getAuth function incorrectly.
To fix this, you need to correct the name usage when calling the function.
As you have imported getAuth as auth in the import statement at the top of your file, you need to use it as so.
import { auth } from "../../firebase.js";
Right now, you are importing the auth variable, which is set to the return value of getAuth, as defined below in the firebase.js file.
// As you are calling the function with '()', you are getting the return value.
export const auth = getAuth();
To fix this, simply change your function call to auth. Also, rename the variable to something other than auth to avoid name collisions, and/or confusing naming patterns.
Also, as you are getting and setting the return value of getAuth to auth, the return value may not be a function1. In that case, you can't call auth with brackets (()), as, for instance, it may return an object.
// Change the variable name to what you want.
const userAuth = auth;
To check if it is a string/object/function/etc, you can use typeof (for checking/debugging; remove this line once done).
console.log(typeof auth); // Should return: 'string' | 'function' | 'object' | ...
Depending on the return type, you can change your usage to match it.
In conclusion, to fix your issue, you need to correctly use the name (auth instead of getAuth). Also, make sure to check the return value of getAuth, and use it appropriately!
1 Please correct me in the comments if I am incorrect; the return value is a function. In that case, I can remove that part. Thank you for the clarification!

Cannot Read properties of undefined React. Log only works on object

I'm currently trying to create a weather website using weatherapi, but I'm running into a problem. If I log the object of location there's no error, but if I try to log anything deeper than that object, like the name of the city it cannot read properties of undefined. If I comment out the log when it's using the name of the city, then uncomment it again and don't reload the page, then it will log the name of the city without error.
import React from 'react';
import './index.css';
import Navbar from "./components/Navbar"
import {useState} from "react"
import Weather from './components/Weather';
function App() {
const [inputData, setInputData] = useState({})
const [currentWeather, setCurrentWeather] = useState([])
const [loc, setLoc] = useState({loc:"Arlington"})
let apiKey = "xxxxxxxx"
// console.log("Location: "+ loc.loc)
React.useEffect(() =>{///Finds weather data of certain location
console.log(loc.loc)
fetch(`https://api.weatherapi.com/v1/current.json?key=${apiKey}&q=${loc.loc}&aqi=no`)
.then(res => {
if(res.ok){
return res.json()
}
})
.then(data => {
if(data !=null){//Only change currentWeather when there is data for it
setCurrentWeather(data)
}else{
alert(`${loc.loc} was not found`)
}
})
}, [loc])
React.useEffect(() =>{///Finds locations with search bar
fetch(`https://api.weatherapi.com/v1/search.json?key=${apiKey}&q=${loc.loc}&aqi=no`)
.then(res => res.json())
.then(data => {
if(data.loc == null){
}else{
setLoc(data)
}
})
}, [])
//console.log(currentWeather.location.name)
return (
<div className="App">
<Navbar inputData={inputData} setLoc={setLoc} setInputData={setInputData}/>
<Weather currentWeather={currentWeather}/>
</div>
);
}
export default App;
A few issues I see:
You are initializing currentWeather to an array, but it should probably be either undefined or an object ({}) based on your use of it. To fix this, use one of these:
const [currentWeather, setCurrentWeather] = useState()
// or
const [currentWeather, setCurrentWeather] = useState({})
You are trying to access currentWeather.location.name before you have updated currentWeather to have those properties. That's why you're getting that error.
The best solution for this is to use optional chaining https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining
So try this:
console.log(currentWeather?.location?.name)
Your console.log is just in the body of the function component, meaning it will only be called once (with the initial value), I think. To fix this, and log every time the value of currentWeather changes, you can do this instead:
React.useEffect(() => { console.log(currentWeather?.location?.name) }, [currentWeather])

How to show display data in React

I am new in react. Last day i tried to do work with Firebase in a react JS project. But i stuck on that. I set up sign in method according to the Firebase and i wanted to display all display name,email,photoUrl from the user who are sign in through sign in button.But i faced a problem i couldn't show these data but my console said i passed data successfully. I saw all data by console but why i don't show on the page. I try to fix all error but it's not change anything.My code is below:
import './App.css';
import firebase from "firebase/app";
import "firebase/auth";
import "firebase/firestore";
import firebaseConfig from './firebase.confige';
import React,{useState} from 'react';
// firebase.initializeApp(firebaseConfig)
if(!firebase.apps.length){
firebase.initializeApp(firebaseConfig);
}
function App() {
const [user,setUser] = useState(
{
isSignIn: false,
name: '',
email: '',
photo: ''
})
console.log(user)
const provider = new firebase.auth.GoogleAuthProvider();
const handleSignIn = ()=>{
firebase.auth().signInWithPopup(provider)
.then(res => {
const {displayName, photoURL, email} = res.user;
const signedInUser = {
isSignIn:true,
name: displayName,
email: email,
photo:photoURL
}
setUser(signedInUser);
// console.log(displayName,email,photoURL);
})
.catch(err => {
console.log(err);
console.log(err.message);
})
}
return (
<div className="App">
<button onClick={handleSignIn}>Sign In</button>
{
user.isSignedIn &&
<div>
<p> Welcome, {user.name}</p>
<p>Your email: {user.email}</p>
<img src={user.photo} alt=""/>
</div>
}
</div>
);
}
export default App;
firebase.Confige.js file is here:
const firebaseConfig = {
apiKey: "AIzaSyBPt45m9rGYcMJy5Ynq4PtEroNsSDYUcUM",
authDomain: "ema-john-simple-61839.firebaseapp.com",
projectId: "ema-john-simple-61839",
storageBucket: "ema-john-simple-61839.appspot.com",
messagingSenderId: "813068797804",
appId: "1:813068797804:web:297c9d66d20a005cd15549",
measurementId: "G-8DGK2DEVBS"
};
export default firebaseConfig;
first starting with sign in
console show user name email and photo
display show nothing
I didn't find out any solution please help me why this is happening i have to find out
You're checking for value user.isSignedIn in your JSX and, if truthy, are displaying your values.
However, earlier in your handleSignIn method, you adjust the property isSignIn value to true if the sign-in is successful.
You should be checking for isSignIn in your JSX instead of isSignedIn.

React Hooks State is reset after use a fuction

I stuck using React Hooks state. I am practicing with a weather app. I am requesting to Open Weather Map and a component called algolia-places-react. It connects to Alglia API to request name places.
I'm trying to make the request when the city name change. It looks it is working OK, But something is changed the value state to the initial state, before setting the new state. It happens on handleChange function. For example I added a new variable state and its function for checking that: count and setCount. When it enters to handleChange the console always prints: 1, except the first time. It isn't incresing. The same case with {name: 'boston', countryCode:'us'} always it is printing {name: 'boston', ountryCode:'us'}, except the first time.
import React, { useState, useEffect } from "react";
import { Segment, Form } from "semantic-ui-react";
import AlgoliaPlaces from "algolia-places-react";
export default function TempetureForm() {
const [count, setCount] = useState(0);
const [city, setCity] = useState({name: 'boston', countryCode:'us'});
const [data, setData] = useState(null);
const handleChange = ({ suggestion }) => {
console.dir(suggestion)
setCity({name: suggestion.name, countryCode: suggestion.countryCode})
setCount(count + 1)
console.dir(count) // Always prints 1
console.dir(city) // Always prints {name: 'boston', countryCode:'us'}
}
useEffect(() => {
console.dir(city)
fetch(`http://api.openweathermap.org/data/2.5/weather?q=${city.name},${city.countryCode}&appid=XXXXXXXXXXXXXXXXXXX`)
.then(res => res.json())
.then(data => setData(data));
},[city]);
return (
<Segment basic>
<Form>
<Form.Field width={6}>
<AlgoliaPlaces
placeholder='How weather is in ...'
options={{
appId: 'XXXXXXXXXXXXX',
apiKey: 'XXXXXXXXXXX',
language: 'en',
type: 'city',
}}
onChange={handleChange}
/>
</Form.Field>
{city.name + " " + city.countryCode}
<p>{data && data.weather[0].description}</p>
</Form>
</Segment>
);
}
first, checking whether the value updated just after calling setCount is not the way to go since the value is not updated yet. someone suggested it's because setting the value is asynchronous. i didn't know that but it makes sense.
the console.dir(city) in your useEffect should show the updated value after handleChange was called as it's being called when city is updated.

How to treat global user/user data in react?

I'm building a ReactJS project and I'm using something like this, to provide user data trough the app:
function Comp1() {
const [user, setUser] = useState({});
firebase.auth().onAuthStateChanged(function (_user) {
if (_user) {
// User is signed in.
// do some firestroe queryes to get all the user's data
setUser(_user);
} else {
setUser({ exists: false });
}
});
return (
<div>
<UserProvider.Provider value={{user, setUser}}>
<Comp2 />
<Comp3 />
</UserProvider.Provider>
</div>
);
}
function Comp2(props) {
const { user, setUser } = useContext(UserProvider);
return (
<div>
{user.exists}
</div>
)
}
function Comp3(props) {
const { user, setUser } = useContext(UserProvider);
return (
<div>
{user.exists}
</div>
)
}
//User Provider
import React from 'react';
const UserProvider = React.createContext();
export default UserProvider;
So, in this case, Comp1 provides user data to Comp2 & Comp3. The only problem is that when the user state changes or the page loads, it creates an infinite loop. If I'm not using an useState for storing the user data, then when it changes, the components do not get re-rendered. I also tried to do something like this in the index.js file:
firebase.auth().onAuthStateChanged(function (user) {
if (user) {
ReactDOM.render(<Comp1 user={user} />, document.getElementById('root'));
} else {
ReactDOM.render(<Comp1 user={{exists: false}} />, document.getElementById('root'));
}
});
But this worked a bit weirdly sometimes, and it's kinda messy. What solutions are there? Thanks.
Edit: I'm triening to do it in the wrong way? How should I provide all user data with only one firebase query?
I suggest using some state container for the application to easily manipulate with a user. The most common solution is to use Redux. With redux, you will be able to have a global state of your app. Generally, all user data stored in it. https://redux.js.org/
The other solution is to use MobX with simple store access. It doesn't use Flux pattern if you don't like it.
If you don't want to use a global store you can use HOCs to propagate your user data. Finally, you can use Context to React, but it is bad approach.
Let's, for example, choose the most popular representer of Flux architecture - Redux.
The first layer is the View layer. Here we will dispatch some action to change global, e.g user data.
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import { logIn, logOut } from 'actions'
export default class Page extends React.Component {
useEffect(() => {
firebase.auth().onAuthStateChanged((user) => {
logIn(user)
} else {
logOut()
})
}, [])
render () {
...
}
}
const mapDispatchToProps = dispatch => bindActionCreators({
logIn,
logOut
}, dispatch)
export default connect(null, mapDispatchToProps)(App)
The second layer are actions. Here we work we with our data, working with api, format state and so on. The main goal of actions is to create data to pass it to the reducer.
actions.js
export const logIn = user => dispatch => {
// dispatch action to change global state
dispatch({
type: 'LOG_IN',
payload: user
})
}
export const logOut = user => dispatch => {
dispatch({ type: 'LOG_OUT' })
}
The last thing is the reducers. The only goal of them is to change state. We subscribe here for some actions. Reducer always should be a pure function. State shouldn't be mutated, only overwritten.
appReducer.js
const initialState = {
user: null
}
export default function appReducer (state = initialState, action) {
const { type, payload } = action
switch(type) {
case 'LOG_IN':
return {
...state,
user: payload
}
case: 'LOG_OUT':
return {
...state,
user: null
}
}
}
Then, we can work with the global app state whenever we want.
To do it, we should use react-redux library and Provider HOC
const App = () =>
<Provider store={store}>
<Navigation />
</Provider>
Now, we can have access to any stores inside any component though react-redux connect HOF. It works with React Context API inside of it.
const Page2 = ({ user }) => {
//... manipulate with user
}
// this function gets all stores that you have in the Provider.
const mapStateToProps = (state) => {
user: state.user
}
export default connect(mapStateToProps)(Page2)
By the way, you should choose middleware to work with async code in redux. The most popular that is used in my example is redux-thunk.
More information you can find in the official documentation.
There you can find information about how to make initial store configuration
In Comp1, a new onAuthStateChanged observer is added to firebase.Auth on every render.
Put that statement in a useEffect like:
useEffect(() => {
firebase.auth().onAuthStateChanged(function (_user) {
if (_user) {
// User is signed in.
// do some firestroe queryes to get all the user's data
setUser(_user);
} else {
setUser({ exists: false });
}
});
}, []);
Issue:-
The issue for the loop to happen was due to the way Comp1 was written.
Any statement written within the Comp1 functional component will get executed after ever change in prop or state. So in this case whenever setUser was called Comp1 gets re-render and again it subscribes to the auth change listener which again executes setUser on receiving the data.
function Comp1() {
const [user, setUser] = useState({});
firebase.auth().onAuthStateChanged(function (_user) {
if (_user) {
// User is signed in.
// do some firestroe queryes to get all the user's data
setUser(_user);
} else {
setUser({ exists: false });
}
});
return (
<div>
<UserProvider.Provider value={{user, setUser}}>
<Comp2 />
<Comp3 />
</UserProvider.Provider>
</div>
);
}
Solution:-
You can use useEffect to make statements execute on componentDidMount, componentDidUdate and componentWillUnmount react's life cycles.
// [] is passed as 2 args so that this effect will run once only on mount and unmount.
useEffect(()=> {
const unsubscribe = firebase.auth().onAuthStateChanged(function (_user) {
if (_user) {
// User is signed in.
// do some firestroe queryes to get all the user's data
setUser(_user);
} else {
setUser({ exists: false });
}
});
// this method gets executed on component unmount.
return () => {
unsubscribe();
}
}, []);
I created a replica for the above case, you can check it running here
Take a look at this logic:
function Comp1() {
const [user, setUser] = useState({});
firebase.auth().onAuthStateChanged(function (_user) {
if (_user) {
// User is signed in.
// do some firestroe queryes to get all the user's data
setUser(_user);
} else {
setUser({ exists: false });
}
});
return (
<div>
<UserProvider.Provider value={{user, setUser}}>
<Comp2 />
<Comp3 />
</UserProvider.Provider>
</div>
);
}
You are calling setUser in any case. Instead, you should check whether user is already set and if so, whether it matches _user. Set user to _user if and only if _user differs from user. The way it goes, setUser is triggered, which triggers Comp2 and Comp3 change, which triggers the event above which triggers setUser.

Categories