React router-firebase private route with reactContext - javascript

I have this user context wit useEffect for firebase authentication. The whole app is wrapped around this.
export const UserProvider = ({ children }) => {
const [currentUser, setCurrentUser] = useState()
useEffect(() => {
auth.onAuthStateChanged((user) => {
setCurrentUser(user)
})
}, [])
return (
<UserContext.Provider value={currentUser}>{children}</UserContext.Provider>
)
}
In my component, I check if the user is authenticated, and I use router history to redirect the user if not.
const history = useHistory()
const currentUser = useContext(UserContext)
const handleRedirect = () => {
return history.push('/login')
}
return (
<>
{currentUser ? (
<div>
<MiniDrawer></MiniDrawer>
<Container maxWidth="md">
<h1>
Hello {currentUser.displayName}
</h1>
<Container>
<h2>Team Activity</h2>
</Container>
</Container>
</div>
) : (
handleRedirect()
)}
</>
Whenever I reload the page and I am logged in it will first execute the handleredirect method and then it will go to the login Page for like half a second) specified in this method and it renders the correct component (first in the condition). What to do about it? It seems that it takes a while for the app to realize if it user exists or not.

useEffect(() => {
setTimeout(() => {
fire.auth().onAuthStateChanged((user) => {
this.setState( { stateChanged: true } )
});
}, 1000)
}, [])

Related

useEffect with many dependencies

I am trying to fetch employees and here is what I am trying to do using useEffect
function AdminEmployees() {
const navigate = useNavigate();
const dispatch = useDispatch();
// fetching employees
const { adminEmployees, loading } = useSelector(
(state) => state.adminFetchEmployeeReducer
);
useEffect(() => {
dispatch(adminFetchEmployeeAction());
if (adminEmployees === "unAuthorized") {
navigate("/auth/true/false");
}
}, [adminEmployees, navigate,dispatch]);
console.log("Here i am running infinie loop");
console.log(adminEmployees);
return (
<>
{loading ? (
<Loader></Loader>
) : adminEmployees === "no employees" ? (
<h1>No Employees</h1>
) : (
<>
{adminEmployees &&
adminEmployees.map((employee) => {
return (
<div className="admin__employee__container" key={employee.id}>
<AdminSingleEmployee
employee={employee}
></AdminSingleEmployee>
</div>
);
})}
</>
)}
</>
);
}
Here I want to achieve 2 goals:
fetch adminEmployees
if (adminEmployees==='unAuthorized') then go to loginPage
but when doing this as in the code, it creates infinite loop.
How can I achieve the desired functionality?
Easy dirty path: split useEffect into 2
useEffect(() => {
if (adminEmployees === "unAuthorized") {
navigate("/auth/true/false");
}
}, [adminEmployees, navigate]);
useEffect(() => {
dispatch(adminFetchEmployeeAction());
}, [dispatch]);
Better way: handle that case in reducer or action creator to flip flag in the store and then consume it in component:
const { shouldNavigate } = useSelector(state => state.someSlice);
useEffect(() => {
if(shouldNavigate) {
// flipping flag back
dispatch(onAlreadyNavigated()));
navigate("/yourPath...");
},
[navigate, dispatch, shouldNavigate]
);

Why props.children are not rendered on the first render? I have to refresh the page, so then props.children appear on the page

