React - Call method after redirect - javascript

I have two different links. One is main Page, other is gallery. I have on main page 3 links with method scrollIntoView onClick that are taking me to different sections. I want to implement method redirecting me from gallery component to main page and when it's done call method scrollIntoView.
goToContact method:
goToContact = e => {
if (window.location.pathname === "/fotobudka/") {
this.fbContactElement.scrollIntoView({
behavior: "smooth",
block: "start"
});
}
if (window.location.pathname !== "/fotobudka") {
this.changeUrl();
this.goto();
}
};
Change Url redirect me to main page:
changeUrl() {
return <Redirect to="/fotobudka/" />;
}
When it's done:
goto = () => {
setTimeout(() => {
let fbContactElement = document.getElementById("contact-form");
fbContactElement.scrollIntoView({
behavior: "smooth",
block: "start"
});
}, 100);
};
My method works perfectly, but I know SetTimeout isn't good solution. I've tried async/await but I think I am not good enough to implement that.
How to refactor this without using SetTimeout function ?

You have to use setState to set a property that will render the inside your render() method.
class MyComponent extends React.Component {
state = {
redirect: false
}
goTo = () => {...}
goToContact = e => {
this.setState({ redirect: true },
()=>goto());
}
render () {
const { redirect } = this.state;
if (redirect) {
return <Redirect to='/fotobudka'/>;
}
return (...)
}
}
You can also see an example in the official documentation: https://reacttraining.com/react-router/web/example/auth-workflow

I solved my issue, in one forum I've checked that via interval we can check if item already exist on page. In React it looks like this:
goto = selector => {
if (window.location.pathname !== "/fotobudka/") {
this.constructor.changeUrl();
}
let checkExist = setInterval(() => {
let element = document.getElementById(selector);
if (element) {
element.scrollIntoView({
behavior: "smooth",
block: "start"
});
clearInterval(checkExist);
}
}, 100);
};
static changeUrl() {
return <Redirect to="/fotobudka/" />;
}

You can use the componentDidMount() lifecycle hook for executing some code when a component is loaded. But you have to avoid an infinite update cycle by passing some query param something like doScroll=true. Now you can simply check inside your componentDidMount() hook for your query param and execute your scroll function

Related

Recursive function call in React component

I have a question regarding the following (very weird) issue.
I have a parent component like this:
function ParentComponent() {
const [myParam, setMyParam] = React.useState(1);
useEffect(() => {
setTimeout(() => {
console.log('Setting myParam to 0...');
setMyParam(0);
}, 3000);
}, []);
return (
<div>
<ChildComponent myparam={myParam}></ChildComponent>
</div>
)
}
From this parent component I pass the param myParam (with default value 1) to the child component as a prop.
Child component looks like this:
function ChildComponent(props) {
const loop = () => {
if(props.myparam) { // DOES NOT DETECT PROP CHANGE PROPERLY!!!
setTimeout(() => loop(), 1000)
}
}
React.useEffect(() => {
if(props.myparam) {
loop();
}
}, [props.myparam])
return (
<div>
Loop demo
</div>
)
}
The initial value of myparam is 1 so the loop function starts its recursion.
Then, the parent component updates the prop after 3 seconds, and it is being properly detected in useEffect() hook in child component BUT the loop function does not detect the prop change and keeps running recursively.
The expected behavior, from my point of view, is that the recursive function (loop()) should stop its execution after the prop myparam gets the value 0 (changed from parent component) because in loop() function I have this IF statement:
if(props.myparam) { // DOES NOT DETECT PROP CHANGE PROPERLY!!!
setTimeout(() => loop(), 1000)
}
Any explanations for this strange behavior?
Thanks in advance!
I am answering to myself, as I believe I found the answer.
Instead of using props.myparam as a condition in loop() function:
if(props.myparam) { // DOES NOT DETECT PROP CHANGE PROPERLY!!!
setTimeout(() => loop(), 1000)
}
I use a local variable as a flag:
if(runLoop) {
setTimeout(() => loop(), 1000)
}
In useEffect() in ChildComponent I have this:
React.useEffect(() => {
if(props.myparam) {
runLoop = 1;
loop();
} else {
runLoop = 0;
}
return () => {
runLoop = 0; // this solves the issue
}
}, [props.myparam])
Then in the useEffect() in ChildComponent I add a cleanup function to set runLoop to false - the script works as expected.
So my conclusion is that React basically instantiates the component on every render and the variables from previous render will not be updated (unless if they are explicitly updated on cleanup).

