How to hide component after delay in React Native - reactjs

I'm trying to make a chronometer component who hides himself after a delay. It works but I have a warning when the Chronometer disappear and I don't know how to deal with it.
Warning: Cannot update a component (WorkoutScreen) while rendering a different component (Chronometer). To locate the bad setState() call inside Chronometer
WorkoutScreen.tsx
const WorkoutScreen = ({
navigation,
route,
}: RootStackScreenProps<"Workout">) => {
const [inRest, setInRest] = useState(false)
const [restTime, setRestTime] = useState(5)
//I pass it to child
const handleEndRestTime = () => {
setInRest(false)
}
//
return (
<Layout style={styles.container}>
<Button
onPress={() => {
setInRest(!inRest)
}}
>
Trigger chronometer
</Button>
{inRest && (
<Chronometer onEnd={handleEndRestTime} seconds={restTime}></Chronometer>
)}
</Layout>
)
}
Chronometer.tsx
const Chronometer = ({ seconds, onEnd }: Props) => {
const [timer, setTimer] = useState<number>(seconds)
const [pause, setPause] = useState(false)
const [running, setRunning] = useState(true)
useEffect(() => {
let interval: NodeJS.Timer
if (pause === true || running === false) {
;() => clearInterval(interval)
} else {
interval = setInterval(() => {
setTimer((timer) => timer - 1)
}, 1000)
}
return () => {
clearInterval(interval)
}
}, [pause, running])
if (timer === 0 && running === true) {
setRunning(false)
//From parent
onEnd()
//
}
return (
<View style={styles.container}>
<View style={styles.chronometer}>
<View style={styles.controls}>
<Text>{formatHhMmSs(timer)}</Text>
</View>
<Button
onPress={() => {
setPause(!pause)
}}
>
Pause
</Button>
</View>
</View>
)
}
When I remove the "{inRest && " the warning disappear.
In the future I want that the User can retrigger Chronometer as he want
Thanks in advance !
Warning on my emulator (1)
Warning on my emulator (2)
Warning on my emulator (3)
Warning on my terminal

There are two state updates that happen simultaneously and conflict with React rendering UI reconciliation
setRunning(false) inside the Chronometer component will rerender this component when the timer ends.
setInRest(false) inside WorkoutScreen component will also rerender when the timer ends.
Both those rerenders happen at the same timer and WorkoutScreen rerender is triggered by the child component.
The solution is to avoid triggering state change inside the parent component caused by the child component.
const WorkoutScreen = ({
navigation,
route,
}: RootStackScreenProps<"Workout">) => {
const [restTime, setRestTime] = useState(5);
//I pass it to child
const handleEndRestTime = () => {
// Handle logic when workout time end
};
//
return (
<Layout style={styles.container}>
<Chronometer onEnd={handleEndRestTime} seconds={restTime}></Chronometer>
</Layout>
);
};
const Chronometer = ({ seconds, onEnd }: Props) => {
const [timer, setTimer] = useState < number > seconds;
const [pause, setPause] = useState(false);
const [running, setRunning] = useState(true);
const [inRest, setInRest] = useState(false);
useEffect(() => {
let interval: NodeJS.Timer;
if (pause === true || running === false) {
() => clearInterval(interval);
} else {
interval = setInterval(() => {
setTimer((timer) => timer - 1);
}, 1000);
}
return () => {
clearInterval(interval);
};
}, [pause, running]);
if (timer === 0 && running === true) {
setRunning(false);
setInRest(false);
//From parent
onEnd();
//
}
return (
<View style={styles.container}>
<Button
onPress={() => {
setInRest(!inRest);
}}
>
Trigger chronometer
</Button>
{inRest && (
<View style={styles.chronometer}>
{/* Show Timer counter only when is running */}
{running && (
<View style={styles.controls}>
<Text>{formatHhMmSs(timer)}</Text>
</View>
)}
<Button
onPress={() => {
setPause(!pause);
}}
>
{running ? "Pause" : "Start"}
</Button>
</View>
)}
</View>
);
};

Related

How to scroll into child component in a list from parent in react?

