In my home screen I want to auto hide my header in 2 seconds, then I will have a button to show the header when pressed. I have tried with HomeStack.Screen but could not achieve it, I have to create my custom header called HeaderHomeComponent.js and imported it on my homescreen, still I could not achieve it. Please I need help on this issue.
Here is my code:
const [showHeader, setShowHeader] = useState(true);
const onRecord = async () => {
if (isRecording) {
camera.current.stopRecording();
} else {
setTimeout(() => setIsRecording && camera.current.stopRecording(), 23*1000);
const data = await camera.current.recordAsync();
}
};
const visibility = () => {
setTimeout(() => setShowHeader(false), 2000);
}
return (
<View style={styles.container}>
<RNCamera
ref={camera}
type={cameraType}
flashMode={flashMode}
onRecordingStart={() => setIsRecording(true)}
onRecordingEnd={() => setIsRecording(false)}
style={styles.preview}
/>
<HeaderHomeComponent />
You can create a function with useeffect.
Make sure you passs show and handleClose functions from Parent. (Example given below).
const MessageBox = (props) => {
useEffect(() => {
if (props.show) {
setTimeout(() => {
props.handleClose(false);
}, 3000);
}
}, [props.show]);
return (
<div className={`messageBox ${props.show ? "show" : null}`}>
{props.message}
</div>
);
};
UseEffect will be called everytime props.show state will change. And we only want our timer to kick in when the show becomes true, so that we can hide it then.
Also, now to use this, it's simple, in any component.
const [showMessageBox, setShowMessageBox] = useState(false);
return(
<MessageBox
show={showMessageBox}
handleClose={setShowMessageBox} />
);
Also, make sure to handle css, part as well for show and hide.
Simple Example below.
.messageBox {
display: none;
}
.messageBox.show {
display: block;
}
Hope this helps, :-)
You need to do something like this as Mindaugas Nakrosis mentioned in comment
const [showHeader, setShowHeader] = useState(true);
useEffect(() => {
setTimeout(() => setShowHeader(false), 2000);
}, []);
In return where your header is present
{
showHeader && <HeaderHomeComponent/>;
}
I think the approach gonna fit "auto hide and show in 2 seconds", is using Animetad opacity, and giving fix height or/and z-index (as fit you) to the element
// HeaderHomeComponent.js
const animOpacity = useRef(new Animated.Value(1)).current // start with showing elem
//change main view to
<Animated.View
style={{ ...yourStyle... ,
opacity: animOpacity,
}}
>
and then for creating the animation somewhere
() => {
Animated.timing(animOpacity, {
toValue: +(!animOpacity), // the numeric value of not current
duration: 2000, // 2 secs
}).start();
}}
The hieraric location of the declaration of the ref should control usage as calling the effect. maybe you can create useEffect inside the header that can determine if it should be visible or not depends navigation or some other props.
hope its helpful!
Related
So I am trying to implement a pull to refresh feature in my app. And to say I have successfully implemented this in my Android platform where (in simulator) If I pull down (by mouse) then the refreshing indicator will stay visible until I leave the mouse click and the component will not update until the mouse click as well. It will update the component view when I leave the mouse click and refreshing indicator will be hidden in 2 sec. The similar thing is not exactly working as expected in ios, so when I pull down the screen, the component somehow updates even when I haven't left the mouse click. I have given it a googling but probably haven't been able to find the right search keyword.
Below is code snippet of mine. Thanks in advance.
render() {
const { loadingCart } = this.props;
return (
<View style={styles.container}>
<ScrollView
style={styles.scrollView}
contentContainerStyle={styles.contentContainer}
contentInsetAdjustmentBehavior="automatic"
horizontal={false}
refreshControl={this._renderRefreshingControl()}
>
{this._renderProduct()}
{loadingCart && this._renderLoadingCart()}
</ScrollView>
{this._renderCartButton()}
{this._renderAddToCartPopover()}
</View>
);
}
_renderRefreshingControl = () => {
const { refreshing } = this.state;
return (
<RefreshControl refreshing={refreshing} onRefresh={this._handleRefreshingControlVisibility} />
);
};
_handleRefreshingControlVisibility = async () => {
const { fetchProductByCode, navigation } = this.props;
this.setState({
refreshing: true,
});
const resultAction = await fetchProductByCode('38186');
if (resultAction.type === PRODUCT_FETCH_SUCCESS || resultAction.type === PRODUCT_FETCH_FAILURE) {
this.setState({
refreshing: false,
});
};
};
I hope I have been able to clarify my question :-)
Try this out:
_handleRefreshingControlVisibility = () => {
this.setState({ refreshing: true });
setTimeout(function() {
// here do what you want
}, 1500);
};
So the RefreshControl was working as expected, I had a loading flag in my render method, somehow this would be set to false when then refreshing is set true in this.setState({ refreshing: true, })
eventually removing the loading flag solved my issue.
Turns out the problem was my computer. However, James made some good points about how to isolate the problem and utilizing useCallback and useMemo to optimize.
I am having problems with the performance of my react app. For now I'm excluding the code because I feel there might be some common sense answers.
This is the demo video
Here some pointers
I don't have unnecessary re-renders. Only individual components get rendered when they are hovered.
The animations are confined to a container div of the hovered element so no re-paints happen on the page outside of that container when hovering.
I am not using any heavy code for the hover effect or detection.
I am wondering what else could be cause for performance problems like this. As far as I understand, the number of components shouldn't matter if they are just sitting there, not rerendering.
Here is the code for the card component that is being animated. I wasn't quite sure whats important to show here. The parent component showing all the cards does not re-render.
export default function CardFile(props) {
// Input field
const input = useRef(null)
//Input state
const [inputActive, setInputActive] = useState(false);
const [title, setTitle] = useState(props.file.name)
const [menuActive, setMenuActive] = useState(false)
const [draggable, setDraggable] = useState(true)
const [isDragged, setIsDragged] = useState(false)
// counter > 0 = is hovered
const [dragCounter, setDragCounter] = useState(0)
//_________________ FUNCTIONS _________________//
// Handle file delete
const handleDelete = (e) => {
firebase.firestore().collection('users').doc(props.file.owner).collection('files').doc(props.file.id).delete().then(() => {
console.info('Deleted')
}).catch((err) => console.err(err))
}
// Prevent default if necessary
const preventDefault = (e) => {
e.preventDefault()
e.stopPropagation()
}
// Handle rename
const handleRename = (e) => {
e.stopPropagation()
setMenuActive(false)
setInputActive(true)
}
// Handle change
const handleChange = () => {
setTitle(input.current.value)
}
// Handle focus loss
const handleFocusLoss = (e) => {
e.stopPropagation()
setInputActive(false)
firebase.firestore().collection('users').doc(props.file.owner).collection('files').doc(props.file.id).update({ name: title })
.then(() => {
console.info('Updated title')
}).catch((err) => console.error(err))
}
// Handle title submit
const handleKeyPress = (e) => {
console.log('key')
if (e.code === "Enter") {
e.preventDefault();
setInputActive(false)
firebase.firestore().collection('users').doc(props.file.owner).collection('files').doc(props.file.id).update({ name: title })
.then(() => {
console.info('Submitted title')
}).catch((err) => console.error(err))
}
}
// Set input focus
useEffect(() => {
if (inputActive) {
input.current.focus()
input.current.select()
}
}, [inputActive])
//_____________________________DRAGGING___________________________//
//Handle drag start
const onDragStartFunctions = () => {
props.onDragStart(props.file.id)
setIsDragged(true)
}
// Handle drag enter
const handleDragEnter = (e) => {
// Only set as target if not equal to source
if (!isDragged) {
setDragCounter(dragCounter => dragCounter + 1)
}
}
//Handle drag end
const handleDragEnd = (e) => {
e.preventDefault()
setIsDragged(false)
}
// Handle drag exit
const handleDragLeave = () => {
// Only remove as target if not equal to source
if (!isDragged) {
setDragCounter(dragCounter => dragCounter - 1)
}
}
// Handle drag over
const handleDragOver = (e) => {
e.preventDefault()
}
// Handle drag drop
const onDragDropFunctions = (e) => {
setDragCounter(0)
// Only trigger when target if not equal to source
if (!isDragged) {
props.onDrop({
id: props.file.id,
display_type: 'file'
})
}
}
return (
<div
className={`${styles.card} ${dragCounter !== 0 && styles.is_hovered} ${isDragged && styles.is_dragged}`}
test={console.log('render')}
draggable={draggable}
onDragStart={onDragStartFunctions}
onDragEnter={handleDragEnter}
onDragOver={handleDragOver}
onDragEnd={handleDragEnd}
onDragLeave={handleDragLeave}
onDrop={onDragDropFunctions}
>
<div className={styles.cardInner}>
<div className={styles.videoContainer} onClick={() => props.handleActiveMedia(props.file, 'show')}>
{props.file.thumbnail_url && props.file.type === 'video' &&
<MdPlayCircleFilled className={styles.playButton} />
}
{!props.file.thumbnail_url && props.file.type === 'image' &&
<MdImage className={styles.processingButton} />
}
{!props.file.thumbnail_url && props.file.type === 'video' &&
<FaVideo className={styles.processingButton} />
}
<div className={styles.image} style={props.file.thumbnail_url && { backgroundImage: `url(${props.file.thumbnail_url})` }}></div>
</div>
<div className={styles.body}>
<div className={styles.main}>
{!inputActive ?
<p className={styles.title}>{title}</p>
:
<input
ref={input}
className={styles.titleInput}
type="text"
onKeyPress={handleKeyPress}
onChange={handleChange}
onBlur={handleFocusLoss}
defaultValue={title}
/>
}
</div>
<ToggleContext onClick={() => setMenuActive(prevMenuActive => !prevMenuActive)}>
{
menuActive && <div className={styles.menuBackground} />
}
<Dropdown top small active={menuActive}>
<ButtonLight title={'Rename'} icon={<MdTitle />} onClick={handleRename} />
<ButtonLight title={'Label'} icon={<MdLabel />} onClick={() => props.handleActiveMedia(props.file, 'label')} />
<ButtonLight title={'Share'} icon={<MdShare />} onClick={() => window.alert("Sharing is not yet supported. Stay put.")} />
{/*props.file.type === 'video' && <ButtonLight title={'Split'} icon={<RiScissorsFill />} />*/}
<ButtonLightConfirm
danger
title={'Delete'}
icon={<MdDelete />}
onClick={(e) => preventDefault(e)}
confirmAction={handleDelete}
preventDrag={() => setDraggable(false)}
enableDrag={() => setDraggable(true)}
/>
</Dropdown>
</ToggleContext>
</div>
</div>
</div>
);
}
And here is the css for animating it:
.is_hovered {
box-shadow: 0 0 0 3px var(--blue);
}
.is_hovered > div {
transform: scale(0.9);
box-shadow: 0 0 5px rgba(0, 0, 0, 0.08);
transition: .1s;
}
Edit: Added code
Edit2: Updated sample video to show re-renders
The thing I think you should try first is 'memoizing' all your functions with useCallback. Especially as you're passing some of these functions down to other components, it's possible they are causing unnecessary re-rendering deeper in the DOM.
I don't know if you're familiar with useCallback, but basically it just wraps around your function, and only updates it when specific values change. This allows React to avoid re-creating it on every render and causing components deeper in the DOM to re-render.
You can read the docs here, but the gist of it is that instead of const getA = () => a you would write getA = useCallback(() => a, [a]), and the array contains all the dependencies for the function which cause it to update if changed.
Make sure you use these in your JSX, and avoid arrow functions like onClick={(e) => preventDefault(e)}. The function you have called preventDefault can even live outside the component entirely, since it makes no reference to anything specific to the component.
Try making these updates and see if it makes a difference. Also test without the console.log, since that can also slow things down.
I have the following Redux Reducer to handle the offset of an infinite scroll component:
const offset = handleActions(
{
[questionListTypes.ON_QUESTIONS_SCROLL]: state => state + QuestionsLoadChunkTotal,
[combineActions(questionListTypes.RESET_QUESTIONS_OFFSET)]: () => {
document.getElementById('question-list-infinite-scroll').scrollTop = 0;
return 0;
},
},
0,
);
When the offset of the component resets I want to scroll the HTML element to the top. I have added the following line in the reducer to handle this:
document.getElementById('question-list-infinite-scroll').scrollTop = 0;
This doesn't feel right to me to put it here because it has nothing to do with my state. Is there a better way to handle this situation?
You may use a Redux middleware, which purpose is to handle side effects.
It receives every action that goes through and enables us to have any side effect.
const scrollReseter = store => next => action => {
next(action);
if (action.type === combineActions(questionListTypes.RESET_QUESTIONS_OFFSET)) {
document.getElementById('question-list-infinite-scroll').scrollTop = 0;
}
}
See https://redux.js.org/advanced/middleware/
You can use a ref to get a reference to the DOM element and use an effect to manipulate that element when a certain value in the state changes.
Here is an example using local state:
const App = () => {
//this would be data that comes from state
// maybe with useSelector or with connect
const [scrollToTop, setScrollToTop] = React.useState(0);
//create a ref to the element you want to scroll
const scrollRef = React.useRef();
//this would be an action that would set scrollToTop with a new
// value
const goToTop = () => setScrollToTop((val) => val + 1);
//this is an effect that runs every time scrollToTop changes
// it will run on mount as well so when scrollToTop is 0 it
// does nothing
React.useEffect(() => {
if (scrollToTop) {
scrollRef.current.scrollTop = 0;
}
}, [scrollToTop]);
return (
<div
ref={scrollRef}
style={{ maxHeight: '250px', overflow: 'scroll' }}
>
{[...new Array(10)].map((_, key) => (
<h1
key={key}
onClick={goToTop}
style={{ cursor: 'pointer' }}
>
click to scroll to top
</h1>
))}
</div>
);
};
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
I have read through the entire react-spring docs and there doesn't seem to be a clear way to do this.
My attempt:
import React, { useRef, useState } from "react"
import { animated, useSpring } from "react-spring"
const App = () => {
const scrollDestinationRef = useRef()
const [elementScroll, setElementScroll] = useState(false)
const buttonClickHandler = () => setElementScroll(prevState => !prevState)
const scrollAnimation = useSpring({
scroll: elementScroll
? scrollDestinationRef.current.getBoundingClientRect().top
: 0
})
return (
<main>
{/* Click to scroll to destination */}
<animated.button
onClick={buttonClickHandler}
scrollTop={scrollAnimation.scroll}
style={{
height: "1000px",
width: "100%",
backgroundColor: "tomato"
}}
>
Scroll to destination
</animated.button>
{/* Scroll destination */}
<div
ref={scrollDestinationRef}
style={{
height: "200px",
width: "200px",
backgroundColor: "green"
}}
></div>
</main>
)
}
export default App
I'm using a ref and hooks for my attempt.
The useRef is attached the scroll destination in-order to find its offset top from the website's ceiling.
I use useState to toggle between the state on click to trigger the scroll.
I use useSpring to trigger an animation that goes from 0 to the scroll destination's scroll top a.k.a. getBoundingClientRect().top.
Can anyone assist in solving this?
There doesn't to be much explanation online, thanks!
useSpring returns a function to set/update animated values. You can use that function to assign a new value to your animated variable. Then, you can use the onFrame property to update the scroll position.
Define your spring like this:
const [y, setY] = useSpring(() => ({
immediate: false,
y: 0,
onFrame: props => {
window.scroll(0, props.y);
},
config: config.slow,
}));
Then use setY function to start the spring, like this:
<button
onClick={() => {
setY({ y: scrollDestinationRef.current.getBoundingClientRect().top });
}}
>
Click to scroll
</button>
When you click the button it will assign a new value to y variable in your spring, and onFrame function will be called upon every update.
Note that we call window.scroll function from onFrame property in useSpring.
See working demo here.
Finally after getting through https://github.com/pmndrs/react-spring/issues/544, answer from Yannick Schuchmann here worked for me, I just had to change onFrame to onChange
I made a custom hook for the solution: https://github.com/TomasSestak/react-spring-scroll-to-hook
react-sring version: 9.0.0-rc.3
const targetElement = useRef(null)
const [, setY] = useSpring(() => ({ y: 0 }))
let isStopped = false
const onWheel = () => {
isStopped = true
window.removeEventListener('wheel', onWheel)
}
const scrollToTarget = () => {
const element = targetElement.current
const value = window.scrollY + element.getBoundingClientRect().top
window.addEventListener('wheel', onWheel)
setY({
y: value,
reset: true,
from: { y: window.scrollY },
onRest: () => {
isStopped = false
window.removeEventListener('wheel', onWheel)
},
onFrame: props => {
if (!isStopped) {
window.scroll(0, props.y)
}
}
})
}
This version also allows the user to break out of the forced scrolling by using wheel event. (I personally hate forced scrolling :D)
useSpring(fn) does return a stop method besides the set. But I couldn't make it work with that. I posted it to a related github issue. If you read this, maybe there's a good answer for that already :)
For now it uses a little workaround with isStopped flag.
UPDATE:
Seems like there is actually something fishy with stop fn which should be addressed in v9 canary. Haven't tested since v9 is not stable yet.
I'm trying to migrate my app from old one to the new React-trancition-group API, but it's not so easy in case of using the manual <Transition> mode for transition creation of the particular React component.
My animation logic is:
we have an array of the components, each child of him comes one by one in the <TransitionGroup> API by onClick action. Where every new income component smoothly replace and hide previous one, which is already present in <Transition> API.
I almost finish unleash this tangle in react-trancition-group .v2, but one thing is still not solved - the component, that already been in <Transition> API does not disappear after the new one is overlayed him, which was automatically happen in react-trancition-group .v1 instead. So now they all just stack together...
So, maybe you can look on my code and luckly say where is my problem located...
I'll be grateful for any help. Thanks for your time
My code:
import React, { Component } from 'react'
import { findDOMNode } from 'react-dom'
import { TweenMax, Power1 } from 'gsap'
import { Transition } from 'react-transition-group'
class Slider extends Component {
_onEntering = () => {
const { id, getStateResult, isCurrentRow, removeAfterBorder, calcHeight } = this.props
const el = findDOMNode(this)
const height = calcHeight(el)
TweenMax.set(el.parentNode, {
height: `${height}px`
})
TweenMax.fromTo(
el,
0.5,
{
y: -120,
position: 'absolute',
width: `${100}%`,
zIndex: 0 + id
},
{
y: 0,
width: `${100}%`,
zIndex: 0 + id,
ease: Power1.easeIn
}
)
}
_onEntered = () => {
const { activeButton, removeAfterBorder, getCurrentOutcome } = this.props
findDOMNode(this)
}
_onExiting = () => {
const el = findDOMNode(this)
TweenMax.to(el, 2, {
onComplete: () => {
el.className = ''
}
})
}
_onExited = () => {
const { getStateResult } = this.props
getStateResult(true)
}
render() {
const { children } = this.props
return (
<Transition
in={true}
key={id}
timeout={2000}
onEntering={() => this._onEntering()}
onEntered={() => this._onEntered()}
onExiting={() => this._onExiting()}
onExited={() => this._onExited()}
unmountOnExit
>
{children}
</Transition> || null
)
}
}
export default Slider
```
So, you problem is very typically. As you written here: the component, that already been in <Transition> API does not disappear after the new one is overlayed him - it's happen because you does not changing the status flag in for Transition Component. You set always true for him, it's not a right.
By the way, you need to understand what is you trying to do in your code. There is a big difference between methods <Transition></Transition> and <TransitionGroup><CSSTransition></CSSTransition><TransitionGroup>. You need to use pure <Transition></Transition> API only for very rare cases, when you need explicitly manipulate animation scenes.
As I see in you code you trying to replace one component by the other and in this case you need to use the second method that I have provided above.
So, try this:
import { TransitionGroup, CSSTransition } from 'react-transition-group'
...some code
return (
<TransitionGroup>
<CSSTransition
key={id}
timeout={2000}
onEntering={() => this._onEntering()}
onEntered={() => this._onEntered()}
onExiting={() => this._onExiting()}
onExited={() => this._onExited()}
unmountOnExit
>
{children}
</CSSTransition> || null
</TransitionGroup>
)
It should help you and start to render Compenents by normal overlaying each other.