Communicating between React Components to conditionally render component - reactjs

i have a react project whose setup is like
<Navbar/> //A components which contains a hamburger menu which on clicking should open sidebar from left side
<Sidebar/> //the component which opens from left side when hamburger icon from <Navbar/> component is clicked
So both my components are then called and store in another
Header.js
which contains code as
import React, { useState } from "react";
import Navbar from "./Navbar/Navbar";
import Sidebar from "./Sidedrawer/Sidedrawer";
export default function Index() {
return (
<React.Fragment>
<Navbar/>
<Sidebar />
</React.Fragment>
);
}
so how do i somehow on clicking hamburger icon in Navbar.js toggle state in Header.js so that i can conditionally render ""Sidebar"" component ?
I am getting confuse to make components communicate with each other cause its not parent - child relationship where i could use callback and all
Navbar.js
const Navbar = (props) => {
return (
<React.Fragment>
<i className="fas fa-bars"/> //This on clicking should somehow change state on header.js or here and then send updated state to the file in order to conditionally render Sidebar Component
</React.Fragment>
);
};

Related

How can i unmount a functional component from DOM on click of a Button

I would like to "Unmount a simple Functional Component" from the DOM. I searched a lot and saw most of the tutorials are based on Class Components and I did'nt see any simple example on it. My requirement is Unmounting a Functional component from the DOM on click on a button. Following is the component with the button which i likes to unmount when click on it. Hopes someone can help me to do it. Thanks in Advance !
import React from 'react'
function App() {
return (
<div className='app-component'>
<h2 className="h2">App Component</h2>
<button>Unmount This Component</button>
</div>
)
}
export default App
If you want to unmount a component then you can use conditional rendering where you can declare state in parent component and based on the state you can mount or unmount component as:
This is the parent component from where you want to mount or unmount
CODESANDBOX DEMO
If you want to toggle component once then you can do the following because there is only one way to change state i.e from Test component. If you unmount this component there is no way to mount it again. So you can also declare button in App component from where you can mount or unmount on click of a button. CODESANDBOX
Parent component
export default function App() {
const [isShowing, setIsShowing] = useState(true); // STATE
return (
<div className="App">
{isShowing && <Test setIsShowing={setIsShowing} />}
</div>
);
}
Child component
function Test({ setIsShowing }) {
function unmountComponent() {
setIsShowing(false);
}
return (
<div className="app-component">
<h2 className="h2">App Component</h2>
<button onClick={unmountComponent}>Unmount This Component</button>
</div>
);
}
You can use state flag for removing element like this:
import React from 'react'
function App() {
const [flag,setFlage]=useState(true);
return (
<div className='app-component'>
{flag?<h2 className="h2">App Component</h2>:null}
<button onClick={()=>setFlag(!flage)} >Unmount This Component</button>
</div>
)
}
export default App

Is it possible to pass different props from two different files in React?