React: change button text while doing work

In my React application, I would like a button that normally says Save, changes its text to Saving... when clicked and changes it back to Save once saving is complete.
This is my first attempt:
import React from 'react';
import { Button } from 'react-bootstrap';
class SaveButton extends React.Component {
constructor(props) {
super(props);
this.state = { isSaving : false };
this.onClick = this.onClick.bind(this);
}
onClick() {
// DOES NOT WORK
this.setState({ isSaving : true });
this.props.onClick();
this.setState({ isSaving : false });
}
render() {
const { isWorking } = this.state;
return (
<Button bsStyle="primary"
onClick={isWorking ? null : this.onClick}>
{isWorking ? 'Saving...' : 'Save'}
</Button>
);
}
}
export default SaveButton;
This doesn't work because both setState and this.props.onClick() (passed in by the parent component) are executed asynchronously, so the calls return immediately and the change to state is probably lost in React optimization (and would probably be visible only for a few milliseconds anuway...).
I read up on component state and decided to lift up state to where it belongs, the parent component, the form whose changes the button saves (which in my app rather is a distant ancestor than a parent). So I removed isSaving from SaveButton, replaced
const { isWorking } = this.state;
by
const { isWorking } = this.props.isWorking;
and tried to set the state in the form parent component. However, this might be architecturally cleaner, but essentially only moved my problem elsewhere:
The actual saving functionality is done at a totally different location in the application. In order to trigger it, I pass onClick event handlers down the component tree as props; the call chain back up that tree upon a click on the button works. But how do I notify about completion of the action in the opposite direction, i.e. down the tree ?
My question: How do I notify the form component that maintains state that saving is complete ?
The form's parent component (which knows about save being complete) could use a ref, but there isn't only one of those forms, but a whole array.
Do I have to set up a list of refs, one for each form ?
Or would this be a case where forwarding refs is appropriate ?
Thank you very much for your consideration! :-)
This doesn't work because both setState and this.props.onClick()
(passed in by the parent component) are executed asynchronously, so
the calls return immediately and the change to state is probably lost
in React optimization
setState can take a callback to let you know once state has been updated, so you can have:
onClick() {
this.setState({ isSaving : true }, () => {
this.props.onClick();
this.setState({ isSaving : false });
});
}
If this.props.onClick() is also async, turn it into a promise:
onClick() {
this.setState({ isSaving : true }, () => {
this.props.onClick().then(() => {
this.setState({ isSaving : false });
});
});
}
can you see ?
https://developer.mozilla.org/tr/docs/Web/JavaScript/Reference/Global_Objects/Promise
parent component's onClick function
onClick(){
return new Promise(function(resolve,reject){
//example code
for(let i=0;i<100000;i++){
//or api call
}
resolve();
})
}
SaveButton component's onClick funtion
onClick(){
this.setState({ isSaving : true });
this.props.onClick().then(function(){
this.setState({ isSaving : false });
});
}
Here is your code that works :
import React from 'react';
import { Button } from 'react-bootstrap';
class SaveButton extends React.Component {
constructor() {
super();
this.state = {
isSaving: false
};
this.handleOnClick = this.handleOnClick.bind(this);
}
handleOnClick() {
this.setState(prevState => {
return {
isSaving: !prevState.isSaving
}
});
console.log("Clicked!",this.state.isSaving);
this.handleSave();
}
handleSave() {
// Do what ever and if true => set isSaving = false
setTimeout(()=>{
this.setState(prevState=>{
return {
isSaving: !prevState.isSaving
}
})
},5000)
}
render() {
const isWorking = this.state.isSaving;
return (
<Button
bsStyle="primary"
onClick={this.handleOnClick}
>
{isWorking ? 'Saving...' : 'Save'}
</Button>
);
}
}
export default SaveButton;
You need to change const isWorking instead of const {isWorking} and you need to handle save function in some manner.
In this example I used timeout of 5 second to simulate saving.

