Switching between two components in React - reactjs

rotateRender() {
if(false) {
return(
<TimerPage></TimerPage>
);
} else {
return(
<RepoPage></RepoPage>
);
}
}
I have two components called TimerPage and RepoPage.
I created a simple conditional render function as above, but cannot come up with a condition to make it render iteratively after a certain amount of time.
For example, I first want to render RepoPage and switch to TimerPage after 5 minutes and then stay in TimerPage for 15 mins before I switch again to the RepoPage.
Any way to do this?

Might not be that elegant, but this works
Actually I was thinking that this block might be more elegant than the first one
const FIRST_PAGE = '5_SECONDS';
const SECOND_PAGE = '15_SECONDS';
const FirstComponent = () => (
<div>5 SECONDS</div>
);
const SecondComponent = () => (
<div>15 SECONDS</div>
);
class App extends Component {
state = {
currentPage: FIRST_PAGE
};
componentDidUpdate() {
const {currentPage} = this.state;
const isFirst = currentPage === FIRST_PAGE;
if (isFirst) {
this._showSecondPageDelayed();
} else {
this._showFirstPageDelayed();
}
}
componentDidMount() {
this._showSecondPageDelayed();
};
_showSecondPageDelayed = () => setTimeout(() => {this.setState({currentPage: SECOND_PAGE})}, 5000);
_showFirstPageDelayed = () => setTimeout(() => {this.setState({currentPage: FIRST_PAGE})}, 15000);
render() {
const {currentPage} = this.state;
const isFirst = currentPage === FIRST_PAGE;
const ComponentToRender = isFirst ? FirstComponent : SecondComponent;
return <ComponentToRender/>;
}
}

As stated in the comment section, you can create a higher order component that will cycle through your components based on the state of that component. Use setTimeout to handle the timer logic for the component.
state = {
timer: true
}
componentDidMount = () => {
setInterval(
() => {
this.setState({ timer: !this.state.timer })
}, 30000)
}
render(){
const {timer} = this.state
if(timer){
return <TimerPage />
} else {
return <RepoPage />
}
}
Edit
Changed setTimeout to setInterval so that it will loop every 5 minutes instead of just calling setState once

You could use the new context API to achieve this. The benefit is now I have a configurable, reusable provider to play with throughout my application. Here is a quick demo:
https://codesandbox.io/s/k2vvy54r8o
import React, { Component, createContext } from "react";
import { render } from "react-dom";
const ThemeContext = createContext({ alternativeTheme: false });
class ThemeWrapper extends Component {
state = {
alternativeTheme: false
};
themeInterval = null;
componentDidMount() {
this.themeInterval = setInterval(
() =>
this.setState(({ alternativeTheme }) => ({
alternativeTheme: !alternativeTheme
})),
this.props.intervalLength
);
}
componentWillUnmount() {
if (this.themeInterval) {
clearInterval(this.themeInterval);
}
}
render() {
return (
<ThemeContext.Provider value={this.state}>
{this.props.children}
</ThemeContext.Provider>
);
}
}
const App = () => (
<ThemeWrapper intervalLength={2000}>
<ThemeContext.Consumer>
{({ alternativeTheme }) =>
alternativeTheme ? <p>Alternative Theme</p> : <p>Common Theme</p>
}
</ThemeContext.Consumer>
</ThemeWrapper>
);
render(<App />, document.getElementById("root"));
Whatever you do make sure on componentWillUnmount to clear your interval or timeout to avoid a memory leak.

Related

How to update prop values in Child class component when Parent class component state is changed? : React Native

