Problem Context
I have a component named <Layout/> which contains my header and footer. It wraps all my routes as such:
<Layout>
<div className="container">
<Route path="/sign-in" exact component={SignIn}/>
<ProtectedRoute path="/" exact component={Home}/>
<ProtectedRoute path="/statistics" exact component={Statistics}/>
...
...
</div>
</Layout>
My <Layout/> component is defined as such:
const Layout = (props) => {
return (
<div>
<Header/>
{props.children}
<Footer/>
</div>
);
}
I did this so that i don't have to include my header and footer in each and every component.
Problem
In my header component, I am using the instance auth which indicates whether a user is logged in or not. The auth changes after the user signs in. However, even though the auth changes, my <Header/> component in the <Layout/> is not re-rendered. I have to manually refresh it to incorporate the changes.
My <Header/> component is defined as:
import auth from '../../auth';
const Header = () => {
return (
{auth.isAuth() ?
//render icon A
: null
}
<div>
Healthcare Management System //header title
</div>
{auth.isAuth() ?
//render icon B
: null
}
</div>
);
}
export default Header;
This is my auth class:
class Auth{
constructor(){
this.authenticated = JSON.parse(sessionStorage.getItem('profile'));
}
login(cb){
this.authenticated = JSON.parse(sessionStorage.getItem('profile'));
cb();
}
logout(cb){
this.authenticated = null;
cb();
}
isAuth(){
return this.authenticated;
}
}
export default new Auth();
What I require
What i want is supposedly simple; when the auth.isAuth() == null, show no icons and only the title (this behaves correctly). When, the auth.isAuth() != null, show icons A and B (this does not behave correctly and requires me to refresh the page in order to render the icons).
Somehow, i want the <Layout/> component to re-render once the auth changes. Thank you!
React component is only rerendered when its props or its state is changing, therefore you should put auth in a state which can be set as followed:
import auth from '../../auth';
const Layout = (props) => {
const [authenticated, setAuthenticated] = React.useState(false);
React.useEffect(() => {
if(!auth.isAuth()) {
setAuthenticated(false);
} else {
setAuthenticated(true);
}
}, []); // this useEffect logic is not working, you need to determine how you want to configure the condition in which authenticated state is set
return (
<div>
<Header authenticated={authenticated} />
{props.children}
<Footer/>
</div>
);
}
Header component:
const Header = ({ authenticated }) => {
return (
{authenticated ?
//render icon A
: null
}
<div>
Healthcare Management System //header title
</div>
{authenticated ?
//render icon B
: null
}
</div>
);
}
export default Header;
Related
Yo-yo everyone,
along my path of practicing the art of React, I noticed a bug that I couldn't seem to find a good source to help me understand what causes the problem.
My array in a child component takes too long to load, resulting in an error.
The data is fetched from "jsonplaceholder," users list.
Data is set as a state.
Sent to "UserProfilePage".
Sent to "UserProfileComponent".
Trying to reach the URL "/user/1" will not succeed since the object is undefined.
*) Commenting the "UserProfileComponent," and then uncomment without refreshing will successfully load the page.
*) Coping (not fetching) the data to the App.js, assigning it to the state, will not crush the system.
APP.js
import { Component } from "react";
import { Redirect, Route, Switch } from "react-router-dom";
import "./App.css";
import Navigation from "./components/header/Navigation";
import PostsLog from "./components/Posts/PostsLog";
import UserProfileCollection from "./pages/UserProfileCollection";
import UserProfilePage from "./pages/UserProfilePage";
const POST_ENDPOINT = "https://jsonplaceholder.typicode.com/posts";
const USER_ENDPOINT = "https://jsonplaceholder.typicode.com/users";
class App extends Component {
constructor() {
super();
this.state = {
exUsersArray: [],
exPostsArray: [],
};
}
async componentDidMount() {
try {
const responseUser = await fetch(USER_ENDPOINT);
const responsePost = await fetch(POST_ENDPOINT);
const dataResponseUser = await responseUser.json();
const dataResponsePost = await responsePost.json();
this.setState({ exUsersArray: dataResponseUser });
this.setState({ exPostsArray: dataResponsePost });
} catch (error) {
console.log(error);
}
}
render() {
const { exUsersArray, exPostsArray } = this.state;
console.log(exUsersArray);
return (
<div className="app">
<Navigation />
<main>
<Switch>
{/* REROUTES */}
<Route path="/" exact>
<Redirect to="/feed" />
</Route>
<Route path="/users" exact>
<Redirect to="/user" />
</Route>
{/* REAL ROUTES */}
<Route path="/feed">
<PostsLog usersInfo={exUsersArray} usersPosts={exPostsArray} />
</Route>
<Route path="/user" exact>
<UserProfileCollection usersInfo={exUsersArray} />
</Route>
{/* DYNAMIC ROUTES */}
<Route path="/user/:userId">
<UserProfilePage usersInfo={exUsersArray} />
</Route>
</Switch>
</main>
</div>
);
}
}
export default App;
UserProfilePage.js
import { useParams } from "react-router-dom"
import UserProfileComponent from "../components/UserProfileComponent";
const UserProfilePage = ({usersInfo}) => {
const params = useParams();
const foundUser = usersInfo.find((user) => Number(user.id) === Number(params.userId))
console.log("found user ", foundUser);
// console.log(usersInfo);
console.log(params, " is params");
return(
<div>
<UserProfileComponent userProfile={foundUser}/>
<p>Yo YO</p>
</div>
)
}
export default UserProfilePage;
UserProfileComponent
const UserProfileComponent = ({userProfile}) => {
console.log(userProfile)
return (
<div className="text-group">
<div className="wrap-post">
<p>
<strong>Info</strong>
</p>
<img
src={`https://robohash.org/${userProfile.Id}.png`}
id="small-profile"
alt="user profile in circle"
/>
<p><u><strong>ID</strong></u> : {userProfile.id}</p>
<p>Name: {userProfile.name}</p>
<p>#{userProfile.username}</p>
<p>Email: {userProfile.email}</p>
<p>
{userProfile.address.street} {userProfile.address.suite}<br/>
{userProfile.address.zipcode} {userProfile.address.city}
</p>
<p>Global position</p>
<p>{userProfile.address.geo.lat}, {userProfile.address.geo.lang}</p>
<p>{userProfile.phone}</p>
<p>{userProfile.website}</p>
<p>Company</p>
<p>{userProfile.company.name}</p>
<p>{userProfile.company.catchPhrase}</p>
<p>{userProfile.company.bs}</p>
</div>
</div>
);
};
export default UserProfileComponent;
Complete repository here.
I will be happy to any tips to help me understand what happened here.
Appreciation will be given to any tip that will help me be a better programmer.
Best wishes y'all.
it seems like usersInfo hasn't loaded a quick way to fix it is to just add this to the users component.
UserProfilePage.js
import { useParams } from "react-router-dom"
import UserProfileComponent from "../components/UserProfileComponent";
const UserProfilePage = ({usersInfo}) => {
const params = useParams();
if(!usersInfo) {
return <p>Loading...</p>
}
const foundUser = usersInfo.find((user) => Number(user.id) === Number(params.userId))
console.log("found user ", foundUser);
// console.log(usersInfo);
console.log(params, " is params");
return(
<div>
<UserProfileComponent userProfile={foundUser}/>
<p>Yo YO</p>
</div>
)
}
export default UserProfilePage;
UserProfileComponent.js
const UserProfileComponent = ({userProfile}) => {
if(!userProfile) {
return <p>Loading...</p>
}
console.log(userProfile)
return (
<div className="text-group">
<div className="wrap-post">
<p>
I see that you're rendering your compoonent without doing any null check in UserProfileComponent. Actually to be a better programmer or doing better work, you have to control every null case in order not to crash your app.
<p><u><strong>ID</strong></u> : {userProfile.id}</p>
<p>Name: {userProfile.name}</p>
<p>#{userProfile.username}</p>
<p>Email: {userProfile.email}</p>
<p>
{userProfile.address.street} {userProfile.address.suite}<br/>
{userProfile.address.zipcode} {userProfile.address.city}
</p>
<p>Global position</p>
<p>{userProfile.address.geo.lat}, {userProfile.address.geo.lang}</p>
<p>{userProfile.phone}</p>
<p>{userProfile.website}</p>
<p>Company</p>
<p>{userProfile.company.name}</p>
<p>{userProfile.company.catchPhrase}</p>
<p>{userProfile.company.bs}</p>
You'll see that there's no null check. It would be better if you have some null check on your userProfile
Also, my suggestion is, you can create a loading in your state.
Before sending your request, you can set the loading to true.
And when your loading is true, you can show some spinner or sth like that. When your request finishes, you can set the loading variable to false and you can show your data.
The main point is, always use a loading variable to check the loading state instead of checking the null | undefined state of your data.
I am creating an example dApp which carries the "Header" component at the top of the page for every page. So, I have created a header component and I make people connect to their MetaMask wallet in Header.tsx, which they do successfully and I keep their wallet ID with currentAccount state.
Header.tsx:
const Header: FunctionComponent<{}> = (props) => {
const [currentAccount, setCurrentAccount] = useState("");
async function checkAccount() {
const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' })
setCurrentAccount(accounts[0]);
}
return (
<header>
<div className="col-xl-3 col-lg-3 col-md-3 col-sm-3">
<ul>
<li>{!connectHidden && <button className="buy connect-wallet" onClick={connectWallet}><b>Connect Wallet</b></button>}</li>
</ul>{currentAccount}
<ul>
<li>{!disconnectHidden && <button className="buy connect-wallet" onClick={disconnectWallet}><b>Disconnect Wallet</b></button>}</li>
</ul>
</div>
</header>
);
};
export default Header;
But at my homepage, there are no codes for anything about getting user's wallet ID, I don't want to rewrite the code here as it is not the right way. As a newbie in react, I couldn't make the codes I have tried work like trying to import a function or variables. How do I call the currentAccount state in my home page?
Home.tsx:
const HomePage: FunctionComponent<{}> = () => {
useEffect(() => {
onInit()
return () => { }
}, [])
async function onInit() {
}
async function onClickMint() {
alert("mint");
}
return (
<>
<div>xx
</div>
</>
);
};
export default HomePage;
Here is my app.tsx and as you can see, I am seeing all of the components at once. But I want to use the state I have got at Header component in my Home component.
App.tsx:
import Header from './components/Header';
const App: FunctionComponent<{}> = () => {
return (
<Router>
<Header />
<Route exact path="/" component={HomePage} />
<Route exact path="/wallet" component={Wallet} />
<Footer />
</Router>
);
};
export default App;
Quick answer:
simply create your state at the top level (App.tsx) and give currentAccount, setCurrentAccount as props for the other components
App.tsx:
import Header from './components/Header';
const App: FunctionComponent<{}> = () => {
const [currentAccount, setCurrentAccount] = useState("");
return (
<Router>
<Header />
<Route exact path="/">
<HomePage currentAccount={currentAccount} setCurrentAccount={setCurrentAccount}/>
</Route>
<Route exact path="/wallet">
<Wallet currentAccount={currentAccount} setCurrentAccount={setCurrentAccount}/>
</Route>
<Footer />
</Router>
);
};
export default App;
Longer answer:
You need to inform yourself about redux or simply the useContext hook
For instance with the useContext hook you can create a context that will contain your state and that you will be able to access in any child component without using props which can be redundant when you have multiple children and grandchildren ...
Here you can find the documentation about how to use the useContext Hook :
https://reactjs.org/docs/hooks-reference.html#usecontext
As shown in the Flowchart (Flowchart), I want to hide the Header component if Main component renders the Login component. But if the Main component renders the Home component, I want to display the Header component.
This is App.js file:
import React from 'react'
import Header from 'Header'
import Main from 'Main'
import Footer from 'Footer'
function App() {
return (
<div className="App">
<Header />
<Main />
<Footer />
</div>
)
}
export default App;
This is Main.js file:
import React from 'react'
import Home from './Home'
import Login from './Login'
function Main() {
let user = true //Toggled by users
return (
<div>
{
user ? ( <Home /> ) : ( <Login /> )
}
</div>
)
}
export default Main
Putting the Header Component in Home itself will not solve the problem as I have to add much more pages and adding a Header component in every page doesn't seem efficient.
That's a use case of lifting the state up, here your user state should be in the scope of Header and Main.
Then just pass the user (isLogged in the example) to Main, via props or Context API.
function Main({ isLogged, toggleLogin }) {
return (
<div>
<button onClick={toggleLogin}>toggle</button>
{isLogged ? <>Home</> : <>Login</>}
</div>
);
}
function App() {
const [isLogged, toggle] = useReducer((p) => !p, false);
return (
<div className="App">
{!isLogged && <>Header</>}
<Main isLogged={isLogged} toggleLogin={toggle} />
<>Footer</>
</div>
);
}
So I originally created a smooth scroll effect with regular HTML css and js, but I'm trying to convert it into react, but I'm not sure if I am doing it properly.
I have a Navbar component and just set the path
<NavLinks to='/#posts'>Posts</NavLinks>
Then I have my Main Content sections which are reusable components that receive data and display a different design based on what data I pass in.
function InfoSection({
lightBg,
id
}) {
return (
<>
<div
id={id} //Passed in the id value
className={lightBg ? 'home__hero-section' : 'home__hero-section darkBg'}
>
// Rest of my JSX is inside here
</div>
</>
);
}
Then in my Data file I'll pass in the values
export const homeObjThree = {
id: 'posts',
lightBg: true,
}
Then I display my reusable components in my index.js page
function Home() {
return (
<>
<InfoSection {...homeObjThree} />
</>
);
}
Then I import that into my App.js file
function App() {
const [isOpen, setIsOpen] = useState(false);
const toggle = () => {
setIsOpen(!isOpen);
};
return (
<Router>
<Sidebar isOpen={isOpen} toggle={toggle} />
<Navbar toggle={toggle} />
<Home />
<Footer />
</Router>
);
}
So when I inspect, it shows the Home HTML with an id="posts" which I set from my data file, but when I click on the Post nav menu item it doesn't scroll down to the section. Nothing actually happens, so I can't tell if my approach even works for padding in ids into my data file or if I would need to write scroll functions to implement it.
I have below layout and in this layout Route pages are being rendered.
const Layout = () => {
return (
<main className="fadeIn animated">
{ this.props.children }
</main>
)
}
return (
<React.Fragment>
<Layout callFunction={ () => console.log('callFunction') }/>
{/*I will set the state using callFunction Function and then pass in modal*/}
<Modals data={this.state}/>
</React.Fragment>
)
The page that is being rendered inside the children using route is below
class Page extends React.Component {
return (
<React.Fragment>
ROUTE PAGE IS . I would like to call callFunction from here
<button onClick={parent.callFunction}>CALL</button>
</React.Fragment>
)
}
I would like to call callFunction function of Layout Component from Children Page in reactjs
If i understand you correctly, you wish "Layout" to run the "loadLayout" function
if so, just let the Layout know what it's receiving
const Layout = ({loadLayout}) => {
loadLayout()
return (
<main className="fadeIn animated">
{ this.props.children }
</main>
)
}
I'm not sure this is what you meant, we need more info, who loads who