I use react navigation. I have a TabNavigator. Each Tab contains a StackNavigator. From one StackNavigator, it is possible to open a Modal.
The Modal is opened when I click on a Button in a certain Component.
export default class CallModalComponent extends Component {
constructor(props) {
super(props)
...
}
...
render() {
const { navigate } = this.props.navigation;
return (
<Button
....
onPress={() => navigate("Modal")}/>
The in the TabNav registered screen <MyModal /> is a stateful Component.
On close of the Modal I need the state of <MyModal /> to be passed down to <CallModalComponent />.
The problem I am having is how that might work with react navigation in between... I know that I can use redux and send/retrieve it through the global store. But I wonder if its possible with only react native.
Any suggestions?
EDIT
I implemented the Code from answer
export default class CallModalComponent extends Component {
constructor(props) {
super(props)
...
}
...
onModalDismis(childVar) {
console.log('modal is closing');
console.log(childVar);
}
render() {
const { navigate } = this.props.navigation;
return (
<Button
....
onPress={(childVar) => navigate("Modal", {onModalDismis: this.onModalDismis()})}/>
// Then in your modal component
componentWillUnmount () {
console.log('unmount');
this.props.navigation.state.params.onModalDismis('here we go');
}
The following gets logged:
When the Modal Component is mounted I get:
modal is closing
undefined
Then, when I actually close the Modal, I get:
unmount
and then the error:
Cannot read property of onModalDismiss of undefined.
I expected to be nothing logged on mounting of the Modal. And then, when I close the Modal I expected
unmount, modal is closing and here we go to be logged.
You can pass parameters to screens while navigating. This allows you to send a function to next screen and then you can initiate it when you want. More detail here.
Example
export default class CallModalComponent extends Component {
constructor(props) {
super(props)
...
}
...
onModalDismis() {
console.log('modal is closing');
}
render() {
const { navigate } = this.props.navigation;
return (
<Button
....
onPress={() => navigate("Modal", {onModalDismis: this.onModalDismis})}/>
// Then in your modal component
componentWillUnmount () {
this.props.navigation.state.params.onModalDismis();
}
#bennygenel was very close. Added a little.
export default class CallModalComponent extends Component {
constructor(props) {
super(props)
...
}
...
onModalDismis(childVar) {
console.log('modal is closing');
console.log(childVar);
}
render() {
const { navigate } = this.props.navigation;
return (
<Button
....
onPress={() => navigate("Modal", {onModalDismis:(childVar) => this.onModalDismis(childVar)})}/>
// Then in your modal component
componentWillUnmount () {
this.props.navigation.state.params.onModalDismis("some var");
}
The reason for using an arrow function is because it binds() the context of this https://medium.freecodecamp.org/react-binding-patterns-5-approaches-for-handling-this-92c651b5af56 and it only gets executed when onModalDismis() is called, and not the render of <CallModalComponent/>. Difference in using functions in react-native
Related
I have one component in which I have one button and I am calling one node js service on that. I am getting a response back from that service and I want to pass that response on next component to display a data there. Below is my component which is doing a node js call.
import { FormGroup } from "react-bootstrap";
import "bootstrap/dist/css/bootstrap.css";
import axios from "axios";
export default class Abc extends React.Component {
constructor(props) {
super(props);
this.state = {
data: {}
}
this.handleClick = this.handleClick.bind(this);
}
handleClick = (e) => {
e.preventDefault();
axios.get(url)
.then(res => {
this.setState({
data: res.data// I need this variable to pass to next component Pqr where I can use it for display purpose.
})
this.props.history.push("/Pqr",{ response:res.data});
})
};
render() {
return (
<form >
<button className="btn btn-info btn-sm" onClick={this.handleClick} style={{ whitespace: 'nowrap' }} >
Launch
</button>
</form>
)
}
}
My Pqr component code is as below.
import React from "react";
export default class ModelLaunch extends React.Component {
constructor(props) {
super(props);
this.state = {
}
}
render() {
const state = this.props.location.state
return (
<h1>This page will display model Inputs : {state} </h1>
)
}
}
I have solved above problem with other way. Instead of calling a node js service on Abc component I am just redirecting it to new coponent and in new component's componentDidMount() method I am calling a node js service and storind a data in props. In this way I have my data on new copmonent. Below is my updated code in Abc component now.
import { FormGroup } from "react-bootstrap";
import "bootstrap/dist/css/bootstrap.css";
import axios from "axios";
export default class Abc extends React.Component {
constructor(props) {
super(props);
this.state = {
data: {}
}
this.handleClick = this.handleClick.bind(this);
}
handleClick = (e) => {
e.preventDefault();
this.props.history.push("/Pqr");
})
};
render() {
return (
<form >
<button className="btn btn-info btn-sm" onClick={this.handleClick} style={{ whitespace: 'nowrap' }} >
Launch
</button>
</form>
)
}
And in pqr coponent's code as below
import React from "react";
import axios from "axios";
export default class Pqr extends React.Component{
constructor(props)
{
super(props);
this.state = {
data :[]
}
}
componentDidMount(){
axios.get(url).then((res) => {
console.log("res****",res.data)
this.setState({
data:res.data
})
}).catch((err) =>{
console.log("err", err)
})
}
render()
{
return(
<h1>This page will display data</h1>
)
}
}
I see you're changing a route (using react-router?).
Remember that this.setState is async and specific for your component, when you call this.props.history.push('/Pqr'), maybe the this.state.data is not updated yet.
To share this data through different routes in the same react project, I actually know that you can:
Store it on window.localStorage and then get on the next route here have a tutorial
Use react contexts to share data between components (if you're not reloading the page)
Send data through routes with react-router, as explained here
If its not the case, and you just want to pass the property down or above the hierarchy tree, in addition to the comments above, maybe it can help:
As you probably know, react projects are composed of components that are put all together to work in a specific way. In the example below, there are two components (father and child)
import React from 'react';
// We can say that this component is "below" Father
function Child(props) {
return (
<button>
Hey, I'm a button!
</button>
);
}
// We can say that this component is "above" Child
function Father(props) {
return (
<div>
<Child />
</div>
);
}
I couldn't find in the provided code/question, one child component, maybe you forgot to write it?
If the response is "yes", I'll create a fictional component called... FictionalComponent (I'm a Genius!), and pass the data on state as a property named... data.
In order to pass this property, if its the case, you just need to update your render method to look like this:
render() {
return (
<form >
<button
className="btn btn-info btn-sm"
onClick={this.handleClick}
style={{ whitespace: 'nowrap' }}
>
Launch
<FictionalComponent data={this.state.data} />
</button>
</form>
)
}
This way, when this.state.data changes, the FictionalComponent will be re-rendered with the new data value.
But, maybe you want the reverse operation and you need to pass the this.state.data to the component above your Abc, listed there when the button is pressed.
To achieve it you need to have a "Father" component to your Abc, the "Father" component must provide an onDataChanged callback in order to capture the event. This callback will receive the data and handle it.
In this case, I'll create another component to be the component above your Abc. I'll name it... AboveAbcComponent, perfect!
...
class AboveAbcComponent extends React.Component {
constructor(props) {
this.state = {
dataFromChild: null
};
this.onDataChanged = this.onDataChanged.bind(this);
}
onDataChanged(dataReceived) {
console.log("Yey! It works!");
this.setState({ dataFromChild: dataReceived });
}
render() {// Just the passed props changes here
...
<Abc
onDataChanged={this.onDataChanged}
/>
...
}
}
export default class Abc extends React.Component {
constructor(props) { ... } // No changes here
handleClick = (e) => {
e.preventDefault();
axios.get(url)
.then(res => {
this.setState({
data: res.data
});
this.props.onDataChanged(res.data);
this.props.history.push("/Pqr"); // I really didn't understand Why this push is here... but ok
})
};
render() { ... } // No changes here
}
Hope it helps, have fun!
I try to using popovers from Reactstrap.
Here is the snippet of my code:
constructor(props) {
super(props);
this.state = {
popoverOpen: false
};
}
toggle = () => {
this.setState({popoverOpen: !this.state.popoverOpen})
};
<div>
<Button id="Popover1" type="button">
Launch Popover
</Button>
<Popover placement="bottom" isOpen={this.state.popoverOpen} target="Popover1" toggle={this.toggle}>
<PopoverHeader>Popover Title</PopoverHeader>
<PopoverBody>Hello there :)</PopoverBody>
</Popover>
</div>
The code above already works.
But, for now I want to separate the button on another component.
So, any example how to do that..?
Is it possible to taking the button ID from another component..?
Or, should I setState from another component..?
if so, please give an example or source to learn that.
I think this task can be solved with React state lifting.
The idea is to create Button component as you wish and pass to ittoggle function, so Button component will call it on each call. Also you may pass this.state.popoverOpen so Button component will know if popover currently open. Your min component will have the same state, but your Button component will have parent's state as props.
Here is example (this code is not tested! Use it as hint only!)
// ButtonComponent.js
// import React and other nesessary things
export default class ButtonComponent extends React.Component {
constructor(props) {
super(props);
}
render () {
return <Button id={this.porps.ButtonID} type="button" onClick={this.props.toggle}>
Launch Popover
</Button>
}
}
// mainComponent.js
import ButtonComponent from './ButtonComponent.js'
export default MainComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
popoverOpen: false
};
}
toggle = () => {
this.setState({popoverOpen: !this.state.popoverOpen})
};
render () {
return <div>
<ButtonComponent toggle={this.toggle.bind(this)} ButtonID={"Popover1"}/>
<Popover placement="bottom" isOpen={this.state.popoverOpen} target="Popover1" toggle={this.toggle}>
<PopoverHeader>Popover Title</PopoverHeader>
<PopoverBody>Hello there :)</PopoverBody>
</Popover>
</div>
}
}
I have an App component and a function 'modalToggled' inside its.
I want to pass the function to multiple child components until I get to the last one, the 'interiores' component.
Like this:
<App> -> <Coluna1> -> <MenuPrincipal> -> <Portfolio> -> <PortfolioMenu> -> <interiores>
App Component, the parent of all components:
import React, { Component } from 'react';
import Coluna1 from './Coluna1'
class App extends Component {
constructor(props) {
super(props)
this.state = {
modalOn: false
}
this.modalToggled = this.modalToggled.bind(this)
}
modalToggled = (on) => {
this.setState({modalOn: on});
}
render() {
return (
<div>
<Coluna1 onModalToggle={this.modalToggled}/>
</div>
)
}
}
export default App;
This is the 'Coluna1' the first child component. I did the same thing in the another ones: 'MenuPrincipal', 'Portfolio', 'PortfolioMenu'
class Coluna1 extends Component {
constructor(props){
super(props)
}
render() {
return (
<div>
<Header />
<MenuPrincipal onModalToggle={this.props.modalToggled} />
</div>
)
}
}
export default Coluna1
Therefore here is the last component interiores, when I click on the button there appears an error message:
TypeError: _this.props.onModalToggle is not a function
import React, { Component } from 'react'
import Modal from 'react-responsive-modal';
class Interiores extends Component {
constructor(props) {
super(props)
this.state = {
open: false
}
}
onOpenModal = () => {
this.setState({ open: true });
this.props.onModalToggle(true);
};
onCloseModal = () => {
this.setState({ open: false });
this.props.onModalToggle(false);
};
render() {
const { open } = this.state;
return (
<div>
<button onClick={this.onOpenModal}>Open modal</button>
<Modal open={open} onClose={this.onCloseModal} center></Modal>
</div>
)
}
}
export default Interiores;
Does anybody know how to solve it? Thank you
It happens, because in App class you pass prop with name onModalToggle:
<Coluna1 onModalToggle={this.modalToggled}/>
But in Coluna1 you receive this props with wrong name, modalToggled:
<MenuPrincipal onModalToggle={this.props.modalToggled} />
Just make the names of props equal. In Coluna1 and other intermediate components pass and receive this props as onModalToggle:
<MenuPrincipal onModalToggle={this.props.onModalToggle} />
This is the problem
modalToggled = (on) => {
this.setState({modalOn: on});
}
Since this is a class function it needs to be defined like
modalToggled(on) {
this.setState({modalOn: on});
}
My component this.props.save(); when componentWillUnmount? however if i leave the page to an external link in a different tab it does not trigger componentWillUnmount
my component
export default class AutoSave extends React.Component {
constructor(props) {
super(props);
this.setIntervalId = null;
}
componentDidMount() {
if (!window.onbeforeunload) {
window.onbeforeunload = () => {
this.props.save();
};
}
this.setIntervalId = setInterval(() => this.props.save(), this.props.intervalInMs);
}
componentWillUnmount() {
this.props.save();
clearInterval(this.setIntervalId);
}
render() {
return <span className="saving">Last saved at {now()}</span>;
}
}
My link looks like this
<Link href="http://www.google.com" target="_blank">
Google Link
</Link>
How can I make component save when user clicks link? It's a react/redux app.
Why are you doing some kind of job in a class that extends from "React.Component". In ReactJS Component is not meant for to do jobs like that. From official documentation "Components let you split the UI into independent, reusable pieces, and think about each piece in isolation.".There is no part in your component except the span that is related to ui. Anyway you should create AutoSave as Pure Component and use it in all React Components. :
export default class AutoSave
{
constructor(saveFn, intervalInMs) {
this.saveFn = saveFn;
this.setIntervalId = setInterval(
() => {
this.save();
},
intervalInMs
);
}
unmount() {
this.save();
clearInterval(this.setIntervalId);
}
save() {
this.saveFn();
this.callOnSaveCallback();
}
onSave(callback) {
this.onSaveCallback = callback;
}
// private method that calls onSaveCallback
callOnSaveCallback() {
if(this.onSaveCallback != null)
this.onSaveCallback();
}
}
And use it in my React Components like this :
import AutoSave from './AutoSave';
export default class ReactComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
// ... other properties
"lastSave": new Date()
}
this.save = this.save.bind(this);
this.autoSave = new AutoSave(this.save, 100);
this.autoSave.onSave(
() => {
this.setState(
{
"lastSave": new Date()
}
);
}
)
}
componentWillUnmount() {
this.autoSave.unmount();
this.autoSave = null;
}
onClick() {
this.autoSave.save();
}
save() {
// some saving job
}
render() {
return (
<div>
// ... other components
<Link href="http://www.google.com" target="_blank" onClick={this.onClick.bind(this)}>
Google Link
</Link>
<span className="saving">Last saved at {this.state.lastSave}</span>
</div>
);
}
}
Opening a new tab will not trigger componentWillUnmount() nor onbeforeunload. In both cases because your app is still open in the original tab. React does not unmount components immediately before you close or navigate away from the page, so componentWillUnmount() would do nothing even if the page were to close. onbeforeunload only triggers when you navigate away from the page in the same tab, or close the tab/window.
If you want to reliably call this.props.save() every time the user clicks your button, then simply add an onClick handler to the button.
render() {
return <span onClick={this.props.save} className="saving">Last saved at {now()}</span>;
}
I've been trying to call a function which is in a screen of a navigator from its screen.
To clarify the point, here is a snippet of my code...
//ScreenA.js
export default class ScreenA extends React.Component {
showWarning(){
this.setState({showWarning: true});
setTimeout(function() {
this.setState({showWarning: false});
}.bind(this), 3000);
}
render() {
return (
<View style={{backgroundColor : this.state.showWarning ? "#red" : "#blue"}}>
{this.state.showWarning && <Warning />}
</View>
)
}
}
//Nagigator.js
default export const Navigator = StackNavigator({
ScreenA: {screen: ScreenA},
ScreenB: {screen: ScreenB},
});
//App.js
export default class App extends React.Component {
handleSubmit(qrCode){
if(qrCodeIsInvalid){
this.navigator.ScreenA.showWarning();
//This is just a sudo code.
//How do we call ScreenA.showWarning here?
}
}
render() {
let props = {/*some props here*/};
return (//This "ref" stops the application without describing any reason
<Navigator screenProps={props} ref={nav => { this.navigator = nav; }}/>
);
}
}
There is an example of how to call a function from a navigation header, but not from the class which exports the navigator.
I thought that each screen can be accessed via ref, but this causes an error without explaining what's happening.
Has anyone encountered a similar situation?
Any advice will be appreciated.
P.S.
# Nimrod Argov
Here are details of what I've been trying to achieve.
ScreenA has a QR code reader and submit function, which submits QR codes to App.js.
App.js has handleSubmit function, where submitted QR codes are sent to a server and labelled as either valid or invalid.
If a submitted QR code turns out to be invalid, ScreenA has to show a warning message and change its background colour for 3 seconds.
It might be achieved by having App.js pass a prop {showWarning:true} to ScreenA and pass {showWarning:false} in 3 seconds.
However, I thought it would be ScreenA's responsibility to change its background colour. Thus, I set setTimeout and setState in the showWarning().
I did it this way:
ScreenA.js
While navigate to ScreenB, including the function you want to call.
export default class ScreenA extends React.Component {
constructor(props) {
super(props);
this.doSomething = this.doSomething.bind(this);
}
doSomething() {
this.setState({blah: true});
}
navigateToB() {
this.props.navigation.navigate('ScreenB', {
doSomething: this.doSomething,
});
}
}
ScreenB.js
So you can do this in ScreenB.
const { state, setParams, navigate } = this.props.navigation;
const params = state.params || {};
params.doSomething();