I have a parent class component called CardView.js which contains a child class component called Tab.js (which contains a FlatList).
When a button is pressed in CardView.js, a modal appears with various options. A user chooses an option and presses 'OK' on the modal. At this point the onOKHandler method in the parent component updates the parent state (tabOrderBy: orderBy and orderSetByModal: true). NOTE: These two pieces of state are passed to the child component as props.
Here is what I need:
When the onOKHandler is pressed in the parent, I need the child component to re-render with it's props values reflecting the new state values in the parent state. NOTE: I do not want the Parent Component to re-render as well.
At the moment when onOKHandler is pressed, the child component reloads, but it's props are still showing the OLD state from the parent.
Here is what I have tried:
When the onOKHandler is pressed, I use setState to update the parent state and then I use the setState callback to call a method in the child to reload the child. The child reloads but its props are not updated.
I have tried using componentDidUpdate in the child which checks when the prop orderSetByModal is changed. This does not work at all.
I have tried many of the recommendations in other posts like this - nothing works! Where am I going wrong please? Code is below:
Parent Component: CardView.js
import React from "react";
import { View } from "react-native";
import { Windows} from "../stores";
import { TabView, SceneMap } from "react-native-tab-view";
import { Tab, TabBar, Sortby } from "../components";
class CardView extends React.Component {
state = {
level: 0,
tabIndex: 0,
tabRoutes: [],
recordId: null,
renderScene: () => {},
showSortby: false,
orderSetByModal: false,
tabOrderBy: ''
};
tabRefs = {};
componentDidMount = () => {
this.reload(this.props.windowId, null, this.state.level, this.state.tabIndex);
};
reload = (windowId, recordId, level, tabIndex) => {
this.setState({ recordId, level, tabIndex });
const tabRoutes = Windows.getTabRoutes(windowId, level);
this.setState({ tabRoutes });
const sceneMap = {};
this.setState({ renderScene: SceneMap(sceneMap)});
for (let i = 0; i < tabRoutes.length; i++) {
const tabRoute = tabRoutes[i];
sceneMap[tabRoute.key] = () => {
return (
<Tab
onRef={(ref) => (this.child = ref)}
ref={(tab) => (this.tabRefs[i] = tab)}
windowId={windowId}
tabSequence={tabRoute.key}
tabLevel={level}
tabKey={tabRoute.key}
recordId={recordId}
orderSetByModal={this.state.orderSetByModal}
tabOrderBy={this.state.tabOrderBy}
></Tab>
);
};
}
};
startSortByHandler = () => {
this.setState({showSortby: true});
};
endSortByHandler = () => {
this.setState({ showSortby: false});
};
orderByFromModal = () => {
return 'creationDate asc'
}
refreshTab = () => {
this.orderByFromModal();
this.child.refresh()
}
onOKHandler = () => {
this.endSortByHandler();
const orderBy = this.orderByFromModal();
this.setState({
tabOrderBy: orderBy,
orderSetByModal: true}, () => {
this.refreshTab()
});
}
render() {
return (
<View>
<TabView
navigationState={{index: this.state.tabIndex, routes: this.state.tabRoutes}}
renderScene={this.state.renderScene}
onIndexChange={(index) => {
this.setState({ tabIndex: index });
}}
lazy
swipeEnabled={false}
renderTabBar={(props) => <TabBar {...props} />}
/>
<Sortby
visible={this.state.showSortby}
onCancel={this.endSortByHandler}
onOK={this.onOKHandler}
></Sortby>
</View>
);
}
}
export default CardView;
Child Component: Tab.js
import React from "react";
import { FlatList } from "react-native";
import { Windows } from "../stores";
import SwipeableCard from "./SwipeableCard";
class Tab extends React.Component {
constructor(props) {
super(props);
this.state = {
currentTab: null,
records: [],
refreshing: false,
};
this.listRef = null;
}
async componentDidMount() {
this.props.onRef(this);
await this.reload(this.props.recordId, this.props.tabLevel, this.props.tabSequence);
}
componentWillUnmount() {
this.props.onRef(null);
}
//I tried adding componentDidUpdate, but it did not work at all
componentDidUpdate(prevProps) {
if (this.props.orderSetByModal !== prevProps.orderSetByModal) {
this.refresh();
}
}
getOrderBy = () => {
let orderByClause;
if (this.props.orderSetByModal) {
orderByClause = this.props.tabOrderBy;
} else {
orderByClause = "organization desc";
}
return orderByClause;
};
async reload() {
const currentTab = Windows.getTab(this.props.windowId, this.props.tabSequence, this.props.tabLevel);
this.setState({ currentTab });
let response = null;
const orderBy = this.getOrderBy();
response = await this.props.entity.api.obtainRange(orderBy);
this.setState({ records: response.dataList })
}
refresh = () => {
this.setState({ refreshing: true }, () => {
this.reload(this.props.recordId, this.props.tabLevel, this.props.tabSequence)
.then(() => this.setState({ refreshing: false }));
});
};
renderTabItem = ({ item, index }) => (
<SwipeableCard
title={"Card"}
/>
);
render() {
if (!this.state.currentTab) {
return null;
}
return (
<>
<FlatList
ref={(ref) => (this.listRef = ref)}
style={{ paddingTop: 8 }}
refreshing={this.state.refreshing}
onRefresh={this.refresh}
data={this.state.records}
keyExtractor={(item) => (item.isNew ? "new" : item.id)}
/>
</>
);
}
}
export default Tab;

