Apply CSS transition to styled-component when React state changes - reactjs

I have some state that changes after button click. The state changes the size of a sidebar.
Here is my CSS made with styled-components and conditional rendering:
const SidebarStyled = styled.div`
width: ${this.state.sidebarOpen ? '200px' : '70px'};
position: fixed;
left: 0px;
top: 0px;
height: 100vh;
background-color: #0c1635;
display: flex;
flex-direction: column;
`;
Any idea how I can apply a transition 0.2s on the conditional rendering?
I have tried to add transition: all 0.2s ease-in-out; and it didn't work.

As I mentioned in my comment, you need to pass in a prop and interpolate it out to change your CSS. Otherwise your component will re-render, and the CSS transition won't be applied.
const { React, ReactDOM, styled } = window;
class WontWork extends React.Component {
constructor(props) {
super(props);
this.state = { sidebarOpen: false };
}
render() {
const Sidebar = styled.div`
width: ${this.state.sidebarOpen ? "200px" : "70px"};
height: 20px;
background: red;
transition: width 1s;
`;
return (
<main>
<p>This won't work:</p>
<Sidebar />
<button
onClick={() => this.setState({ sidebarOpen: !this.state.sidebarOpen })}
>
Expand
</button>
</main>
);
}
}
const WorkingSidebar = styled.div`
width: ${(props) => (props.open ? "200px" : "70px")};
height: 20px;
background: green;
transition: width 1s;
`;
class WillWork extends React.Component {
constructor(props) {
super(props);
this.state = { sidebarOpen: false };
}
render() {
return (
<main>
<p>You need to pass in a <b>prop</b> to a predefined styled-component:</p>
<WorkingSidebar open={this.state.sidebarOpen} />
<button
onClick={() => this.setState({ sidebarOpen: !this.state.sidebarOpen })}
>
Expand
</button>
</main>
);
}
}
ReactDOM.render(
<div>
<WontWork />
<hr />
<WillWork />
</div>,
document.getElementById("app")
);
<script crossorigin src="https://unpkg.com/react#16.14.0/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16.14.0/umd/react-dom.development.js"></script>
<script crossorigin src="https://unpkg.com/react-is#16.13.1/umd/react-is.production.min.js"></script>
<script crossorigin src="https://unpkg.com/styled-components#5.3.3/dist/styled-components.min.js"></script>
<div id="app"></div>

Try this:
You can passe sidebarOpen as props:
<SidebarStyle sidebarOpen = {this.state.sidebarOpen}>
Then:
const sidebarStyle = styled.div`
width: 70px;
transition: transform .2s ease-in-out;
transform: ${props.sidebarOpen ? "scaleX(3)" : "scaleX(1)"};
.
.
.
`
I helped from here:
See here: Adding transitions to styled components
Does it work In this case?

Add a transition property like: transition:all 200ms ease-in;

Related

React body scroll lock issue on IOS