I'm working on the web-app that has a role choice when you visit for the first time. After I choose the admin role for example, it sets the value of userRole in localStorage.
Here is App you can see that depending on the current role there are different routes.
After I have the role chosen, I'm redirected to the '/' route and I actually want to see the component that renders this route which is TableBoard in this case, but instead I firstly get render={() => <Container>its from app.tsx {route.component}</Container>}. So on the first render I only see its from app.tsx, then I refresh the page and everything is fine.
How to fix routing here?
Please ask if something is unclear.
function App() {
const currentRole = useReduxSelector(state => state.auth.currentRole);
const accessToken = localStorage.getItem('accessToken');
const role = localStorage.getItem('role');
const getCurrentRoutes = () => {
if (role === 'userCollaborator') {
return AccRoutes;
} else if (role === 'userAdmin') {
return AdminRoutes;
} else if (role === 'userHr') {
return HRRoutes;
} else if (role === 'userPartner') {
return Routes;
} else {
return RoutesAuth;
}
};
const routes = getCurrentRoutes();
let routesComponents;
if (currentRole && accessToken) {
routesComponents = routes?.map(route => {
return (
<Route
key={route.name}
exact
path={route.path}
render={() => <Container>its from app.tsx {route.component}</Container>}
/>
);
});
} else {
routesComponents = routes?.map(route => {
return (
<Route
key={route.name}
exact
path={route.path}
component={route.component}
/>
);
});
}
return (
<BrowserRouter>
<Provider store={store}>{routesComponents}</Provider>
</BrowserRouter>
);
}
export default App;
Component that should be rendered for the route I'm redirected:
export const TableBoard = () => {
return (
<Container>
<div>here I have a lot of other content so it should be rendered as props.children in Container</div>
</Container>
);
};
And the code for Components.tsx looks this way:
const Container: React.FC<ContainerProps> = ({children}) => {
return (
<div>
{children}
</div>
);
};
export default Container;
Because localStorage.getItem('role') won't make react rerender your component. You can make a provider to handle this situation.
For example:
// RoleProvider.jsx
const RoleContext = React.createContext();
export const useRoleContext = () => {
return React.useContext(RoleContext)
}
export default function RoleProvider({ children }) {
const [role, setRole] = React.useState();
React.useEffect(
() => {
setRole(localStorage.getItem('role'));
},
[setRole]
)
const value = React.useMemo(
() => ({
role,
setRole: (role) => {
localStorage.setItem('role', role);
setRole(role);
}
}),
[role, setRole]
);
return (
<RoleContext.Provider value={value}>
{children}
</RoleContext>
)
};
Then, you can put the RoleProvider above those children who need the role value. And use const { role } = useRoleContext() in those children. Since role is stored in a state. Whenever you update the role by the following code, those children who use const { role } = useRoleContext() will be rerendered.
const { setRole } = useRoleContext();
// Please put it in a useEffect or handler function
// Or this will cause "Too many rerenders"
setRole('Some Role You Want')

Child props is null when loading component from parent

I'm very new to React - so bear with me.
I'm trying to create a set of authentication protected routes/components. I have the below code that I am using to achieve that.
However, my issue is that when the child component loads, the userInfo is {} (i.e. not set). I know that the userInfo is being returned from the userService as my console.log returns the correct data.
Am I going about this right? I want to be able to protect a component/route, and pass through the userInfo to any protected route so I can do stuff with the data in the respective component.
const UserAuthenticatedRoute = ({component: Component, ...rest}) => {
const [isLoggedIn, setIsLoggedIn] = useState(false);
const [userInfo, setUserInfo] = useState({});
useEffect(async () => {
const r = await userService.isUserLoggedIn();
console.log(r.status);
if (r.status === 200){
setIsLoggedIn(true);
const userInfo = await r.json();
console.log(userInfo);
setUserInfo(userInfo);
} else {
setIsLoggedIn(false);
}
}, []);
return (
<Route {...rest} render={props => (
<>
<main>
{isLoggedIn &&
<Component {...props} userInfo={userInfo}/>
}
</main>
</>
)}
/>
);
};

How to render a component when state and props are changed?