component not re rendering when call action in mobx

I'm using mobx v6.
HomePage calls roomStore.fetchRooms when scrolls down to bottom, yes I use IntersectionObserver and lodash/throttle function for implement infinite scroll.
I checked roomStore.fetchRooms been called when loadMore function called, and roomStore.homeRoomList been updated.
All functions change states in Mobx stores are decorated with #action.
I wonder why my HomePage component is not re-rendered.
//RoomStore
export default class RoomStore extends BasicStore {
#observable homeRoomList: GetRoomsPayload["rooms"] | null;
constructor({root, state}: { root: RootStore, state: RoomStore}){
super({root, state});
makeObservable(this);
this.homeRoomList = state?.homeRoomList ?? null;
}
async fetchRooms(category?: string, page:number = 0){
const [error,response] = await this.api.GET<GetRoomsPayload>(`/room/${category}?page=${page}`);
if(error){
throw Error(error.error)
}
if(response && response.success){
const { data } = response
this.feedFetchHomeRooms(data.rooms);
return response.data;
}
return Promise.resolve();
}
#action.bound
feedFetchHomeRooms(rooms: GetRoomsPayload["rooms"]){
if(rooms){
if( this.homeRoomList) {
this.homeRoomList = [...this.homeRoomList, ...rooms];
}
else {
this.homeRoomList = rooms;
}
}
}
}
// HomePage Component
const HomePage: FC & HomePageInitStoreOnServer = ({}) => {
const { pathname } = useLocation();
const homeRef = useRef<HTMLUListElement>(null);
const infiniteScrollTargetRef = useRef<HTMLDivElement>(null);
const { roomStore } = useMobxStores();
const handleLoadMore = () => {
throttleFetch();
}
const throttleFetch = useCallback(throttle(() => {
roomStore.fetchRooms()
},500),[]);
useInfiniteScroll({
target: infiniteScrollTargetRef,
cb: handleLoadMore,
});
useEffect(() => {
if(!roomStore.homeRoomList){
roomStore.fetchRooms()
}
},[]);
return (
<section >
<RoomContainer ref={homeRef}>
{roomStore.homeRoomList?.map((room: any) => {
return (
<Card
room={room}
key={room.id}
/>
);
})}
</RoomContainer>
<InfiniteScroll targetRef={infiniteScrollTargetRef}/>
</section>
);
};
export default observer(HomePage);
The component (HomePage) that renders observable data needs to be wrapped into the observer.
import { observer } from 'mobx-react-lite'
const HomePage = observer(() => {
// your code of component
})
you can find more details in official docs here

React: triggering method inside HOC component

