getDocs function does not work in first run - javascript

I try to get all docs from Firebase when user connect from localhost:3000/ (it automatically redirects to /profile) but it does not work in the first run. Then when the page is refreshed by a user, it works. How can I run it in first try? Code below:
try {
const querySnapshot = await getDocs(collection(db, "links"));
await querySnapshot.forEach((doc) => {
if (stUser.uid == doc.data().uid) {
links.push(doc.id);
}
});
} catch (e) {
console.log(e);
}
Redirects:
function first() {
if (!isLoggedIn.isLoggedIn) return <Redirect to="/auth" />;
}
function second() {
if (isLoggedIn.isLoggedIn) return <Redirect to="/profile" />;
}
return (
<div>
<Route exact path="/">
<Redirect to="/auth" />
</Route>
{handleRoute(images)}
<Route path="/auth" component={Dashboard}>
{second()}
</Route>
<Route strict path="/profile" component={HomePage}>
{first()}
</Route>
</div>
);

The problem here is that you are not setting this variable in your state, as mentioned by Frank in the comments, so whatever changes you make to this variable might not be refreshed until actually force them by refreshing the page. I recommend you try something like the following code:
const updatedArray = links;
await querySnapshot.forEach((doc) => {
if (stUser.uid == doc.data().uid) {
updatedArray.push(doc.id);
}
});
setLinks(updatedArray);
Also, you will need to set this earlier in your code:
const [cars, setLinks] = useState([]);
finally, I would recommend you to check this documentation for a useState deepdive.

Related

localStorage removing elements in array after browser refresh

I have a react app and I want to persist the array of favorites when the page refreshes.
The data is set correctly, I can see it in the dev tools. But when i refresh the page, the data is removed. Any ideas why this may be?
Link to sandbox - https://codesandbox.io/s/sad-surf-sqgo0q?file=/src/App.js:368-378
const App = () => {
const [favourites, setFavourites] = useState([]);
useEffect(() => {
localStorage.setItem("favourites", JSON.stringify(favourites));
}, [favourites]);
useEffect(() => {
const favourites = JSON.parse(localStorage.getItem("favourites"));
if (favourites) {
setFavourites(favourites);
}
}, []);
return (
<FavContext.Provider value={{ favourites, setFavourites }}>
<HashRouter>
<Routes>
<Route path={"/"} element={<Dashboard />} />
<Route path={"/favorites"} element={<Favorites />} />
</Routes>
</HashRouter>
</FavContext.Provider>
);
};
export default App;
Make sure to set item if array is not empty
useEffect(() => {
if(favourites.length) localStorage.setItem("favourites", JSON.stringify(favourites));
}, [favourites]);
Yes, it is because when you reload the app the useEffect will trigger and you have an empty array in your favorite for the first time so it set the empty array in local storage.
You can fix it by adding a simple check
useEffect(() => {
if(favourites.length > 0){
localStorage.setItem("favourites", JSON.stringify(favourites));
}
}, [favourites]);
By this setItem only work when there is something in the favorites state

React Router v2.7 to v6 onEnter Migration

