Side Drawer Not Closing - javascript

I have a side-drawer nav component that opens when a hamburger icon is clicked (mobile view).
When the component is open, it shows nav links. The issue is that the side-drawer stays open even when a link is a clicked and a new page loads. What is the best way to close the side nav when a navlink gets clicked? Edit: added componentDidUpdate to app.js, side drawer still won't close
//side-drawer.jsx
import React from 'react'
import './side-drawer.css'
import { Link } from 'react-router-dom';
const SideDrawer = props => {
let drawerClasses = 'side-drawer';
if (props.show){
drawerClasses = 'side-drawer open';
}
return(
<nav className={drawerClasses}>
<div className="side-nav">
<Link className="side-items" to="/about">ABOUT</Link>
<Link className="side-items" to="/contact">CONTACT US</Link>
</div>
</nav>
);
};
export default SideDrawer;
//app.js
class App extends React.Component {
state = {
sideDrawerOpen: false
};
drawerToggleClickHandler = () => {
this.setState((prevState) => {
return {sideDrawerOpen: !prevState.sideDrawerOpen};
});
};
componentDidUpdate(prevProps) {
const { location } = this.props;
if (location !== prevProps.location && this.state.sideDrawerOpen) {
this.setState({ sideDrawerOpen: false });
}
}
render(){
return (
<div style={{height: '100%'}}>
<Header drawerClickHandler={this.drawerToggleClickHandler}/>
<SideDrawer show={this.state.sideDrawerOpen}/>
<Switch>
<Route exact path='/' component={Homepage}/>
<Route exact path='/about' component={About}/>
<Route exact path='/contact' component={Contact}/>
</Switch>
<Footer/>
</div>
);
}
}
//.side-drawer.css
.side-drawer {
height: 100%;
background: linen;
box-shadow: 1px 0px 7px rgba(0, 0, 0, 0.5);
position: fixed;
top: 0;
left: 0;
width: 70%;
max-width: 400px;
z-index: 200;
transform: translateX(-100%);
transition: transform 0.3s ease-out;
}
.side-drawer.open{
transform: translateX(0);
}
.side-nav{
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
}
.side-items {
margin: 0.5rem auto;
}
.side-items:hover{
text-decoration-line: overline underline;
color: #99bbff;
}
#media (min-width: 769px){
.side-drawer-open{
display: none;
}
}

Well if you're conditionally applying classnames to the SideDrawer component you can do this instead...
<nav className={props.show ? 'side-drawer open' : 'side-drawer'}>
Or you could conditionally show the component in the App component by...
return (
<div style={{height: '100%'}}>
<Header drawerClickHandler={this.drawerToggleClickHandler}/>
{
this.state.sideDrawerOpen && <SideDrawer />
}
<Switch>
<Route exact path='/' component={Homepage}/>
<Route exact path='/about' component={About}/>
<Route exact path='/contact' component={Contact}/>
</Switch>
<Footer/>
</div>
);