componentDidMount not being called after goBack navigation call

I've set up a StackNavigator which will fire a redux action to fetch data on componentDidMount, after navigating to another screen and going back to the previous screen, componentDidMount is no longer firing and I'm presented with a white screen. I've tried to reset the StackNavigator using StackActions.reset but that just leads to my other screens not mounting as well and in turn presenting an empty screen. Is there a way to force componentDidMount after this.props.navigation.goBack()?
Go Back Function
_goBack = () => {
this.props.navigation.goBack();
};
Navigation function to new screen
_showQuest = (questId, questTitle ) => {
this.props.navigation.navigate("Quest", {
questId,
questTitle,
});
};
Edit :
Hi , I'm still stuck on this matter and was wondering if the SO Community has a solution to force componentDidMount to be called after Navigation.goBack() is called or rather how to Unmount the component after this.props.navigation.navigate is called
The components are not removed when the navigation changes, so componentDidMount will only be called the first time it is rendered.
You could use this alternative approach instead:
class MyComponent extends React.Component {
state = {
isFocused: false
};
componentDidMount() {
this.subs = [
this.props.navigation.addListener("didFocus", () => this.setState({ isFocused: true })),
this.props.navigation.addListener("willBlur", () => this.setState({ isFocused: false }))
];
}
componentWillUnmount() {
this.subs.forEach(sub => sub.remove());
}
render() {
if (!this.state.isFocused) {
return null;
}
// ...
}
}
The didFocus event listener didn't work for me.
I found a different listener called focus which finally worked!
componentDidMount() {
const { navigation } = this.props;
this.focusListener = navigation.addListener('focus', () => {
// call your refresh method here
});
}
componentWillUnmount() {
// Remove the event listener
if (this.focusListener != null && this.focusListener.remove) {
this.focusListener.remove();
}
}

react, x3d custom element and event listener

