Is there a way of delegating in react native just like in swift/ios.. If we want to pass a information to a child class from parent class, we can do it by passing props. But what if we want to pass some information from the child class to parent class.
PS: No Redux or singleton approach.
Callbacks are common in React and could be used for such a thing.
// Child.js
export default class Child extends Component {
componentDidMount() {
this.props.onEvent({ type : "event" });
}
render() {
return (
<Text>I'm a child</Text>
);
}
}
// Parent.js
export default class Parent extends Component {
const onChildEvent = (event) => {
console.log(event); // { type : "event" }
}
render() {
return (
<Child onEvent={this.onChildEvent} />
);
}
}
Related
In my React Native application I'm trying to change state and trigger a re-render of a component. It should be done when NavBottom calls this.props.navigation.navigate('captureView') to navigate to CaptureView. The state update should reset the CaptureView photo state variable back to its original value.
How can state be changed in React Native with react-navigation on navigate? https://reactnavigation.org/docs/en/navigation-actions.html
CaptureView is part of CaptureStack
import { createStackNavigator } from "react-navigation";
const CaptureStack = createStackNavigator({
captureView: CaptureView,
detailView: DetailView,
reportView: ReportView,
});
const Tab = createBottomTabNavigator({
capture: CaptureStack,
}, {
initialRouteName: 'capture',
tabBarComponent: NavBottom
});
CaptureView.js:
import { StackActions, NavigationActions, NavigationEvents } from 'react-navigation';
class CaptureView extends Component {
static navigationOptions = {}
constructor(props) {
super(props);
console.log("CaptureView: constructor(props)");
}
componentDidMount() { // called just once
console.log("CaptureView: componentDidMount()");
// this.setState = { // undefined???
// photo: null // this needs to be RESET
// };
}
componentWillUnmount() {
console.log("CaptureView: componentWillUnmount()");
}
async onButtonPress() {
CameraService.takePicture().then((photo)=>{
this.setState({
photo: photo
});
// More actions
this.props.navigation.navigate(
"detailView",
{
id: 'DetailView',
photo
}
);
});
}
render() {
return (
<Camera
cameraSetter={(cam) => {
CameraService.setCamera(cam)
}}
photo={this.state.photo}
/>
<TouchableOpacity onPress={this.onButtonPress.bind(this)}>
<Text>TAKE PHOTO</Text>
</TouchableOpacity>
);
}
}
Then in other part of the application there is a button to navigate back to CaptureView.
NavBottom.js:
export default class NavBottom extends Component {
constructor(props) {
super(props);
}
render() {
return (
<View>
<TouchableOpacity onPress={() => this.props.navigation.navigate('captureView')}>
<Text>CAMERA</Text>
</TouchableOpacity>
</View >);
}
}
Notes
I've tried different ways from ReactJS (not React Native) documentation that failed:
https://reactjs.org/docs/react-component.html#componentdidmount - componentDidMount() seems to bee the recommended way in documentation, but in my application it gets called ONLY ONCE?
Even when componentDidMount() is called once at the beginning, but even then this.setState() is undefined. It's strange, the documentation says it should be available
have you tried using navigation events to reset the state of your CaptureView component?
I think onWillFocus might do the trick.
https://reactnavigation.org/docs/en/navigation-events.html
React Navigation doesn't re-render a component in a stack that resides in Tab Naviagtor
in order perform forceful re-render than you can use event listener like this
componentDidMount() {
this._navListener = this.props.navigation.addListener('didFocus', () => {
//perform any action you want, place your logic here
});
}
you can also use React Navigation's HOC withNavigation this pass a ( isFocused ) prop to the connected component
componentDidUpdate(prevProps, prevState) {
if(prevProps.isFocused !== this.props.isFocused){
//place your desired logic here
}
}
Usage of withNavigation
import { withNavigationFocus } from 'react-navigation';
...
...
...
export default withNavigationFocus(YourComponent)
I am trying to make a dynamic component for the popup to create view and edit page for different content. I have made a popup component in which I want to pass a new component name and title of the page. However, I am not getting new component data in the popup component. Please have a look at code, if you need any more detail, please ask. Thanks in Advance.
I have tried to inject service in another component and it gets data on button click, but in the popup component, I am not getting data. For now, I am doing just console.log data to popup.component.ts file but there is no result in console.log.
popup.service.ts
export class PopupService {
isShowing = false;
private popup = new Subject();
loadingPopup = new Subject();
outputEmitter = new Subject();
popupContent: any = {
isShowing: false,
content: null,
contentParams: PopupModel
}
constructor() { }
public getPopup() {
return this.popup;
}
public showLoading(isLoading: boolean = true) {
this.loadingPopup.next(isLoading);
}
public create(component: any, parameters?: PopupModel): any {
this.showLoading(true);
this.popupContent.isShowing = true;
this.popupContent.content = component;
this.popupContent.contentParams = parameters;
this.popup.next(this.popupContent);
console.log(this.popupContent)
}
}
Popupcomponent.ts
export class PopupComponent implements OnInit, OnDestroy {
public popupObservable: Subscription;
constructor(private popupService: PopupService) { }
ngOnInit() {
this.popupObservable = this.popupService.getPopup().subscribe((popupContent: any) => {
console.log(popupContent)
//code here to use createDynamicComponent method }
}
private createDynamicComponent(component: Type<any>): void {
//code here using ComponentFactoryResolver and ViewContainerRef to create dynamic component
}
ngOnDestroy() {
if (this.popupObservable && !this.popupObservable.closed) {
this.popupObservable.unsubscribe();
}
}
}
This is the code where the dynamic component is being called and the popup should be created.
Component.ts
AddRecord(){
this.popupService.create( NewRecordComponent, {
title: 'Add',
emitterMethod: 'saved'
})
}
component.html
<button (click)="AddRecord()">Add</button>
You are not emitting a value from your subject anywhere.
You would need to call popup.next(popupContent).
However, I don't think you have the right model here.
If you're not doing any async calls (api, filesystem etc..) in your getPopup method, then just return the popupContent directly
public getPopup() : {} {
return this.popupContent;
}
You should also define an interface for your popupContent somewhere so you can import it and use a strong interface to avoid runtime errors.
e.g.
export interface IPopupContent {
isShowing: boolean;
content: string;
contentParams: PopupModel;
}
Also, don't expose subjects directly, rather expose an observable linked to the subject.
See When to use asObservable() in rxjs?
So I started converting my application from ES2015 to ES6 which uses React.
I have a parent class and a child class like so,
export default class Parent extends Component {
constructor(props) {
super(props);
this.state = {
code: ''
};
}
setCodeChange(newCode) {
this.setState({code: newCode});
}
login() {
if (this.state.code == "") {
// Some functionality
}
}
render() {
return (
<div>
<Child onCodeChange={this.setCodeChange} onLogin={this.login} />
</div>
);
}
}
Child class,
export default class Child extends Component {
constructor(props) {
super(props);
}
handleCodeChange(e) {
this.props.onCodeChange(e.target.value);
}
login() {
this.props.onLogin();
}
render() {
return (
<div>
<input name="code" onChange={this.handleCodeChange.bind(this)}/>
</div>
<button id="login" onClick={this.login.bind(this)}>
);
}
}
Child.propTypes = {
onCodeChange: React.PropTypes.func,
onLogin: React.PropTypes.func
};
However this causes the following error,
this.state is undefined
It refers to,
if (this.state.code == "") {
// Some functionality
}
Any idea what could be causing this ?
You can use arrow function to bind you functions. You need to bind you functions both in child as well as parent components.
Parent:
export default class Parent extends Component {
constructor(props) {
super(props);
this.state = {
code: ''
};
}
setCodeChange = (newCode) => {
this.setState({code: newCode});
}
login = () => {
if (this.state.code == "") {
// Some functionality
}
}
render() {
return (
<div>
<Child onCodeChange={this.setCodeChange} onLogin={this.login} />
</div>
);
}
}
Child
export default class Child extends Component {
constructor(props) {
super(props);
}
handleCodeChange = (e) => {
this.props.onCodeChange(e.target.value);
}
login = () => {
this.props.onLogin();
}
render() {
return (
<div>
<input name="code" onChange={this.handleCodeChange}/>
</div>
<button id="login" onClick={this.login}>
);
}
}
Child.propTypes = {
onCodeChange: React.PropTypes.func,
onLogin: React.PropTypes.func
};
There are other ways to bind the functions as well such as the one you are using but you need to do that for parent component too as <Child onCodeChange={this.setCodeChange.bind(this)} onLogin={this.login.bind(this)} />
or you can specify binding in the constructor as
Parent:
constructor(props) {
super(props);
this.state = {
code: ''
};
this.setCodeChange = this.setCodeChange.bind(this);
this.login = this.login.bind(this);
}
Child
constructor(props) {
super(props);
this.handleCodeChange = this.handleCodeChange.bind(this);
this.login = this.login.bind(this);
}
I agree with all different solutions given by #Shubham Kathri except direct binding in render.
You are not recommended to bind your functions directly in render. You are recommended to bind it in constructor always because if you do binding directly in render then whenever your component renders Webpack will create a new function/object in bundled file thus the Webpack bundle file size grows. For many reasons your component re-renders eg: doing setState but if you place it in constructor it gets called called only once.
The below implementation is not recommended
<Child onCodeChange={this.setCodeChange.bind(this)} onLogin={this.login.bind(this)} />
Do it in constructor always and use the ref wherever required
constructor(props){
super(props);
this.login = this.login.bind(this);
this.setCodeChange = this.setCodeChange.bind(this);
}
<Child onCodeChange={this.setCodeChange} onLogin={this.login} />
If you are using ES6 then manual binding is not required but if you want you can. You can use arrow functions if you want to stay away with scope related issues and manual function/object bindings.
Sorry if there are any typos I am answering in my mobile
I have implemented this but the store has no values (all undefined):
This is the store:
export default class AppState {
// Is authenticated
#observable authenticated;
#action get authenticated() {
return this.authenticated;
}
doSomethingWithNoDecorator() {
return this.authenticated;
}
}
This is index.js:
const stores = {
AppState
};
const renderApp = Component => {
render(
<AppContainer>
<Provider { ...stores }>
<Router>
// Routes
</Router>
</Provider>
</AppContainer>,
document.getElementById("root")
);
};
This is the Component:
#inject("AppState")
#observer
export default class SidebarListItem extends Component {
constructor(props) {
super(props)
this.store = this.props.AppState;
}
doSomething() {
this.store.authenticated();
this.store.doSomethingWithNoDecorator();
this.store.authenticated;
}
}
The store is not null... I can see the function. But I can't get any field or invoke any method.
What did I do wrong?
Regards,
Idob
You need to initialise your store:
const stores = { AppState: new AppState() }
By the way, #actions cannot be applied to getters.
How can I get Component from React Relay Container to get static variable ?
import Home from './pages/home';
console.log(Home.route) // undefined
I need something like Home.Component.route // object
class Home extends React.Component {
static route = {
component: Home,
route: HomeRoute,
leftButton: false,
sideMenu: false,
};
render() {
return (
<View style={styles.container}>
<Text>
Home
</Text>
<Text>
{this.props.greetings.hello}
</Text>
</View>
);
}
}
export default Relay.createContainer(Home, {
fragments: {
greetings: () => Relay.QL`
fragment on Greetings {
hello,
}
`,
}
});
I don't think there's a way to get at this statically without doing one of the following.
Alternative 1: Add an explicit export of the wrapped component
// In "Home.js":
export class HomeComponent extends React.Component {
static route = {
// ...
}
}
export default Relay.createContainer(HomeComponent, {
// ...
});
// In "Other.js":
import Home, {HomeComponent} from './Home.js';
console.log("should have access here", HomeComponent.route);
Alternative 2: Add properties to the container before exporting
const HomeContainer = Relay.createContainer(
// ...
);
HomeContainer.route = {
// ...
};
export default HomeContainer;
Alternative 3: Access via refs at runtime
Once the container is mounted and you have a reference to it you can get at the original component via the "component" ref. Note that this is not documented and therefore not guaranteed to work in the future.
// In "Other.js":
render() {
return (
<Home
ref={
container => (
console.log(container.refs.component.route)
)
}
/>
);
}
Obviously, this is the hackiest of the three alternatives, so I probably wouldn't recommend it.