How to access naviagtion options from imported file in react-native - reactjs

I'm passing data through different pages down to the last page in my app, its been working fine.
But the issue is the last page has 2 components so the typical </ChatActivity navigation="{this.props.navigation}" />, here's what I mean:
I have an App.js
content of App.js
import ChatScreen from './chat'
class ChatActivity extends Component {
static navigationOptions = {
...
}
render() {
return(
<ChatScreen navigation={this.props.navigation} />
)
}
}
I also have chat.js that contains the chat component. Chat.js itself, needs to import Fire from './fire.js'
so now, this.props.navigation was only passed to Chat.js...but I need to access it from fire.js as well.
I've read about import {useNavigation}, but from what i have tried it didn't work cause my fire.js doesn't even look like the example in the docs
this is my fire.js
class Fire extends React.Component{
constructor (props) {
super(props)
this.init()
this.checkAuth()
}
init = () => {
firebase.initializeApp({
})
};
checkAuth = () => {
firebase.auth().onAuthStateChanged(user => {
if (!user) {
firebase.auth().signInAnonymously();
}
})
}
send = messages => {
messages.forEach(item => {
const message = {
text: item.text,
timestamp: firebase.database.ServerValue.TIMESTAMP,
// image: item.image,
//video: item.video,
user: item.user
}
this.db.child(`NEED NAVIGATION PARAMS HERE`).push(message)
})
}
parse = message => {
const {user, text, timestamp} = message.val();
const {key, _id} = message
const createdAt = new Date(timestamp)
return {
_id,
createdAt,
text,
user
}
}
get = callback => {
this.db.child(`NEED NAVIGATION PARAMS HERE`).on('child_added', snapshot => callback(this.parse(snapshot)))
}
off() {
this.db.off()
}
get db() {
return firebase.database().ref(`NEED NAVIGATION PARAMS HERE`);
}
get uid(){
return(firebase.auth().currentUser || {}).uid
}
}
export default new Fire();
Since i couldn't access navigation params, I tried AsyncStorage, but thats probably not the best practice and it isn't working too well. Not sure if its the AsyncStorage or react-native-gifted-chat but when I load the chat page once, it shows the same messages for other chats till I restart the app which shouldn't be cause i'm fetching the data based on unique parameters.

You have just missed one step here...
Since you have passed the navigation as props by using the following approach:
<ChatScreen navigation={this.props.navigation} />
the chat screen gets to use navigation properties of ChatActivity.
For Fire.js to be able to use the navigation as well, that was provided to Chat.js by ChatActivity you will need to pass the navigation props received by Chat.js to Fire.js in the same way.
This is how your Chat.js should look like:
import Fire from './Fire'
class Chat extends Component {
static navigationOptions = {
...
}
render() {
return(
<Fire navigation={this.props.navigation} />
)
}
}
That should solve the issue. Cheers!

Related

React - what are the steps to get data from api and render it?

