I am implementing a slider for components rendered by the router. Created a component with TransitionGroup to automate appending of the classes by CSSTransition, and the code can be seen in a sandbox here. I have added 8 classes (2x enter, enter active, exit and exit active) to have left-right and right-left behavior ("Next" and "Back" buttons).
App.css file:
.slideIn-enter {
opacity: 0;
transform: translateX(-500px);
}
.slideIn-enter-active {
opacity: 1;
transition: all 600ms ease-out;
transform: translateX(0);
}
.slideIn-exit {
opacity: 1;
}
.slideIn-exit-active {
opacity: 0;
transition: all 600ms ease-in;
transform: translateX(500px);
}
/* */
.slideOut-enter {
opacity: 0;
transform: translateX(500px);
}
.slideOut-enter-active {
opacity: 1;
transition: all 600ms ease-in-out;
transform: translateX(0);
}
.slideOut-exit {
opacity: 1;
}
.slideOut-exit-active {
opacity: 0;
transition: all 600ms ease;
transform: translateX(-500px);
}
Slide component which renders the component that is routed to:
import React from 'react';
import { CSSTransition, TransitionGroup } from 'react-transition-group';
import { useLocation } from 'react-router-dom';
import './App.css';
export default function Slide(props) {
const location = useLocation();
return (
<>
<div className="box-container">
<TransitionGroup component={null}>
<CSSTransition
in={props.visible}
timeout={1200}
appear
classNames={props.isForward ? 'slideIn' : 'slideOut'}
unmountOnExit
key={location.key}
>
{props.children}
</CSSTransition>
</TransitionGroup>
</div>
</>
);
}
And App.js where the Slide component is wrapping a Switch which renders one of 2 routes with each press of the 2 buttons below:
import React, { useState } from 'react';
import Slide from './Slide';
import { Route, Switch } from 'react-router-dom';
import { useHistory } from 'react-router-dom';
import './App.css';
export default function App() {
const [isForward, setIsForward] = useState(true);
const history = useHistory();
const BoxRed = () => {
return <div className="box1">Box1</div>;
};
const BoxGreen = () => {
return <div className="box2">Box2</div>;
};
const pushNewRoute = () => {
if (history.location.pathname.endsWith('red')) {
history.push('/green');
} else {
history.push('/red');
}
};
return (
<>
<Route
render={({ location }) => (
<Slide isForward={isForward}>
<Switch location={location}>
<Route path="/green" component={BoxRed} />
<Route path="/red" component={BoxGreen} />
</Switch>
</Slide>
)}
></Route>
<div>
<button
className="btn-container"
onClick={() => {
setIsForward(true);
pushNewRoute();
}}
>
Next
</button>
<button
className="btn-container"
onClick={() => {
setIsForward(false);
pushNewRoute();
}}
>
Back
</button>
</div>
</>
);
}
In my index.js, I am wrapping the App.js in a BrowserRouter:
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter, Route } from 'react-router-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
ReactDOM.render(
<BrowserRouter>
<Route path="/" component={App} />
</BrowserRouter>,
document.getElementById('root')
);
reportWebVitals();
Main issue, that can be seen in the codebox, is that the component stops when exiting component is in exit-active state (lets call it state) and the entering component is in enter-active state.
When entering component is set to enter-done, seems like it is translated half of its width more in the direction defined by the CSS and I cannot figure out why. I presumed the timeout on TransitionGroup was causing it, but whatever value greater than the transition time in CSS classes I'd put in, it just delayed the effect and cannot figure out why.
There is also a smaller issue with switching sets of classes which can be observed when going "Next" then "Back", but feel free to ignore it unless the issue is obvious (probably CSS classes aren't removed when appending new ones, but haven't had the time to tackle it). Thank you in advance!
Related
I created a NavBar component where I have some links.
I then call this component on my App.js, so I can display the NavBar.
When I click on a link I navigate to the route but the component is not displayed.
Here is my code:
MainNavigation.js
import {
AppBar,
Toolbar,
Typography,
Button,
} from "#material-ui/core";
import React from "react";
import { Link as RouterLink } from "react-router-dom";
import { useStyles } from "./styles";
function MainNavigation() {
const classes = useStyles();
const MyApp = (
<Typography variant="h6" component="h1" button='/'>
<RouterLink to='/' className={classes.logo}>MyApp</RouterLink>
</Typography>
);
const headersData = [
{
label: "First",
href: "/first",
},
{
label: "Second",
href: "/second"
}
];
const getMenuButtons = () => {
return headersData.map(({ label, href }) => {
return (
<Button
{...{
key: label,
color: "inherit",
to: href,
component: RouterLink,
className: classes.menuButton,
}}
>
{label}
</Button>
);
});
};
const displayDesktop = () => {
return (
<Toolbar className={classes.toolbar}>
{MyApp}
<div>{getMenuButtons()}</div>
</Toolbar>
);
};
return (
<header>
<AppBar className={classes.header}>{displayDesktop()}</AppBar>
</header>
);
}
export default MainNavigation;
App.js
import './App.css';
import { Route, Routes } from 'react-router-dom';
import Second from './pages/Second';
import First from './pages/First';
import Home from './pages/Home';
import MainNavigation from './components/layout/MainNavigation';
function App() {
return (
<div>
<MainNavigation/>
<Routes>
<Route path="/" exact element={<Home />} />
<Route path="/first" element={<First />} />
<Route path="/second" element={<Second />} />
</Routes>
</div>
);
}
export default App;
Second.js
function Second() {
return <div>Second</div>;
}
export default Second
The Component is called when I click on a link because I console logged just to check.
I don't know what is happening, since I don't get any errors, any clue?
I don't see any overt issues with your routing/navigation implementation, it's working just fine...
BUT
You should be aware that the AppBar component uses fixed positioning by default, so it actually overlays your content.
AppBar props
You can either use some other position value and make the app bar render with the content (probably not what you want in an app header), or you can app margin-top to your contents to push them down below the app bar header.
Example:
h1.header {
margin-top: 5rem;
height: 200vh;
}
I'm using UI5 web components with React and I'm trying to give a class with styles on certain component, but the class doesn't take the styles from the CSS files.
Can someone tell my why?
Here is the "./Navbar.css" CSS files:
.nav-menu {
position: fixed;
left: -100%;
transition: 850ms;
margin-top: 50px;
width: 10rem;
}
.nav-menu.active {
left: 0;
transition: 350ms;
}
And here is the jsx file:
import React, { useState } from "react";
import "./Navbar.css";
import {
Avatar,
ShellBar,
ShellBarItem,
SideNavigation,
SideNavigationItem,
SideNavigationSubItem,
} from "#ui5/webcomponents-react";
import "#ui5/webcomponents-icons/dist/line-chart.js";
import "#ui5/webcomponents-icons/dist/horizontal-bar-chart.js";
import "#ui5/webcomponents-icons/dist/add.js";
import "#ui5/webcomponents-icons/dist/list.js";
import "#ui5/webcomponents-icons/dist/table-view.js";
import { Switch, Route, Redirect } from "react-router-dom";
import { Home } from "./home";
import { Detail } from "./detail";
import { useHistory } from "react-router-dom";
export function MyApp() {
const history = useHistory();
const backToHomeClick = () => {
history.push("./");
};
const [sidebar, setSidebar] = useState(false);
setTimeout(function () {
//Start the timer
const sideNavigation = document.querySelector("ui5-side-navigation");
document
.querySelector("#startButton")
.addEventListener("click", (event) => setSidebar(!sidebar));
}, 1000);
return (
<div>
<ShellBar
logo={<img src="reactLogo.png" />}
profile={<Avatar image="profilePictureExample.png" />}
primaryTitle="My App"
// onClick={backToHomeClick}
>
<ui5-button
icon="menu"
slot="startButton"
id="startButton"
></ui5-button>
<ShellBarItem icon="add" text="Add" />
</ShellBar>
<ui5-side-navigation
// style={{
// position: "fixed",
// left: 0,
// "margin-top": "50px",
// width: "10rem",
// }}
className={sidebar ? "nav-menu active" : "nav-menu"}
>
</ui5-side-navigation>
<Switch>
<Route path="/home" component={Home} />
<Route path="/detail" component={Detail} />
<Redirect from="/" to="/home" />
</Switch>
</div>
);
}
As you can see on the ui5-side-navigation, I give a class and give it on click reaction to change the class on click and it's already working. The class changes whenever I click, but the class doesn't take any style from the import "./Navbar.css";.
Does anyone know why?
I have two react projects Parent and Common project (contains common component like header, footer)
I have material theme defined in Parent and configured in standard way using MuiThemeProvider.
However, this theme object is available inside components defined in Parent, but not in share project common.
Suggestions are appreciated.
Added below more details on Oct 30, 2020
Parent Component
import React from "react";
import "./App.css";
import { BrowserRouter, Switch, Route } from "react-router-dom";
import themeDefault from "./CustomTheme.js";
import { MuiThemeProvider } from "#material-ui/core/styles";
import { createMuiTheme } from "#material-ui/core/styles";
import Dashboard from "./containers/Dashboard/Dashboard";
import { Footer, Header } from "my-common-react-project";
function App() {
const routes = () => {
return (
<BrowserRouter>
<Switch>
<Route exact path="/" component={Dashboard} />
</Switch>
</BrowserRouter>
);
};
return (
<MuiThemeProvider theme={createMuiTheme(themeDefault)}>
<div className="App">
<Header
logo="some-logo"
userEmail={"test#email"}
/>
... app components here..
<Footer />
</div>
</MuiThemeProvider>
);
}
export default App;
Shared component
import React from "react";
import {
Box,
AppBar,
Toolbar,
Typography,
} from "#material-ui/core/";
import styles from "./Header.styles";
import PropTypes from "prop-types";
const Header = (props) => {
const classes = styles();
const { options, history } = props;
const [anchorEl, setAnchorEl] = React.useState(null);
const handleCloseMenu = () => {
setAnchorEl(null);
};
const goto = (url) => {
history.push(url);
};
return (
<Box component="nav" className={classes.headerBox}>
<AppBar position="static" className={classes.headerPart}>
<Toolbar className={classes.toolBar}>
{localStorage && localStorage.getItem("isLoggedIn") && (
<>
{options &&
options.map((option) => (
<Typography
key={option.url}
variant="subtitle1"
className={classes.headerLinks}
onClick={() => goto(option.url)}
>
{option.name}
</Typography>
))}
</>
)}
</Toolbar>
</AppBar>
</Box>
);
};
Header.propTypes = {
options: PropTypes.array
};
export default Header;
Shared Component style
import { makeStyles } from "#material-ui/core/styles";
export default makeStyles((theme) => ({
headerPart: {
background: "white",
boxShadow: "0px 4px 15px #00000029",
opacity: 1,
background: `8px solid ${theme.palette.primary.main}`
borderTop: `8px solid ${theme.palette.primary.main}`
}
}));
The Parent component defined theme.palette.primary.main as say Red color and I expect same to be applied in Header but it is picking a different theme (default) object which has theme.palette.primary.main blue.
Which results in my header to be in blue color but body in read color.
Any suggestion how to configure this theme object so that header too picks the theme.palette.primary.main from parent theme object.
here is the answer for mui V5
import { useTheme } from '#mui/material/styles' // /!\ I fixed a typo from official doc here
function DeepChild() {
const theme = useTheme();
return <span>{`spacing ${theme.spacing}`}</span>;
}
Taken from mui documentation
You can use either useTheme or withTheme to inject the theme object to any nested components inside ThemeProvider.
Use useTheme hook in functional components
Use withTheme HOC in class-based components (which can't use hook)
function DeepChild() {
const theme = useTheme<MyTheme>();
return <span>{`spacing ${theme.spacing}`}</span>;
}
class DeepChildClass extends React.Component {
render() {
const { theme } = this.props;
return <span>{`spacing ${theme.spacing}`}</span>;
}
}
const ThemedDeepChildClass = withTheme(DeepChildClass);
Live Demo
I wanna know where <ThemeProvider/> should be placed in React app.
I'd come up with two solutions about it.
1, <ThemeProvider/> should be used 'Just Once' in top-root component
like index.js or App.js file created by 'create-react-app' tool.
2, <ThemeProvicer/> should be placed in 'Each root of React-component'
literally.
for clarification, I'll show you some example.
there is just two component, 'Red' and 'Blue' <div> tag.
1, <ThemeProvider/> used 'Just Once'
// In './red.js'
import React from 'react'
import styled from "styled-components"
const Red = styled.div`background: ${props => props.theme.mainColor}`
export default function RedDiv() {
return (
//NOT using ThemeProvider
<Red />
)
}
// In './blue.js'
......
const Blue = styled.div`background: ${props => props.theme.subColor}`
export default function BlueDiv() {
return (
<Blue />
)
}
// In './App.js'
import React, { Component } from 'react'
import { ThemeProvider } from "styled-components"
import myTheme from "./myTheme
import Red from "./red"
import Blue from "./blue"
export default class App extends Component {
render() {
return (
//only used here just once
<ThemeProvider theme={myTheme}>
<>
<Red />
<Blue />
</>
</ThemeProvider>
)
}
}
2, <ThemeProvider/> used 'Each root of React-component'
// In './red.js'
import React from 'react'
import styled, { ThemeProvider } from "styled-components"
const Red = styled.div`background: ${props => props.theme.mainColor} `
export default function RedDiv() {
return (
<ThemeProvider theme={myTheme}>
<Red />
</ThemeProvider>
)
}
// In './blue.js'
......
const Blue = styled.div`background: ${props => props.theme.mainColor}`
export default function BlueDiv() {
return (
<ThemeProvider theme={myTheme}>
<Blue />
</ThemeProvider>
)
}
// In './App.js'
import React, { Component } from 'react'
import Red from "./red"
import Blue from "./blue"
export default class App extends Component {
render() {
return (
<>
// <ThemeProvider/> is not used
<Red />
<Blue />
</>
)
}
}
there is maybe some typo on the code above, but I hope that this example will convey my idea clearly.
I use it only once, inside index.js.
Also a good place to add some global styles, if you need them. I use them for resetCSS (http://meyerweb.com/eric/tools/css/reset/) and some baseCSS rules like box-sizing etc.
index.js
import { createGlobalStyle, ThemeProvider } from 'styled-components';
import theme from './styles/theme';
import resetCSS from './styles/resetCSS';
import baseCSS from './styles/baseCSS';
import { BrowserRouter as Router} from "react-router-dom";
const GlobalStyle = createGlobalStyle`
${resetCSS}
${baseCSS}
`;
React.DOM.render(
<React.Fragment>
<GlobalStyle/>
<Router>
<ThemeProvider theme={theme}>
<App/>
</ThemeProvider>
</Router>
</React.Fragment>
,document.getElementById('root')
);
I am using material UI with React. I have a modal component and a ButtonAppBar. Inside the ButtonAppBar there is a shopping cart icon that I added. I am still a bit new to React and would like to know the best way to display the modal when the shopping cart is clicked. Thanks in advance.
So here is my App.js:
import React, { Component } from 'react';
import { BrowserRouter } from 'react-router-dom';
import { Route } from 'react-router-dom';
import './App.css';
import ShopHome from './containers/ShopHome';
import ButtonAppBar from './components/ButtonAppBar';
import SimpleModalWrapped from './containers/ShoppingCartModal';
class App extends Component {
handle
render() {
return (
<BrowserRouter>
<div>
<ButtonAppBar />
<SimpleModalWrapped />
<Route exact path="/" component={ShopHome} />
</div>
</BrowserRouter>
);
}
}
export default App;
Here is my ButtonAppBar:
import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '#material-ui/core/styles';
import AppBar from '#material-ui/core/AppBar';
import Toolbar from '#material-ui/core/Toolbar';
import Typography from '#material-ui/core/Typography';
import Button from '#material-ui/core/Button';
import IconButton from '#material-ui/core/IconButton';
import MenuIcon from '#material-ui/icons/Menu';
import SvgIcon from '#material-ui/core/SvgIcon';
import ShoppingCart from '#material-ui/icons/ShoppingCart';
const styles = {
root: {
flexGrow: 1,
},
grow: {
flexGrow: 1,
},
menuButton: {
marginLeft: -12,
marginRight: 20,
},
appBar: {
marginBottom: 50,
}
};
function ButtonAppBar(props) {
const { classes } = props;
return (
<div className={classes.root}>
<AppBar style={styles.appBar} position="static">
<Toolbar>
<IconButton className={classes.menuButton} color="inherit" aria-label="Menu">
<MenuIcon />
</IconButton>
<Typography variant="h6" color="inherit" className={classes.grow}>
Velo-Velo
</Typography>
<Button color="inherit">Checkout</Button>
<SvgIcon>
<ShoppingCart />
</SvgIcon>
</Toolbar>
</AppBar>
</div>
);
}
ButtonAppBar.propTypes = {
classes: PropTypes.object.isRequired,
};
export default withStyles(styles)(ButtonAppBar);
Here is the modal:
import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '#material-ui/core/styles';
import Typography from '#material-ui/core/Typography';
import Modal from '#material-ui/core/Modal';
import Button from '#material-ui/core/Button';
function rand() {
return Math.round(Math.random() * 20) - 10;
}
function getModalStyle() {
const top = 50 + rand();
const left = 50 + rand();
return {
top: `${top}%`,
left: `${left}%`,
transform: `translate(-${top}%, -${left}%)`,
};
}
const styles = theme => ({
paper: {
position: 'absolute',
width: theme.spacing.unit * 50,
backgroundColor: theme.palette.background.paper,
boxShadow: theme.shadows[5],
padding: theme.spacing.unit * 4,
},
});
class SimpleModal extends React.Component {
state = {
open: false,
};
handleOpen = () => {
console.log('clicked')
this.setState({ open: true });
};
handleClose = () => {
this.setState({ open: false });
};
render() {
const { classes } = this.props;
return (
<div>
{/* <Typography gutterBottom>Click to get the full Modal experience!</Typography>
<Button onClick={this.handleOpen}>Open Modal</Button> */}
<Modal
aria-labelledby="simple-modal-title"
aria-describedby="simple-modal-description"
open={this.state.open}
onClose={this.handleClose}
>
<div style={getModalStyle()} className={classes.paper}>
<Typography variant="h6" id="modal-title">
Text in a modal
</Typography>
<Typography variant="subtitle1" id="simple-modal-description">
Duis mollis, est non commodo luctus, nisi erat porttitor ligula.
</Typography>
<SimpleModalWrapped />
</div>
</Modal>
</div>
);
}
}
SimpleModal.propTypes = {
classes: PropTypes.object.isRequired,
};
// We need an intermediary variable for handling the recursive nesting.
const SimpleModalWrapped = withStyles(styles)(SimpleModal);
export default SimpleModalWrapped;
Your best bet is to use something like Redux. As a lazier strategy have a Higher Order Component house both components. The higher order component can have the function which needs to run when you click on the icon and it changes something in the state of the higher order Component.
Say you have:
import React, { Component } from 'react'
import ComponentWithIcon from './icon-holder'
import ModalComponent from './modal'
class OuterContainer extends Component {
state = { modalOpen: false }
setModal = openOrClose => this.setState({modalOpen: openOrClose})
render(){
const {setModal, state: { modalOpen } } = this
return (
<>
<ComponentWithIcon
handleModalClick={setModal}
/>
<ModalComponent
isOpen={modalOpen}
handleModalClick={setModal}
/>
</>
)
}
}
As a result rather than the Modal component determining whether it is open (having it in its own state) which means that only it will know about whether it is presently open that will be determined by the OuterContainer... The OuterContainer is also the one which will handleTheClickEvent which clicking on the icon causes, so instead of running its own function, the icon will run the OuterComponent's function to setState...
This functionality (the clickHandler) and the state is passed to each of those child components through their props. Since the modal needs to be able to close that too can be passed in from the OuterComponent and it can be kept with the same state.modalOpen and possibly the same passed in action (clickHandler) which is toggle (or if you need for some reason then you can have an openModal/closeModal defined by the OuterComponent and pass both of those callbacks to be run by the modal.
Using redux (with react-redux) you have a single <Provider> component which is the higher order components to all of its children. The provider keeps in state a general store which is generated through redux's createStore() this store then gets actions which manipulate the central store and because it is at the top level it is able to inject its current state (or specific parts of it) to any component that is held within the Provider (which is every single component). Each of those components are able to send messages to that provider using dispatch() they then dispatch actions to the store and that results in the store being replaced with a new store that has things updated inside of it (such as whether the modal is open).
import React from 'react'
const MyModal = ({isOpen, handleModal }) => (
<div className={`modal ${isOpen ? 'is-showing' : 'is-hidden'}`}>
<div
className='toolbar'
>
<CloseButton onClick={() => handleModal(false)} />
</div>
<div>
{/* Modal content */}
</div>
</>
)