Suppose I am calling a Component from the index.js and the same component is called off from a different file with a completely different set of props inside a return value. The below images are the two files and the component itself. The Modal component has a dependency on the Home component's toggle state. It will only render when the toggle is true, which is controlled by the button in Home Component. Keep in mind I'm rendering the Modal component in App.js and in Home.js
**This is a small demo of a more realistic situation.
After running this application Modal component is not rendering at all.
So the question is why is this happening internally and how to resolve this situation?
import React, {useState} from 'react'
import logo from './logo.svg';
import './App.css';
import Home from './components/Home';
import Modal from './components/Modal';
function App() {
const [logoR, setLogoR] = useState(logo);
const [currDate, setCurrDate] = useState(new Date());
console.log(currDate);
return (
<div className="App">
<header className="App-header">
<img src={logoR} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
<Home/>
<Modal logoR={logoR} currDate={currDate} />
</header>
</div>
);
}
export default App;
Github repo - React-props
In your handleClick() your return the Modal but nothing is being done with that. It's not being inserted into the DOM anywhere.
Something like this might get you closer.
import React, {usestate} trom 'react' 7.2K (gzipped: 3K)
import Modal from './Modal';
const Home = () => {
const [toggle, set Toggle] = usestate(false);
console.log(toggle);
const handleclick () => {
setToggle(!toggle);
}
return (
<div>
<button
className="App-button"
onclick={handleclick)>
whats up!
</button>
<Modal toggle={toggle} />
</div>
}
export default Home
I would suggest checking out react portals if you have a modal that you will want to trigger from various parts of the app. They have a good example of how to use this with a modal.
https://reactjs.org/docs/portals.html

React-Router Route is creating a null ref from unmounted functional component

Someone with React-Router experience can probably answer this. I have an application with a landing page, a dropdown menu on that landing page and a "sign up" button that <Link>'s to a simple page with a login form.
The dropdown menu is using the react ref system to put a ref on the dropdown menu. There is a click event listener that references this ref so that the state of the dropdown is not toggled when anything inside that ref is clicked. And there is a cleanup function for that click event listener so that when the menu is closed, the event listener is no longer present.
What is happening is that if the dropdown menu is open, and you click on the <Link> to the login page, it is creating a null ref on the dropdown menu from the click event listener, even though I'm trying to completely unmount these components and mount another one with react-router.
It's not clear to me whether I should be converting this LandingMenu component to class component so that I can use componentDidUnmount to trigger the cleanup function, or whether I am using react router incorrectly, or if there is another "correct" method to solve this problem that i'm not aware of. Here is the code.
App.js
import React, { Component } from "react";
import { BrowserRouter, Route, Switch } from "react-router-dom";
import "../styles/index.css";
import Landing from "./landing/Landing";
import Login from "./login/Login";
class App extends Component {
render() {
return (
<div>
<BrowserRouter>
<Switch>
<Route path="/" exact component={Landing} />
<Route path="/login" exact component={Login} />
</Switch>
</BrowserRouter>
</div>
);
}
}
export default App;
Login Button
import React from "react";
import { Link } from "react-router-dom";
import Button from "../common/Button";
import ChevronRight from "../icons/ChevronRight";
const LandingAuthentication = () => {
return (
<div className="login-buttons-wrapper">
<Link to="/login">
<Button Text="Sign in" Style="button-primary" Icon={<ChevronRight />} />
</Link>
</div>
);
};
export default LandingAuthentication;
Landing Menu
import React, { useState, useEffect, useRef } from "react";
import "../../styles/landing-menu.css";
const LandingMenu = ({ Title, Icon, children }) => {
// visibility toggle, default closed
const [open, setOpen] = useState(false);
// set menu ref to prevent double click event
const menuRef = useRef();
// close menu if clicked anywhere outside of menu
// on initial render, add click event listener
useEffect(() => {
const onBodyClick = (event) => {
// check if element clicked is inside of menu
// if so no action is required from this event listener so exit
if (menuRef.current.contains(event.target)) {
return;
}
// else close the menu
setOpen(false);
};
// add event listener and cleanup only if menu is open
if (open === true) {
document.body.addEventListener("click", onBodyClick);
// CLEANUP
// remove event listener
return () => {
document.body.removeEventListener("click", onBodyClick);
};
}
}, [open]);
// on click toggle state
return (
<nav className="landing-menu" ref={menuRef}>
<div
className="landing-menu-title-wrapper"
onClick={() => {
setOpen(!open);
}}
>
<h3 className="landing-menu-title">{Title}</h3>
<div className="landing-menu-title-icon">{Icon}</div>
</div>
<ul className={`dropdown ${open ? "visible" : "hidden"}`}>{children}</ul>
</nav>
);
};
export default LandingMenu;
The error ended up being caused by an update to the way React handles references in React 17. The solution is to update the if statement from:
if (menuRef.current.contains(event.target)) {
to
if (menuRef.current && menuRef.current.contains(event.target)) {

Passing props from parent to sibling in React

I am recreating a simple React app that I have already created in Angular. The React app has two components: one (menus.js) for a side menu and a second (content.js) that will display the content from each item in the menu when each link is clicked (just like an iframe of sorts). In the App.js I am making a REST API call to populate the state for the menus.js component. Note that both components are in the App.js as follows:
App.js
import React,{Component} from 'react';
import Menus from './components/menus';
import Content from './components/content';
class App extends Component {
state = {
menus: []
}
componentDidMount(){
fetch('api address')
.then(res => res.json())
.then((data)=> {
this.setState({menus: data})
})
.catch(console.log)
}
render(){
return (
<div>
<div><Menus menus={this.state.menus} /></div>
<div><Content /></div>
</div>
);
}
}
export default App;
here is the menu.js component; it takes a prop (menus) from App.js and builds the menu links with items from it:
import React from 'react';
import { BrowserRouter as Router, Link,} from "react-router-dom";
const Menus = ({ menus }) => {
return (
<Router>
<div>
<center><h1>Lessons</h1></center>
{menus.map(menu => (
<li key={menu.lesson}>
<Link to={`/lesson/${menu.lesson}`}>{menu.lessonName}</Link>
</li>
))}
</div>
</Router>
);
};
export default Menus;
Here is what I need - how do I pass items from the same prop (from App.js) to the content component? FYI - I need this to happen each time a link in the menu in menu.js is clicked (which is why a key is used in the list The simple idea is content will update in the content component each time a menu link in the menu component is clicked.
**content.js**
import React from 'react'
const Content = () => {
return (
<div>{menu.content}</div>
)
};
export default Content
Based on your description of the problem and what I can see of what you've written, it seems to me like you are trying to build an application where the menu persists, but the content changes based on menu clicks. For a simple application, this is how I would structure it differently.
<ParentmostComponent>
<MenuComponent someProp={this.state.some_data} />
<Switch>
<Route path={"/path"} render={(props) => <Dashboard {...props} someProp={this.state.some_other_data_from_parents} />
</Switch>
</ParentMostComponent>
This would allow the menu to always stay there no matter what the content is doing, and you also won't have to pass the menu prop to two components.
In your menu.js, attach the menu object to the Link
...
{menus.map(menu => (
<li key={menu.lesson}>
<Link to={{
pathname: `/lesson/${menu.lesson}`,
state: menu
}}> {menu.lessonName} </Link>
</li>
))}
...
In your content.js receive the menu like this:
import React from 'react'
const Content = () => {
console.log(props.location.state.menu.content);
return (
<div>{props.location.state && props.location.state.menu.content }</div>
)
};
export default Content
Read more here
Your example uses React Router, so this answer uses it as well.
First of all, move the Router up the hierarchy from Menus to App to make the router props available to all components. Then wrap your Content inside a Route to render it conditionally (i.e. if the path matches "/lesson/:lesson"):
class App extends Component {
state = {
menus: [
{
lesson: '61355373',
lessonName: 'Passing props from parent to sibling in React',
content: 'I am recreating a simple React app…'
},
{
lesson: '27991366',
lessonName: 'What is the difference between state and props in React?',
content: 'I was watching a Pluralsight course on React…'
}
]
}
render() {
const { menus } = this.state
return (
<Router>
<div>
<div><Menus menus={menus}/></div>
<Route path="/lesson/:lesson" render={({ match }) => (
<div><Content menu={menus.find(menu => menu.lesson === match.params.lesson)}/></div>
)}>
</Route>
</div>
</Router>
)
}
}
With the help of the render prop, you can access the router props (in this case match.params.lesson) before rendering your child component. We use them to pass the selected menu to Content. Done!
Note: The basic technique (without React Router, Redux etc.) to pass props between siblings is to lift the state up.

how can I dynamically attach sidebar components to multiple instances of sidebar?

I'm new to React and building out a design a ran into a problem.
I have a component called SideBar. I am using this component two times, one on each side of the page.
The problem is that I would like to add different components to each instance of the SideBar component. These would be lists of various items and etc. I assumed I could next component tags but the sidebar component doesn't output.
import React, { Component } from "react";
import SideBar from "./WorkspaceComponents/SideBar";
import ScrollerBox from "./WorkspaceComponents/SideBarComponents/ScrollerBox";
class Workspace extends Component {
render() {
return (
<main className="reely-workspace">
<SideBar position="SideBarLeft">
<ScrollerBox />
</SideBar>
<SideBar position="SideBarRight" />
</main>
);
}
}
export default Workspace;
Your sidebar component should receive a children prop and render it out.
Something like this:
class Sidebar extends Component {
render() {
const {children} = this.props;
return (
<div className="sidebar">
<h1>Sidebar</h1>
{children}
</div>
)
}
}
Check out this post on react docs to understand how to compose react components: https://reactjs.org/docs/composition-vs-inheritance.html
You can make your SideBar Component a wrapper component which wraps around the content given in it.
Making SideBar Component a Wrapper Component :
class Sidebar extends Component {
render() {
return (
<div className="sidebar">
// You can add any custom element here //
{this.props.children}
</div>
)
}
}
All your element passed inside the SideBar Component will now be rendered as a part of SideBar along with what it contains.
Way to consume the wrapper component:
<SideBar>
<Content1></Content1>
<Content2></Content2>
<Content3></Content3>
</SideBar>

Resources