What I am basically trying to create is a navbar that has two completely different html hierarchy based on the window size. I want it to be different for mobile than for a desktop version. eg a nav bar that is on the right on desktop and one that is on the top for mobile.
A simply state of what was doing. I created a const that would use a state of the screen size. I had used the useState() to get a default for now but I know that if I was first loading on desktop and it it was defaulted to mobile. I would have to resize first to get the desktop version instead.
const [sizeState, setSizeState] = useState("mobile");
const changeNavbar = () => {
if (window.innerWidth <= 900) {
setSizeState("mobile");
} else {
setSizeState("desktop");
}
};
window.addEventListener('resize', changeNavbar);
the sizeState would then call an if function determin what state it curently is set to.
if (sizeState === "mobile") {
return ( //some code for mobile) }
else {
// return some code for desktop
}
for now it always returns the mobile version even if loading upon a innderwidth that is above 900 abd only on resize it would do something.
I have been trying to use a onload stuff and an eventlistener that would listen to load. but i cant manage to call the changeNavbar function on the first load of the page.
I saw people recomending usein useMediaQuerry but i dont know how to get it to work based on my if (mediaquery is set to md) { return( mobile navbar) }
if someone could help me use the useMediaQuerry in this instead of my previous attempt, so that i can have two seperated returns i would also be soooooo thankful for the help!
You can simply implement it using styled-components and styled-breakpoints packages and its hooks API.
Here is an example: https://codesandbox.io/s/styled-breakpoints-n8csvf
import { down } from "styled-breakpoints";
import { useBreakpoint } from "styled-breakpoints/react-styled";
export default function App() {
const isMobile = useBreakpoint(down("sm"));
return (
<div className="App">
{isMobile ? "Mobile View" : "Desktop View"}
</div>
);
}
Or you can create custom hooks like this: https://github.com/jasonjin220/use-window-size-v2
import useWindowSize from "use-window-size-v2";
export default function App() {
const { width, height } = useWindowSize();
return (
<div className="box">
<h1>useWindowSize Hook</h1>
<p>
height: {height}
<br />
width: {width}
</p>
</div>
);
}
Related
I'm currently learning ReactJS.
I created a simple application and I got two buttons.
On a desktop everything works fine and smooth.
On my iPhone using Safari however, when I click the button there is a delay before the onClick action changes appear.
I read about the 300ms delay on mobile devices but none of the solutions given worked for me. Ref: https://www.sitepoint.com/5-ways-prevent-300ms-click-delay-mobile-devices/
import React, { useState } from "react";
import './form_donation.scss';
function FormDonationAmount() {
const [amount, setAmount] = useState(0);
const [amountText, setAmountText] = useState(amount.toString());
function updateAmount(amount: number) {
if (amount > 99999) {
setAmount(99999);
} else if (amount < 0) {
setAmount(0);
} else {
setAmount(amount);
}
setAmountText(amount.toString());
}
function handleAmountChange(e: any) {
var parsed = parseInt(e.target.value);
if (isNaN(parsed)) {
setAmountText("");
updateAmount(0);
return;
}
updateAmount(parsed);
}
return (
<div className="inputs">
<div id="amount_input">
<input type="number" onChange={handleAmountChange} value={amountText} />
<button onClick={() => updateAmount(amount - 10)}>-</button>
<button onClick={() => updateAmount(amount + 10)}>+</button>
</div>
</div>
);
}
export default FormDonationAmount;
This is very frustrating for the user experience. Any ideas how I could fix this ?
Thanks
As it turns out, it was my css that was absolutely massive for the mobile to handle. So yeah, big moving blurry things in the background are cool but not very optimised...
I'm using Redux in my app, inside a Component I want to scroll to an specific div tag when a change in the store happens.
I have the Redux part working so it triggers the componentDidUpdate() method (I routed to this compoennt view already).
The problem as far as I can tell, is that the method scrollIntoView() doesn't work properly cos componentDidUpdate() has a default behavior that scrolls to the top overwriting the scrollIntoView().
To work-around it I wrapped the function calling scrollIntoView() in a setTimeout to ensure that happens afeterwards.
What I would like to do is to call a preventDefault() or any other more elegant solution but I can't find where to get the event triggering the 'scrollTop'
I looked through the Doc here: https://facebook.github.io/react/docs/react-component.html#componentdidupdate
and the params passed in this function are componentDidUpdate(prevProps, prevState) ,since there is no event I don't know how to call preventDefault()
I've followd this Docs: https://facebook.github.io/react/docs/refs-and-the-dom.html
And tried different approaches people suggested here: How can I scroll a div to be visible in ReactJS?
Nothing worked though
Here is my code if anyone has any tip for me, thanks
class PhotoContainer extends React.Component {
componentDidUpdate(){
setTimeout(() => {
this.focusDiv();
}, 500);
}
focusDiv(){
var scrolling = this.theDiv;
scrolling.scrollIntoView();
}
render() {
const totalList = [];
for(let i = 0; i < 300; i += 1) {
totalList.push(
<div key={i}>{`hello ${i}`}</div>
);
}
return (
<div >
{totalList}
<div ref={(el) => this.theDiv = el}>this is the div I'm trying to scroll to</div>
</div>
)
};
}
Ok it's been a while but I got it working in another project without the setTimeOut function so I wanted to answer this question.
Since Redux pass the new updates through props, I used the componentWillRecieveProps() method instead of componentDidUpdate() , this allowes you a better control over the updated properties and works as expected with the scrollIntoView() function.
class PhotoContainer extends React.Component {
componentWillReceiveProps(newProps) {
if (
this.props.navigation.sectionSelected !==
newProps.navigation.sectionSelected &&
newProps.navigation.sectionSelected !== ""
) {
this.focusDiv(newProps.navigation.sectionSelected);
}
}
focusDiv(section){
var scrolling = this[section]; //section would be 'theDiv' in this example
scrolling.scrollIntoView({ block: "start", behavior: "smooth" });//corrected typo
}
render() {
const totalList = [];
for(let i = 0; i < 300; i += 1) {
totalList.push(
<div key={i}>{`hello ${i}`}</div>
);
}
return (
<div >
{totalList}
<div ref={(el) => this.theDiv = el}>
this is the div I am trying to scroll to
</div>
</div>
)
};
}
I also struggled with scrolling to the bottom of a list in react that's responding to a change in a redux store and I happened upon this and a few other stackoverflow articles related to scrolling. In case you also land on this question as well there are a few ways this could be a problem. My scenario was that I wanted a 'loading' spinner screen while the list was rendering. Here are a few wrong ways to do this:
When loading = true, render spinner, otherwise render list.
{loading ?
<Spinner />
:
<List />
}
as stated above this doesn't work because the list you might want to scroll to the bottom of isn't rendered yet.
When loading set the display to block for the spinner and none for the list. When done loading, reverse the display.
<div style={{display: loading ? 'block' : 'none'>
<Spinner />
</div>
<div style={{display: loading ? 'none' : 'block'>
<List />
</div>
This doesn't work either since the list you want to scroll to the bottom of isn't actually being displayed likely when you call the scroll.
The better approach for the above scenario is to use a loading that acts as an overlay to the component. This way both the spinner and list are rendered and displayed, the scroll happens, and when the loading is complete, the spinner can be de-rendered or set to be invisible.
I'm attempting to do an animation with React and CSS classes. I have created a live demo, if you visit it and click the Start button you will see the text fade in and up one by one. This is the desired animation that I am after.
However, there seems to be issues of consistency when you hit Start multiple times and I cannot pinpoint why.
The Issue: Below is a recording of the issue, you can see the number 1 is not behaving as expected.
live demo
The process: Clicking Start will cancel any previous requestAnimationFrame' and will reset the state to it's initial form. It then calls the showSegments() function with a clean state that has no classNames attached to it.
This function then maps through the state adding a isActive to each segment in the state. We then render out the dom with a map and apply the new state.
This should create a smooth segmented animation as each class gets dropped one by one. However when i test this in Chrome (Version 56.0.2924.87 (64-bit)) and also on iOS, it is very inconsistent, sometimes it works perfectly, other times the first DOM element won't animate, it will just stay in up and visible it's completed transitioned state with "isActive".
I tried to replicate this issue in safari but it worked perfectly fine, I'm quite new to react so i am not sure if this is the best way to go about things, hopefully someone can offer some insight as to why this is behaving quite erratic!
/* MotionText.js */
import React, { Component } from 'react';
import shortid from 'shortid';
class MotionText extends Component {
constructor(props) {
super(props);
this.showSegments = this.showSegments.bind(this);
this.handleClickStart = this.handleClickStart.bind(this);
this.handleClickStop = this.handleClickStop.bind(this);
this.initialState = () => { return {
curIndex: 0,
textSegments: [
...'123456789123456789123456789123456789'
].map(segment => ({
segment,
id: shortid.generate(),
className: null
}))
}};
this.state = this.initialState();
}
handleClickStop() {
cancelAnimationFrame(this.rafId);
}
handleClickStart(){
cancelAnimationFrame(this.rafId);
this.setState(this.initialState(), () => {
this.rafId = requestAnimationFrame(this.showSegments);
});
}
showSegments() {
this.rafId = requestAnimationFrame(this.showSegments);
const newState = Object.assign({}, this.state);
newState.textSegments[this.state.curIndex].className = 'isActive';
this.setState(
{
...newState,
curIndex: this.state.curIndex + 1
},
() => {
if (this.state.curIndex >= this.state.textSegments.length) {
cancelAnimationFrame(this.rafId);
}
}
);
}
render(){
const innerTree = this.state.textSegments.map((obj, key) => (
<span key={obj.id} className={obj.className}>{obj.segment}</span>
));
return (
<div>
<button onClick={this.handleClickStart}>Start</button>
<button onClick={this.handleClickStop}>Stop</button>
<hr />
<div className="MotionText">{innerTree}..</div>
</div>
)
}
}
export default MotionText;
Thank you for your time, If there any questions please ask
WebpackBin Demo
Changing the method to something like this works
render(){
let d = new Date();
const innerTree = this.state.textSegments.map((obj, key) => (
<span key={d.getMilliseconds() + obj.id} className={obj.className}>{obj.segment}</span>
));
return (
<div>
<button onClick={this.handleClickStart}>Start</button>
<button onClick={this.handleClickStop}>Stop</button>
<hr />
<div className="MotionText">{innerTree}..</div>
</div>
)
}
How this helps is that, the key becomes different than previously assigned key to first span being rendered. Any way by which you can make the key different than previous will help you have this animation. Otherwise React will not render it again and hence you will never see this in animation.
I have a sub component that does not need to be loaded immediately that I want to split out. I am trying to conditionally load in a react component via require.ensure. I am not getting any console errors but I am also not seeing anything being loaded. Here is the code I am calling :
renderContentzones() {
if (this.props.display ) {
return require.ensure([], () => {
const Component = require('./content-zones/component.jsx').default;
return (
<Component
content={this.props.display}
/>
);
});
}
return null;
}
It is just rendering a blank screen currently (no errors). This previously worked when I used import 'displayComponent' from './content-zones/component.jsx' and just returned it like you normally would in react, instead of this require.ensure but. Not sure what I am doing wrong here, any idea how to make something like this work? Thanks!
This is one way to do it, using the state to show the dynamic loaded component:
constructor(){
this.state = {cmp:null};
}
addComponent() {
const ctx = this;
require.ensure(['../ZonesComponent'], function (require) {
const ZonesComponent = require('../ZonesComponent').default;
ctx.setState({cmp:<ZonesComponent />});
});
}
render(){
return (
<div>
<div>Some info</div>
<div><button onClick={this.addComponent.bind(this)}>Add</button></div>
<div>
{this.state.cmp}
</div>
</div>
);
}
When you press the button add the component will be shown.
Hope this help.
My React native application screen has View component with few text inputs. How can touch be detected on screen outside that View? Please help.
Thanks
As Andrew said: You can wrap your View with TouchableWithoutFeedback and adding a onPress you can detect when the view is tapped.
Another way to achieve that is having responses for touch events from the view.
/* Methods that handled the events */
handlePressIn(event) {
// Do stuff when the view is touched
}
handlePressOut(event) {
// Do stuff when the the touch event is finished
}
...
<View
onStartShouldSetResponder={(evt) => true}
onMoveShouldSetResponder={(evt) => true}
onResponderGrant={this.handlePressIn}
onResponderMove={this.handlePressIn}
onResponderRelease={this.handlePressOut}
>
...
</View>
The difference between Grant and move is that Grant is just when the user press, and Move is when the user is pressing and moving the position of the press
I don't take no for an answer, so I dug up a lot to find a solution matching my needs.
In my situation I have multiple components which need to collapse when I open another one.
This behavior has to be automatic, and easy to code-in by any contributor.
Passing parent refs to the children or calling a special global method are not acceptable solutions in my circumstances.
Using a transparent background to catch all clicks will not cut it.
This Question perfectly illustrates the need.
Demo
Here is the final result. Clicking anywhere except the component itself will collapse it.
WARNING
The solution includes usage of private React components properties. I know the inherent risks of using such an approach and I'm happy to use them as long as my app does what I expect and all other constraints are satisfied. Short disclaimer, probably a smarter, cleaner solution exists out there. This is the best I could do with my own limited knowledge of React.
First we need to capture all click in the UI, both for Web and Native. It seems that this is not easily done. Nested TouchableOpacityseem to allow only one responder at a time. So I had to improvise a bit here.
app.tsx (trimmed down to essentials)
import * as div from './app.style';
import { screenClicked, screenTouched } from './shared/services/self-close-signal.service';
// ... other imports
class App extends React.Component<Props, State> {
public render() {
return (
<div.AppSafeArea
onTouchStart={e => screenTouched(e)}
onClick={e => screenClicked(e)}>
{/* App Routes */}
<>{appRoutes(loginResponse)}</>
</div.AppSafeArea>
);
}
}
self-close-signal.service.ts
This service was built to detect all clicks on the app screen. I use reactive programming in the entire app so rxjs was employed here. Feel free to use simpler methods if you want. The critical part here is detecting if the clicked element is part of the hierarchy of an expanded component or not. When I write a mess like this I usually fully document why this was built this way in order to protect it from "eager" developers doing cleanups.
import { AncestorNodeTrace, DebugOwner, SelfCloseEvent } from '../interfaces/self-close';
import { GestureResponderEvent } from 'react-native';
import { Subject } from 'rxjs';
/**
* <!> Problem:
* Consider the following scenario:
* We have a dropdown opened and we want to open the second one. What should happen?
* The first dropdown should close when detecting click outside.
* Detecting clicks outside is not a trivial task in React Native.
* The react events system does not allow adding event listeners.
* Even worse adding event listener is not available in react native.
* Further more, TouchableOpacity swallows events.
* This means that a child TouchableOpacity inside a parent TouchableOpacity will consume the event.
* Event bubbling will be stopped at the responder.
* This means simply adding a backdrop as TouchableOpacity for the entire app won't work.
* Any other TouchableOpacity nested inside will swallow the event.
*
* <!> Further reading:
* https://levelup.gitconnected.com/how-exactly-does-react-handles-events-71e8b5e359f2
* https://stackoverflow.com/questions/40572499/touchableopacity-swallow-touch-event-and-never-pass
*
* <!> Solution:
* Touch events can be captured in the main view on mobile.
* Clicks can be captured in the main view on web.
* We combine these two data streams in one single pipeline.
* All self closeable components subscribe to this data stream.
* When a click is detected each component checks if it was triggered by it's own children.
* If not, it self closes.
*
* A simpler solution (with significant drawbacks) would be:
* https://www.jaygould.co.uk/2019-05-09-detecting-tap-outside-element-react-native/
*/
/** Combines both screen touches on mobile and clicks on web. */
export const selfCloseEvents$ = new Subject<SelfCloseEvent>();
export const screenTouched = (e: GestureResponderEvent) => {
selfCloseEvents$.next(e);
};
export const screenClicked = (e: React.MouseEvent) => {
selfCloseEvents$.next(e);
};
/**
* If the current host component ancestors set contains the clicked element,
* the click is inside of the currently verified component.
*/
export const detectClickIsOutside = (event: SelfCloseEvent, host: React.Component): boolean => {
let hostTrace = getNodeSummary((host as any)._reactInternalFiber);
let ancestorsTrace = traceNodeAncestors(event);
let ancestorsTraceIds = ancestorsTrace.map(trace => trace.id);
let clickIsOutside: boolean = !ancestorsTraceIds.includes(hostTrace.id);
return clickIsOutside;
};
// ====== PRIVATE ======
/**
* Tracing the ancestors of a component is VITAL to understand
* if the click originates from within the component.
*/
const traceNodeAncestors = (event: SelfCloseEvent): AncestorNodeTrace[] => {
let ancestorNodes: AncestorNodeTrace[] = [];
let targetNode: DebugOwner = (event as any)._targetInst; // <!WARNING> Private props
// Failsafe
if (!targetNode) { return; }
traceAncestor(targetNode);
function traceAncestor(node: DebugOwner) {
node && ancestorNodes.push(getNodeSummary(node));
let parent = node._debugOwner;
parent && traceAncestor(parent);
}
return ancestorNodes;
};
const getNodeSummary = (node: DebugOwner): AncestorNodeTrace => {
let trace: AncestorNodeTrace = {
id: node._debugID,
type: node.type && node.type.name,
file: node._debugSource && node._debugSource.fileName,
};
return trace;
};
interfaces/self-close.ts - Some boring typescript interfaces to help with project maintenance.
import { NativeSyntheticEvent } from 'react-native';
/** Self Close events are all the taps or clicks anywhere in the UI. */
export type SelfCloseEvent = React.SyntheticEvent | NativeSyntheticEvent<any>;
/**
* Interface representing some of the internal information used by React.
* All these fields are private, and they should never be touched or read.
* Unfortunately, there is no public way to trace parents of a component.
* Most developers will advise against this pattern and for good reason.
* Our current exception is an extremely rare exception.
*
* <!> WARNING
* This is internal information used by React.
* It might be possible that React changes implementation without warning.
*/
export interface DebugOwner {
/** Debug ids are used to uniquely identify React components in the components tree */
_debugID: number;
type: {
/** Component class name */
name: string;
};
_debugSource: {
/** Source code file from where the class originates */
fileName: string;
};
_debugOwner: DebugOwner;
}
/**
* Debug information used to trace the ancestors of a component.
* This information is VITAL to detect click outside of component.
* Without this script it would be impossible to self close menus.
* Alternative "clean" solutions require polluting ALL components with additional custom triggers.
* Luckily the same information is available in both React Web and React Native.
*/
export interface AncestorNodeTrace {
id: number;
type: string;
file: string;
}
And now the interesting part.
dots-menu.tsx - Trimmed down to the essentials for the example
import * as div from './dots-menu.style';
import { detectClickIsOutside, selfCloseEvents$ } from '../../services/self-close-signal.service';
import { Subject } from 'rxjs';
// ... other imports
export class DotsMenu extends React.Component<Props, State> {
private destroyed$ = new Subject<void>();
constructor(props: Props) {
// ...
}
public render() {
const { isExpanded } = this.state;
return (
<div.DotsMenu ...['more props here'] >
{/* Trigger */}
<DotsMenuItem expandMenu={() => this.toggleMenu()} ...['more props here'] />
{/* Items */}
{
isExpanded &&
// ... expanded option here
}
</div.DotsMenu>
);
}
public componentDidMount() {
this.subscribeToSelfClose();
}
public componentWillUnmount() {
this.destroyed$.next();
}
private subscribeToSelfClose() {
selfCloseEvents$.pipe(
takeUntil(this.destroyed$),
filter(() => this.state.isExpanded)
)
.subscribe(event => {
let clickOutside = detectClickIsOutside(event, this);
if (clickOutside) {
this.toggleMenu();
}
});
}
private toggleMenu() {
// Toggle visibility and animation logic goes here
}
}
Hope it works for you as well.
P.S. I'm the owner, feel free to use these code samples. Hope you will enjoy this answer and check Visual School for future React Native tutorials.
Put your View inside of TouchableWithoutFeedback, expand TouchableWithoutFeedback fullscreen and add onPress handler to it.
<TouchableWithoutFeedback
onPress={ /*handle tap outside of view*/ }
style={ /* fullscreen styles */}
>
<View>
...
</View
</TouchableWithoutFeedback>
You could try to use a Modal to create this behavior.
When you click the input field you show the Modal containing the multiple texts inputs. If you click outside the Modal it hides.
you can use
<View>
<TouchableWithoutFeedback
onPress={()=>{
//do something
}}
style={{position:'absolute',top:0 , right:0 , bottom:0 ,left:0}}/>
<YourComp></YourComp>
</View>
An easier solution, as stated here, is to detect the start of a touch action outside of the menu and close the menu in this case.
Keep in mind that for this to work, the first View that will catch the touch should take the full screen height, and that the app content as well as the menu should be inside. This allow the touch event to cascade correctly.
eg:
const [isOverflowMenuDisplayed, setOverflowMenuDisplayed] = useState(false)
const [childrenIds, setChildrenIds] = useState([])
const handleTouchShouldSetResponder = (event) => {
// To be able to close the overflow menu, the content of the screen need to be inside this top view, and detect if the pressed view if the menu item or the app content
if (childrenIds.length) {
if (childrenIds.includes(event.target)) {
return true
}
setOverflowMenuDisplayed(false)
return false
}
return false
}
return <View
onStartShouldSetResponder={handleTouchShouldSetResponder}
onMoveShouldSetResponder={handleTouchShouldSetResponder}>
<AppBar title={title} onLeftIconPress={onLeftIconPress} isCloseLeftIcon={isCloseLeftIcon}>
{actions}
{overflowAction && <AppBarActionOverflow onOpen={() => setOverflowMenuDisplayed(true)} />}
</AppBar>
<AppBarOverflowMenu
overflowAction={overflowAction}
isOpen={isOverflowMenuDisplayed}
childrenIds={childrenIds}
setChildrenIds={setChildrenIds}
onPress={() => setOverflowMenuDisplayed(false)}
/>
{children}
</View>
And the Overflow menu:
export const AppBarOverflowMenu = ({ isOpen, setChildrenIds, childrenIds, onPress, overflowAction }) => {
if (!isOpen) {
return null
}
return (
<View
style={thisStyles.menuContainer}
ref={(component) => {
if (component) {
const ids = component._children[0]._children.map((el) => el._nativeTag)
if (ids.length > 0 && (childrenIds.length !== ids.length || !childrenIds.includes(ids[0]))) {
setChildrenIds(ids)
}
}
}}>
<View style={thisStyles.menu}>
{React.cloneElement(overflowAction, {
onPress: () => {
onPress(false)
overflowAction.props.onPress()
},
})}
</View>
</View>
)
}