Here is the complete solution..
You need a specific handler for closing the sideDrawer and some css changes.
sideDrawer.js
import React from "react";
import style from "./side-drawer.module.css";
import { Link } from "react-router-dom";
const SideDrawer = (props) => {
let drawerClasses = [style.sideDrawer, style.Close];
if (props.show) {
drawerClasses = [style.sideDrawer, style.Open];
}
return (
<div className={drawerClasses.join(" ")}>
<div className={style.sideNav}>
<Link
to="/about"
// className={style.Items}
onClick={props.closed}
style={{ textDecoration: "none", color: "black" }}
>
ABOUT
</Link>
<Link
to="/contact"
// className={style.Items}
onClick={props.closed}
style={{ textDecoration: "none", color: "black" }}
>
CONTACT US
</Link>
</div>
</div>
);
};
export default SideDrawer;
App.js
import React, { Component } from "react";
import { Route, Switch } from "react-router-dom";
import SideDrawer from "./sideDrawer";
import { Homepage, About, Contact } from "./Components";
import Header from "./Header";
class App extends Component {
state = {
sideDrawerOpen: false
};
drawerToggleClickHandler = () => {
this.setState((prevState) => {
return { sideDrawerOpen: !prevState.sideDrawerOpen };
});
};
drawerClosedHandler = () => {
this.setState({ sideDrawerOpen: false });
};
render() {
return (
<div style={{ height: "100%" }}>
<Header drawerClickHandler={this.drawerToggleClickHandler} />
<SideDrawer
show={this.state.sideDrawerOpen}
closed={this.drawerClosedHandler}
/>
<Switch>
<Route exact path="/" component={Homepage} />
<Route exact path="/about" component={About} />
<Route exact path="/contact" component={Contact} />
</Switch>
{/* <footer /> */}
</div>
);
}
}
export default App;
side-drawer.module.css
module.css because of using join function in sideDrawer.js
.sideDrawer {
position: fixed;
width: 700px;
max-width: 80%;
height: 100%;
left: 0;
top: 0;
z-index: 600;
background-color: white;
box-sizing: border-box;
transition: transform 0.3s ease-out;
background-color: yellow;
}
#media (min-width: 900px) {
.sideDrawer {
display: none;
}
}
.sideNav a{
border: 1px solid black;
margin: 200px 30px ;
}
.Open {
transform: translateX(0);
}
.Close {
transform: translateX(-100%);
}
full source code

Related

React, implementing Dark-Light-Mode with localStrorage

