How to trigger state update with react-navigation? - ios

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)

Related

react native google sign in - cannot press next, forgot email, or create account

image of simulator
unable to press the forgot email, create account or next button. When I press them there is no action, it stays on that same signin page. help, privacy and terms work.
wrote the function in the googlesignin.tsx file
import React, { Component } from 'react'
import { Button } from 'react-native'
import {
GoogleSignin,
statusCodes,
} from '#react-native-google-signin/google-signin';
export class GoogleSign extends Component {
constructor(props) {
super(props);
this.state = {
userInfo: null,
};
}
render() {
return(
<Button title={'Sign in with Google'} onPress={async () => {
GoogleSignin.configure({
iosClientId: '552669576534-ninopsfqvitpk59v9kt42mn0r2e4o37h.apps.googleusercontent.com',
webClientId: '552669576534-vpmbo9vbodnaeqghnjai6d0fhcl2enhc.apps.googleusercontent.com',
offlineAccess: true,
});
try {
await GoogleSignin.hasPlayServices();
const userInfo = await GoogleSignin.signIn();
this.setState({userInfo});
console.log(userInfo);
} catch (error) {
if (error.code === statusCodes.SIGN_IN_CANCELLED) {
// user cancelled the login flow
} else if (error.code === statusCodes.IN_PROGRESS) {
// operation (e.g. sign in) is in progress already
} else if (error.code === statusCodes.PLAY_SERVICES_NOT_AVAILABLE) {
// play services not available or outdated
} else {
// some other error happened
}
}
}}/>
);
}
}
I also have added my inverse url to the workspace.
Please refer this doc, you might have missed to configure signIn which is mandatory
import { GoogleSignin } from '#react-native-google-signin/google-signin';
GoogleSignin.configure();

How to use delegate in React Native?

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} />
);
}
}

How to fix data duplication/deletion when clicking back/forward buttons in react router

I'm building an moving web application with the following relationships: a USER has many MOVES --> a MOVE has many BOXES --> a BOX has many ITEMS.
I have the functionality working so that when you click on a specific MOVE, you are routed to a list of all the BOXES associated with that MOVE.
However, if I am on the list of BOXES page and click back to the list of MOVES page, my MOVES duplicate. And similarly, when I click back into a specific MOVE, my BOXES duplicate. And at some points, if I do a total page refresh, my data either 1. Completely disappears (i.e., componentDidMount() is perhaps not working on page refresh?) OR 2. my app breaks and receive this error: "Invalid attempt to spread non-iterable instance".
My goal is just to have the back and forward functionality working properly so that my data is mounted once and just stays the same.
I have tried fixing the issue within my componentDidMount() method inside of both my MoveList and BoxList component. I looked into some posts about browser history and tried the following in order to limit my componentDidMount() to ONE TIME.
componentDidMount() {
if (this.props.history.action === "POP") {
this.props.getMoves(this.props.user.user_id)
}
}
MoveList.js
import React from 'react';
import { connect } from 'react-redux';
import { getMoves } from '../actions/moveActions'
import Move from './Move'
import { withRouter } from 'react-router-dom'
class MoveList extends React.Component {
componentDidMount() {
if (this.props.history.action === "POP") {
this.props.getMoves(this.props.user.user_id)
}
}
render() {
const mappedMoves = this.props.moves.map((move) => {
return <Move move={move} key={move.id} />
})
return (
<div className="row">
{mappedMoves}
</div>
)
}
}
const mapStateToProps = (state) => {
return {
moves: state.moves,
user: state.user
}
}
const mapDispatchToProps = (dispatch) => {
return {
getMoves: () => dispatch(getMoves())
}
}
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(MoveList));
BoxList.js (u click a Move to see this box list)
import React from 'react';
import Box from './Box';
import { withRouter } from 'react-router-dom'
import { connect } from 'react-redux';
import { getBoxes } from '../actions/boxActions'
class BoxList extends React.Component {
componentDidMount() {
if (this.props.history.action === "PUSH") {
this.props.getBoxes(this.props.user.user_id, this.props.move.id)
}
}
render() {
console.log("boxlist props", this.props.history);
const mappedBoxes = this.props.boxes.map((box, idx) => {
return <Box box={box} key={box.id} idx={idx}/>
})
return (
<div className="col s9">
{mappedBoxes}
</div>
)
}
}
const mapStateToProps = state => {
return {
move: state.move,
boxes: state.boxes,
user: state.user
}
}
const mapDispatchToProps = dispatch => {
return {
getBoxes: (userId, moveId) => dispatch(getBoxes(userId, moveId))
}
}
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(BoxList))
The expected result happens on the first load. However, if I click back and forth the data either dupes or disappears.
Here's my router:
class App extends Component {
render() {
return (
<Fragment>
<div className="App">
<NavBar />
<Switch>
<Route exact path="/moves" component={MoveContainer} />
<Route path="/boxes" render={(props) => <BoxContainer {...props} />} />
</Switch>
</div>
</Fragment>
);
}
}
This problem occurs, because every time you go back to the page, the component is reassembled, and the getMoves action is triggered, thus adding the data to the array, even the ones already in it. To resolve this, you can add elements to your array as follows:
function uniqueData(value, index, self) {
     return self.indexOf(value) === index;
}
moves = moves.concat(data).filter(uniqueData);
As we can see above, the array moves is concatenated with the array data, which in this case would refer to the data that will be added in the array moves, and soon after being concatenated, it is filtered, using the uniqueData function, thus removing all duplicate data from the array moves.

How to trigger render() method when navigating back with `react-navigation`?

I have a simple app with two screens inside a StackNavigator:
export const AppNavigator = StackNavigator({
Main: { screen: MainPage },
ChooseColor: { screen: ChooseColorPage }
}, {
initialRouteName: 'Main',
});
Upon pressing a button, the app navigates to ChooseColorPage:
class MainPage extends Component {
onChooseColor() {
const { navigation } = this.props;
navigation.navigate('ChooseColor', {});
}
}
Then, the user can choose a color by pressing a button, which triggers a navigation back to the MainPage:
class ChooseColorPage extends Component {
onSelectColor(colorKey) {
// this updates the state inside a `ColorReducer`
this.props.colorChanged({ colorKey });
const { navigation } = this.props;
navigation.goBack();
}
}
I want the MainPage to update based on the chosen color, however the render() method is not called on back navigation.
Question: Under which cases is the render() method called when navigating between screens with react-navigation?
I would have assumed that updating the state in the ColorReducer would have been enough to trigger a render() call in MainPage but this does not happen.
Turns out this is not a navigation problem, but a state management problem.
I was incorrectly setting up mapStateToProps on my MainPage. After adding this, render() is called as a result of a navigation update.
My app reducer:
const AppReducer = combineReducers({
color: ColorReducer,
nav: NavReducer,
});
My ColorReducer:
const initialState = {
colorKey: null,
};
const ColorReducer = (state = initialState, action) => {
switch (action.type) {
case 'COLOR_CHANGED':
return { ...state, colorKey: action.payload.colorKey };
default:
return state;
}
};
My mapStateToProps:
const mapStateToProps = state => {
return { colorKey: state.color.colorKey };
};

Get component from react relay Container

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.

Resources