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 };
};
Related
I am getting the following error when trying to run my react-native app with iOS device and I am unsure why, any ideas? Just a heads up the app works fine on android simulator.
ERROR TypeError: null is not an object (evaluating '_$$_REQUIRE(_dependencyMap[0], "react-native").NativeModules.RNGetRandomValues.getRandomBase64')
I am building a Tik Tok clone and I am trying to publish a video I record on my device to my AWS database.
So far I have tried to:
adding import 'react-native-get-random-values';,
running the app in release mode,
My code doesn't work when I try to publish the video I record on my phone to the database. I get the error posted above and a warning a created letting me know the video hasn't been published. Again I am only having this error on the iOS side of the application.
Here is my code for the screen:
import React, {
useState,
useRef,
useEffect
} from 'react';
import {
View,
Text,
TouchableOpacity,
TextInput,
Button
} from 'react-native';
import styles from '/Users/Documents/TikTok/src/screens/CreatePost/styles.js';
import {
Storage,
API,
graphqlOperation,
Auth
} from 'aws-amplify';
import {
useRoute,
useNavigation
} from '#react-navigation/native';
import {
createPost
} from '/Users/Documents/TikTok/src/graphql/mutations.js';
import {
v4 as uuidv4
} from 'uuid';
const CreatePost = () => {
const [description, setDescription] = useState();
const [videoKey, setVideoKey] = useState();
const route = useRoute();
const navigation = useNavigation();
const uploadToStorage = async(imagePath) => {
try {
const response = await fetch(imagePath);
const blob = await response.blob();
const filename = `${uuidv4()}.mp4`;
const s3Response = await Storage.put(filename, blob);
setVideoKey(s3Response.key);
} catch (e) {
console.error(e);
}
};
useEffect(() => {
uploadToStorage(route.params.videoUri);
}, []);
const onPublish = async() => {
if (!videoKey) {
console.warn("Video is not yet uploaded");
return;
}
try {
const userInfo = await Auth.currentAuthenticatedUser();
const newPost = {
videoUri: videoKey,
description: description,
userID: userInfo.attributes.sub,
songID: '6957a5ce-5f8b-40b0-9f6b-aa68eba19c2b',
};
const response = await API.graphql(
graphqlOperation(createPost, {
input: newPost
}),
);
navigation.navigate("Home", {
screen: "Home"
});
console.warn('Video Uploaded');
} catch (e) {
console.log(e);
}
};
return ( <
View style = {
styles.container
} >
<
TextInput value = {
description
}
onChangeText = {
setDescription
}
numberOfLines = {
5
}
placeholder = {
"Post Description"
}
style = {
styles.textInput
}
/> <
TouchableOpacity onPress = {
onPublish
} >
<
View style = {
styles.button
} >
<
Text style = {
styles.buttonText
} > Publish < /Text> <
/View> <
/TouchableOpacity> <
/View>
);
};
export default CreatePost;
It's my understanding that when filterChangedCallback is called that the grid filters the current data and calls doesFilterPass and isFilterActive as it does so.
Here I have column that uses a custom filter, and a floating filter. The floating filter (a checkbox) gets displayed ok, and when I click on it onFloatingFilterChanged in the custom filter is called, the green filter active icon appears, and the grid refreshes, but doesFilterPass does not get called at all and I can't figure out why.
There's something fundamental I'm not getting with custom filters so if anyone can shed any light on this I'd be grateful.
(Edit: it may be the interplay between the custom/floating filters that's at issue. There was a similar ag-grid-angular issue posted to github last year but nothing came of that. So perhaps it's a bug?)
Custom filter
export default class BooleanFilter {
init(params) {
this.params = params;
this.valueGetter = params.valueGetter;
this.filterChangedCallback = params.filterChangedCallback;
this.status = false;
}
onFloatingFilterChanged(status) {
this.status = status;
this.filterChangedCallback();
}
getGui() {
return '<div />';
}
getModel() {
return this.isFilterActive() ? { filterType: 'boolean', filter: this.status } : undefined;
}
setModel(model) {
this.state.status = model ? model.value : '';
}
isFilterActive() {
return this.status === true;
}
doesFilterPass(params) {
console.log(this.status, params);
}
}
Floating filter
export default class FloatingCheckboxFilter extends Component {
state = { status: false };
handleToggle = (e) => {
const { target: { checked } } = e;
const { parentFilterInstance } = this.props;
this.setState({ status: checked });
parentFilterInstance((instance) => {
instance.onFloatingFilterChanged(checked);
});
}
onParentModelChanged = (parentModel) => {
this.setState({
status: !parentModel ? false : parentModel.filter
});
}
render() {
const { status } = this.state;
return (
<input
style={{ marginTop: '0.75em' }}
type="checkbox"
checked={status}
onChange={this.handleToggle}
/>
);
}
}
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'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.
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.