Hello guys I have an issue that may be simple but I'm stuck.
I have a parent that call an endpoint and render a list of child components once the data is received, at the same time in the URL could (or not) exists a parameter with the same name as the "name" property of one of the child components, so if parameter exists I need to scroll the page down until the children component that have the same "name" as id.
Here is part of the code:
const ParentView = () => {
const [wines, setWines] = React.useState([]);
const [loading, setLoading] = React.useState(true);
const params = new URLSearchParams(document.location.search);
const isMx = params.get('lang') ? false : true;
const wineId = params.get('wine');
const ref = createRef();
const scroll = () => ref && ref.current && ref.current.scrollIntoView({ behavior: 'smooth' });
React.useEffect(() => {
retrieveData();
}, []);
React.useEffect(() => {
if (!isEmptyArray(wines) && !loading && wineId) scroll();
}, [wineId, wines, loading]);
function renderWines() {
if (loading) return <Loading />;
if (isEmptyArray(wines) && !loading) return <h2>No items found</h2>;
if (!isEmptyArray(wines) && !loading)
return (
<React.Fragment>
{wines
.filter(p => p.status === 'published')
.map((w, idx) => (
<ChildComponent
wine={w}
isMx={isMx}
idx={idx}
openModal={openModal}
ref={wineId === w.name.toLowerCase() ? ref : null}
/>
))}
</React.Fragment>
);
}
return (
<React.Fragment>
{renderWines()}
</React.Fragment>
);
};
And this is the child component...
import React, { forwardRef } from 'react';
import { Row,} from 'reactstrap';
const WineRow = forwardRef(({ wine, isMx, idx, openModal }, ref) => {
const {
name,
} = wine;
// const ref = React.useRef();
React.useEffect(() => {
// console.log({ ref, shouldScrollTo });
// shouldScrollTo && ref.current.scrollIntoView({ behavior: 'smooth' });
}, []);
return (
<Row id={name} ref={ref}>
...content that is irrelevant for this example
</Row>
);
});
Of course I remove a lot of irrelevant code like retrieveData() function and all the logic to handle the data from api
I've been trying many ways but I can't make it works :(
Well after a headache I just realized that I don't need react to do this 😂
so I just fixit with vanilla js 🤷🏻‍♂️
Parent:
const Public = () => {
const [wines, setWines] = React.useState([]);
const [loading, setLoading] = React.useState(true);
const params = new URLSearchParams(document.location.search);
const isMx = params.get('lang') ? false : true;
const wineId = params.get('wine');
React.useEffect(() => {
retrieveData();
}, []);
React.useEffect(() => {
if (!isEmptyArray(wines) && !loading && wineId) scroll(wineId);
}, [wineId, wines, loading]);
const scroll = wineId => document.getElementById(wineId).scrollIntoView({ behavior: 'smooth' });
const retrieveData = async () => {
....logic to handle data
};
function renderWines() {
if (loading) return <Loading />;
if (isEmptyArray(wines) && !loading) return <h2>No items found</h2>;
if (!isEmptyArray(wines) && !loading)
return (
<React.Fragment>
{wines
.filter(p => p.status === 'published')
.map((w, idx) => (
<WineRow wine={w} isMx={isMx} idx={idx} />
))}
</React.Fragment>
);
}
return (
<React.Fragment>
{renderWines()}
</React.Fragment>
);
};
and children:
const WineRow =({ wine, isMx, idx,}) => {
const {
name,
} = wine;
return (
<Row id={name.toLowerCase()}>
...content that is irrelevant for this example
</Row>
);
};
And that's it 😂 sometimes we are used to do complex things that we forgot our basis 🤦🏻‍♂️
Hope this help someone in the future

How to Use componentDidMount() in Functional Component to execute a function

I have a functional component which had a button to call a method in it. Now i want to get rid of the button and call that method without any actions once the component loads.
I am making API calls inside this method and passing on the results to another component.
Also I am replacing the button with a progress bar meaning when a "search" is taking place, display the progress bar but I am having no luck. What am I doing wrong ?
export const Search = (props) => {
const { contacts, setContacts, onSearchComplete } = props;
const [msgBox, setMsgBox] = useState(null);
const [loading, setLoading] = useState(false);
const onSearch = async () => {
setLoading(true);
const emails = contacts
.filter(x => x.isChecked)
.map(item => item.emailAddress);
try {
const searchResults = await AppApi.searchMany(emails);
let userList = [];
for (let i = 0; i < searchResults.length; i++) {
//process the list and filter
}
userList = [...userList, ..._users];
}
onSearchComplete(userList); //passing the results.
} catch (err) {
console.log({ err });
setMsgBox({ message: `${err.message}`, type: 'error' });
}
setLoading(false);
}
return (
<Box>
{loading ? <LinearProgress /> : <Box>{msgBox && (<a style={{ cursor: 'pointer' }} onClick={() => setMsgBox(null)} title="Click to dismiss"><MessageBox type={msgBox.type || 'info'}>{msgBox.message}</MessageBox></a>)}</Box>}
/*{onSearch()}*/ // function that was executed onclick.
</Box>
);
}
You will want to use the useEffect hook with an empty dependency array which will make it act as componentDidMount source.
export const Search = (props) => {
const { contacts, setContacts, onSearchComplete } = props;
const [msgBox, setMsgBox] = useState(null);
const [loading, setLoading] = useState(false);
const onSearch = async () => {
...
}
useEffect(() => {
onSearch();
}, []);
return (
<Box>
{loading ? <LinearProgress /> : <Box>{msgBox && (<a style={{ cursor: 'pointer' }} onClick={() => setMsgBox(null)} title="Click to dismiss"><MessageBox type={msgBox.type || 'info'}>{msgBox.message}</MessageBox></a>)}</Box>}
</Box>
);
}