I am trying to migrate an application running on router v3, using the onEnter attribute for route after auth.
onEnter function in YAMain.jsx
static onEnter(store) {
return (nextState, replaceState, callback) => {
// Check if the user is logged in and allowed to make requests before letting them proceed
store.dispatch({
type: IS_LOGGED_IN,
onLoggedIn: () => {
store.dispatch({
type: GET_SELECTED_LOCATION_AND_CLASSROOM,
onSuccess: callback,
onFailure: () => {
// Get all of the required information from the store
const profile = getProfile(store.getState());
const selectedClassroom = getSelectedClassroom(store.getState());
const selectedLocation = getSelectedLocation(store.getState());
// No location has been selected by an admin, go to the locations page
if (profile.get('accessLevel') !== 'E' && !selectedLocation.get('id')) {
// Return early if this is the page we are going to
if (nextState.location.pathname.startsWith('/location')) {
return callback();
}
replaceState('/location');
return callback();
}
// No classroom has been selected by a user, go to the classrooms page
if (!selectedClassroom.get('id')) {
// Return early if this is the page we are going to
if (nextState.location.pathname.startsWith('/classroom')) {
return callback();
}
replaceState('/classroom');
return callback();
}
return callback();
}
});
},
onNotLoggedIn: () => {
replaceState('/login');
callback();
},
onFailure: (error) => {
if (isTimeGateError(error)) {
replaceState('/locked');
callback();
}
}
});
};
render function in YARouter.jsx, both classes extend component.
render() {
return (
<BrowserRouter>
<Routes>
{/* Handles the main logic and renders all but one of the pages */}
<Route
exact path="/"
element={<YAMain/>
}
onEnter={YAMain.onEnter(this.props.store)}
>
<Route path="/" element={YADashboard}/>
{/* Locations page displays the available list of locations */}
<Route
path="location"
element={YALocations}
/>
{/* Classrooms page displays the available list of classrooms */}
<Route
path="classroom"
element={YAClassrooms}
/>
this is not the entirety of the routing but should be enough to give you an idea of what's going on.
This is what I have now, I have tried a number of things suggested on various places. I'm trying to understand how I can either make this work so I can move on and work on this and make it proper later, OR make it proper now and fix this issue.
How can I go about ensuring proper redirection for user authentication, I've spent 2 days at work trying to figure anything out and am completely stuck.
Thanks.
If you are just looking for a way to call onEnter when the route is matched and rendered then I think calling it in a mounting useEffect hook in a wrapper component is probably what you are after.
Example:
const YAMainWrapper = ({ children, onEnter }) => {
useEffect(() => {
onEnter();
}, []);
return children;
};
...
render() {
return (
<BrowserRouter>
<Routes>
{/* Handles the main logic and renders all but one of the pages */}
<Route
path="/"
element={(
<YAMainWrapper onEnter={YAMain.onEnter(this.props.store)}>
<YAMain />
</YAMainWrapper>
)}
>
<Route path="/" element={<YADashboard />} />
{/* Locations page displays the available list of locations */}
<Route path="location" element={<YALocations />} />
{/* Classrooms page displays the available list of classrooms */}
<Route path="classroom" element={<YAClassrooms />} />
...
</Route>

How to call a react component from another component and pass required props

At the moment, I have the following routes in my App.js file:
<Switch>
<Route exact path="/new-job"
render={(props) => <NewJob jobName={jobName} setMenuSelection={handleMenuSelection} />}
/>
<Route exact path="/past-jobs"
render={(props) => <PastJobs setMenuSelection={handleMenuSelection} />}
/>
</Switch>
Now within my PastJobs component, I have the following button with onClick process:
<Button
onClick={() => {
setConfirmDialog({
isOpen: true,
title: `Copy Job ${item.id}?`,
onConfirm: () => { onCopy(item.job_info) }
})
}}
>
Copy
</Button>
that calls the following function:
const onCopy = (job_info) => {
setConfirmDialog({
...confirmDialog,
isOpen: false
})
history.push({
pathname: '/new-job',
state: { detail: job_info }
})
}
Within my <NewJob /> component, I have now setup the following as I thought I could access the state.detail but unfortunately it's null, i.e.:
import { useLocation } from 'react-router-dom';
function NewJob( { jobName, setMenuSelection } ) {
const { state } = useLocation();
if (typeof state !== 'undefined') {
const myVal = state.detail
console.log("myVal", myVal )
}
}
The issue that I am having and unsure how to approach is that within my onCopy function that is called from button onClick, how do I call the the <NewJob /> component whose path is exact path="/new-job" in App.js above and pass in the prop job_info ?
Direct calls to components actually does not exist. But what you are looking can be achieved in different ways.
Using state machine with event bus (redux, redux-saga)
Render props https://reactjs.org/docs/render-props.html
Bunch of callbacks drilled via props (HOC's)
Ref's https://reactjs.org/docs/refs-and-the-dom.html
I suggest to read more about them to actually understand if it matches your use case. Anyhow it is great experience to develop your skills also!

Make React only return after value is not null

I want React to return with a code only after a value was given to a variable.
This piece of code starts my ChatRoom:
<section>
{user ? <ChatRoom/> : <SignIn />}
</section>
Inside my function I have this code. I am trying to get information from Firestore.
firestore.collection("mailpairing").doc(auth.currentUser.email).get().then((d) => {
othermail=d.data().mail;
console.log("test123",auth.currentUser.email," + ",othermail);
firestore.collection("tokens").doc(othermail).get().then((da)=> {
othertoken=da.data().token;
console.log("másik token: " + othertoken);
});
});
At the end I have this return statement:
if(othertoken!=null){
console.log("token???");
return(
<>
<BrowserRouter>
<Route exact path="/" render={() => {window.location.href="tv/indexTv.html?m="+auth.currentUser.email+"&p="+othermail+"&t="+othertoken}} />
</BrowserRouter>
</>
)}
else{
return null;
}
If I leave the code like this, nothing gets returned. If I remove the if statements, the site gets returned I want, but the token I want to pass into the URL is undefined.
How can I fix this? I can't make the function async for some reason, asking for data from another function did not work.
Edit: Solved! I added a UseState.
const [HaveToken, setTokenValue] = useState(false);
I change it to true after I get the data from Firestore, and I added stuff to the return:
{HaveToken ? (
<BrowserRouter>
<Route exact path="/" render={() => {window.location.href="tv/intv.html?m="+auth.currentUser.email+"&p="+othermail+"&t="+othertoken}} />
</BrowserRouter>
):null}
Now it works well.
You can use async/await to get your token in this way, you just need to replace return with resolve function and you will be good to go.
const othertoken = await Promise((resolve, reject) => {
firestore
.collection('mailpairing')
.doc(auth.currentUser.email)
.get()
.then(d => {
othermail = d.data().mail;
console.log('test123', auth.currentUser.email, ' + ', othermail);
firestore
.collection('tokens')
.doc(othermail)
.get()
.then(da => {
othertoken = da.data().token;
console.log(`másik token: ${othertoken}`);
resolve(othertoken);
});
});
});
In a better way
const othertoken = await Promise(async (resolve, reject) => {
const d = await firestore.collection('mailpairing').doc(auth.currentUser.email).get();
const othermail = d.data().mail;
console.log('test123', auth.currentUser.email, ' + ', othermail);
const da = await firestore.collection('tokens').doc(othermail).get();
const othertoken = da.data().token;
console.log(`másik token: ${othertoken}`);
resolve(othertoken);
});
The reason why nothing gets returned is because the calls you are making to firestore are asynchronous. That is, when the execution actually reaches the if statement, the calls that you have made are not finished yet, and therefore othertoken has not received a value. As I see it, you can either make your method method asynchronous by marking it with the async keyword, and use await, or you can pass a callback to the function that accepts the token.
Using await:
const doc = await firestore.collection("mailpairing").doc(auth.currentUser.email).get();
const mail = doc.data().mail;
const token = await firestore.collection("tokens").doc(othermail).get();
const othertoken= da.data().token;
if(othertoken!=null){
return (
<>
<BrowserRouter>
<Route exact path="/" render={() =>
{window.location.href="tv/indexTv.html?m="+auth.currentUser.email+"&p="+othermail+"&t="+othertoken}} />
</BrowserRouter>
</>
)}
else {
return null;
}
If using a callback, then you would declare a method that accepts the token as an argument:
const myCallback = token => {
// do something with the token
}
and change your line othertoken=da.data().token; to myCallback(da.data().token) and pass myCallback to your function as argument.

Child component not rerendering on change of props

Here is the parent component :
state = {
books: undefined
}
componentDidMount() {
BooksAPI.getAll().then(books => {
this.setState({books},this.filterBooks);
});
}
filterBooks() {
this.currentlyReading = this.state.books.filter((book) => book.shelf === 'currentlyReading');
this.read = this.state.filter((book) => book.shelf === 'read');
this.wantToRead = this.state.filter((book) => book.shelf === 'wantToRead');
}
render() {
return (
<div className="app">
<Route path="/search" component={SearchBook} />
<Route exact
path="/"
render={() => <BookScreen
currentlyReading={this.currentlyReading}
read={this.read}
wantToRead={this.wantToRead}
/>} />
</div>
)
}
I expect that the props will change for BookScreen component after filterBooks is called and the component should rerender, but it doesn't. What am I doing wrong?
In my opinion, the best way for now, which still follows React's way is: after having books data, call 1 function only, and this function will process everything and update state after finishing (we pass the books object as parameter), like this:
componentDidMount() {
BooksAPI.getAll().then(books => {
this.filterBooks({books});
});
}
filterBooks = (books) => {
this.currentlyReading = books.filter((book) => book.shelf === 'currentlyReading');
this.read = books.filter((book) => book.shelf === 'read');
this.wantToRead = books.filter((book) => book.shelf === 'wantToRead');
this.setState({ books: books });
}
If you have any error, feel free to post here then we can get through together!
========
Added explanation on why the author's original code doesn't work:
Based on my little experience with React and JS:
When a new state is set, it may take time (maybe 100-300ms, that is why you execute the original this.filterBooks using the syntax this.setState({books},this.filterBooks)
=> This seems to be right, which means after a new state of books is already set, you can access it the filterBooks function.)
HOWEVER: after new state of books is set, the page will be re-rendered, and filterBooks will be executed (perhaps at the same time => not sure which one ends first, so let's say for example, this.currentlyReading is still undefined in render() if render() happens first, before the result of filterBooks is completely set!
In other words, React is Javascript, and Javascript's asynchronization is troublesome!
You can try to do this, Just update the books state like this:
componentDidMount() {
BooksAPI.getAll().then(books => this.setState({books}));
}
This will cause a re-render. But since you're not using the state directly to populate your child component, we need to call the filterBooks() inside the render() method.
render() {
this.filterBooks()
return (
<div className="app">
<Route path="/search" component={SearchBook} />
<Route exact
path="/"
render={() => <BookScreen
currentlyReading={this.currentlyReading}
read={this.read}
wantToRead={this.wantToRead}
/>} />
</div>
)
}
The call to that method will update the data that you pass as props to your child component.

Categories

Resources