How can I set styling twice in the same functions with react? - reactjs

I wish to use css-transition to animate an object:
<div style={this.setStyle()}>
setStyle () {
const style = {}
if (this.state.fullScreen) {
style.background = 'white'
style.position = 'absolute'
style.transition = 'top 2s'
style.top = '20px'
}
//here I wish to set style.top = 0
return style
}
I wish to first set the style.top = 20px (this is where the item is already and then re render the dom and then set the style.top = 0 to trigger the animation. How can this be done?
state declaration:
constructor (props) {
super(props)
this.state = {
active: -1,
fullScreen: false,
fullScreenStyle: {
background: 'transparent',
position: 'static',
top: 'auto'
}
}
this.flky = {}
}
setStyle () {
if (this.state.fullScreen) {
this.setState({
fullScreenStyle.background: 'white,
fullScreenStyle.position: 'absolute'
fullScreenStyle.top: '20px'
})
}

To rerender the Dom you have two options:
1) by setting the state use setState.
2) by using lifecycle function that is forceUpdate()
But you have to take care before using forceupdate function because it stops other operations and invoke render function, using setState is recommended.
In this you can do one thing:
constructor(props)
{
super(props)
this.state={
style:{
background = 'white',
position = 'absolute',
transition = 'top 2s',
top:'20px'
}
}
}
<div style={this.setStyle()}>
setStyle () {
//you can set state as follows
if (this.state.fullScreen) {
this.setState({
style:{background: 'white'},
style:{position:'absolute'},
style:{transition: 'top 2s'},
style:{top: '20px'}
)}
}
//here I wish to set style.top = 0
else
{
this.setState({ style:{background: 'white'},
style:{position:'absolute'},
style:{transition: 'top 2s'},
style:{top: '0px'}
)}
}
}

Related

Move a square using the coordinate system in reactjs

