React - change state right after previous state change was rendered - javascript

I wanna make cool box grow animation (expand) when user clicks on it and I want to do it following way:
user clicks on expand button -> get div dimensions and top/left positions via ref, store it in state and assign div's style to these values
changed expanded state variable and change div's position to fixed, also change left, top values and width, height css values
My problem is in initial div expand click. It seems that both state's changes are rendered in one cycle so I don't see smooth animation on first expand click. I've tried to do it via setState callback, also tried to update expanded in componentDidUpdate method once div dimensions are in state, nothing worked except delaying expanded set via setTimeout.
Code example via setState callbacks
if (chartsExpanded.get(chart) === "collapsed-end" || !chartsExpanded.get(chart)) {
this.setState({
chartsProportions: chartsProportions.set(
chart,
Map({
left: chartProportions.left,
top: chartProportions.top,
width: chartProportions.width,
height: chartProportions.height
})
)
}, () => {
this.setState({
chartsExpanded: chartsExpanded.set(chart, "expanded")
})
})
}
...
<div
className={`box customers-per-sources-count ${
customersPerSourcesCount.loading ? "loading" : ""
} ${
chartsExpanded.get("customersPerSourcesCount")
? chartsExpanded.get("customersPerSourcesCount")
: "collapsed-end"
}`}
ref={el => {
this.chartRefs["customersPerSourcesCount"] = el
}}
style={{
left: chartsProportions.getIn(["customersPerSourcesCount", "left"], "auto"),
top: chartsProportions.getIn(["customersPerSourcesCount", "top"], "auto"),
width: chartsProportions.getIn(["customersPerSourcesCount", "width"], "100%"),
height: chartsProportions.getIn(["customersPerSourcesCount", "height"], "100%")
}}
>
How can I achieve that style from chartsProportions will be rendered before class based on expanded value is changed? I don't want to use setTimeout nor want to update all charts proportions onScroll event etc.

You just need to pass setState a function instead of an object, to ensure the state changes are applied in order:
this.setState(previousState => ({
previousChange: "value"
}))
this.setState(previousState => ({
afterPreviousChange: previousState.previousChange
}))
https://reactjs.org/docs/react-component.html#setstate
Another option might be to pass a callback to setState that runs after the state changes have been applied, like:
this.setState({ someChange: "value" }, () => this.setState({
otherChange: "value"
}))
CSS transitions could help with this too.

Using React state to animate properties is not the right way to do it. State updates will always get batched and you generally don't want to re-render your entire component 60 times per second
Store 'expanded' boolean in your state, and change element's class accordingly. Use css to add animations between two states
handleClick = () => {
this.setState({ expanded: !this.state.expanded })
}
render() {
return (
<div
className={`box ${this.state.expanded ? 'expanded' : ''}`}
onCLick={this.handleClick}
/>
)
}
in your css
.box {
width: 100px;
height: 100px;
transition: width 2s;
}
.expanded {
width: 300px;
}
added based on comments:
What you want to do is:
set position: fixed to your element. This would snap it to the top of the screen instantly, so you need to pick the right top and left values so that position fixed starts off where it was when position was static (default). For that you can use element.getBoundingClientRect()
calculate desired top and left attributes that would make your element appear in the middle of a screen, and apply them
very important: between step 1 and 2 browser has to render the page to apply position and initial top and left values, in order to have something to start animation from. It won't be able to do that if we apply both of these styles synchronously one after another, as page will not render until JS stack frame is clear. Wrap stage 2 logic in setTimeout which will make sure that browser renders at least once with styles applied at stage 1
rough working example:
class Example extends React.Component {
constructor(props) {
super(props)
this.state = {
expanded: false,
style: {}
}
}
handleClick = (e) => {
if (!this.state.expanded) {
const r = e.target.getBoundingClientRect()
const style = {
top: r.y,
left: r.x,
}
this.setState({
expanded: !this.state.expanded,
style
})
setTimeout(() => {
this.setState({
style: {
top: (window.innerHeight / 2) - 50,
left: (window.innerWidth / 2) - 50,
}
})
})
} else {
this.setState({
expanded: false,
style: {}
})
}
}
render() {
return (
<div className={'container'}>
<div className={'empty'} />
<div className={'empty'} />
<div className={'empty'} />
<div
onClick={this.handleClick}
className={`box ${this.state.expanded ? 'expanded' : ''}`}
style={this.state.style}
/>
</div>
)
}
}
and styles.css
* {
box-sizing: border-box;
padding: 0;
margin: 0;
}
.container {
height: 200vh;
}
.empty {
height: 100px;
width: 100px;
border: 1px solid gray;
}
.box {
height: 100px;
width: 100px;
border: 3px solid red;
transition: all 0.5s;
}
.expanded {
position: fixed;
}

