React Router how to setup transition on parent element - css

I have a simple router configuration:
<Router history={browserHistory}>
<Route lang="en" path="en" component={App}>
<Route path="*" component={PageFactory} />
</Route>
<Redirect from="*" to="/en/*" />
</Router>
and main App component has the following:
<div className="app">
<Home />
<Sidebar {...sidebarProps}>
{this.props.children}
</Sidebar>
</div>
So, the Home component always opens when you open the react application. When I click a link in Home component (e.g /en/pages/about-page), the contents of the page gets loaded to Sidebar component and Sidebar gets a prop isOpen (That's what ...sidebarProps is for). Everything works fine. When I click a link Sidebar opens, and contents show with overlay etc. However, transitions do not animate. Here is my CSS:
.sidebar {
position: fixed;
left: 0px;
top: 0px;
width: 100%;
height: 100%;
z-index: 200;
visibility: hidden;
background: rgba(0, 0, 0, 0.8);
overflow: scroll;
#include transition(visibility 0s linear 0.3s);
&.is-open {
visibility: visible;
#include transition-delay(0s);
.sidebar-content {
#include transform(translateX(0));
}
}
}
The isOpen prop in Sidebar adds class is-open to root element of Sidebar component. Here is Sidebar component for reference:
const Sidebar = props =>
<aside className={`sidebar ${props.isOpen ? ' is-open' : ''}`}>
<SidebarContent>{props.children}</SidebarContent>
</aside>
;
For testing to see whether it is a React problem, I added a button like the following to my App component:
constructor(props) {
super(props);
this.enableSidebar = this.enableSidebar.bind(this);
this.state = { isOpen: false };
}
enableSidebar() {
this.setState({ isOpen: true });
}
render() {
const sidebarProps = { isOpen: this.state.isOpen };
return (
<div className="app">
<button onClick={this.enableSidebar}>Enable Sidebar</button>
<Home />
<Sidebar {...sidebarProps}>{this.props.children></Sidebar>
</div>
);
}
And when I click the button, sidebar opens smoothly. It is React Router that completely reloads the page on every request, making every page be a static page instead of creating a dynamic application.
How can I change this behavior? How can I make React Router to not reload all components on route change? I am thinking that maybe the structure that I have is problematic. If you also think it is, can you suggest me a solution?

I have found the problem. I had to use React Router Link component instead of HTML anchor element. So instead of using:
About
I have to use:
<Link to="/en/pages/about-page">About</Link>

Related

CSS Transition not working with react and styled components

I have a problem with css transition, i use styled component and the element add its className based on the changing of react useState which is triggered by onClick back and forth,
here is the part of the code that dont work as expected:
export const SearchProduct = ({ product }) => {
const [descStatus, setdescStatus] = useState(false);
const handleDesc = () => {
setdescStatus(!descStatus);
};
return (
<li>
<Item>
<Photo>
<img src={`${product.productImg}`} alt={product.productTitle}></img>
</Photo>
<Information>
<h3> {product.productTitle} </h3>
<Desclook>
<div className={descStatus ? 'active' : null} onClick={handleDesc}>
{descStatus ? 'Close' : 'See Desc ...'}
</div>
</Desclook>
{descStatus && (
<Description --> this is part that dont work
className={descStatus ? 'showContent content' : 'content'}
>
{product.productDesc}
</Description>
)}
Here is the styled components part :
const Description = styled.p`
margin: 10px;
padding: 0;
transition: all 0.3s ease-in-out;
&.content {
height: 0;
overflow: hidden;
}
&.showContent {
height: 70px;
overflow-y: scroll;
}
`;
Does anybody have any idea what happened with my code here cause i'm kinda new to react and styled component
Remove the check for descStatus and always render <Description> instead.
So instead of this:
{descStatus && (
<Description
className={descStatus ? 'showContent content' : 'content'}
>
{product.productDesc}
</Description>
)}
Do this:
<Description
className={descStatus ? 'showContent content' : 'content'}
>
{product.productDesc}
</Description>
The reason behind this is a CSS transition needs to transition from a different value than the current value. In your code when you check if descStatus is true before rendering, your Description component will never have the className="content" and will always be rendered initially with a height of 70px, so no transition will occur.
Hey you can solve it easily if you send the state as a prop instead of setting className
And you should update the state based on previous state and as useState setter sets the state asynchronously you might need asynchronous version of setState this is irrelevant for this problem but can cause problems in some cases
const handleDesc = () => {
setdescStatus(p => !p);
};
For the styled component part
<Description --> this is part that dont work
show={descStatus}
>
{product.productDesc}
</Description>
and inside the styled component you can handle it like
import styled,{css} from 'styled-components';
const Description = styled.p`
margin: 10px;
padding: 0;
transition: all 0.3s ease-in-out;
//content class styles applied by default
height: 0;
overflow: hidden;
//these styles will be applied only if show is true (css you can import from
//styled component as a named import)
${({show}) => show && css`
height: 70px;
overflow-y: scroll;
`}
`;

Hiding Element in React Based on SCSS/CSS

I have a global element that needs to be hidden only on a specific page.
The problem I encounter is that when I try to hide the element on a specific page base on importing the css, it also hides on all pages. It needs to hide only on this page. The <Main /> page is where my main pages are located like the /products
<body>
<Main />
<div style={{ height: 800, position: "absolute" }} className="products">
Hide this on none /products page
</div>
</body>
products.scss
body {
.products {
display: none;
}
}
Product
import { useRouter } from 'next/router'
const Products = () => {
const router = useRouter()
if (router.pathname === '/products') {
require('../styles/products.scss')
}
}
<div style={{ height: 800, position: "absolute" }} className={router.pathname === '/products' ? 'hidden' : ''}>
Hide this on none /products page
</div>
and now set display: none; for the hidden class. :)

React Modals visible for a split-second on page load

I am rendering modals in React.
My index.html looks like this:
<div id="root"></div>
<div id="modal"></div>
And all my modals are rendered (through a portal) as a child of .modal.
Each modal element has the following form:
<div class="modal-background open">
<!-- children -->
</div>
Where the class can be modal-background open or modal-background closed. The entire component is:
interface OwnProps {
children: React.ReactNode
isOpen: boolean
onExit: () => void
}
export class Modal extends React.Component<OwnProps, any> {
_exit = () => this.props.onExit();
_renderModal = () => (
<div className={`modal-background ${this.props.isOpen ? "open" : "closed"}`} onClick={this._exit}>
{this.props.children}
</div>
);
render() {
if (this.props.isOpen) {
document.body.className += " no-scroll";
} else {
document.body.classList.remove("no-scroll");
}
let elem = document.querySelector("#modal");
if (elem == null) {
console.log("Could not render modal.");
return null;
}
return ReactDOM.createPortal(this._renderModal(), elem);
}
}
And the CSS looks like:
.modal-background {
/* Other styling - this a dark backdrop for a modal child */
background-color: rgba(0,0,0,0.2);
transition: opacity 150ms ease-out;
&.closed {
opacity: 0;
pointer-events: none;
}
&.open {
pointer-events: all;
opacity: 1;
&:hover {
cursor: pointer;
}
}
}
So my modal is used like <Modal><CustomModalElement/></Modal>.
When I load the page, my modal elements briefly flash, indicating that they are not hidden on load (but a split-second afterwards).
I can fix this by adding display: none and display: inherit into the css, but then I miss the nice transitions.
Is there a better way to do this?
Not sure you need to do anything else inside your index.html file except
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<div id="modal"></div>
And for your Modal.js, you could try something along these lines:
import React from "react";
import ReactDOM from "react-dom";
const Modal = props => {
return ReactDOM.createPortal(
<div className="ui dimmer modals visible active">
<div className="ui standard modal visible active">
<div className="header">Delete Object</div>
<div className="content">
Are you sure you want to delete this?
</div>
<div className="actions">
<button className="ui primary button">Delete</button>
<button className="ui button">Cancel</button>
</div>
</div>
</div>,
document.querySelector("#modal")
);
};
export default Modal;
and then inside your other component where the user will execute the modal:
import React from "react";
import Modal from "../Modal"; // or wherever your Modal is in the file tree
const ObjectDelete = () => {
return (
<div>
ObjectDelete
<Modal />
</div>
);
};
export default ObjectDelete;
Keep in mind that the example of modal I offer here is not a reusable component.

Have a "sticky" button right underneath another "sticky" navbar

I have an app file which contains my own custom appbar and different page components:
const styles = theme => ({
appBar: {
width:'100%',
},
});
class App extends Component {
render() {
const {classes} = this.props;
return (
<React.Fragment>
<CssBaseline />
<AppBar position="sticky" className={classes.appBar} />
<Page1 show={someCondition} />
<Page2 show={someCondition} />
.
.
<Page99 show={someCondition} />
</React.Fragment>
);
}
}
The Appbar is sticky so it always shows on the top.
Each page component has a button which is always on the top of that page:
const styles = theme => ({
button: {
width:'100%',
},
});
class Page99 extends Component {
render() {
const {classes} = this.props;
return (
<React.Fragment>
<div>
<Button variant="contained" className= {classes.button}>
Action Button
</Button>
</div>
{/* Some other stuff */>
</React.Fragment>
);
}
}
I know want this button to always be right under the appbar. So when the user scrolls down this button should remain sticky just like the appbar does. I tried to set the positioning to sticky hoping it would stack underneath it but it wouldn't. The appbar is dynamic so I don't know the exact height it will be since on different resolutions it will look different so I couldn't use something like fixed positioning.
You can set position of page container as relative and set button as absolute.
the you can align it to top right of the page or wherever you want.
Check this fiddle is this is what you need
.componentparent {
position: relative;
height:100px;
max-height: 50px;
overflow: auto;
}
.button {
position: fixed;
top: 30px;
}
.otherelements{
top: 70px;
position: relative;
}
<div id="parent-container">
<div> your app bar </div>
<div class='componentparent'>
<button class='button'>my button</button>
<div class='otherelements'>your component</div>
</div>
</div>
Place your button inside your appbar and set your button to position to absolute and add top: 100% to move it exactly at the bottom of appbar.

How to make a sticky footer in react?

I've made a sticky footer higher-level component that wraps other components inside itself:
Footer.js
//this is a higher-order component that wraps other components placing them in footer
var style = {
backgroundColor: "#F8F8F8",
borderTop: "1px solid #E7E7E7",
textAlign: "center",
padding: "20px",
position: "fixed",
left: "0",
bottom: "0",
height: "60px",
width: "100%",
};
const Footer = React.createClass({
render: function() {
return (
<div style={style}>
{this.props.children}
</div>
);
}
});
export default Footer;
Usage:
<Footer><Button>test</Button></Footer>
But it is hiding the contents of the page:
This looks like a common problem, so I searched a bit and found this issue, where is FlexBox is recommended for the sticky footer. But at this demo the footer is at the very bottom of the page, while I need the footer to be always displayed on the page and the content being scrolled inside the above area (like in SO chat). In addition to that, there is an advice to change all the other components with custom stylesheet rules. Is it possible to achieve what I need using styling only the footer component so the code will remain modular?
Here's an idea (sandbox example link).
Include a phantom div in your footer component that represents the footer's position that other dom elements will respect (i.e. affecting page flow by not being position: 'fixed';).
var style = {
backgroundColor: "#F8F8F8",
borderTop: "1px solid #E7E7E7",
textAlign: "center",
padding: "20px",
position: "fixed",
left: "0",
bottom: "0",
height: "60px",
width: "100%",
}
var phantom = {
display: 'block',
padding: '20px',
height: '60px',
width: '100%',
}
function Footer({ children }) {
return (
<div>
<div style={phantom} />
<div style={style}>
{ children }
</div>
</div>
)
}
export default Footer
Much easier idea (following the trend), i imported both bootstrap and reactstrap, used the bootstrap fixed bottom class and workaround with that like this.
class AppFooter extends Component{
render() {
return(
<div className="fixed-bottom">
<Navbar color="dark" dark>
<Container>
<NavbarBrand>Footer</NavbarBrand>
</Container>
</Navbar>
</div>
)
}
There is a much simpler way. I am creating a portfolio site with React, and some of my pages are not very long, so in some devices, like kindle fire hd for example, the footer would not stick to the bottom. And of course to set this up in the traditional fashion with would not work, because the would be wrapped in there. And we don't want that. So this is what I did:
In App.js:
import React, { Component } from 'react';
import {Header} from './components/Header';
import {Main} from './components/Main';
import {Footer} from './components/Footer';
class App extends Component {
render() {
return (
<div className="App Site">
<div className="Site-content">
<div className="App-header">
<Header />
</div>
<div className="main">
<Main />
</div>
</div>
<Footer />
</div>
);
}
}
export default App;
And then in _sticky-footer.css (I use POSTCSS):
:root {
--space: 1.5em 0;
--space: 2em 0;
}
.Site {
display: flex;
flex-direction: column;
min-height: 100vh;
}
.Site-content {
flex: 1 0 auto;
padding: var(--space) var(--space) 0;
width: 100%;
}
.Site-content:after {
content: '\00a0';
display: block;
margin-top: var(--space);
height: 0;
visibility: hidden;
}
The original solution for this was created by Philip Walton: https://philipwalton.github.io/solved-by-flexbox/demos/sticky-footer/
You can fix this by adding margin-bottom: 60px; to the body of your website. With the 60px being the height of your footer.
.footer{
width: 100%;
position: fixed;
bottom: 0;
}
This should do the trick! Cheers! (:
.App will be the main component you load to your Root.
Assume that the footer is the last child of .App in the document flow
.App {
height: 100vh;
display: flex;
flex-direction: column;
}
footer {
margin-top: auto;
}
I found that if you wrap your 'footer' component in a standard html
<footer>
tag, it pretty much sorts out all of the positioning for you
I wanted to share this solution that worked. I cribbed this from https://react.semantic-ui.com/modules/sticky. Scroll to the bottom of this page and inspect the text 'This is the bottom' to see where I stole it. Its a site built on react so it should work for your situation.
Here it is:
{
padding-top: 50vh;
}
Conceptually, this solution is creating negative space like jacoballenwood's phantom div to push the footer down to the bottom and stick it there. Just add it to your css style class for the footer and adjust the value to taste.
Very late answer, but someone can find this useful. You can, instead of phantom style, set Toolbar. I have build some standard layout for the components, where {children} is component from the parent component - App.js. This is example:
import React from "react";
import { Route } from "react-router-dom";
import { makeStyles } from "#material-ui/core/styles";
import AppBar from "#material-ui/core/AppBar";
import CssBaseline from "#material-ui/core/CssBaseline";
import Toolbar from "#material-ui/core/Toolbar";
import Header from "../components/header";
import Footer from "../components/footer";
import SideBar from "../components/sidebar";
const useStyles = makeStyles((theme) => ({
root: {
display: "flex",
},
appBar: {
zIndex: theme.zIndex.drawer + 1,
},
content: {
flexGrow: 5,
padding: theme.spacing(3),
},
}));
const StandardLayout = ({ children }) => {
const classes = useStyles();
return (
<div className={classes.root}>
<CssBaseline />
<AppBar position="fixed" className={classes.appBar}>
<Route path="/" component={Header} />
</AppBar>
<SideBar />
<main className={classes.content}>
<Toolbar />
<br />
{children}
<Toolbar/>
</main>
<AppBar className={classes.appBar}>
<Route path="/" component={Footer} />
</AppBar>
</div>
);
};
export default StandardLayout;
Its rule for me
<footer style={{position:"fixed",bottom:"0"}}>
Try this html code:
/public/index.html
<html lang="en" class="h-100">
<body class="h-100">
<div id="root" class="d-flex flex-column h-100"></div>
...
/src/App.js
<main role='main' className='flex-shrink-0'>
You can follow this template:
react-bootstrap-sticky-footer/public/index.html
react-bootstrap-sticky-footer/src/App.js

Resources