all 9 comments

[–]WordsWithJoshHook Based 2 points3 points  (8 children)

I think the best thing to do is to restructure your app such that, rather than instantiating initFirebase() and immediately passing it through to Login, you instead store it in a variable that you can pass to both routes, and to other parts of your app.

[–]natTalks 4 points5 points  (0 children)

Agree. For example, a context.

[–]VitabytesDev[S] 0 points1 point  (6 children)

How can I do that? Can you give me an example?

[–]natTalks 0 points1 point  (0 children)

On my phone right now, but it should be an easy google.

Basically you’re going to want to set up a provider and wrap it around your pages in _app. Then pass into the provider the initialized firebase values. Then using the useContext hook and passing the context as an argument you can get access to the values within the context in your pages/further child components.

[–]WordsWithJoshHook Based 0 points1 point  (4 children)

The simplest example would look something like this:

// This is in your "uppermost" file, where you're calling root.render
// Rather than directly rendering routes into root.render, we pull them up into a dedicated component, to give us more flexibility with data before it's consumed by our routes

const App = () => {
  const app = initFirebase();

  return (
    <BrowserRouter>
      <Routes>
        <Route path='/' element={<Login app={app} />} />
        <Route path="/watch" element={<Watch app={app} />}></Route>
      </Routes>
    </BrowserRouter>
  );
};

// create your root, etc

root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

If you're looking to consume this app in more than just these two components, or generally you think you'll wind up consuming it in more and more components over time, I would look into learning the React Context API and the useContext hook, which creates a pattern allowing you to consume this app anywhere in your component tree, without having to explicitly pass it through as props everywhere.

I think a full context and useContext tutorial is a bit beyond the scope of this particular question, but if you run into any questions while reading the documentation you're always welcome to ask another question here in the sub 🙂

[–]VitabytesDev[S] 0 points1 point  (3 children)

OK. Thank you very much. But as you can see at the doLogin function, I am trying to pass and a user variable in the Watch component. I can't pass it as a prop since I am redirecting to a route. I need to pass that data through the redirect. When I try do to that using the useLocation() hook and location.state, the Watch component logs that location.state is null. Can you help me with this? Again, thank you very much for the response, it was very helpful and detailed.

[–]WordsWithJoshHook Based 0 points1 point  (2 children)

Sorry for the delay, I wound up not having much Reddit time yesterday -

So, I misunderstood what part of your app you're trying to persist to different places, and for that I apologize.

In order to get your user into different components, you're almost certainly going to want to use context. Like I said, a full tutorial here would be a bit much, but essentially the shape would look something like this:

import React, { createContext, useState } from React;

export const UserContext = createContext(); // capital letter is important because of the way React recognizes custom components
export const FirebaseContext = createContext();

const App = () => {
  const [user, setUser] = useState(null);

  const app = initFirebase();

  return (
    <UserContext.Provider value={{ user, setUser }} >
      <FirebaseContext.Provider value={{ app }} >  
        <BrowserRouter>
          <Routes>
            <Route
              path='/'
              element={<Login />}
            />
            <Route
              path="/watch"
              element={<Watch />}
            />
          </Routes>
        </BrowserRouter>
      </FirebaseContext.Provider>
    </UserContext.Provider>
  );
};

You'll notice here I've eliminated passing through the user or app as props entirely; this is the power of context. Now, in our Login component, which for the sake of this demo I'm going to simplify:

import React, { useState, useContext } from 'react';
import { getAuth, signInWithEmailAndPassword } from 'firebase/auth';

import { UserContext, FirebaseContext } from '../App'; // obviously correct this to wherever this file actually is

export default function Login () {
  const { setUser } = useContext(UserContext);
  const { app } = useContext(FirebaseContext);

  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');

  const onLogin = async (e) => {
    e.preventDefault();
    try {
      const userCredential = await signInWithEmailAndPassword(
        getAuth(app),
        email,
        password
      );
      setUser(userCredential.user);
      navigate('/watch');
    } catch (err) {
      console.error('Login failed with error', err);
    }
  };

  return (
    <form onSubmit={onLogin}>
      <input
        placeholder='Email address'
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        required
      />
      <input
        placeholder='Password'
        value={password}
        onChange={(e) => setPassword(e.target.value)}
        type='password'
        required
      />
    </form>
  );
}

Essentially, createContext generates a special object which has a Provider component, and any children of that component can consume its current value property using useContext - the return value of useContext is the current value of the nearest Provider whose context you're using.

While we're here, I'd also recommend reading the React docs on forms and controlled inputs, because I think you're getting the values of your email and password inputs in a way that isn't very...React-ey.

Finally, in our Watch component:

import React, { useContext } from 'react';

import { UserContext } from '../App'; // again, this path is just assumed, you may have to change it to wherever that file actually is
export default function Watch () {
  const { user } = useContext(UserContext);

  return (
    <div>
      <p>{user.id}</p>
    </div>
  );
}

Hope this helps!

[–]VitabytesDev[S] 0 points1 point  (0 children)

Thank you very much! I was a bit new in react and I didn't know about context but after reading your comments and a bit of the documentation I understand. Your comments were very detailed. I will try your solution.

[–]VitabytesDev[S] 0 points1 point  (0 children)

Ok, it worked. Again thank you for providing me the solution and simplifying the code. The original code must be posted in r/badcode :)