Related

ReactJS changing styles via State breaks CSS

So I've been learning react.
And have been learning about states/props and dynamically changing things. As such I set the states set up on a component as such:
constructor(props) {
super(props);
this.modifyStyle = this.modifyStyle.bind(this);
this.state = {
navigation: [
{ route: "/", title: "Home" },
{ route: "/about", title: "About Me" },
{ route: "/portfolio", title: "Portfolio" },
{ route: "/contact", title: "Contact" },
{ route: "/", title: "Services" },
],
styling: "nav",
};
}
Notice the "Styling" state.
This is used to give the list element style as such:
render() {
return (
<div>
<div className="trigram">
<p>☰</p>
</div>
<ul className={this.state.styling}>
{this.state.navigation.map((items) => (
<NavItem route={items.route} title={items.title} />
))}
</ul>
</div>
);
The css for the "Styling" state is this:
.nav {
width: 100%;
float: left;
list-style: none;
padding: 15px;
transition: 1s;
}
Which produces, along with the relevant li styling the following on the webpage:
[![Screenshot of menu][1]][1]
The idea is to use the following function to change the list style to a smaller one on a "Scroll" event:
componentDidMount() {
document.addEventListener("scroll", this.modifyStyle, true);
}
modifyStyle = () => {
this.setState({
styling: "nav2",
});
};
The "nav2" style which is being assigned to the state should be identical to the main menu style but with lowered padding.
.nav2 {
width: 100%;
float: left;
list-style: none;
padding: 5px;
transition: 1s;
}
The function is called and everything works as intended. The style is changed. Yet for some reason the updated styling breaks completely and is stuck looking like this:
[![screenshot issue][2]][2]
I have no idea why this is happening and it seems no amount of debugging the CSS will resolve the issue.
The Styling will just not play game here.
I expect this is something to do with the way React handles states, but I'm not really sure. Any help would be greatly appreciated.
TIA
[1]: https://i.stack.imgur.com/bK1dt.png
[2]: https://i.stack.imgur.com/w7Wh2.png
Not a React Question, was CSS.
Issue resolved by generalising the "li" tag css. Not specifying it in regards to a specific class

Watching for div width changes in vue

I have an nuxt app where I have two sidebars, one on the left and one on the right.
Both are fixed and body has padding from right and left.
In the middle I have <nuxt/> that loads pages.
Left sidebar can be minimized to 60px so I cannot use media queries for this and I need to watch for <nuxt/> width changes, in case that width is < 500px I would add some other classes. Something like media queries for element instead of viewport.
Is there a way to do this without additional javascript libraries, plugins etc..?
updated with two slider example:
A way to achieve the desired behavior will be to bind a dynamic width to your sliders and watch that width prop then bind the classes using the element ref upon changes :
Vue.config.devtools = false;
Vue.config.productionTip = false;
new Vue({
el: "#app",
data() {
return {
silderWidth: {
first: '100',
second: '100'
}
}
},
computed: {
first() {
return this.silderWidth.first
},
second() {
return this.silderWidth.second
}
},
methods: {
toggleWidth(ele) {
this.silderWidth[ele] === '100' ?
this.silderWidth[ele] = "200" :
this.silderWidth[ele] = "100"
}
},
watch: {
first() {
this.$nextTick(() => {
this.silderWidth.first === '200' ?
this.$refs.silderWidth1.classList.add('background') :
this.$refs.silderWidth1.classList.remove('background')
})
},
second() {
this.$nextTick(() => {
this.silderWidth.second === '200' ?
this.$refs.silderWidth2.classList.add('background') :
this.$refs.silderWidth2.classList.remove('background')
})
}
}
})
.silder {
background-color: red;
height: 200px;
}
.silder--1 {
position: fixed;
left: 0;
top: 0;
}
.silder--2 {
position: fixed;
right: 0;
top: 0;
}
.background {
background-color: yellow;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div class="silder silder--1" ref="silderWidth1" :style="{ width : first + 'px'}" #click="toggleWidth('first')"></div>
<div class="silder silder--2" ref="silderWidth2" :style="{ width : second + 'px'}" #click="toggleWidth('second')"></div>
</div>
Hope this helps:
window.onresize = () => {
let width = document.querySelector('nuxt').offsetWidth;
console.log(width);
};

Add hover effect to react div using inline styling

I have a div which takes the shape of a circle the css property to display a circle is taken from the circle class. The color of the circular div is taken from the inline styling. Here a function called Status() is used where it will return a hex color code. The circle renders with the colors according to the status we pass to the Status function. To achieve the hover effect i added a ':hover' property to the styling object but it doesn't work. Here is the code that i have tried. Any idea on how to achieve this?. i need to add a boarder/glow to the circle on mouse hover.
<div
className="circle"
style={{
backgroundColor: Status('new'),
':hover': {
boxShadow: `0px 0px 4px 2px ${Status('complience')}`,
},
}}
/>
Try adding & before :hover
This is not possible with inline styles, you may want to use onMouseEnter and onMouseLeave props to get the hover state and use it, for example :
class MyComponent extends React.Component {
state= {
hover: false,
}
handleMouseEnter = () => {
this.setState({ hover: true });
}
handleMouseLeave = () => {
this.setState({ hover: false });
}
render() {
const { hover } = this.state;
return(
<div
className="circle"
style={{
backgroundColor: Status('new'),
...(hover && { boxShadow: `0px 0px 4px 2px ${Status('complience')}`}),
}}
onMouseEnter={this.handleMouseEnter} // Or onMouseOver
onMouseLeave={this.handleMouseLeave}
/>
)
}
}
Alternatives :
Use a third party styling library (e.g. Styled-components)
Use classnames / css stylesheets

How to animate image for 5 seconds in React using CSS?

I'm using React with Redux, and I have the following situation. In my component I have a div that holds and image, and the component is also receiving a property from my Redux state which is called showIcon. So, if showIcon is not null, I want the image to be displayed for 5 seconds, and once the 5 seconds passes, I want it to disappear and set the showIcon value to null by dispatching an action that I have like updateShowIcon(null);. How can I do this properly in React, and how can I use CSS to show and animate the icon as I want?
import React, { Component } from 'react';
class MyComp extends Component {
render() {
return (
<div style={styles.mainDiv}>
<div style={styles.childDiv}>
{
this.props.showIcon &&
<div style={styles.iconStlyes}>
<img src={process.env.PUBLIC_URL + '/icons/myicon.png'}/>
</div>
}
// partially removed for brevity, some other component
</div>
</div>
);
}
}
const styles = {
iconStlyes: {
position: 'absolute',
zIndex: 10,
},
mainDiv: {
overflow: 'auto',
margin: 'auto',
height: 'calc(100vh - 64px)',
padding: 0,
},
childDiv: {
height: 'calc(100vh - 64px)',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
},
};
export default MyComp;
Whenever I detect a change in componentWillReceiveProps I would create a timer and dispatch the action. Remember to clear the timeout on componentWillUnmount.
The idea is based on you showing and hiding the icon with css and not with react conditional rendering, so once you need to show the icon you add the class show or remove it once you don't need to show it.
It would probably look like this:
componentWillReceiveProps(nextProps){
if (nextProps.showIcon && nextProps.showIcon !== this.props.showIcon){
this.timer = setTimeout(() => {nextProps.updateShowIcon(null)}, 5000);
}
}
componentWillUnmount(){
clearTimeout(this.timer);
}
render() {
const {showIcon} = this.props;
return (
<div style={styles.mainDiv}>
<div style={styles.childDiv}>
<div style={styles.iconStlyes} className={`${showIcon ? 'show':''} icon-container`}>
<img src={process.env.PUBLIC_URL + '/icons/myicon.png'}/>
</div>
</div>
</div>
);
}
and your css for a simple fade animation:
.icon-container{
opacity: 0;
transition: opacity: 500ms ease-in;
}
.icon-container.show{
opacity: 1;
}
If it is important for you to use the store state then you can manage the showIcon property via componentWillReceiveProps(nextProps) and do something like:
componentWillReceiveProps(nextProps){
if(!this.props.showIcon && nextProps.showIcon){
setTimeout(()=>dispatch(updateShowIcon(null)),5*1000);
}
//manage clear timeout if necessary
}
But for the animation part its better to use the showIcon property as a class and not for adding/removing it from the DOM, like:
<div style={styles.iconStlyes} className={this.props.showIcon?'show':'hide'}>
<img src={process.env.PUBLIC_URL + '/icons/myicon.png'}/>
</div>
and the styles should manage it:
iconStyles: {
position: 'absolute',
zIndex: 10;
transition: //effects of specified or all attributes
&.show{
visibility: visible;//display:block
}
&.hide{
visibility: hidden;//display:none
}
}

How to use React TransitionMotion willEnter()

Using React Motion's TransitionMotion, I want to animate 1 or more boxes in and out. When a box enters the view, it's width and height should go from 0 pixels to 200 pixels and it's opacity should go from 0 to 1. When the box leaves the view, the reverse should happen (width/height = 0, opacity = 0)
I have tried to solve this problem here http://codepen.io/danijel/pen/RaboxO but my code is unable to transition the box in correctly. The box's style jumps immediately to a width/height of 200 pixels instead of transitioning in.
What is wrong with the code?
let Motion = ReactMotion.Motion
let TransitionMotion = ReactMotion.TransitionMotion
let spring = ReactMotion.spring
let presets = ReactMotion.presets
const Demo = React.createClass({
getInitialState() {
return {
items: []
}
},
componentDidMount() {
let ctr = 0
setInterval(() => {
ctr++
console.log(ctr)
if (ctr % 2 == 0) {
this.setState({
items: [{key: 'b', width: 200, height: 200, opacity: 1}], // fade box in
});
} else {
this.setState({
items: [], // fade box out
});
}
}, 1000)
},
willLeave() {
// triggered when c's gone. Keeping c until its width/height reach 0.
return {width: spring(0), height: spring(0), opacity: spring(0)};
},
willEnter() {
return {width: 0, height: 0, opacity: 1};
},
render() {
return (
<TransitionMotion
willEnter={this.willEnter}
willLeave={this.willLeave}
defaultStyles={this.state.items.map(item => ({
key: item.key,
style: {
width: 0,
height: 0,
opacity: 0
},
}))}
styles={this.state.items.map(item => ({
key: item.key,
style: {
width: item.width,
height: item.height,
opacity: item.opacity
},
}))}
>
{interpolatedStyles =>
<div>
{interpolatedStyles.map(config => {
return <div key={config.key} style={{...config.style, backgroundColor: 'yellow'}}>
<div className="label">{config.style.width}</div>
</div>
})}
</div>
}
</TransitionMotion>
);
},
});
ReactDOM.render(<Demo />, document.getElementById('app'));
As per the documentation of styles under the TransitionMotion section (and I don't claim to have understood all of it entirely :)):
styles: ... an array of TransitionStyle ...
The key thing to note here is that there are 2 types of style objects that this library deals with (or at least this TransitionMotion part of it) and it calls them TransitionStyle and TransitionPlainStyle.
The previous values passed into styles attribute were of TransitionPlainStyle. Changing them to TransitionStyle magically starts animating the Enter sequence.
You can read more about 2 different types mentioned above over here.
styles={this.state.items.map(item => ({
key: item.key,
style: {
width: spring(item.width),
height: spring(item.height),
opacity: spring(item.opacity)
}
}))}
Forked codepen demo.
Again, I do not fully understand the inner workings of it just yet. I just know that your styles had to be changed in the above way to make it work.
I will be happy if someone can educate me on this as well.
Hope this helps.

Categories

Resources