I need to show the props value (which is a simple string). Each time I get new search results, I'm sending in the props. At the very first render the props will always be undefined.
Edit:
Header.jsx
function Header() {
const [searchString, setString] = useState('');
const onChangHandler = (e) => {
setString(e.target.value);
};
const activeSearch = () => {
if (searchString.length > 0) {
<Home searchResults={searchString} />;
}
};
return (
<div>
<input
placeholder='Search here'
value={searchString}
onChange={(e) => onChangHandler(e)}
/>
<button onClick={activeSearch}>Search</button>
</header>
</div>
);
}
I searched for previous stackoverflow questions and reactjs.org but found no answer.
Home.jsx
import React, { useEffect, useState } from 'react';
function Home({ searchResults }) {
const [itemSearchResults, setResults] = useState([]);
const [previousValue, setPreviousValue] = useState();
// What function will re-render when the props are first defined or changed ?
useEffect(() => { // Doesn't work
setResults(searchResults);
}, [searchResults]);
return (
<div>
<h3>Home</h3>
<h1>{itemSearchResults}</h1>
</div>
);
}
export default Home;
App.js
function App() {
return (
<div className='App'>
<Header />
<Home />
<Footer />
</div>
);
}
I'm sending the input string only to check if the props will change at the child component ("Home").
Any experts here know what's the problem?
Why it doesn't work?
It's because the Home component is never used, even if it's included in the following snippet:
const activeSearch = () => {
if (searchString.length > 0) {
<Home searchResults={searchString} />;
}
};
The activeSearch function has a couple problems:
it is used as an event handler though it uses JSX (outside the render phase)
it doesn't return the JSX (would still fail inside the render phase)
JSX should only be used within the render phase of React's lifecycle. Any event handler exists outside this phase, so any JSX it might use won't end up in the final tree.
The data dictates what to render
That said, the solution is to use the state in order to know what to render during the render phase.
function Header() {
const [searchString, setString] = useState('');
const [showResults, setShowResults] = useState(false);
const onChangHandler = (e) => {
// to avoid fetching results for every character change.
setShowResults(false);
setString(e.target.value);
};
const activeSearch = () => setShowResults(searchString.length > 0);
return (
<div>
<input
value={searchString}
onChange={(e) => onChangHandler(e)}
/>
<button onClick={activeSearch}>Search</button>
{showResults && <Home query={searchString} />}
</div>
);
}
useEffect to trigger effects based on changing props
And then, the Home component can trigger a new search request to some service through useEffect.
function Home({ query }) {
const [results, setResults] = useState(null);
useEffect(() => {
let discardResult = false;
fetchResults(query).then((response) => !discardResult && setResults(response));
// This returned function will run before the query changes and on unmount.
return () => {
// Prevents a race-condition where the results from a previous slow
// request could override the loading state or the latest results from
// a faster request.
discardResult = true;
// Reset the results state whenever the query changes.
setResults(null);
}
}, [query]);
return results ? (
<ul>{results.map((result) => <li>{result}</li>))}</ul>
) : `Loading...`;
}
It's true that it's not optimal to sync some state with props through useEffect like the article highlights:
useEffect(() => {
setInternalState(externalState);
}, [externalState]);
...but in our case, we're not syncing state, we're literally triggering an effect (fetching results), the very reason why useEffect even exists.
const { useState, useEffect } = React;
const FAKE_DELAY = 5; // seconds
function Home({ query }) {
const [results, setResults] = useState(null);
useEffect(() => {
let queryChanged = false;
console.log('Fetch search results for', query);
setTimeout(() => {
if (queryChanged) {
console.log('Query changed since last fetch, results discarded for', query);
return;
}
setResults(['example', 'result', 'for', query])
}, FAKE_DELAY * 1000);
return () => {
// Prevent race-condition
queryChanged = true;
setResults(null);
};
}, [query]);
return (
<div>
{results ? (
<ul>
{results.map((result) => (
<li>{result}</li>
))}
</ul>
) : `Loading... (${FAKE_DELAY} seconds)`}
</div>
);
}
function Header() {
const [searchString, setString] = useState('');
const [showResults, setShowResults] = useState(false);
const onChangHandler = (e) => {
// to avoid fetching results for every character change.
setShowResults(false);
setString(e.target.value);
};
const activeSearch = () => setShowResults(searchString.length > 0);
return (
<div>
<input
placeholder='Search here'
value={searchString}
onChange={(e) => onChangHandler(e)}
/>
<button onClick={activeSearch}>Search</button>
{showResults && <Home query={searchString} />}
</div>
);
}
ReactDOM.render(<Header />, document.querySelector("#app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<div id="app"></div>
Better solution: Uncontrolled inputs
Another technique in your case would be to use an uncontrolled <input> by using a ref and only updating the search string on click of the button instead of on change of the input value.
function Header() {
const [searchString, setString] = useState('');
const inputRef = useRef();
const activeSearch = () => {
setString(inputRef.current.value);
}
return (
<div>
<input ref={inputRef} />
<button onClick={activeSearch}>Search</button>
{searchString.length > 0 && <Home query={searchString} />}
</div>
);
}
const { useState, useEffect, useRef } = React;
const FAKE_DELAY = 5; // seconds
function Home({ query }) {
const [results, setResults] = useState(null);
useEffect(() => {
let queryChanged = false;
console.log('Fetch search results for', query);
setTimeout(() => {
if (queryChanged) {
console.log('Query changed since last fetch, results discarded for', query);
return;
}
setResults(['example', 'result', 'for', query])
}, FAKE_DELAY * 1000);
return () => {
// Prevent race-condition
queryChanged = true;
setResults(null);
};
}, [query]);
return (
<div>
{results ? (
<ul>
{results.map((result) => (
<li>{result}</li>
))}
</ul>
) : `Loading... (${FAKE_DELAY} seconds)`}
</div>
);
}
function Header() {
const [searchString, setString] = useState('');
const inputRef = useRef();
const activeSearch = () => {
setString(inputRef.current.value);
}
return (
<div>
<input
placeholder='Search here'
ref={inputRef}
/>
<button onClick={activeSearch}>Search</button>
{searchString.length > 0 && <Home query={searchString} />}
</div>
);
}
ReactDOM.render(<Header />, document.querySelector("#app"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<div id="app"></div>
Passing the state around
[The following line] brings the Home component inside the Header component, which makes duplicate
{searchString.length > 0 && <Home query={searchString} />}
In order to make the Header component reusable, the quickest way would be to lift the state up.
// No state needed in this component, we now receive
// a callback function instead.
function Header({ onSubmit }) {
const inputRef = useRef();
const activeSearch = () => {
// Uses the callback function instead of a state setter.
onSubmit(inputRef.current.value);
}
return (
<div>
<input ref={inputRef} />
<button onClick={activeSearch}>Search</button>
</div>
);
}
function App() {
// State lifted up to the parent (App) component.
const [searchString, setString] = useState('');
return (
<div className='App'>
<Header onSubmit={setString} />
{searchString.length > 0 && <Home query={searchString} />}
<Footer />
</div>
);
}
If that solution is still too limited, there are other ways to pass data around which would be off-topic to bring them all up in this answer, so I'll link some more information instead:
Thinking in React
What's the right way to pass form element state to sibling/parent elements?
Passing data to sibling components with react hooks?
Application State Management with React
How can I update the parent's state in React?
Top 5 React state management libraries in late 2020 (Redux, Mobx, Recoil, Akita, Hookstate)
if your props are passed as searchResults, then change the props to,
function Home({ searchResults}) {...}
and use
useEffect(() => { // code, function },[searchResults]) ).

React Component wont render with given state from the parent component

I need to render a component that has a route using react router. the first component has a button that when clicked needs to render another component that has state passed in from the first component. All objects and strings from the first component show in the console.log of the child component but it wont set state when I use setProfile(p).
const Member = (props)=> {
const [user, setUser] = useState({});
const [profile, setProfile] = useState({});
// run effect when user state updates
useEffect(() => {
const doEffects = async () => {
try {
const pro = socialNetworkContract.members[0]
console.log(pro)
const p = await incidentsInstance.usersProfile(pro, { from: accounts[0] });
const a = await snInstance.getUsersPosts(pro, { from: accounts[0] });
console.log(a)
console.log(p)
setProfile(p)
} catch (e) {
console.error(e)
}
}
doEffects();
}, [profile, state]);
const socialNetworkContract = useSelector((state) => state.socialNetworkContract)
return (
<div class="container">
<a target="_blank">Name : {profile.name}</a>
{socialNetworkContract.posts.map((p, index) => {
return <tr key={index}>
{p.message}
</tr>})}
</div>
)
}
export default Member;
This is the parent component I want to redirect from
const getProfile = async (member) => {
const addr = dispatch({ type: 'ADD_MEMBER', response: member })
console.log(member)
}
const socialNetworkContract = useSelector((state) => state.socialNetworkContract)
return (
<div>
{socialNetworkContract.posts.map((p, index) => {
return <tr key={index}>
<button onClick={() => getProfile(p.publisher)}>Profile</button>
</tr>})}
</div>
)
}
export default withRouter(Posts);
I have this component working when I don't have a dynamic route that needs data passing in from the parent component It's redirecting from.
This is my routes.js file
const Routes = () => {
return (
<Switch>
<Route path="/posts" exact component={Posts} />
<Route path="/member" exact component={Member} />
<Redirect exact to="/" />
</Switch>
)
}
export default Routes
https://codesandbox.io/s/loving-pine-tuxxb

Categories

Resources