I need to move this square using _dragTask() function, and i also need to do to it by using the coordinate system. Anyone can do that?
import React from 'react';
import './App.css';
class App extends React.Component {
componentDidMount() {
}
constructor(props) {
super(props);
this.state = {
MainContainer: {backgroundColor: "#282c34", display: "flex", minHeight: "100vh"},
ObjectMap: [],
}
this._createObject = this._createObject.bind(this);
this._createBox = this._createBox.bind(this);
this._buttonCreateBox1 = this._buttonCreateBox1.bind(this);
this._dragTask = this._dragTask.bind(this);
}
_createObject = (object) => {
var ObjectMap = this.state.ObjectMap;
ObjectMap.push(object);
this.setState({ObjectMap: ObjectMap});
}
_createBox = (style) => {
var object = {position: "absolute", top: this.state.positionX, left: this.state.positionY};
var styleObject = Object.assign({},
object, style
)
return (
<div style={styleObject} draggable="true" onDragEnd={(event) => {this._dragTask(event)}}>
</div>
);
}
_dragTask(event) {
event.persist();
this.setState({positionX: event.screenX, positionY: event.screenY}, () => { console.log("Page X:"+ this.state.positionX + " Page Y:" + this.state.positionY); });
}
_buttonCreateBox1 = () => {
this._createObject(this._createBox({backgroundColor: "white", width: "300px", height: "300px"}));
}
render() {
return (
<div style={this.state.MainContainer}>
<button type="button" onClick={ this._buttonCreateBox1 }>Click Me!</button>
{
this.state.ObjectMap.map((item, index) => {
return item
})
}
</div>
);
}
}
export default App;
Right now, the object it created by printed in the screen by the mainloop, and added to it by _createObject() function.
just change your _dragTask method to this
_dragTask(event) {
event.target.style.top = event.clientY + "px";
event.target.style.left = event.clientX + "px";
}
and change the style of the wrapper to have position: relative
constructor(props) {
super(props);
this.state = {
MainContainer: {
backgroundColor: "#282c34",
position: "relative",
display: "flex",
minHeight: "100vh"
},
ObjectMap: []
};
...
checkout this sandbox to see full example

Changing styles when scrolling React

I want to add the scrolling effect. At the start, the elements have the opacity: 0.2 property. When element is reached in the browser window, it is to replace the property with opacity: 1. At this moment, when I scroll, all elements change the property to opacity: 1. How to make this value when element is reached in the browser, the rest of the elements have the property opacity: 0.2
class QuestionListItem extends Component {
constructor() {
super();
this.state = {
opacity: 0.2,
};
}
componentDidMount = () => {
window.addEventListener('scroll', () => {
this.setState({
opacity: 1,
});
});
};
render() {
const { question } = this.props;
const { opacity } = this.state;
return (
<div>
<li
key={question.id}
className="Test__questions-item"
style={{ opacity: `${opacity}` }}
ref={
(listener) => { this.listener = listener; }
}
>
<p>
{question.question}
</p>
<QuestionAnswerForm />
</li>
</div>
);
}
}
I want effect like this https://anemone.typeform.com/to/jgsLNG
A proper solution could look like this. Of course, this is just a concept. You can fine-tune the activation/deactivation logic using props from getBoundingClientRect other than top (e.g. height, bottom etc).
Important that you should not set the component's state on every single scroll event.
const activeFromPx = 20;
const activeToPx = 100;
class ScrollItem extends React.Component {
state = {
isActive: false
}
componentDidMount = () => {
window.addEventListener('scroll', this.handleScroll);
this.handleScroll();
};
handleScroll = () => {
const { top } = this.wrapRef.getBoundingClientRect();
if (top > activeFromPx && top < activeToPx && !this.state.isActive) {
this.setState({ isActive: true });
}
if ((top <= activeFromPx || top >= activeToPx) && this.state.isActive) {
this.setState({ isActive: false });
}
}
setWrapRef = ref => {
this.wrapRef = ref;
}
render() {
const { isActive } = this.state;
return (
<div
className={`scroll-item ${isActive && 'scroll-item--active'}`}
ref={this.setWrapRef}
>
{this.props.children}
</div>
)
}
}
class ScrollExample extends React.Component {
render() {
return (
<div className="scroll-wrap">
<ScrollItem>foo</ScrollItem>
<ScrollItem>bar</ScrollItem>
<ScrollItem>eh</ScrollItem>
</div>);
}
}
ReactDOM.render(<ScrollExample />, document.getElementById('root'))
.scroll-wrap {
height: 300vh;
background: lightgray;
padding-top: 55px;
}
.scroll-item {
height: 60vh;
background: lightcyan;
margin: 10px;
opacity: 0.2;
}
.scroll-item--active {
opacity: 1;
}
<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>
<div id="root"></div>
You can include an isInViewport-like implementation as this one: https://gist.github.com/davidtheclark/5515733 then use it on your component.
componentDidMount = () => {
window.addEventListener('scroll', (event) => {
if (isElementInViewport(event.target) {
this.setState({
opacity: 1,
});
}
});
};
There's also read-to-use react-addons for this: https://github.com/roderickhsiao/react-in-viewport

How to add transition while moving items in a state or props rendered with a map

Say I am rendering a list from a prop of a state using .map function.
I want to change the order of the list; so I change the order in my state or my prop. The change is reflected accordingly, but how am I to add a smooth transition in this case such that the user would feel like he/she has done something. Otherwise the user experience degrades.
He is a basic idea of what I want to achieve
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
export class App extends React.Component {
constructor() {
super();
this.state = {
items: [
{ key: 0, name: "Hello" },
{ key: 0, name: "Sello" }, // move this down with transition
{ key: 0, name: "Wello" }, // move this up with transition
{ key: 0, name: "Zello" },
{ key: 0, name: "Pello" },
{ key: 0, name: "Aello" }
]
};
}
reShuffle = () => {
this.setState({
items: this.state.items.map((item, index) => {
if (index === 1) {
return this.state.items[2];
}
if (index === 2) {
return this.state.items[1];
}
return item;
})
});
};
render() {
return (
<div className="App" style={{ transition: "all 0.5s ease" }}>
<button onClick={this.reShuffle}> Click Me </button>
{this.state.items.map(item => <li key={item.key}> {item.name} </li>)}
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Easily replicable from here
https://codesandbox.io/s/648z61kvrn
When doing css transition on react element you need to deal with an additional state: the transition itself.
So first you change the state of your components so that css transition will alter them, then you change the state at the end of the transition to make react match the style of the component at the end of the transition.
In you case when you click to switch the element you have to compute a css alteration that will have the same effect as switching the elements, then when the transition ends, you actually switch the elements.
We will need to add a top property to the items, and a proper key:
constructor() {
super();
this.state = {
items: [
{ key: 0, name: "Hello", top: 0 },
{ key: 1, name: "Sello", top: 0 }, // move this down
{ key: 2, name: "Wello", top: 0 }, // move this up
{ key: 3, name: "Zello", top: 0 },
{ key: 4, name: "Pello", top: 0 },
{ key: 5, name: "Aello", top: 0 }
],
transitioning: {}
};
this.itemTransitionCount = 0;
}
Note the itemTransitionCount for later.
First wrap your <li> elements in an <ul> element and attach a ref to the <ul>:
ulLoad = c => (this.ulDom = c);
render() {
...
<ul
ref={this.ulLoad}
style={{ position: "relative", display: "inline-block" }}
>
{
this.state.items.map(item => (
<li
key={item.key}
onTransitionEnd={this.itemTransitionEnd}
style={{
transition: item.top ? "top 0.5s ease" : "none",
position: "relative",
top: item.top
}}
>
{item.name}
</li>
))
}
</ul>
....
}
This will allow to compute the relative position of the children <li>s
Then change your reShuffle handler this way:
reShuffle = () => {
this.setState({
items: this.state.items.map((item, index) => {
if (index === 1) {
const top2 = this.ulDom.children[2].offsetTop;
const top1 = this.ulDom.children[1].offsetTop;
const top = top2 - top1;
return { ...this.state.items[1], top };
}
if (index === 2) {
const top1 = this.ulDom.children[1].offsetTop;
const top2 = this.ulDom.children[2].offsetTop;
const top = top1 - top2;
return { ...this.state.items[2], top };
}
return item;
})
});
This code will initiate the top transition that we want to set on the li items => the switching animation will occur during the next render.
The we catch the end of both animation with the onTransitionEnd handler (the check on evt.target !== evt.currentTarget is there because transition event bubbles, the count is there because both elements will raise the event but we want to act once at the end of both transitions):
itemTransitionEnd = evt => {
if (evt.target !== evt.currentTarget) return;
// alert("transitionEnd");
this.itemTransitionCount++;
if (this.itemTransitionCount < 2) return;
this.itemTransitionCount = 0;
this.setState({
items: this.state.items.map((item, index) => {
if (index === 1) {
return { ...this.state.items[2], top: 0 };
}
if (index === 2) {
return { ...this.state.items[1], top: 0 };
}
return item;
})
});
};
The code above actually swithes the element at the end of the transition and reset their relative top (the transition count is also reset for next transition).
This illustrates a two step rendering for a css transition animation in React.
Working sandbox here

How do I access the variable in componentWillMount()

I am trying to set a global style variable from the componentWillMount() but I am not able to do that. When I am logging the variable it says undefined.
export default class Post extends React.Component {
constructor(props) {
super(props)
this.state = {
postImageUri: {
uri: this.props.postImageUri
}
}
}
componentWillMount() {
Image.getSize(this.props.postImageUri, (width, height) => {
...
styles.postImage.height = height
styles.postImage.width = Dimensions.get('window').width
console.log(styles.postImage.height) //Undefined
console.log(styles.postImage.width) //Undefined
...
});
}
render() {
return (
<View>
<Image source={this.state.postImageUri} style={styles.postImage}/>
</View>
}
}
val styles = StyleSheet.create({
postImage: {
height: 0,
width: 0
}
})
Instead of mutating the style, you should store the height in state:
state = {
height: null,
};
componentDidMount() {
Image.getSize(this.props.postImageUri, (width, height) => {
this.setState({ height });
});
}
Then use style composition to apply the height from the state:
<Image
source={this.state.postImageUri}
style={[styles.postImage, { height: this.state.height } ]}
/>
Your width computation is synchronous so you can set it directly in style:
const styles = StyleSheet.create({
postImage: {
width: Dimensions.get('window').width,
},
});
Store the style in state instead of storing in a global variable. This way the component will itself figure out whenever the state is changed and component will re-render.
You can change the state using this.setState({stateName: someValue});.

Implementing a momentary indicator in React

My goal is to have a save indicator that flashes a save icon when data has just been saved (not while it's being saved), as an indication to the user that their edit was successful. React seems better suited towards state than one-off "actions," but this was the best I was able to come up with:
import React, { PureComponent, PropTypes } from 'react';
import Radium from 'radium';
import moment from 'moment';
import { ContentSave as SaveIcon } from 'material-ui/svg-icons';
class SaveIndicator extends PureComponent {
getStyles = () => {
if (!this.props.saving) return { opacity: 0 };
return {
animation: 'x 700ms ease 0s 3 normal forwards',
animationName: saveAnimation,
};
};
render() {
return <div style={styles.root}>
<div style={{ display: 'flex' }}>
<div style={{ marginRight: 16 }}>
Last saved {moment(this.props.lastSaved).fromNow()}
</div>
<div
style={this.getStyles()}
onAnimationEnd={() => this.props.onIndicationComplete()}
>
<SaveIcon color="#666" />
</div>
</div>
</div>
}
}
const saveAnimation = Radium.keyframes({
'0%': { opacity: 0 },
'50%': { opacity: 1 },
'100%': { opacity: 0 },
});
const styles = {
root: {
display: 'inline-block',
},
};
SaveIndicator.defaultProps = {
saving: false,
};
SaveIndicator.propTypes = {
lastSaved: PropTypes.object.isRequired,
onIndicationComplete: PropTypes.func,
saving: PropTypes.bool,
};
export default Radium(SaveIndicator)
It works, but is there a way I could streamline this and make it even shorter?
How about this. I had a component a while back that needed something similar to what you're describing. I'll paste it in because it works fully but the strategy is kind of like this: pass in a time to start the animation. This prop triggers a function to start the animation which grabs the difference between that time and "now". It iteratively sets the state to close the gap between the initial time and now until it exceeds the passed in duration prop.
class SaveIndicator extends Component {
static propTypes = {
children: Types.element,
// time in milliseconds when the save is started; can change
indicationStartTime: Types.number.isRequired,
// time in milliseconds that the animation will take to fade
duration: Types.number,
// time in milliseconds to wait between renderings
frameRate: Types.number,
};
static defaultProps = {
duration: 7000,
frameRate: 100,
}
constructor(props) {
super(props);
this.state = { opacity: 0 };
}
componentDidMount() {
this.startAnimation();
}
componentWillReceiveProps({ indicationStartTime }) {
if (indicationStartTime !== this.props.indicationStartTime) {
this.startAnimation();
}
}
startAnimation() {
const { indicationStartTime, duration, frameRate } = this.props;
const now = new Date().getTime();
const newOpacity = 1 - ((now - indicationStartTime) / duration);
if (now - indicationStartTime < duration) {
this.setState({ opacity: newOpacity }, () =>
setTimeout(::this.startAnimation, frameRate)
);
} else {
this.setState({ opacity: 0 });
}
}
render() {
const { children } = this.props;
const { opacity } = this.state;
const style = { opacity };
return <div style={style}>{children}</div>;
}
}

Resources