I am using X3DOM inside my React application to display an external X3D file
export class X3DComponent extends React.Component {
constructor(props) {
super(props);
this.x3dLoaded = this.x3dLoaded.bind(this);
}
x3dLoaded(e){
console.log('inside');
console.log(e);
}
render() {
return (
<div>
<x3d id='x3dElement' is='x3d'>
<scene is="x3d">
<navigationInfo type='EXAMINE' is='x3d'/>
<viewpoint id="viewPoint" is="x3d"
centerOfRotation='0 0 0 '
position="-1.94639 1.79771 -2.89271"
orientation="0.03886 0.99185 0.12133 3.7568"
isActive="true"
/>
<inline id="x3dInline" DEF='x3dInline' nameSpaceName="tanatloc" is="x3d" mapDEFToID="true"
url={this.props.fileUrl}/>
<loadSensor is='x3d' DEF='InlineLoadSensor' isLoaded={this.x3dLoaded} timeOut='5'>
<inline is='x3d' USE='x3dInline' containerField='watchList'/>
</loadSensor>
</scene>
</x3d>
</div>
);
}
}
I would like to listen to the event emitted by isLoaded in loadSensor.
According to X3D specs
An isLoaded TRUE event is generated when all of the elements have been loaded. (http://www.web3d.org/documents/specifications/19775-1/V3.3/Part01/components/networking.html#LoadSensor)
How can I do it using React ? This event is emitted after componentDidMount.
Thank you !
I don't think loadSensor has been implemented in X3DOM, c.f. https://doc.x3dom.org/author/nodes.html. But you can use a 'simple' onload event on your inline file:
<inline id="x3dInline" DEF='x3dInline' nameSpaceName="tanatloc" is="x3d"
mapDEFToID="true" url={this.props.fileUrl} onload={this.x3dLoaded} />
You can also check out https://github.com/x3dom/x3dom/blob/7d8ad13c2d2c17d9fd317e27b9e121379a53407f/test/regression-suite/test/cases/inlineEvents/inlineEvents.html for more examples.
I met the same problem as you do. I just write a simple polling in componentDidMount to solve this problem.
componentDidMount() {
const poller = () => {
this.timer = setTimeout(() => {
const loaded = document.querySelector('inline').getAttribute('load')
if (loaded) {
// do sth
} else {
poller()
}
}, 500)
}
poller()
}
Here's my approach... Similar to #neo_seele's answer, but using rxjs
EDIT added some timeout logic to avoid waiting indefinitely
import { interval, timer } from 'rxjs';
import { takeUntil, takeWhile } from 'rxjs/operators';
//reference to the <inline> element
const refInline = useRef(null);
const [error, setError] = useState("");
useEffect(() => {
window.x3dom && window.x3dom.reload();
//check every 250ms if model is already loaded, when it does, it calls the complete callback
//however if after 5s it hasn't loaded, it shows an error
interval(250)
.pipe(takeUntil(timer(5000)))
.pipe(takeWhile(()=>!refInline.current.querySelectorAll("Shape").length))
.subscribe({complete: ()=>{
if (!refInline.current.querySelectorAll("Shape").length){
setError(`Model could not be loaded, please check ${conf.url} format and try again.`);
} else {
// ... my logic
}
}});
}, [...dependencies]);

React native List View onEndReached calling multiple times

I am facing some trouble using the List View onEndReached component in react native.
Render code:
#autobind
_fetchMoreHistory(){
console.log("Fetch more history called");
}
<View style={[styles.Ctr]}>
<ListView dataSource={this.state.txHistorySource}
renderRow={ this._renderRow }
onEndReached ={ this._fetchMoreHistory }
onEndReachedThreshold = {10}/>
</View>
The moment I open the screen _fetchMoreHistory is called twice and works normally after that onEndReached reached. Can someone help debug this ?
I faced the same issue and searched a lot but didn't find any answers, so I used a condition to check if the first request got the data I fire onendreashed again else I don't
Example
// onEndReached
If(condition) {
Make the call
}
So my solution is simple. Don't use onEndReached at all. Use onScroll and detect the end of the scroll.
isCloseToBottom = ({ layoutMeasurement, contentOffset, contentSize }) => {
const paddingToBottom = 20; // how far from the bottom
return layoutMeasurement.height + contentOffset.y >=
contentSize.height - paddingToBottom;
};
and the FlatList component
<FlatList
data={data}
onScroll={({ nativeEvent }) => {
if (this.isCloseToBottom(nativeEvent)) {
// Dont forget to debounce or throttle this function.
this.handleOnEndReached();
}
}}
/>
I had the same issue. But I figured out that I had the ScrollView that wraps my FlatList.
When I removed it all started working properly.
It's a pity that NOTHING WAS SAID ABOUT THAT IN THE OFFICIAL DOCS
You can try my solution
You should configure limit > 10. Example limit = 15
Add onMomentumScrollBegin prop to your ListView declaration.
<ListView
data={this.props.data}
onEndReached={...}
onEndReachedThreshold={0.5}
...
onMomentumScrollBegin={() => { this.onEndReachedCalledDuringMomentum = false; }}
/>
Modify your onEndReached callback to trigger data fetching only once per momentum.
onEndReached =()=> {
if(!this.onEndReachedCalledDuringMomentum) {
this.props.fetchData();
this.onEndReachedCalledDuringMomentum = true;
}
};
I've solved this problem by creating a state variable that tells me is the service is loading or not.
class Component1 extends Component {
public constructor(props) {
super(props);
this.state = {
isLoading: false,
listOfItems: []
};
}
public componentDidMount() {
this.loadData();
}
public render(){
return (
<FlatList
data={this.state.listOfItems}
renderItem={this.renderItem}
onEndReachedThreshold={0.1}
onEndReached={this.loadData}
/>
);
}
private loadData = () => {
if (this.state.isLoading === true) {
/// Avoid make another request is there's happening a request right now
return;
}
this.setState({isLoading: true});
this.fetchData()
.then(() => {
/// Handle success response
this.setState({isLoading: false});
})
.catch(() => {
this.setState({isLoading: false});
});
}
}
You can write the code for fetching data in onMomentumScrollEnd rather than onEndReached, like this:
onMomentumScrollEnd={() => this.props.onEndReached()}
It might not be written as the available prop in the react native documentation, but if you will see the source code for FlatList, it uses Virtualized List which in return has the mentioned prop available.

Categories