I'm trying to use use-local-storage to achieve a theme changer in React.
App component:
import './App.css';
import React from 'react';
import { Navbar, SearchBar, Header, Main, Chart, Map } from './components';
import { Routes, Route, BrowserRouter } from 'react-router-dom';
import useLocalStorage from 'use-local-storage';
function App() {
// a function that toggles between darkmode and lightmode in css
const defaultDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
const [theme, setTheme] = useLocalStorage('theme', defaultDark ? 'dark' : 'light');
const switchTheme = () => {
const newTheme = theme === 'light' ? 'dark' : 'light';
setTheme(newTheme);
}
console.log(theme)
return (
<BrowserRouter>
<div className='App' data-theme={theme} >
<Header />
<SearchBar />
<Navbar switchTheme={switchTheme} />
<Routes>
<Route path="/" element={<Main />} />
<Route path="/map" element={<Map />} />
<Route path="/chart" element={<Chart />} />
</Routes>
</div>
</BrowserRouter>
);
}
export default App;
Navbar component:
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
import { faMapLocationDot, faChartLine, faHouseUser } from '#fortawesome/free-solid-svg-icons'
import React from 'react'
import { Link } from 'react-router-dom'
const Navbar = ({switchTheme}) => {
return (
<nav className='nav'>
<button onClick={switchTheme}>Toggle</button>
<Link to='/'>
<FontAwesomeIcon icon={faHouseUser} size='4x' color='blue' />
<br></br>
Home
</Link>
<Link to='/map'>
<FontAwesomeIcon icon={faMapLocationDot} size='4x' />
<br></br>
Map</Link>
<Link to='/chart'>
<FontAwesomeIcon icon={faChartLine} size='4x' color='red' />
<br></br>
Chart</Link>
</nav>
)
}
export default Navbar
CSS:
*, *::after, *::before {
box-sizing: border-box;
margin: 0;
padding: 0;
}
/****************** VARIABLES ******************/
:root {
--background-color:coral;
}
[data-theme="light"] {
--background-color:red;
}
[data-theme="dark"] {
--background-color:yellow;
}
body {
background-color:var(--background-color);
font-family: 'Roboto', sans-serif;
font-size: 16px;
color: #333;
line-height: 1.5;
margin: 2vmin;
}
.App {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
/********************** SearchBar **********************/
form {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
max-width: 500px;
margin: 0 auto;
}
form > svg {
margin-left: -20px;
}
input {
font-size:inherit;
border-radius: 1vmin;
border: .5px solid #ccc;
padding: .5rem;
}
input:focus {
border-color: #333;
}
nav {
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
padding: 0.5rem;
background-color: yellow;
border-bottom: 1px solid #eaeaea;
width: 10vw;
height: 50vh;
border: 3px dotted purple;
align-self: flex-start;
}
a {
text-decoration: none;
}
/* a:active {
/* do sth with selected Link
} */
I am getting the correct values from console.log(theme) in App.js but I can't change the background colour of the whole app.
Any ideas to solve this issue ?
You are having a cascading issue, as you are setting your theme on body, and trying to change it later through the App component. Add the data-theme on the body itself or on html, which comes before, not on something that comes after.
Adding this useEffect in App.js just before your return would work:
useEffect(() => {
document.documentElement.setAttribute("data-theme", theme);
}, [theme]);
Find the full example as well as a CodeSandbox below:
import './App.css';
import React, {useEffect} from 'react';
import { Navbar, SearchBar, Header, Main, Chart, Map } from './components';
import { Routes, Route, BrowserRouter } from 'react-router-dom';
import useLocalStorage from 'use-local-storage';
function App() {
// a function that toggles between darkmode and lightmode in css
const defaultDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
const [theme, setTheme] = useLocalStorage('theme', defaultDark ? 'dark' : 'light');
const switchTheme = () => {
const newTheme = theme === 'light' ? 'dark' : 'light';
setTheme(newTheme);
}
useEffect(() => {
document.documentElement.setAttribute("data-theme", theme);
}, [theme]);
return (
<BrowserRouter>
<div className='App'>
<Header />
<SearchBar />
<Navbar switchTheme={switchTheme} />
<Routes>
<Route path="/" element={<Main />} />
<Route path="/map" element={<Map />} />
<Route path="/chart" element={<Chart />} />
</Routes>
</div>
</BrowserRouter>
);
}
export default App;

Overflow is messing up with scrollY value

I have been trying to read the value of scrollY in my reactapp. But I found that it is being messed by overflow.
Here is the code that I used to read the scrollY:
import React from "react";
import{useEffect, useState} from "react"
export default function Test() {
const [offset, setOffset] = useState(0);
const setScroll = () => {
setOffset(window.scrollY);
};
useEffect(() => {
window.addEventListener("scroll", setScroll);
return () => {
window.removeEventListener("scroll", setScroll);
};
}, []);
return (
<div>
<div style={{color:"red"}}>{offset}</div>
</div>
);
}
Here is my app.jsx >>>
import React, { Component } from 'react';
import { Route } from 'react-router';
import { Layout } from './components/Layout';
import { Home } from './components/Home';
import { FetchData } from './components/FetchData';
import { Counter } from './components/Counter';
import abc2 from './img/abc2.jpg'
import './custom.css'
import Resume from './components/resumePage/Resume';
import Test from './components/Test.jsx';
var backGStyle={
backgroundImage:`url(${abc2})`,
minHeight: "100vh",
backgroundSize:'cover',
color:'red'
}
export default class App extends Component {
static displayName = App.name;
render () {
return (
<div className="app" style={backGStyle}>
<div className="mask">
<Layout>
<Route exact path='/' component={Home} />
<Route path='/counter' component={Counter} />
<Route path='/resume' component={Resume} />
<Route path='/test' component={Test} />
</Layout>
</div>
</div>
);
}
}
and my custom.css >>
a {
color: #0366d6;
}
code {
color: #E01A76;
}
.btn-primary {
color: #fff;
background-color: #1b6ec2;
border-color: #1861ac;
}
.backgroundBody{
position: relative;
}
.app .mask{
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
color:#fff;
background-color:rgba(0, 0, 0, 0.6);
overflow:auto;
}
.abc{
color: aqua;
}
And if I remove the overflow: auto from custome.css .app .mask, I am being able to get the value of scrollY but my background image will be messed up, there will be white space in the background of half of the content like this >>>>
try element.scrollTop since your using divs as masks. ScrollY is only for the scroll position of the window (HTML element). Your question is a little vague, if your trying to align you background images use background-position and background-size. also set your overflow to clip.
https://developer.mozilla.org/en-US/docs/Web/CSS/background-position

Ant Design Layout is not working as expected

I have a React application. I have decided to add a layout using Ant Design. And i followed below example.
https://2x.ant.design/components/layout/#components-layout-demo-custom-trigger
This is my component code. (Full.js)
import React, { Component, Fragment } from 'react'
import { Layout, Menu } from 'antd'
import Icon from '#ant-design/icons'
import './Full.css'
import ReactDOM from 'react-dom'
import DailyData from '../DailyData'
import Container from 'react-bootstrap/Container'
import Toggle from './Toggle'
const { Header, Footer, Sider, Content } = Layout;
class Full extends React.Component {
state = {
collapsed: false,
};
toggle = () => {
this.setState({
collapsed: !this.state.collapsed,
});
}
render() {
return (
<Layout style={{ height: "100vh" }}>
<Sider
trigger={null}
collapsible
collapsed={this.state.collapsed}
>
<div className="logo" />
<Menu theme="dark" mode="inline" defaultSelectedKeys={['1']}>
<Menu.Item key="1">
<Icon type="user" />
<span>nav 1</span>
</Menu.Item>
<Menu.Item key="2">
<Icon type="video-camera" />
<span>nav 2</span>
</Menu.Item>
<Menu.Item key="3">
<Icon type="upload" />
<span>nav 3</span>
</Menu.Item>
</Menu>
</Sider>
<Layout>
<Header style={{ background: '#fff', padding: 0 }}>
<Icon
className="trigger"
type={this.state.collapsed ? 'menu-unfold' : 'menu-fold'}
onClick={this.toggle}
/>
</Header>
<Content style={{ margin: '24px 16px', padding: 24, background: '#fff', minHeight: 280 }}>
Content
</Content>
</Layout>
</Layout>
);
}
}
export default Full;
I'm using this component as below in App.js file.
import React from 'react';
import 'bootstrap/dist/css/bootstrap.min.css';
import Header from './components/layout/Header.js'
import DailyData from './components/DailyData.js'
import Container from 'react-bootstrap/Container'
import Toggle from './components/layout/Toggle.js'
import Full from './components/layout/Full'
class App extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="App">
<Full />
</div >
);
}
}
export default App;
My Full.css file as below.
#components-layout-demo-custom-trigger .trigger {
font-size: 18px;
line-height: 64px;
padding: 0 16px;
cursor: pointer;
transition: color .3s;
}
#components-layout-demo-custom-trigger .trigger:hover {
color: #108ee9;
}
#components-layout-demo-custom-trigger .logo {
height: 32px;
background: #333;
border-radius: 6px;
margin: 16px;
}
But the output will display like this.
Well, I also used Ant design Sider and I am using version 4.4.1. Not sure what you want to acheive, so I am sharing the whole code with you.
import React from 'react';
import { Route, Switch, NavLink, Redirect, withRouter } from 'react-router-dom'
import {RouteComponentProps} from "react-router";
import { Layout, Menu } from 'antd';
import {
DesktopOutlined,
PieChartOutlined,
UserOutlined,
SettingOutlined
} from '#ant-design/icons';
import Shipper from '../shipper/shipper'
const { Header, Content, Footer, Sider } = Layout;
const { SubMenu } = Menu;
// Type whatever you expect in 'this.props.match.params.*'
type PathParamsType = {
param1: string,
}
// Your component own properties
type PropsType = RouteComponentProps<PathParamsType> & {
}
class Sidebar extends React.Component<PropsType> {
state = {
collapsed: false,
};
onCollapse = (collapsed: any) => {
this.setState({ collapsed });
};
render() {
let { pathname } = this.props.location;
return (
<Layout style={{ minHeight: '100vh' }}>
<Header className="header">
<div className="logo" />
<Menu mode="horizontal" theme="dark" className="flex-setting">
<SubMenu icon={<SettingOutlined />} title="Settings">
<Menu.ItemGroup title="Basic Settings">
<Menu.Item key="setting:2">
<a href={"/accounts/logout/"} target="_self">
Signout
</a>
</Menu.Item>
</Menu.ItemGroup>
</SubMenu>
</Menu>
</Header>
<Layout>
<Sider collapsible collapsed={this.state.collapsed} onCollapse={this.onCollapse}>
{/* <div className="logo" /> */}
<Menu theme="dark"
defaultSelectedKeys={['/shipper']}
selectedKeys={[pathname]}
mode="inline">
<Menu.Item key="/shipper" icon={<PieChartOutlined />}>
<NavLink to="/shipper">
<span>Shipper</span>
</NavLink>
</Menu.Item>
</SubMenu>
</Menu>
</Sider>
<Layout className="site-layout">
{/* <Header className="site-layout-background" style={{ padding: 0 }} /> */}
<Content style={{ margin: "16px 16px", background: "#fff" }}>
<div style={{ padding: 24, background: "#fff", minHeight: 360 }}>
<Switch>
<Route exact path="/" render={() => (
<Redirect to="/shipper"/>
)}/>
<Route path="/shipper">
<Shipper />
</Route>
</Switch>
</div>
</Content>
<Footer style={{ textAlign: 'center' }}>Copyrights © 2020 All Rights Reseverd by Company.</Footer>
</Layout>
</Layout>
</Layout>
);
}
}
export default withRouter(Sidebar);
My css is following:
.header {
display: inherit;
}
.logo {
height: 32px;
width: 35px;
margin: 16px;
background-image: url("main-logo.jpg");
}
.flex-setting {
display: flex;
flex-direction: row-reverse;
width: 100%;
}
Alot of things not necessary for you, so remove that part. Once you follow this, you will achieve something link this:
Remove " #components-layout-demo-custom-trigger" from css file and it will work.
I am sharing my css file.
App.css
#import "~antd/dist/antd.css";
.trigger {
font-size: 18px;
line-height: 64px;
padding: 0 24px;
cursor: pointer;
transition: color 0.3s;
}
.trigger:hover {
color: #1890ff;
}
.logo {
height: 32px;
background: rgba(255, 255, 255, 0.2);
margin: 16px;
}
.site-layout .site-layout-background {
background: #fff;
}
Because the example has trigger class ID # component-layout-demo-custom-trigger in front of it but the copied code also has # component-layout-demo-custom-trigger removed
enter image description here