Auto focus Input within React Native Viewpager

I'm using a React Native Viewpager to take in user entry, and move to the next page on button press. Important to note that moving to the next page happens on button press, and not by normal scrolling, which is disabled.
The best way I could think to handle this was to have a state on the ViewPager, which would propagate into the child Entries.
ViewPager.tsx:
export default function ViewPager({ route, navigation }) {
const ref: React.RefObject<ViewPager> = React.createRef();
const [currentPage, setCurrentPage] = useState(0);
let setEntryPage = (page: number) => {
ref.current?.setPage(page);
setCurrentPage(page);
}
return (
<View style={{flex: 1}}>
<ViewPager
style={styles.viewPager}
initialPage={0}
ref={ref}
scrollEnabled={false}
>
{
GlobalStuff.map((entry, index) => {
return (
<Entry
key={index}
index={index}
pagerFocusIndex={currentPage}
pagerLength={quizDeck?.litems.length!}
setEntryPage={setEntryPage}
/>
)
})
}
</ViewPager>
</View>
);
};
Entry.tsx:
export function Entry(props: EntryProps) {
const inputRef: React.RefObject<Input> = React.createRef();
if (props.pagerFocusIndex === props.index) {
inputRef.current?.focus();
}
return (
<View>
<Input
// ...
ref={inputRef}
/>
<IconButton
icon="arrow-right-thick"
color={colorTheme.green}
onPress={() => {
props.index !== props.pagerLength - 1 ?
props.setEntryPage(props.index + 1) :
props.navigation!.reset({ index: 0, routes: [{ name: recapScreenName as any }] });
}}
/>
// ...
Unfortunately, inputRef appears to be null, and there is probably a better way of achieving what I'm trying to achieve anyway.
Anything in your render loop will be called every time the component renders.
// This is called on every render
const inputRef: React.RefObject<Input> = React.createRef();
// So is this, it's always null
if (props.pagerFocusIndex === props.index) {
inputRef.current?.focus();
}
Put side effects in effects.
// Untested
const inputRef = useRef();
useEffect(() => {
if (props.pagerFocusIndex === props.index) {
inputRef.current?.focus();
}
}, [inputRef.current, props.pagerFocusIndex, props.index]);

So, I don't understand how 'self' is never defined

How to implement auto onClick button event in reactjs after some interval?
The button will be clicked every 10 seconds, you can use ref to click the button wherever you want:
import React, { useEffect, useRef } from "react";
function App() {
const button = useRef();
const interval = useRef();
useEffect(() => {
interval.current = setInterval(() => {
button.current.click();
}, 10000);
return () => {
clearInterval(interval.current);
}
}, []);
return (
<div>
<button
type="button"
ref={button}
onClick={() => { console.log('button clicked') }}
>
AutoClickable
</button>
</div>
);
}
It should be clicked every 5 second. If you need it once use setTimeout instead
return (
<button onClick={setInterval(() => {return null}, 5000)}>Autoclickable</button>
)
OR
const handleClick = () => {
console.log('Auto click')
}
React.useEffect(() => {
const interval = setInterval(() => {
handleClick()
}, 5000)
})
return (
<button onClick={handleClick}>Autoclickable</button>
)

How do you prevent the component from disappearing too soon?

const FlashMessage = (props) => {
const [isOpen, setIsOpen] = useState(true);
const hideComponent = () => {
setisOpen(false);
};
useEffect(() => {
setIsOpen(true);
setTimeout(() => hideComponent(), 9000);
}, [props]);
return (
(props.flashMessage === true && isOpen) ?
<View style={styles.main}>
<Text style={styles.message}>{props.message}</Text>
</View>
: null
);
}
I have this Flash Message component in my React Native app, and sometimes, the Flash Message disappears after 2 second. It seems to appear on a random basis and it's probably due to a problem with useEffect and setTimeout, but I have not been able to figure out what might be causing this.
The effect you have with [props] as dependency doesn't make sense to me.
But you can have an isolated effect for the isOpen boolean.
useEffect(() => {
setTimeout(() => {
setIsOpen(false);
}, 9000);
}, [isOpen]);
Here is a full working example, simplified:
export default function App() {
const [show, setShow] = useState(false);
useEffect(() => {
setTimeout(() => {
setShow(false);
}, 2000);
}, [show]);
return (
<div className="App">
<button onClick={e => setShow(true)}>Show</button>
{show && <div>hello</div>}
</div>
);
}

Resources