I'm literally fighting in finding a clean solution to the scroll issue in the IOS devices. In my App.js i've simply the background body and a modal with some contents. When the modal is shown i'd like to block the scroll in the background (myBodyContent) and still let the scroll in the modal component. I'm quite new to both javascript and React and this not helping me at all.
The cleanest solution (according to me) i was able to find is the body-scroll-lock package but it seems i'm not able to successfully use it. here is my code:
App.js
class App extends Component {
targetRef = React.createRef();
targetElement = null;
constructor(props) {
super(props);
}
componentDidMount() {
this.targetElement = this.targetRef.current;
disableBodyScroll(this.targetElement);
}
render() {
const myModal = (
<Modal ref={this.targetRef}>
// my long content here
</Modal>);
return (
<React.Fragment>
{myModal}
<Layout>
<myBodyContent>
</Layout>
</React.Fragment>
);
}
}
Modal.js
class Modal extends Component {
shouldComponentUpdate(nextProps, nextState){
return (nextProps.show !== this.props.show)
}
render () {
return (
<div>
<Auxi>
<Backdrop
show = {this.props.show}
clicked = {this.props.modalClosed}
/>
<div className={style.Modal}
style={{
transform: this.props.show ? 'translateY(0)' : 'translateY(-100vh)', // vh is special unit for outside screen
opacity: this.props.show ? '1': '0'
}}>
{this.props.children}
</div>
</Auxi>
</div>
);
}
}
Modal css
.Modal {
position: fixed;
z-index: 500;
background-color: white;
width: 80%;
overflow-y: hidden;
overflow: auto;
padding-right: 15px; /* Avoid width reflow */
border: 1px solid #ccc;
box-shadow: 1px 1px 1px black;
padding: 16px;
top: 5%;
left: 5%;
box-sizing: content-box;
transition: all 0.3s ease-out;
}
#media (min-width: 600px) {
.Modal {
width: 80%;
height: 80%;
left: 10%;
top: 10%
}
}
With the above code, simply everything is locked and i cannot scroll neither the modal nor the myBodyContent.
Can you help me understanding what i'm doing wrong? Or suggest me some other ways to achieve the same result?
Thanks in advance for your help.
You don't have targetElement (it's null) inside App componentDidMount because you try to set ref for React component but not HTML element.
To fix this you need to forward ref inside Modal component like that:
const myModal = (
<Modal forwardedRef={this.targetRef}>
// my long content here
</Modal>
);
and then :
class Modal extends Component {
shouldComponentUpdate(nextProps, nextState){
return (nextProps.show !== this.props.show)
}
render () {
return (
<div ref={this.props.forwardedRef}>
<Auxi>
<Backdrop
show = {this.props.show}
clicked = {this.props.modalClosed}
/>
<div className={style.Modal}
style={{
transform: this.props.show ? 'translateY(0)' : 'translateY(-100vh)', // vh is special unit for outside screen
opacity: this.props.show ? '1': '0'
}}>
{this.props.children}
</div>
</Auxi>
</div>
);
}
}
Thanks Max, i've tried but unfortunately the result is the same. I've also tried to enclose the Modal in a div directly in the App.js and apply the ref directly there without passing it as props...but it's the same. No way to scroll anything.

Transitions with React - should I use TransitionGroup?

I want page contents in my application to transition smoothly. I have been attempting to do this using react-transition-group but I have struggled to achieve the correct implementation. The following link was informative:
https://coursework.vschool.io/react-transitions-with-react-transition-group/
It shows how to make modularize and use TransitionGroup (although not both at the same time, unfortunately).
I created a demo project (based on the above link) to troubleshoot this issue. I have two items in an array ‘contactComponents’. All I am trying to do at the moment is make this information appear and disappear using the show/hide button.
Here is the main body of the code:
const contactDetails = ['Gryffindor Tower, Hogwarts','Gryffindor Tower, Hogwarts'];
const contacts = ['Harry', 'Ron'];
export default class App extends React.Component {
constructor(props){
super(props);
this.state = {
count: 0,
showMyContact: false
};
this.showContact = this.showContact.bind(this);
}
showContact() {
this.setState({showMyContact: !this.state.showMyContact})
}
render() {
const styles = {
container: { display: 'flex', justifyContent: 'center', width: '100vw', height: 100, flexDirection: 'column', padding: 100 },
btn: { width: '100%', display: 'flex', justifyContent: 'center'},
h1: { border: '2px solid blue', padding: 5, display: 'flex'}
};
let contactComponents = [contacts[this.state.count], contactDetails[this.state.count]];
console.log(this.state.showMyContact)
return (
<div>
<div style={ styles.container }>
<TransitionGroup component={null}>
{ contactComponents.map((item, key) =>
<CSSTransition
in={this.state.showMyContact}
key={key}
timeout={800}
classNames={"fade"}>
<h1 style={styles.h1}>
{
item
}
</h1>
</CSSTransition>
)}
</TransitionGroup>
<div style={ styles.btn }>
<button onClick={ this.showContact }>show/hide</button>
</div>
</div>
</div>
)
}
}
scss file:
.fade-appear,
.fade-enter {
opacity: 0;
z-index: 1;
}
.fade-appear-active,
.fade-enter.fade-enter-active {
opacity: 1;
transition: opacity 600ms linear 200ms;
}
.fade-exit {
opacity: 1;
}
.fade-exit.fade-exit-active {
opacity: 0;
transition: opacity 200ms linear;
}
Currently, the contents appears even though showMyContact is false when the render function first calls. Changing the state of showMyContact with the show/hide button has no effect. The content does not fade in and out as expected.
This post:
page transitions without React-Router
suggests it might be better to use pure css to carry out transitions rather than react-transition-group. Am I just barking up the wrong tree?
I found out that using pure css transitions provides the desired solution. I do not know if a solution using TransitionGroup and CSSTransition is feasible but it doesn't look like it.
By changing the contents of the render function to:
render() {
let contactComponents = [contacts[this.state.count], contactDetails[this.state.count]];
let cssList = [
"List",
this.state.showMyContact ? "ListShow" : "ListHide"
];
console.log(this.state.showMyContact);
return (
<div>
<div className={"container"}>
<List show={cssList.join(' ')} myContent={contactComponents}/>
<div className={"btn"}>
<button onClick={ this.showContact }>show/hide</button>
</div>
</div>
</div>
)
}
...and adding the following const:
const List = (props) => {
return (
<div className={props.show}>
<h1 className={"h1"}> { props.myContent[0] } </h1>
<h1 className={"h1"}> { props.myContent[1] } </h1>
</div>
)};
...and importing the following css file:
.container {
display: flex;
justify-content: center;
width: 500px;
height: 100px;
flex-direction: column;
padding: 100px;
}
.h1 {
border: 2px solid blue;
padding: 5px;
display: flex;
}
.btn {
width: 100%;
display: flex;
justify-content: center;
}
.List {
display: flex;
flex-direction: column;
transition: all 0.4s ease-out;
}
.ListShow {
opacity: 1;
}
.ListHide {
opacity: 0;
}
...I can get the desired behaviour.