What I want to do, is create a HOC that has a method that can be triggered by whatever Parent Component is using that HOC to wrap.
For this HOC, I'm trying to fade out the HOC and any components inside it:
HOC:
export function fadeOutWrapper(WrappedComponent) {
return class extends Component {
constructor(props) {
super(props);
this.state = {
showElement: true,
removeElement: false,
};
}
_triggerFade = () => {
this._fadeOut(this.props.time).then(time => this._removeElement(time));
}
_fadeOut = time => {
let _this = this;
return new Promise((resolve, reject) => {
_this.setState({
showElement: false
});
setTimeout(() => {
resolve(time);
}, time);
});
};
_removeElement = time => {
let _this = this;
setTimeout(() => {
_this.setState({
removeElement: true
});
}, time + 500);
};
render() {
return this.state.removeElement ? null : (
<div
className={
this.state.showElement
? "cfd-container"
: "cfd-container cfd-fadeout"
}
>
<WrappedComponent {...this.props} />
</div>
);
}
};
}
How this component is being used in parent component:
import ComponentToBeFaded from '...';
import { fadeOutWrapper } from '...';
const WrappedComponent = fadeOutWrapper(ComponentToBeFaded);
class ParentComponent extends Component {
const...
super...
handleChildClick = () => {
// ? how to trigger the HOC _triggerFade method?
// WrappedComponent._triggerFade()
}
render() {
return (
<WrappedComponent time={1000} handleClick={this.handleChildClick} {...other props component needs} />
)
}
}
What I want to be able to do is call a method that is inside the HOC, can't seem to check for a change in props inside the HOC... only inside the HOC's render()
Need to keep writing more to meet the submission quota. Any thoughts on how to do this is appreciated. Hope your day is going well!
You don't need showElement in local state of the wrapped component because it's not controlled by that component. Pass it as props and use componentDidUpdate to start fading out.
const { Component, useState, useCallback } = React;
const Button = ({ onClick }) => (
<button onClick={onClick}>Remove</button>
);
function App() {
const [show, setShow] = useState(true);
const onClick = useCallback(() => setShow(s => !s), []);
return (
<WrappedButton
time={1000}
onClick={onClick}
showElement={show}
/>
);
}
function fadeOutWrapper(WrappedComponent) {
return class extends Component {
constructor(props) {
super(props);
this.state = {
removeElement: false,
fadeout: false,
};
}
componentDidUpdate(prevProps) {
if (
this.props.showElement !== prevProps.showElement &&
!this.props.showElement
) {
this._triggerFade();
}
}
_triggerFade = () => {
this._fadeOut(this.props.time).then(() =>
this._removeElement()
);
};
_fadeOut = time => {
this.setState({ fadeout: true });
return new Promise(resolve => {
setTimeout(() => {
resolve();
}, time);
});
};
_removeElement = time => {
this.setState({
removeElement: true,
});
};
render() {
return this.state.removeElement ? null : (
<div>
{JSON.stringify(this.state)}
<WrappedComponent {...this.props} />
</div>
);
}
};
}
const WrappedButton = fadeOutWrapper(Button);
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>

createPortal does not overwrite div contents (like ReactDOM.render)

I am trying to get ReactDOM.createPortal to override the contents of the container I am mounting it too. However it seems to appendChild.
Is it possible to override contents? Similar to ReactDOM.render?
Here is my code:
import React from 'react';
import { createPortal } from 'react-dom';
class PrivacyContent extends React.Component {
render() {
return createPortal(
<div>
<button onClick={this.handleClick}>
Click me
</button>
</div>,
document.getElementById('privacy')
)
}
handleClick() {
alert('clicked');
}
}
export default PrivacyContent;
If you know what you're doing, here is a <Portal /> component that under the hoods creates a portal, empties the target DOM node and mounts any component with any props:
const Portal = ({ Component, container, ...props }) => {
const [innerHtmlEmptied, setInnerHtmlEmptied] = React.useState(false)
React.useEffect(() => {
if (!innerHtmlEmptied) {
container.innerHTML = ''
setInnerHtmlEmptied(true)
}
}, [innerHtmlEmptied])
if (!innerHtmlEmptied) return null
return ReactDOM.createPortal(<Component {...props} />, container)
}
Usage:
<Portal Component={MyComponent} container={document.body} {...otherProps} />
This empties the content of document.body, then mounts MyComponent while passing down otherProps.
Hope that helps.
In the constructor of the component, you could actually clear the contents of the div before rendering your Portal content:
class PrivacyContent extends React.Component {
constructor(props) {
super(props);
const myNode = document.getElementById("privacy");
while (myNode.firstChild) {
myNode.removeChild(myNode.firstChild);
}
}
render() {
return createPortal(
<div>
<button onClick={this.handleClick}>
Click me
</button>
</div>,
document.getElementById('privacy')
)
}
handleClick() {
alert('clicked');
}
}
export default PrivacyContent;
I find this is better and doesn't need useState:
export const Portal = () => {
const el = useRef(document.createElement('div'));
useEffect(() => {
const current = el.current;
// We assume `root` exists with '?'
if (!root?.hasChildNodes()) {
root?.appendChild(current);
}
return () => void root?.removeChild(current);
}, []);
return createPortal(<Cmp />, el.current);
};
Bit of an old question, but here's another sync solution (without useState). Also in a reusable component format.
const Portal = ({ selector, children, replaceContent = true }) => {
const target = useRef(document.querySelector(selector)).current;
const hasMounted = useRef(false);
if (!target) return null;
if (replaceContent && !hasMounted.current) {
target.innerHTML = '';
hasMounted.current = true;
}
return createPortal(children, target);
};
A solution with zero hook dependencies
import { createPortal } from 'react-dom';
const getNode = (id) => {
const domNode = document.getElementById(id);
const div = document.createElement("div");
domNode?.replaceChildren(div);
return div;
};
const Portal = ({ children }) => {
const domNode = getNode("privacy");
if (domNode) {
return createPortal(children, domNode);
}
return null;
};