I am building a site just like stackoverflow.com. I want my home page to display top questions. For that, I have sample questions on the backed. Now, I want to display only the question and tags from the questions array.
The code is in the image
I have made axios connection for that:
const instance = axios.create({
baseURL: "https://2w2knta9ag.execute-api.ap-south-1.amazonaws.com/dev", });
instance.defaults.headers.post["Content-Type"] = "application/json";
To connect it, I wrote the command: instance.get("/questions)
Now, how do I display only the question and tags??
EDIT:
On using the code given bellow, my js file now becomes:
import React from 'react';
import instance from '../../api';
class QuestionList extends React {
componentDidMount() {
instance
.get("/questions")
.then((res) => {
this.setState({ data: res.data });
});
}
render () {
const { data } = this.state;
return <div>
{
data && data.map(d => {
return <div>question: {d.question}, tags: {d.tags}</div>;
})
}
</div>
}
}
export default QuestionList;
But, this is just making my site in a loading state, and it gets hanged!!
If I understood correctly, you want to get an array only with the tags and the question. if so, you can use Array.prototype.map for this
const questions = result.map(({ question, tags }) => ({ question, tags }))
First you export the axios instance so that it can be used from other components.
Now you can send the api request in componentDidMount and update your component's state with the data.
And in render function, you just get the value from state and display.
If you are new to react, learn React Hooks and know that componentDidMount method is the best place to send api requests.
For Example:
import React from 'react';
import instance from '../../api';
class QuestionList extends React.Component {
constructor() {
super();
this.state = {
data: [],
};
}
componentDidMount() {
instance.get('/questions').then((res) => {
this.setState({ data: res.data });
});
}
render() {
const { data } = this.state;
return (
<div>
{data &&
data.map((d) => {
return (
<div>
question: {d.question}, tags: {d.tags}
</div>
);
})}
</div>
);
}
}
export default QuestionList;

How to architect handling onSuccess of a redux dispatched request that becomes a React Navigation change of screen

I have a Registration screen.
The result of a successful registration will update the account store with the state:
{error: null, token: "acme-auth" ...}
On the Registration screen I render an error if there is one from the store.
What I want to do is navigate to the Dashboard with this.props.navigation.navigate when the store state changes.
I can do this hackily:
render() {
const {account} = this.props
const {token} = account
if (token) {
this.props.navigation.navigate('Dashboard')
}
}
I can also use callbacks:
sendRegistration = () => {
const {email, password} = this.getFormFields()
this.props.registerStart({email, password, success: this.onRegisterSuccess, failure: this.onRegisterFailure}) //using mapDispatchToProps
}
Passing the callback through the redux path seems redundant since I already have the changed state thanks to linking the account store to my Registration component props.
I am toying with the idea of a top-level renderer that detects a change in a userScreen store then swaps out the appropriate component to render.
Is there a simpler, or better way?
Yes there is a better way. If you want to navigate in an async fashion the best place to do it is directly in the thunk, sagas, etc after the async action is successful. You can do this by creating a navigation Service that uses the ref from your top level navigator to navigate.
In app.js:
import { createStackNavigator, createAppContainer } from 'react-navigation';
import NavigationService from './NavigationService';
const TopLevelNavigator = createStackNavigator({
/* ... */
});
const AppContainer = createAppContainer(TopLevelNavigator);
export default class App extends React.Component {
// ...
render() {
return (
<AppContainer
ref={navigatorRef => {
NavigationService.setTopLevelNavigator(navigatorRef);
}}
/>
);
}
}
This sets the ref of the navigator. Then in your NavigationService file:
// NavigationService.js
import { NavigationActions } from 'react-navigation';
let _navigator;
function setTopLevelNavigator(navigatorRef) {
_navigator = navigatorRef;
}
function navigate(routeName, params) {
_navigator.dispatch(
NavigationActions.navigate({
routeName,
params,
})
);
}
// add other navigation functions that you need and export them
export default {
navigate,
setTopLevelNavigator,
};
Now you have access to the navigator and can navigate from redux directly. You can use it like this:
// any js module
import NavigationService from 'path-to-NavigationService.js';
// ...
NavigationService.navigate(''Dashboard' });
Here is the documentation explaining more:
https://reactnavigation.org/docs/en/navigating-without-navigation-prop.html

React Native - data is lost when switching between screens using react-navigation and react-redux

I am using react-redux and react-navigation with my react native app.
I have a component called photolist that gets all the photos from a database. There are two screens that call this component. The userProfile screen passes true and userId to photolist for the user's photos; the feed screen passes false and null to photolist for the all the photos.
In App.js, I put the Feed screen and User screen in the same stack so I can navigate easily.
With this approach, I am able to load the main page on App load, see all the photos, then go to a user's page and see the user's photos. But from the user's page, when I click the back button to go back to the main page, no photos are loaded anymore. Note that in this sequence of actions, the photolist component's componentDidMount() function is called exactly twice; when going back to the main feed from userProfile, it is not called again.
Any idea on why is this happening and how may I resolve this? Is there a way to keep the navigation structure where clicking the back button from userProfile will take you back to where you were in the main feed page without needing to reload the main feed again?
photolist.js:
class PhotoList extends React.Component {
constructor(props) {
super(props);
}
componentDidMount = () => {
const { isUser, userId } = this.props;
// load a single user's photos or all photos
if (isUser) {
this.props.loadFeed(userId);
} else {
this.props.loadFeed();
}
}
render(){
return (
<View style={{flex:1}}>
<FlatList
data = {(this.props.isUser) ? this.props.userFeed : this.props.mainFeed}
keyExtractor = {(item, index) => index.toString()}
...
/>
</View>
)
}
}
const mapStateToProps = state => {
return {
mainFeed: state.feed.mainFeed,
userFeed: state.feed.userFeed
}
}
const mapDispatchToProps = {
loadFeed
};
export default connect(mapStateToProps, mapDispatchToProps)(PhotoList);
feed.js:
<PhotoList isUser={false} navigation={this.props.navigation}/>
userProfile.js:
<PhotoList isUser={true} userId={this.state.userId} navigation={this.props.navigation}/>
App.js:
const FeedScreenStack = createStackNavigator({
FeedStack: { screen: feed },
UserProfile: { screen: userProfile }
});
React Navigation doesn't mount and unmount components when navigating within a stack. Instead, the components stay mounted and have custom react-navigation lifecycle events.
Adding a <NavigationEvents> component to your scene is one way to fix your use case:
import { NavigationEvents } from 'react-navigation';
class PhotoList extends React.Component {
componentDidMount() {
this.loadFeed();
}
loadFeed = () => {
const { isUser, userId } = this.props;
// load a single user's photos or all photos
if (isUser) {
this.props.loadFeed(userId);
} else {
this.props.loadFeed();
}
}
render() {
return {
<View>
<NavigationEvents
onDidFocus={this.loadFeed}
/>
</View>
}
}
}

ReactJS update view after receiving new props

After installing ReactJS again after a few months not working with it, I noticed the latest version (16) is now using getDerivedStateFromProps and there is no more will receive props functions and stuff.
Currently I have my environment running with react-redux included. My new data gets into the mapStateToProps function of my container script, but I want to update the view accordingly. Basically a loading screen, and after the data is fetched via an API call, update the view with the API's response data.
However, I don't seem to be able to find a solution to update my view anywhere up till now.
I noticed that the getDerivedStateFromProps only gets triggered once.
Am I missing some functions or anything?
Short example:
import React from 'react';
import { connect } from "react-redux";
import Files from '../components/files';
class ProjectContainer extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
this.props.getFilesByShare('sharename');
}
componentDidUpdate (prevProps) {
console.warn('does not get here?');
}
render() {
const { loading, files } = this.props;
let content = (
<div className="loading">Loading... Requesting file urls</div>
);
if (!loading && files && files.length) {
content = (
<div>
File urls requested!
<Files files={files} />
</div>
);
}
return (
{content}
);
}
}
const mapStateToProps = state => {
console.warn(state, 'this shows the new data');
return {
files: state.files,
loading: state.files_loading,
};
};
export default connect( mapStateToProps, {
getFilesByShare,
}) (ProjectContainer);