No animation when using CSSTransition and Group Transition in Reactjs

In REACTJS, I am working with creating a simple App that contains Transitions. I have imported CSSTransitions and Group Transition in my file but when I am trying to apply CSSTransition for some of my news item but I am not getting the animation. It is as if it doesn't even exist.
I can see that my items are wrapped inside the component, but I cannot get them to animate.
Could someone please help me figure out what I'm doing wrong?
import React, { Component } from 'react';
import {CSSTransition, TransitionGroup} from 'react-transition-group';
import {Link} from 'react-router-dom';
import Axios from 'axios';
import {URL} from '../../../Config';
import styles from './NewsList.module.css';
export default class NewsList extends Component {
state={
items:[],
start: this.props.start,
end: this.props.start+this.props.amount,
amount: this.props.amount
}
componentWillMount(){
this.request(this.state.start,this.state.end)
}
request=(start,end)=>{
Axios.get(`${URL}/articles?_start=${start}&_end=${end}`)
.then(response=>{
this.setState({
items:[...this.state.items,...response.data]
})
})
}
loadMore=()=>{
let end = this.state.end + this.state.amount
this.request(this.state.end, end)
}
renderNews=(type)=>{
let template= null;
switch(type){
case('Card'):
template= this.state.items.map((item, i)=>(
<CSSTransition
classNames={{
enter: styles.newList_wrapper,
enterActive: styles.newList_wrapper_enter
}}
timeout= {500}
key={i}
>
<div>
<div className={styles.newslist_item}>
<Link to={`/articles/${item.id}`}>
<h2>{item.title}</h2>
</Link>
</div>
</div>
</CSSTransition>
)
);
break;
default:
template = null;
}
return template;
}
render() {
return (
<div>
<TransitionGroup
component="div"
className="list"
>
{this.renderNews(this.props.type)}
</TransitionGroup>
<div onClick={this.loadMore}>
Load More
</div>
</div>
);
}
}
.newslist_item{
border: 1px solid #f2f2f2;
background: #ffffff;
margin-top: 0px;
padding: 8px 5px 0 5px;
}
.newslist_item h2{
font-size: 13px;
line-height: 21px;
margin: 5px 0;
color: #525252
}
.newslist_item a {
text-decoration:none;
}
.newsList_wrapper{
box-sizing: border-box;
opacity: 0;
transform: translateX(-100%);
transition: all .5s ease-in;
}
.newsList_wrapper_enter{
opacity: 1;
transform: translateX(0%);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
classNames={{
enter: **styles.newsList_wrapper**,
enterActive: **styles.newsList_wrapper_enter**
There was a typo with the classnames. An S was missing.

React Context - "this" is undefined

I am using React Context in order to manage a global state.
So I have defined my Context with its Provider and its Consumer.
I have my videoplaying-context.js
import React from "react";
import { createContext } from 'react';
// set the defaults
const VideoContext = React.createContext({
videoPlaying: false,
setPlayingVideo: () => {}
});
export default VideoContext;
In my _app.js I have:
import App from 'next/app'
import { PageTransition } from 'next-page-transitions'
import VideoContext from '../components/videoplaying-context'
class MyApp extends App {
setPlayingVideo = videoPlaying => {
this.setState({ videoPlaying });
};
state = {
videoPlaying: false,
setPlayingVideo: this.setPlayingVideo
}
render() {
console.log('new _app.js defalt page');
const { Component, pageProps, router, state } = this.props
return (
<React.Fragment>
<VideoContext.Provider value={this.state}>
<PageTransition timeout={300} classNames="page-transition">
<Component {...pageProps} key={router.route} />
</PageTransition>
</VideoContext.Provider>
</React.Fragment>
)
}
}
export default MyApp
and then in one of my file I have put the Consumer:
import Layout from "../components/Layout";
import ReactPlayer from 'react-player
import VideoContext from '../components/videoplaying-context'
class Video extends React.Component {
constructor(props) {
super(props);
this.triggerVideo = this.triggerVideo.bind(this);
}
triggerVideo(event) {
console.log("click");
/* doing other stuff here... */
}
render() {
return (
<VideoContext.Consumer>
{context => (
<Layout>
<h1>Videos</h1>
<div>
<div className="group" id="process-video">
<div
className="poster-image"
onClick={() => {
this.triggerVideo.bind(this);
context.setPlayingVideo(true);
}}
/>
<ReactPlayer
url="https://vimeo.com/169599296"
width="640px"
height="640px"
config={{
vimeo: {
playerOptions: {
thumbnail_url: "http://placehold.it/640x640.jpg",
thumbnail_width: 640,
thumbnail_height: 640
}
}
}}
/>
</div>
</div>
<style jsx global>{`
.group {
position: relative;
height: 0;
overflow: hidden;
height: 640px;
width: 640px;
}
.poster-image {
background: url("http://placehold.it/640x640.jpg") center center;
background-size: cover;
bottom: 0;
left: 0;
opacity: 1;
position: absolute;
right: 0;
top: 0;
z-index: 10;
height: 640px;
width: 640px;
transition: all 0.4s ease-in;
}
.poster-image + div {
position: absolute;
top: 0;
left: 0;
width: 640px;
height: 640px;
}
.poster-image.video--fadeout {
opacity: 0;
}
`}</style>
</Layout>
)}
</VideoContext.Consumer>
);
}
}
export default Video;
So, the function "context.setPlayingVideo(true)" is working fine and it's correctly setting the global state "videoPlaying" to true, but, after the introduction of the Context, "this.triggerVideo.bind(this);" is not working anymore because "this" is undefined.
I tried removing it and other stuff but I'm really stuck and I don't know hot to fix it.
Thanks everyone!
On this line you are not calling the method triggerVideo
onClick={() => { this.triggerVideo.bind(this); context.setPlayingVideo(true); }}
Change to:
onClick={() => { this.triggerVideo(); context.setPlayingVideo(true); }}
or to:
onClick={() => { this.triggerVideo.bind(this)(); context.setPlayingVideo(true); }}

Categories