react native show current time and update the seconds in real-time

I want to show the current time(MM/DD/YY hh:mm:ss) in react native app like a clock, and get update every seconds, I tried using new Date() and set it in state, but the time don't update unless I refresh the page.
I also tried using setInterval function in render(), it do got update but it's expensive for CPU. is there a good method to realise the function?
state = {
curTime: null,
}
render(){
setInterval(function(){this.setState({curTime: new Date().toLocaleString()});}.bind(this), 1000);
return (
<View>
<Text style={headerStyle.marginBottom15}>Date: {this.state.curTime}</Text>
</View>
);
}
Just move setInterval into componentDidMount function.
Like this :
componentDidMount() {
setInterval(() => {
this.setState({
curTime : new Date().toLocaleString()
})
}, 1000)
}
This will change state and update every 1s.
in react hooks, it can be done like this:
import React, { useState, useEffect } from "react";
const [dt, setDt] = useState(new Date().toLocaleString());
useEffect(() => {
let secTimer = setInterval( () => {
setDt(new Date().toLocaleString())
},1000)
return () => clearInterval(secTimer);
}, []);
This method works fine and displays MM/DD/YY hh:mm:ss format
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {
time: new Date().toLocaleString()
};
}
componentDidMount() {
this.intervalID = setInterval(
() => this.tick(),
1000
);
}
componentWillUnmount() {
clearInterval(this.intervalID);
}
tick() {
this.setState({
time: new Date().toLocaleString()
});
}
render() {
return (
<p className="App-clock">
The time is {this.state.time}.
</p>
);
}
}
original link : https://openclassrooms.com/courses/build-web-apps-with-reactjs/build-a-ticking-clock-component
I got the answer. The code below also works.
componentWillMount(){
setInterval(function(){
this.setState({
curTime: new Date().toLocaleString()
})
}.bind(this), 1000);
}
I would recommend to prefer using setTimeout instead of setInterval, indeed, the browser may be overhelmed by heavy processing and in that case you would probably prefer updating the clock less often instead of queuing several updates of the state.
With setTimeout it is also a bit easier to leverage the Page Visibility API to completely stop the clock when the page is hidden (see https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API).
export default class MyClock {
constructor(props) {
super(props);
this.state = {
currentTime: Date.now(),
};
}
updateCurrentTime() {
this.setState(state => ({
...state,
currentTime: Date.now(),
}));
this.timeoutId = setTimeout(this.updateCurrentTime.bind(this), 1000);
}
componentWillMount() {
this.updateCurrentTime();
document.addEventListener('visibilitychange', () => {
if(document.hidden) {
clearTimeout(this.timeoutId);
} else {
this.updateCurrentTime();
}
}, false);
}
componentWillUnmount() {
clearTimeout(this.timeoutId);
}
}
Full Code here:
import React, { Component } from 'react';
import { Text, View } from 'react-native';
export default class KenTest extends Component {
componentDidMount(){
setInterval(() => (
this.setState(
{ curTime : new Date().toLocaleString()}
)
), 1000);
}
state = {curTime:new Date().toLocaleString()};
render() {
return (
<View>
<Text>{'\n'}{'\n'}{'\n'}The time is: {this.state.curTime}</Text>
</View>
);
}
}
Using hooks and moment-js:
setInterval(() => {
var date = moment().utcOffset("-03:00").format(" hh:mm:ss a");
setCurrentDate(date);
}, 1000);
Try this,
import * as React from 'react';
import { Text, View } from 'react-native';
export default function App() {
const [time, setTime] = React.useState();
React.useEffect(() => {
const timer = setInterval(() => {
setTime(new Date().toLocaleString());
}, 1000);
return () => {
clearInterval(timer);
};
}, []);
return (
<View>
<Text>{time}</Text>
</View>
);
}

Resources