How to refresh a List View in admin on rest

I am trying to get a list to refresh after a custom action was successfully executed.
i used the saga from the admin on rest tutorial
function * actionApproveSuccess () {
yield put(showNotification('Executed'))
yield put(push('/comments'))
// does not refresh, because the route does not change
// react-redux-router also has no refresh() method, like react-router has...
}
the other idea i had was to somehow trigger the refresh action of the list component, but i have no idea how to access that or how to hook that up to the ACTION_SUCCESS event.
There is no way to refresh a route via react router, and that's a known problem. Admin-on-rest's List component has its own refresh mechanism, but offers no API for it.
My advice would be to use a custom <List> component based on admin-on-rest's one. And if you find a way to expose the refresh action, feel free to open a PR on the aor repository!
#Danila Smirnov's answer above shows this message when I use it now:
Deprecation warning: The preferred way to refresh the List view is to connect your custom button with redux and dispatch the refreshView action.
Clicking the refresh button itself wasn't working either nowadays.
Here's the tweaked version that I got working in mine.
Edit: Modified it a bit more to make it reusable.
RefreshListActions.js
import React, { Component } from 'react'
import FlatButton from 'material-ui/FlatButton'
import { CardActions } from 'material-ui/Card'
import NavigationRefresh from 'material-ui/svg-icons/navigation/refresh'
import { connect } from 'react-redux'
import { REFRESH_VIEW } from 'admin-on-rest/src/actions/uiActions'
import { refreshView as refreshViewAction } from 'admin-on-rest/src/actions/uiActions'
class MyRefresh extends Component {
componentDidMount() {
const { refreshInterval, refreshView } = this.props
if (refreshInterval) {
this.interval = setInterval(() => {
refreshView()
}, refreshInterval)
}
}
componentWillUnmount() {
clearInterval(this.interval)
}
render() {
const { label, refreshView, icon } = this.props;
return (
<FlatButton
primary
label={label}
onClick={refreshView}
icon={icon}
/>
);
}
}
const RefreshButton = connect(null, { refreshView: refreshViewAction })(MyRefresh)
const RefreshListActions = ({ resource, filters, displayedFilters, filterValues, basePath, showFilter, refreshInterval }) => (
<CardActions>
{filters && React.cloneElement(filters, { resource, showFilter, displayedFilters, filterValues, context: 'button' }) }
<RefreshButton primary label="Refresh" refreshInterval={refreshInterval} icon={<NavigationRefresh />} />
</CardActions>
);
export default RefreshListActions
In my list that I want to refresh so often:
import RefreshListActions from './RefreshListActions'
export default (props) => (
<List {...props}
actions={<RefreshListActions refreshInterval="10000" />}
>
<Datagrid>
...
</Datagrid>
</List>
)
Definitely hacky, but a work-around could be:
push('/comments/1') //any path to change the current route
push('/comments') //the path to refresh, which is now a new route
using refreshView action via redux works well.
see example....
import { refreshView as refreshViewAction } from 'admin-on-rest';
import { connect } from 'react-redux';
class MyReactComponent extends Component {
//... etc etc standard react stuff...
doSomething() {
// etc etc do smt then trigger refreshView like below
this.props.refreshView();
}
render() {
return <div>etc etc your stuff</div>
}
}
export default connect(undefined, { refreshView: refreshViewAction })(
MyReactComponent
);
I've solve this task with small hack via Actions panel. I'm sure it is not correct solution, but in some situations it can help:
class RefreshButton extends FlatButton {
componentDidMount() {
if (this.props.refreshInterval) {
this.interval = setInterval(() => {
this.props.refresh(new Event('refresh'))
}, this.props.refreshInterval)
}
}
componentWillUnmount() {
clearInterval(this.interval)
}
}
const StreamActions = ({ resource, filters, displayedFilters, filterValues, basePath, showFilter, refresh }) => (
<CardActions>
{filters && React.cloneElement(filters, { resource, showFilter, displayedFilters, filterValues, context: 'button' }) }
<RefreshButton primary label="Refresh streams" onClick={refresh} refreshInterval={15000} refresh={refresh} icon={<NavigationRefresh />} />
</CardActions>
);
export default class StreamsListPage extends Component {
render() {
return (
<List
{...this.props}
perPage={20}
actions={<StreamActions />}
filter={{ active: true }}
title='Active Streams'>
<StreamsList />
</List>
)
}
}
The push is just a redirect for AOR which did not seem to work for me either. What guleryuz posted was on the right track for me.. Here's what I did building on his example:
// Import Statement
import { refreshView as refreshViewAction } from 'admin-on-rest';
class RemoveButton extends Component {
handleClick = () => {
const { refreshView, record, showNotification } = this.props;
fetch(`http://localhost:33333/api/v1/batch/stage/${record.id}`, { method: 'DELETE' })
.then(() => {
showNotification('Removed domain from current stage');
refreshView();
})
.catch((e) => {
console.error(e);
showNotification('Error: could not find domain');
});
}
render() {
return <FlatButton secondary label="Delete" icon={<DeleteIcon />}onClick={this.handleClick} />;
}
}
These bits are important as well:
RemoveButton.propTypes = {
record: PropTypes.object,
showNotification: PropTypes.func,
refreshView: PropTypes.func,
};
export default connect(null, {
showNotification: showNotificationAction,
refreshView: refreshViewAction,
})(RemoveButton);
So the way this works is it uses AOR's refreshViewAction as a prop function. This uses the underlying call to populate the data grid for me which is GET_LIST. This may not apply to your specific use case. Let me know if you have any questions.
Pim Schaaf's solution worked like a charm for me, Mine looks a bit different
yield put(push('/comments/-1')); // This refreshes the data
yield put(showNotification('')); // Hide error

Resources