I'm facing an issue in which I need to change header text position from left to centre when user begin scroll and back to left if user scroll to top. But I unable to find solution for. I'm using scrollview which have a lot of content inside it. Can anyone tell me how to do that?
You can find a solution here :
Click here
<ScrollView
onScroll={({nativeEvent})=>{
if(isCloseToTop(nativeEvent)){
//do something
}
if(isCloseToBottom(nativeEvent)){
//do something
}
}}
>
...contents
</ScrollView>
isCloseToBottom({layoutMeasurement, contentOffset, contentSize}){
return layoutMeasurement.height + contentOffset.y >= contentSize.height - 20;
}
ifCloseToTop({layoutMeasurement, contentOffset, contentSize}){
return contentOffset.y == 0;
}
Try this way
import React, { useState, useEffect } from "react";
function Example() {
const [scrollPosition, setScrollPosition] = useState(-1);
useEffect(() => {
// console.log("scrollPosition: ", scrollPosition);
if (scrollPosition === 0) {
// condition is true when scroll on top
}
}, [scrollPosition]);
const getscrollposition = (e) => {
const y = e.nativeEvent.contentOffset.y;
setScrollPosition(y);
};
return (
<ScrollView
onMomentumScrollEnd={getscrollposition}
scrollEventThrottle={16}
>
.....
</ScrollView>
);
}
Related
This question already has answers here:
The useState set method is not reflecting a change immediately
(15 answers)
Closed last month.
Using react-chessboard with chess.js. I modified the example code at: https://www.npmjs.com/package/react-chessboard.
My code:
import { Chessboard } from "react-chessboard";
import { useState } from "react";
import { Chess } from "chess.js";
const Board = () =>{
const [game, setGame] = useState(new Chess());
function makeAMove(move) {
const gameCopy = new Chess();
gameCopy.loadPgn(game.pgn());
gameCopy.move(move);
setGame(gameCopy);
}
function onDrop(sourceSquare, targetSquare) {
makeAMove({
from: sourceSquare,
to: targetSquare,
});
if(game.isGameOver()){
if(game.isStalemate() || game.isThreefoldRepetition()){
alert("Stalemate")
}
if(game.turn() == "b"){
alert("White won")
}
else if(game.turn() == "w"){
alert("Black won")
}
}
}
return (
<>
<div style = {{width: '50%',alignItems: 'center', marginLeft: '25%',justifyContent: 'center'}}>
<Chessboard position={game.fen()} onPieceDrop={onDrop} id="BasicBoard"/>
</div>
</>
);
}
export default Board;
Why is the isGameOver() one move behind for me? If white checkmates black then the "White won" alert pops up only after black tries to make another move after being checkmated and vice versa.
It's because makeAMove updates the state but the local instance of game that you check is still the same that it was before the state update. Only on the next render does it get the updated game from the state. Check out The useState set method is not reflecting a change immediately to get a better understanding of why this happens.
You can fix this by running the check on the gameCopy constant when you write it to the state but a more appropriate solution is to move your checks into an effect.
You'll need to import useEffect from React:
import { useState, useEffect } from "react";
Then change your code like this:
function onDrop(sourceSquare, targetSquare) {
makeAMove({
from: sourceSquare,
to: targetSquare,
});
}
useEffect(function() {
if(game.isGameOver()){
if(game.isStalemate() || game.isThreefoldRepetition()){
alert("Stalemate");
}
if(game.turn() == "b"){
alert("White won");
}
else if(game.turn() == "w"){
alert("Black won");
}
}
}, [ game ]);
The effect has the game constant as a dependency, which means that it runs every time game changes and will always run the checks and the latest state.
I would consider storing game in a ref instead of creating a new one every time you want to make a move
import { Chessboard } from "react-chessboard";
import { useState, useReducer } from "react";
import { Chess } from "chess.js";
const Board = () => {
const game = useRef();
const [_, rerender] = useReducer((x) => x + 1, 0);
useEffect(() => {
if (!ref.current) {
ref.current = new Chess();
}
}, []);
function onDrop(sourceSquare, targetSquare) {
game.current.move({
from: sourceSquare,
to: targetSquare,
});
rerender()
if (game.current.isGameOver()) {
if (game.current.isStalemate() || game.current.isThreefoldRepetition()) {
alert("Stalemate");
}
if (game.current.turn() == "b") {
alert("White won");
} else if (game.current.turn() == "w") {
alert("Black won");
}
}
}
return (
<>
<div
style={{
width: "50%",
alignItems: "center",
marginLeft: "25%",
justifyContent: "center",
}}
>
<Chessboard
position={game.current.fen()}
onPieceDrop={onDrop}
id="BasicBoard"
/>
</div>
</>
);
};
export default Board;
Because calling setGame does not immediately change the value of game. It queues a change that will resolve after render (explained in the new React docs site).
One way to solve this would be to inline your makeAMove function and use the updated game in the rest of your event handler, like this:
function onDrop(sourceSquare, targetSquare) {
const updatedGame = new Chess();
updatedGame.loadPgn(game.pgn());
updatedGame.move({
from: sourceSquare,
to: targetSquare,
});
setGame(updatedGame);
if (updatedGame.isGameOver()) {
if (updatedGame.isStalemate() || updatedGame.isThreefoldRepetition()) {
alert('Stalemate');
}
if (updatedGame.turn() == 'b') {
alert('White won');
} else if (updatedGame.turn() == 'w') {
alert('Black won');
}
}
}
I need my drop-down button to show the text below if I press one, and another one for hiding. I tried to use the useState method, but it also doesn't work, and same with the non-useState method. Here is my code, hope someone can fix this, thanks
function AnnouncementCard(props) {
const { announcement } = props;
const triangleDown = <img src={DropDown} alt="No-img" />;
const triangleUp = <img src={DropDownActive} alt="No-img" />;
function Readmore() {
console.log("inject", currentTriangle);
if (currentTriangle == triangleDown) {
DisplayText(null);
ChangeTriangle(triangleUp);
console.log(2, currentTriangle);
} else if (currentTriangle == triangleUp) {
DisplayText(<Text>{announcement.detail}</Text>);
ChangeTriangle(triangleDown);
console.log(1, currentTriangle);
}
}
const [currentTriangle, ChangeTriangle] = useState(triangleUp);
const [currentText, DisplayText] = useState(null);
return (
<Card>
<CardBody>
<PictureBox>
<Picture src={announcement.img} alt="no-img" />
</PictureBox>
<TextBox>
<Title>{announcement.title}</Title>
<Date>{announcement.date}</Date>
<ReadMore onClick={() => {Readmore();}}>
Read more
<Triangle>{currentTriangle}</Triangle>
</ReadMore>
</TextBox>
</CardBody>
<Textarea>{currentText}</Textarea>
</Card>
);
}
export default AnnouncementCard;
You cannot simply compare JSX elements using == operator (currentTriangle == triangleDown). To make it simple, I would use a boolean instead of saving the element in the state, and depending on that you can show up and down buttons.
Replace the ChangeTriangle state hook with the following.
const [toggleValue, setToggleValue] = useState(true);
Update the ReadMore function as below.
function Readmore() {
if (toggleValue === false) {
DisplayText(null);
setToggleValue(true);
} else if (toggleValue === true) {
DisplayText(<Text>{announcement.detail}</Text>);
setToggleValue(false);
}
}
Set the triangle button as below using a ternary operator.
<Triangle>{toggleValue ? triangleUp : triangleDown}</Triangle>
This issue is very simple but I probably overlook very little point. Window screen size is listening by PostLayout component. When window width is less than 768px, I expect that isDesktopSize is false. I tried everything like using arrow function in setIsDesktopSize, using text inside of true or false for state value, using callback method etc... but it's not working.
PostLayout shared below:
import React, {useState,useEffect, useCallback} from 'react'
import LeftSideNavbar from './LeftSideNavbar'
import TopNavbar from './TopNavbar'
export default function PostLayout({children}) {
const [isDesktopSize, setIsDesktopSize] = useState(true)
let autoResize = () => {
console.log("Desktop: " + isDesktopSize);
console.log(window.innerWidth);
if(window.innerWidth < 768 ){
setIsDesktopSize(false)
}else{
setIsDesktopSize(true)
}
}
useEffect(() => {
window.addEventListener('resize', autoResize)
autoResize();
}, [])
return (
<>
<TopNavbar isDesktopSize={isDesktopSize}/>
<main>
<LeftSideNavbar/>
{children}
</main>
</>
)
}
console log is shared below:
Desktop: true
627
This could probably be extracted into a custom hook. There's a few things you'd want to address:
Right now you default the state to true, but when the component loads, that may not be correct. This is probably why you see an incorrect console log on the first execution of the effect. Calculating the initial state to be accurate could save you some jank/double rendering.
You aren't disconnecting the resize listener when the component unmounts, which could result in an error attempting to set state on the component after it has unmounted.
Here's an example of a custom hook that addresses those:
function testIsDesktop() {
if (typeof window === 'undefined') {
return true;
}
return window.innerWidth >= 768;
}
function useIsDesktopSize() {
// Initialize the desktop size to an accurate value on initial state set
const [isDesktopSize, setIsDesktopSize] = useState(testIsDesktop);
useEffect(() => {
if (typeof window === 'undefined') {
return;
}
function autoResize() {
setIsDesktopSize(testIsDesktop());
}
window.addEventListener('resize', autoResize);
// This is likely unnecessary, as the initial state should capture
// the size, however if a resize occurs between initial state set by
// React and before the event listener is attached, this
// will just make sure it captures that.
autoResize();
// Return a function to disconnect the event listener
return () => window.removeEventListener('resize', autoResize);
}, [])
return isDesktopSize;
}
Then to use this, your other component would look like this (assuming your custom hook is just in this same file -- though it may be useful to extract it to a separate file and import it):
import React, { useState } from 'react'
import LeftSideNavbar from './LeftSideNavbar'
import TopNavbar from './TopNavbar'
export default function PostLayout({children}) {
const isDesktopSize = useIsDesktopSize();
return (
<>
<TopNavbar isDesktopSize={isDesktopSize}/>
<main>
<LeftSideNavbar/>
{children}
</main>
</>
)
}
EDIT: I modified this slightly so it should theoretically work with a server-side renderer, which will assume a desktop size.
Try this, you are setting isDesktopSizze to 'mobile', which is === true
const [isDesktopSize, setIsDesktopSize] = useState(true)
let autoResize = () => {
console.log("Desktop: " + isDesktopSize);
console.log(window.innerWidth);
if(window.innerWidth < 768 ){
setIsDesktopSize(true)
}else{
setIsDesktopSize(false)
}
}
I didn't find such a package on npm and I thought it would be nice to create one: https://www.npmjs.com/package/use-device-detect. I think it will help someone :)
I have a custom Hook useWindowSize that determines whether there is a Mobile or Desktop environment. This is fine for most cases except when a mobile user wants to access the desktop version on his/her phone. I have a button to manually override the current windowsize but am not sure how to approach this.
Here I determine the opposite of the loaded windowsize but how can I switch and reload to the appropriate mode on click?
I will need this mode to stay afterwards even if the window is resized to keep the components linked to either mobile or desktop.
import "./styles.css";
import "./app.scss";
import useWindowSize from "./useWindowSize";
export default function App() {
const windowSize = useWindowSize();
const otherMode = windowSize <= "useMobileVersion" ? "useDesktopVersion" : "useDesktopVersion";
return (
<div className="App">
<p>Right now you are in {windowSize} mode. <button onClick={() => setPageMode("otherMode")}>
Switch to {otherMode} mode
</button>
</p>
</div>
);
}
The codesandbox is here.
The custom Hook is her:
import { useState, useEffect } from "react";
//a Util function that will convert the absolute width into breakpoints
function getBreakPoint(windowWidth) {
if (windowWidth) {
if (windowWidth < 420) {
return "useMobileVersion";
} else {
return "useDesktopVersion";
}
} else {
return "useDesktopVersion";
}
}
function useWindowSize() {
const isWindowClient = typeof window === "object";
const [windowSize, setWindowSize] = useState(
isWindowClient ? getBreakPoint(window.innerWidth) : undefined
);
useEffect(() => {
//a handler which will be called on change of the screen resize
function setSize() {
setWindowSize(getBreakPoint(window.innerWidth));
}
if (isWindowClient) {
//register the window resize listener
window.addEventListener("resize", setSize);
//unregister the listener
return () => window.removeEventListener("resize", setSize);
}
}, [isWindowClient, setWindowSize]);
return windowSize;
}
export default useWindowSize;
I try to implement a function in my app that allows the user to reset all the components that he dragged around to be reset to their original position.
I assume that this functionality exists in react-draggable because of this closed and released issue: "Allow reset of dragging position" (https://github.com/idanen/react-draggable/issues/7). However I did not find any hint in the documentation (https://www.npmjs.com/package/react-draggable).
There was one question with the same content in stackoverflow, but it has been removed (https://stackoverflow.com/questions/61593112/how-to-reset-to-default-position-react-draggable).
Thanks for your help :-)
The referenced issue on the GitHub references a commit. After taking a look at the changes made in this commit, I found a resetState callback added to the useDraggable hook. In another place in the commit, I found a change to the test file which shows usage of the hook.
function Consumer(props) {
const {
targetRef,
handleRef,
getTargetProps,
resetState,
delta,
dragging
} = useDraggable(props);
const { style = defaultStyle } = props;
return (
<main
className='container'
ref={targetRef}
data-testid='main'
style={style}
{...getTargetProps()}
>
{dragging && <span>Dragging to:</span>}
<output>
{delta.x}, {delta.y}
</output>
<button className='handle' ref={handleRef}>
handle
</button>
<button onClick={resetState}>reset</button>
</main>
);
}
The hook returns a set of callbacks, including this callback, which can be used to reset the state of the draggable.
I wanted the component to reset back to its original position when the component was dropped.
Using hooks I monitored if the component was being dragged and when it was false reset the position otherwise it would be undefined.
export default function DraggableComponent(props: any) {
const {label} = props
const [isDragging, setIsDragging] = useState<boolean>(false)
const handleStart = (event: any, info: DraggableData) => {
setIsDragging(true)
}
const handleStop = (event: any, info: DraggableData) => {
setIsDragging(false)
}
return (
<Draggable
onStart={handleStart}
onStop={handleStop}
position={!isDragging? { x: 0, y: 0 } : undefined}
>
<Item>
{label}
</Item>
</Draggable>
)
}
Simple approach would be:
creating a new component to wrap our functionality around the Draggable callbacks
reset position when onStop callback is triggered
Example:
import { useState } from 'react';
import Draggable, { DraggableData, DraggableEvent, DraggableProps } from 'react-draggable';
export function Drag({ children, onStop, ...rest }: Partial<DraggableProps>) {
const initial = { x: 0, y: 0 }
const [pos, setPos] = useState(initial)
function _onStop(e: DraggableEvent, data: DraggableData){
setPos(initial)
onStop?.(e, data)
}
return (
<Draggable position={pos} onStop={_onStop} {...rest}>
{children}
</Draggable>
)
}
Usage:
export function App() {
return (
<Drag> Drag me </Drag>
)
}
Note that this answer does not work.
None of these approaches worked for me, but tobi2424's post on issue 214 of the Draggable repo did. Here's a minimal proof-of-concept:
import React from "react";
import Draggable from "react-draggable";
const DragComponent = () => {
// Updates the drag position parameter passed to Draggable
const [dragPosition, setDragPosition] = React.useState(null);
// Fires when the user stops dragging the element
const choiceHandler = () => {
setDragPosition({x: 0, y: 0});
};
return (
<Draggable
onStop={choiceHandler}
position={dragPosition}
>
Drag me
</Draggable>
);
};
export default DragComponent;
Edit
The code above works intermittently but not particularly well. As far as I can work out, react-draggable stores data about the position of the dragged element somewhere outside of React, in order to preserve the position of the element between component refreshes. I was unable to determine how to reset the position of the element on command and none of the other example code solves the problem for me.
You can do this in a very haphazard manner. There may be another way to set state more safely on this but I didn't look too deeply into it.
import React from 'react';
export default class 😊 extends Component {
constructor(props) {
super(props);
this.draggableEntity = React.createRef();
}
resetDraggable() {
try {
this.draggableEntity.current.state.x = 0;
this.draggableEntity.current.state.y = 0;
} catch (err) {
// Fail silently
}
}
render() {
return (
<Draggable
ref={this.draggableEntity}
>
<img onClick={(e) => {this.resetDraggable()}}></img>
</Draggable>
)
}
}
There happens to be another way! You can use it's exposed ref element to reset its offset. This can be achieved like so:
import React, {useRef, useCallback} from "react";
import Draggable from "react-draggable";
const DragComponent = () => {
// Updates the drag position parameter passed to Draggable
const [dragPosition, setDragPosition] = React.useState(null);
const draggerRef = useRef(null);
// Fires when the user stops dragging the element
const resetDrag = useCallback(() => {
setDragPosition({x: 0, y: 0});
draggerRef.current?.setState({ x: 0, y: 0 }); // This is what resets it!
}, [setDragPosition, draggerRef]);
return (
<Draggable
ref={draggerRef}
onStop={resetDrag}
position={dragPosition}
>
Drag me
</Draggable>
);
};
export default DragComponent;