import React, { useEffect, useState } from "react";
import { dbService } from "./myFirebase";
function Editor({ userObj }) {
const [myContent, setMyContent] = useState("");
const [myContents, setMyContents] = useState([]);
const getValue = (event) => {
const { name, value } = event.target;
setMyContent({ ...myContent, [name]: value });
};
useEffect(() => {
console.log("1", myContents);
dbService.collection("contents").onSnapshot((snapshot) => {
const communityArray = snapshot.docs.map((doc) => ({
id: doc.id,
...doc.data(),
}));
setMyContents(communityArray);
console.log("2", myContents);
});
console.log("3", myContents);
}, []);
console.log("4", myContents);
const addContent = async (event) => {
event.preventDefault();
await dbService.collection("contents").add({
content: myContent,
createdAt: Date.now(),
});
setMyContent("");
};
return (
<div>
<button type="submit" onClick={addContent}>
{" "}
input{" "}
</button>
</div>
);
}
export default Editor;
When data is entered in this way, I coded it to be saved and received in Firebase.
If I print the log, it comes out as an empty array and then the value is entered.
So when I get it, if I say console.log("10", myContents[0].id);, it is properly received, but when I refresh it, TypeError: Cannot read property'id' of undefined appears.
I think it's because of that empty array, how do I fix it?
Are you using async await for useEffect? I tried, but it was the same, was it wrong?
Related
I am trying to render listed property information from an array of objects. I used this method in another part of my project with success, but in this instance, I am not getting anything at all.
here is the code I have
import { database } from "../../components/firebase";
import { ref, child, get } from "firebase/database";
import { useState, useEffect } from "react";
export default function Dashboard() {
const dbRef = ref(database);
const [users, setUsers] = useState([]);
const array = [];
const getData = () => {
get(child(dbRef, "users/"))
.then((snapshot) => {
const data = snapshot.val();
setUsers(data);
})
.catch((err) => {
console.log(err);
});
};
const getProperties = () => {
Object.values(users).forEach((user) => {
Object.values(user?.properties).forEach((property) => {
array.push(property);
console.log(property);
});
});
console.log(array);
};
useEffect(() => {
getData();
getProperties();
}, [dbRef]);
return (
<>
<div>Properties </div>
<div>
{array.map((property) => (
<div key={property.property_id}>
<h1>{property?.property_name}</h1>
<p>{property?.description}</p>
<p>{property?.rooms}</p>
<p>{property?.phone}</p>
</div>
))}
</div>
<p>oi</p>
</>
);
}
Nothing happens, it only prints "properties" and "oi"
getData is asynchronous. When you execute getProperties, your users state will still be its initial, empty array value.
You don't appear to be using users for anything else but assuming you want to keep it, the easiest way to drive some piece of state (array) from another (users) is to use a memo hook.
// this is all better defined outside your component
const usersRef = ref(database, "users");
const getUsers = async () => (await get(usersRef)).val();
export default function Dashboard() {
const [users, setUsers] = useState({}); // initialise with the correct type
// Compute all `properties` based on `users`
const allProperties = useMemo(
() =>
Object.values(users).flatMap(({ properties }) =>
Object.values(properties)
),
[users]
);
// Load user data on component mount
useEffect(() => {
getUsers().then(setUsers);
}, []);
return (
<>
<div>Properties </div>
<div>
{allProperties.map((property) => (
<div key={property.property_id}>
<h1>{property.property_name}</h1>
<p>{property.description}</p>
<p>{property.rooms}</p>
<p>{property.phone}</p>
</div>
))}
</div>
<p>oi</p>
</>
);
}
The memo hook will recompute allProperties any time users is changed.
If you don't need the users state, then there's not much need for the memo hook. Instead, just maintain the state you do need
const [allProperties, setAllProperties] = useState([]); // init with empty array
useEffect(() => {
getUsers().then((users) => {
setAllProperties(
Object.values(users).flatMap(({ properties }) =>
Object.values(properties)
)
);
});
}, []);
I was trying to set my value in the input value! but after that, I cannot write anything in the input field! I wanted to set values from the back end in value!
We are writing an admin channel to edit the article for that we need already existing article values to edit the article! What am I doing wrong! or Maybe you can suggest a better way to edit the article in the admin channel!
here is the code:
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import { useParams } from 'react-router';
const EditArticle = (props) => {
const [editValues, setEditValues] = useState([]);
const [changedValues, setChangedValues] = useState('');
console.log('values', editValues);
console.log('changed', changedValues);
const params = useParams();
console.log(params);
const resultsId = params.id;
console.log('string', resultsId);
const [authTokens, setAuthTokens] = useState(
localStorage.getItem('token') || ''
);
const setTokens = (data) => {
localStorage.setItem('token', JSON.stringify(data));
setAuthTokens(data);
// setToken(data['dataValues']['token']);
};
useEffect(() => {
const fetchData = async () => {
try {
const res = await axios.get(
`${process.env.REACT_APP_API_URL}/article/${resultsId}`
);
setEditValues(res.data);
} catch (err) {}
};
fetchData();
}, [resultsId]);
const inputValue = editValues;
const userToken = props.token;
return (
<div>
<form value={{ authTokens, setAuthTokens: setTokens }}>
<input
value={editValues.title || ''}
onChange={(input) => setChangedValues(input.target.value)}
type='text'
/>
<input
// ref={editValues.shortDesc}
value={editValues.shortDesc}
onChange={(input) => setChangedValues(input.target.value)}
type='text'
/>
<button type='submit'>send</button>
</form>
</div>
);
};
export default EditArticle;
your onChange handler is updating a different state property than what is being used as the value on the input (editValues vs changedValues).
Also you can pass a defaultValue to input that will get used as the default value only.
See more here https://reactjs.org/docs/uncontrolled-components.html
you can use just do it just using editValues. try this:
I just reproduced it without the api call to run the code.
import React, { useState, useEffect } from "react";
const EditArticle = (props) => {
const [editValues, setEditValues] = useState([]);
console.log("values", editValues);
const [authTokens, setAuthTokens] = useState(
localStorage.getItem("token") || ""
);
const setTokens = (data) => {
localStorage.setItem("token", JSON.stringify(data));
setAuthTokens(data);
// setToken(data['dataValues']['token']);
};
useEffect(() => {
const fetchData = async () => {
try {
//here get the data from api and setstate
setEditValues({ title: "title", shortDesc: "shortDesc" });
} catch (err) {}
};
fetchData();
}, []);
return (
<div>
<form value={{ authTokens, setAuthTokens: setTokens }}>
<input
value={editValues.title || ""}
onChange={(input) => setEditValues({title: input.target.value})}
type="text"
/>
<input
value={editValues.shortDesc}
onChange={(input) => setEditValues({shortDesc: input.target.value})}
type="text"
/>
<button type="submit">send</button>
</form>
</div>
);
};
export default EditArticle;
As you can see below in the dev tools screen shot, the child element does have props. My issue is I cannot get them to appear in the DOM when the component is first rendered. I have to click on the Link element again to re-render the component and only then does the map function work correctly (second screen shot). Another thing is I am using the same code in another component and it works fine. Help!
import React, { useState, useEffect } from 'react'
import firebase from 'firebase';
import NewsLetterListChildComponent from './children/NewsLetterListChildComponent';
import LoadingComponent from '../Loading/LoadingComponent';
function PublicNewsLetterListComponent({ user }) {
const [ newsLetters, setNewsLetters ] = useState([]);
const [ loading, setLoading ] = useState(false);
const [ errors, setErrors ] = useState(false);
useEffect(() => {
let requestCancelled = false;
const getNewsLetters = () => {
setLoading(true);
let newsLetterArray = [];
firebase
.firestore()
.collection('newsLetters')
.get()
.then((querySnapshot) => {
querySnapshot.forEach((doc) => {
const listRef = firebase.storage().ref().child('newsLetterImagesRef/' + doc.id);
listRef
.getDownloadURL()
.then(url => {
newsLetterArray.push({ id: doc.id, data: doc.data(), image: url });
})
.catch(error => console.log(error))
});
});
setNewsLetters(newsLetterArray);
setLoading(false);
};
getNewsLetters();
return () => {
requestCancelled = true;
};
}, []);
const renderContent = () => {
if(loading) {
return <LoadingComponent />
} else {
return <NewsLetterListChildComponent newsLetters={newsLetters} />
}
}
return renderContent();
}
export default PublicNewsLetterListComponent
import React from 'react';
import { ListGroup, ListGroupItem, Row, Col } from 'reactstrap';
function NewsLetterListChildComponent({ newsLetters }) {
return (
<div>
<Row>
<Col md={{size: 6, offset: 3}}>
<ListGroup>
{newsLetters.map((item, index) => {
return (
<ListGroupItem key={index} className="m-1" ><h1>{item.data.title} </h1><img src={item.image} alt={item.data.title} className="thumb-size img-thumbnail float-right" /></ListGroupItem>
);
})}
</ListGroup>
</Col>
</Row>
</div>
)
}
export default NewsLetterListChildComponent;
Initial render and the list group is empty
after the re-render and now the list group is populated
You need to call setNewsLetters when the data is resolved:
const getNewsLetters = async () => {
setLoading(true);
try {
const newsLetters = await firebase
.firestore()
.collection("newsLetters")
.get();
const data = await Promise.all(
newsLetters.docs.map(async (doc) => {
const url = await firebase
.storage()
.ref()
.child("newsLetterImagesRef/" + doc.id)
.getDownloadURL();
return {
id: doc.id,
data: doc.data(),
image: url,
};
})
);
setNewsLetters(data);
} catch (error) {
console.log(error);
} finally {
setLoading(false);
}
};
The useEffect code contains an async request and you are trying to update an array of newsLetters in state even before it will be fetched. Make use of Promise.all and update the data when it is available
useEffect(() => {
let requestCancelled = false;
const getNewsLetters = () => {
setLoading(true);
firebase
.firestore()
.collection('newsLetters')
.get()
.then((querySnapshot) => {
const promises = querySnapshot.map((doc) => {
const listRef = firebase.storage().ref().child('newsLetterImagesRef/' + doc.id);
return listRef
.getDownloadURL()
.then(url => {
return { id: doc.id, data: doc.data(), image: url };
})
.catch(error => console.log(error))
Promise.all(promises).then(newsLetterArray => { setNewsLetters(newsLetterArray);})
});
});
setLoading(false);
};
getNewsLetters();
return () => {
requestCancelled = true;
};
}, []);
If you check newletters with if, your problem will most likely be resolved.
review for detail : https://www.debuggr.io/react-map-of-undefined/
if (newLetters){
newLetters.map(item=> ...)
}
I'm new to react native, I have a personal project, I am trying to get data from Firestore cloud, but I keep getting this error on the screen change.
It works fine when I comment out the database code, so I'm wondering what could be the cause.
My code
import React from "react";
import auth from "#react-native-firebase/auth";
import firestore from "#react-native-firebase/firestore";
const ProfileStackScreen = ({ navigation }) => {
React.useEffect(() => {
const usr = auth().currentUser;
setuserData(prev => {
return { ...prev, uid: usr.uid };
});
}, []);
const userRef = firestore().collection("users");
const snapshot = userRef
.where("uid", "==", userData.uid)
.onSnapshot()
.then(console.log(uid))
.catch(error => {
Alert.alert(error.message);
});
const [userData, setuserData] = React.useState({
uid: ""
// other field go here
});
return (
<View>
<Text>{userData.uid}</Text>
</View>
);
};
export default ProfileStackScreen;
You can try below code
import React from 'react';
import auth from '#react-native-firebase/auth';
import firestore from '#react-native-firebase/firestore';
const ProfileStackScreen = ({ navigation }) => {
React.useEffect(() => {
const usr = auth().currentUser;
setuserData((prev)=>{
return {...prev,uid: usr.uid};
});
}, []);
React.useEffect(() => {
fetchdata()
}, [userData]);// Once userData value has been updated then only call fetchData()
const fetchdata = ()=>{
const userRef = firestore().collection('users').doc(userData.uid).get()
.then(function (doc) {
if (doc.exists) {
console.log("Document found!");
console.log(doc.data())
} else {
console.log("No such document!");
}
});
}
const [userData, setuserData] = React.useState({
uid: '',
// other field go here
});
return (
<View>
<Text>{userData.uid}</Text>
</View>
);
};
export default ProfileStackScreen;
#Maheshvirus is right. But I think you have tried to fetch data when userData.uid is not empty.
Try this way if looking for such a way.
import React from 'react';
import auth from '#react-native-firebase/auth';
import firestore from '#react-native-firebase/firestore';
const ProfileStackScreen = ({ navigation }) => {
React.useEffect(() => {
const usr = auth().currentUser;
setuserData((prev)=> {
return {...prev,uid: usr.uid};
});
}, []);
React.useEffect(() => {
if(userData.uid !== ''){
getData()
}
}, [userData]);
const getData = () => {
firestore()
.collection('users');
.where('uid', '==', userData.uid)
.onSnapshot()
.then(() => {
console.log(uid)
})
.catch((error)=> {
Alert.alert(error.message);
});
}
const [userData, setuserData] = React.useState({
uid: '',
// other field go here
});
return (
<View>
<Text>{userData.uid}</Text>
</View>
);
};
export default ProfileStackScreen;
I have created a custom Hook which fetches data from the server, sends dispatch to the store and returns data. It is usable if I want to list all comments in my app, however, I wanted to reuse it in the component where I need to fetch all comment replies, and that should happen only when certain button is clicked.
This is the hook down below.
import { useState, useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
const useFetch = (url, options, actionType, dataType) => {
const [response, setResponse] = useState([]);
const dispatch = useDispatch();
useEffect(() => {
(async () => {
const res = await fetch(url);
const json = await res.json();
setResponse(json);
})();
}, []);
useEffect(() => {
dispatch({ payload: response, type: actionType });
}, [response]);
const data = useSelector(state => state[dataType]);
return data;
};
export default useFetch;
Inside of my component I need to fetch replies when a button is clicked
const ParentComment = ({ comment }) => {
const handleShowMoreReplies = (e) => {
e.preventDefault();
}
let replies = useFetch(
`/api/comment_replies?comment_id=${comment.id}`,
null,
"REPLIES_RECEIVED",
"replies"
);
return (
<div>
<Comment comment={comment} />
<div className="replies">
{replies.map(reply => (
<Comment key={reply.id} comment={reply} />
))}
<a href="#" className="show_more" onClick={handleShowMoreReplies}>
Show More Replies ({comment.replies_count - 1})
</a>
</div>
</div>
);
};
If I put useFetch call inside of the handler I hget an error that Hooks can't be called there, but I need to call it only when the button is clicked so I don't know if there is a way to implement that.
I think you have subtle problems in your useFetch hook
1.your useEffect is having dep of ${url} and ${actionType } which you need to define.
2.In order to call this hook by clicking the button, you need to expose the setUrl as follows
const useFetch = ( initialUrl, options, actionType, dataType) => {
const [url, setUrl ] = useState(initialUrl);
const dispatch = useDispatch();
useEffect(() => {
const fetchData = async () => {
try {
const res = await fetch(url);
const data = await res.json();
dispatch({ payload: data, type: actionType });
} catch (error) {
console.log(error);
}
};
fetchData();
}, [url, actionType]);
const data = useSelector(state => state[dataType]);
return [ data, setUrl ];
};
export default useFetch;
Then when you are trying to use this hook, you can
const [data, fetchUrl] = useFetch(
`/api/comment_replies?comment_id=${comment.id}`,
null,
"REPLIES_RECEIVED",
"replies"
);
Then every time you have a button you can simply call
fetchUrl(${yourUrl}).
your hook will receive the new URL, which is the dep of your hook and rerender it.
Here is an related article
https://www.robinwieruch.de/react-hooks-fetch-data