React-modal hides behind elements

I am trying to make use of react-modal for the first time. When I click on the sign-in button, the react-modal component is invoke but seems to be hiding behind the cover page which is a video landing page.
The React devtool displays the appropriate states before the sign-in button is clicked
before the sign-in button is clicked
When the sign-in button is now clicked, the react devtool now displays that the ModalPortal component is rendered showing the appropriate states
when the sign-in button is clicked
SignInModal.scss
.ReactModalPortal>div {
opacity: 0;
}
.ReactModalPortal .ReactModal__Overlay {
align-items: center;
display: flex;
justify-content: center;
transition: opacity 200ms ease-in-out;
}
.ReactModalPortal .ReactModal__Overlay--after-open {
opacity: 1;
}
.ReactModalPortal .ReactModal__Overlay--before-close {
opacity: 0;
}
.modal {
position: relative;
background: #464b5e;
color: white;
max-width: 90rem;
outline: none;
padding: 3.2rem;
text-align: center;
}
.modal__title {
margin: 0 0 1.6rem 0;
}
.modal__body {
font-size: 2rem;
font-weight: 300;
margin: 0 0 3.2rem 0;
word-break: break-all;
}
CoverPage.js Component
import Header from './Header';
import HeaderVideo from './HeaderVideo';
import SignInModal from './SignInModal';
import React, { Component } from 'react';
class CoverPage extends Component {
state = {
modalIsOpen: false
};
onOpenModal = () => {
this.setState(() => ({
modalIsOpen: true
}));
};
onCloseModal = () => {
this.setState(() => ({
modalIsOpen: false
}));
};
render() {
return (
<div>
<Header />
<HeaderVideo onOpenModal={this.onOpenModal} />
<SignInModal
modalIsOpen={this.state.modalIsOpen}
onOpenModal={this.onOpenModal}
onCloseModal={this.onCloseModal}
/>
</div>
);
}
}
export default CoverPage;
HeaderVideo.js Component
import React from 'react';
import Signup from './Signup';
import CoverInfo from './CoverInfo';
const HeaderVideo = props => {
return (
<div className="video-container">
<video preload="true" autoPlay loop volume="0" postoer="/images/1.jpg">
<source src="images/vine.mp4" type="video/mp4" />
<source src="images/vine1.webm" type="video/webm" />
</video>
<div className="video-content">
<div className="container content">
<div className="row">
<div className="col-md-9">
<CoverInfo onOpenModal={props.onOpenModal} />
</div>
<div className="col-md-3">
<Signup />
</div>
</div>
</div>
</div>
</div>
);
};
export default HeaderVideo;
CoverInfo.js Component
import React from 'react';
const CoverInfo = props => {
return (
<div className="info">
<div>
<h1>Welcome to EventCity!</h1>
</div>
<div>
<p>
At EventCity! we pride ourselves on the unrivalled personal {`event`} services,we provide
to our clientele. We guide you from the stressful decision making {`process`},ensuring you
are comfortable,whether it is a wedding, corporate {`function `}or even a kiddies party,we
create a buzz around you, taking you to the next level.
</p>
</div>
<div>
<h3>Innovation, {`Performance`} and Delivery</h3>
</div>
<button type="button" className="btn btn-success btn-lg" onClick={props.onOpenModal}>
Sign In here
</button>
</div>
);
};
export default CoverInfo;
video-cover.scss
video {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
min-width: 100%;
min-height: 100%;
width: auto;
height: auto;
z-index: 1;
}
.video-content {
z-index: 2;
position: absolute;
background: rgba(0, 0, 0, 0.6);
top: 0;
bottom: 0;
left: 0;
right: 0;
}
.content {
padding-top: 120px;
}
You need to set the z-index property on the Modal's overlay, which normally has a z-index of 0. The CSS class is .ReactModal__Overlay
Here is the pure-React way of doing it:
const customStyles = {
content : {
...
},
overlay: {zIndex: 1000}
};
<Modal style={customStyles}>
...
</Modal>
.modal {
position: fixed;
z-index:9999;
top :0;
left:0;
right:0;
bottom:0;
background: #464b5e;
color: white;
outline: none;
padding: 3.2rem;
text-align: center;
}
Example of react-modal inline styles Set the styles in the react-modal inline styles. The z-index to 100 but make just like below
style={{
overlay: {
zIndex: 100,
backgroundColor: 'rgba(70, 70, 70, 0.5)',
},

react Semantic-UI - multi-select checkbox drop-down

i want to built an multi select checkbox dropdown in react with es6
my requirement is as below specified in image
I tried doing this click here but it is not working.
You can use one parent component that will keep values in its state and toggle list items. Then you can create component for each list item that will keep active property in state that you can toggle on click.
class ListItem extends React.Component {
constructor(props) {
super(props);
this.state = {active: false}
}
render() {
return (
<a
onClick={() => {
this.setState(prevState => {
let newState = !prevState.active;
this.props.handleClick(newState, this.props.value);
return {active: newState}
})
}}
className={!this.state.active ? '' : 'selected'}
href="#">
{this.props.value}</a>
)
}
}
class Select extends React.Component {
constructor(props) {
super(props);
this.state = {
showList: false,
value: []
}
this.handleItemClick = this.handleItemClick.bind(this)
}
componentDidMount() {
document.addEventListener('mousedown', (e) => {
if(!this.node.contains(e.target)) {
this.setState({showList: false})
}
})
}
componentWillUnmount() {
document.removeEventListener('mousedown');
}
renderValue() {
let {value} = this.state;
if(!value.length) return "Select..."
else return value.join(', ')
}
toggleList() {
this.setState(prevState => ({showList: !prevState.showList}))
}
handleItemClick(active, val) {
let {value} = this.state;
if(active) value = [...value, val]
else value = value.filter(e => e != val);
this.setState({value})
}
render() {
return (
<div
ref={node => this.node = node}
className="select">
<button onClick={this.toggleList.bind(this)}>
<span className="select_value">
{this.renderValue()}
</span>
</button>
<div
className={"select_list " + (!this.state.showList && 'hide')}>
<ListItem handleClick={this.handleItemClick} value="Lorem" />
<ListItem handleClick={this.handleItemClick} value="Ipsum" />
<ListItem handleClick={this.handleItemClick} value="Dolor" />
</div>
</div>
)
}
}
ReactDOM.render(
<Select />,
document.getElementById('container')
);
button {
background: white;
width: 100%;
padding: 10px 15px;
border: 1px solid rgba(0, 0, 0, .1);
border-radius: 5px;
cursor: pointer;
text-align: left;
}
.select_list {
width: 100%;
background: white;
border: 1px solid rgba(0, 0, 0, .1);
border-radius: 5px;
}
.select_list a {
padding: 10px 15px;
display: flex;
color: black;
text-decoration: none;
position: relative;
align-items: center;
}
.select_list a:before {
width: 15px;
height: 15px;
content: '';
border: 1px solid rgba(0, 0, 0, .1);
border-radius: 5px;
margin-right: 10px;
display: block;
}
.select_list a.selected:before {
background: #0493D1;
content: '✓';
color: white;
font-size: 11px;
text-align: center;
line-height: 15px;
}
.hide {
display: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="container"></div>
Semantic-UI React Approach
After much digging, I found an old conversation between eugenetumachov and Semantic-UI developers(?). One of the users provided incredibly helpful code that answers this question using Semantic-UI's Dropdown component.
This is done by making use of Dropdown's Dropdown.Menu and Dropdown.Item. Then looping through your options via map to create checkboxes. The only downside is that the workaround does not seem to allow scrolling and will require more CSS. Additionally, based on CSS the checkbox items' background color may turn transparent if you double-click on the dropdown, and the dropdown will collapse on mouse hover. You can bypass the transparency issue by using a class or style property for your Dropdown.Menu and Dropdown.Item.
Semantic-UI developer's response to this type of question appears to be a flat "no" or a
Active items are automatically removed from the Dropdown menu. So you cannot show a "checked" state for an item in the menu.
You could create a similar component out of an Input as a trigger for
a Popup containing a Menu or List of Checkboxes.
Are dropdowns with checkboxes possible? #2417
eugenetumachov's workaround

React sharable panel url

Consider my following snippet. Right now on button click it opens a div-three that loads AnotherComponent.The url is simply 'http://localhost:3000/de' i.e. Indexroot
What I want to achieve is: If I hit 'http://localhost:3000/de/?open' then I want the panel i.e. div-three already open.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
showThird: false
}
this.showDivThree = this.showDivThree.bind(this)
/*if(props.location.search=="?open"){
this.showDivThree()
}*/
}
showDivThree() {
this.setState(prevState => ({ showSecond: false, showThird: !prevState.showThird}))
console.log(this.state)
}
render() {
return (
<div className={'wrapper' + ( this.state.showThird ? ' show' : '')}>
<div className="one">one
{/* Show third */}
<div>
<button onClick={this.showDivThree}>{this.state.showThird ? 'hideThird' : 'showThird'}</button>
</div>
</div>
<div className="three">three
<div>
<button onClick={this.showDivThree}>{this.state.showThird ? 'hideThird' : 'showThird'}</button>
<AnotherComponent />
</div>
</div>
</div>
)
}
}
class AnotherComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
}
}
render() {
return (
<div>
<h4>Another component</h4>
</div>
)
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
.wrapper {
overflow: hidden;
white-space: nowrap;
}
.one, .two, .three {
background: #333;
border: 2px solid #787567;
box-sizing: border-box;
color: #fff;
display: inline-block;
font-family: arial;
overflow: hidden;
padding: 20px;
text-align: center;
transition: border 0.2s, padding 0.2s, width 0.2s;
min-height: 50vh;
}
.one {
width: 100%;
}
.two {
border-width: 2px 0;
padding: 20px 0;
width: 0;
}
.three {
border-width: 2px 0;
padding: 20px 0;
width: 0;
}
.show .one, .show .two, .show .three {
border-width: 2px;
padding: 20px;
width: 50%;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<script src="https://unpkg.com/react-router/umd/react-router.min.js"></script>
<div id="root"></div>
I have commented a code where I read search string from props.location, if it is present then I simply call the function that opens the div-three. But as I have mixed conditions to open divs it somehow is not working.
How can I fix this?
You can't call setState (showDivThree method calls setState) in contructor since when constructor is called component hasn't been mounted yet. Please check this SO answer for more details.
You should move if statement checking URL search string from constructor to componentDidMount method which is called immediately after a component is mounted and in which you can safely use setState:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
showThird: false
};
this.showDivThree = this.showDivThree.bind(this)
}
componentDidMount() {
if (props.location.search == "?open") {
this.showDivThree();
}
}
...
}
Besides, I think that your URL should be without slash before search query. So it should be http://localhost:3000/de?open instead of http://localhost:3000/de/?open.

Resources