React native reload child component - react-native

I have 2 screen A and Screen B. Screen A has a sub component SomeList which loads data and i just pass the action to the subcomponent and it does the rest.Currently by design of react-navigation no action on revisit. I googled and came across https://reactnavigation.org/docs/en/navigation-events.html but not sure how to use it to reload SomeList when i navigate from ScreenB to ScreenA.
ScreenA
class ScreenA extends Component {
render() {
return (
<SomeList
loadData={this.props.actions.getAlllist}
/>
)
}
ScreenB
Class ScreenB extends Component {
someAction = () => {
this.props.navigation.navigate("ScreenA")
}
}

Due to react-native's design, I think you'd be better off passing data to your child Component.
Here's how you can do this:
class ScreenA extends Component {
constructor(props) {
super(props)
this.state = {
data: [],
}
this.props.navigation.addListener('didFocus', this.load)
this.props.navigation.addListener('willBlur', this.unmount)
}
load = ()=>{
const _data = this.props.actions.getAlllist;
this.setState({data:_data})
}
unmount = ()=>{
this.setState({data:[]})
}
render() {
return (
<SomeList
dataArray ={this.state.data}
/>
)
}
};

If you want to fetch data each time you render something, you can use componentDidMount lifecycle method
class ScreenA extends Component {
state = {
data: [];
}
componentDidMount() {
this.setState({ data: this.props.actions.getAlllist });
}
render() {
return <SomeList loadData={this.state.data}/>;
}
So every time the ScreenA screen it's rendered it will fetch the data.

Related

How to Re-render a component that was already rendered after activating a props in other component

I'm setting up an application in React-native where I have a:
Component A : a search component with 2 fields
Component B : a button on this page where I click on it, the 3rd field appears
This components are only linked with react-navigation
In my case, the component B is a component where I can buy premium, and I want to update the component A when premium bought.
The problem : when I already rendered the Component A, and the I go to Component B, click the button, the Component A does not re-render, how can I do it ?
I'm looking for something like this :
class ComponentA extends PureComponent {
render() {
if (userHasNotClickedOnComponentB) {
return (
<SearchForm/>
)
} else {
return (
<SearchFormPremium/>
)
}
}
}
SearchForm and SearchFormPremium are two separated Component:
One with the Premium functionalities, the other one for normal users only
I already rendered ComponentA, and then I go to ComponentB and click the button
class ComponentB extends PureComponent {
render() {
return (
<Button onClick={() => setPremium()}/>
)
}
}
How can the ComponentA re-render so i can have the changes of ComponentB ?
Thanks
You may want to look into using Redux, or something of the like to keep a centralized store that all of your components can look at. There are plenty of Redux tutorials out there so I wont go into details, but essentially it will allow you to:
1) Create a data store accessible from any 'connected' component
2) Dispatch actions from any component to update the store
When you connect a component, the connected data becomes props. So, for example, if you connected component A and B to the same slice of your store, when component A updates it, component B will automatically re-render because its props have changed.
Redux github page
Okay, with Redux it worked !
Just connect both component. In ComponentA (the component that has to be automatically updated) use the function componentWillReceiveProps() and refresh it inside of it.
In Reducer :
const initialState = {premium: false};
const tooglePremiumReducer = (state = initialState, action) => {
switch (action.type) {
case "TOOGLE_PREMIUM":
return {
...state,
premium: action.payload.premium,
};
default:
return state;
}
};
export default tooglePremiumReducer;
In Action :
export const tooglePremiumAction = (premium) => {
return dispatch => {
dispatch({
type: "TOOGLE_PREMIUM",
payload: {
premium: premium
}
});
};
};
In ComponentB :
// Import tooglePremiumAction
class ComponentB extends PureComponent {
render() {
return (
<Button onClick={() => this.props.tooglePremiumAction(true)}/>
)
}
}
const actions = {
tooglePremiumAction
};
export default connect(
actions
)(ComponentB);
In ComponentA:
class ComponentA extends PureComponent {
componentWillReceiveProps(nextProps) {
if(this.props.premium !== nextProps.premium) {
//here refresh your component
}
}
render() {
if (!this.props.premium) {
return (
<SearchForm/>
)
} else {
return (
<SearchFormPremium/>
)
}
}
}
const mapStateToProps = state => {
const premium = state.premium.premium
return { premium };
};
export default connect(mapStateToProps)(ComponentA);

Can a component method which modifies its navigation prop be unit tested?

I need to figure out how to test a component method that doesn't return a value and doesn't change any of the states of its component, all it does is push another screen.
I'm using jest and enzyme to access a class methods and states.
This is the method I want to test (if possible):
signUp() {
this.props.navigation.push('Signup');
}
Yep, just pass in a mock for the navigation prop:
import * as React from 'react';
import { shallow } from 'enzyme';
class SimpleComponent extends React.Component {
signUp() {
this.props.navigation.push('Signup');
}
render() { return null; }
}
test('signUp', () => {
const navigationMock = { push: jest.fn() };
const wrapper = shallow(<SimpleComponent navigation={navigationMock}/>);
wrapper.instance().signUp();
expect(navigationMock.push).toHaveBeenCalledWith('Signup'); // Success!
});

How can I call setState from a different class that is in the same file but different class?

I call a function that is in my Homepage class from my ProfileScreen class that is in the same .js file. I successfully did that, but in that function a setState is called, and when the function is called from the other class, the state doesn't change. How can I get this.state.user in HomePage to change from calling the onPressLogout function in the ProfileScreen class?
export default class HomePage extends Component<Props> {
state = {
email:'',
password:'',
firstname:'',
lastname:'',
user:true,
error: '',
}
onPressLogout(){
firebase = require('firebase');
firebase.auth().signOut()
.then(() => this.setState({
user:false
}))
.catch(() => this.setState({
error: 'Logout Failure',
}))
}
render(){
return <AppContainer>
</AppContainer>;
}
}
class ProfileScreen extends React.Component {
constructor(props) {
super(props);
Obj = new HomePage();
}
render() {
return (
...
<TouchableOpacity style={styles.button} onPress =
{()=>Obj.onPressLogout()}>
</TouchableOpacity>
...
}
}
const TabNavigator = createBottomTabNavigator({
Profile: ProfileScreen,
});
const AppContainer = createAppContainer(TabNavigator);
I get this warning when I run the code and the this.state.user doesn't change:
Warning: Can't call "setState" on a component that is not yet mentioned.
You should pass the function of the parent element into the child element as a prop. Then, you can call it in the child to manipulate the state of the parent class.
Here is an example,
class ChangeButton extends React.Component{
render(){
return (
<Button title="Change" onPress={this.props.updateMainState}/>
)
}
}
export default class App extends React.Component {
state = {
name: 'Fatih'
}
changeName = ()=> {
this.setState({
name: 'Faruk'
})
}
render() {
return (
<View style={styles.container}>
<Text>
{this.state.name}
</Text>
<ChangeButton updateMainState={this.changeName}/>
</View>
);
}
}
In the code above, we passed changeName function into the ChangeButton element. The Button in ChangeButton calls the function of the parent element when you press it, which manipulates the state of the main class.
Here is the working code: ProjectLink

Assign to `this.state` directly or define a `state = {};` class property with the desired state in the Dashboard component

I'm passing a string value from one component class to another and try to update the state in another class
class Dashboard extends Component {
constructor(props) {
super(props);
this.state = {
systemDetailsData: null,
}
}
CalledFromHeader = (systemDetailsData11) => {
this.setState({ systemDetailsData:systemDetailsData11 })
}
}
class Header extends Component {
constructor(props) {
super(props);
Dashboard_Obj = new Dashboard();
}
OnPress = () => {
Dashboard_Obj.CalledFromHeader("system data");
}
}
I'm getting this error ---> Warning: Can't call setState on a component that is not yet mounted. This is a no-op, but it might indicate a bug in your application. Instead, assign to this.state directly or define a state = {}; class property with the desired state in the Dashboard component.
I want to update the state in Dashboard class using above code, Can anyone help me how to achieve this?
Call the Header component in Dashboard render method and pass a function as a prop to Header component.
class Dashboard extends Component {
constructor(props) {
super(props);
this.state = {
systemDetailsData: null,
}
}
CalledFromHeader = (systemDetailsData11) => {
this.setState({ systemDetailsData:systemDetailsData11 })
}
redner(){
return <Header changeState={this.CalledFromheader} />
}
}
class Header extends Component {
constructor(props) {
super(props);
}
render(){
return(
// something view onPress handler
<Button onPress={()=>{
this.props.CalledFromHeader('Some parameters')
}} />
)
}
}

React Navigation: Share state between two screens within TabNavigator

I'm learning react native by building a simple chat app. I have two screens wrapped in a TabNavigator where the first screen (Screen A) being the chatbox, and the other screen (Screen B) which displays a list of online users. I'm using SocketIO to fetch these users.
Problem is, how can I access the "onlineUsers" state from ScreenA to ScreenB so I can see an updated list of online users whenever I receive a "user joins" event?
Screen A:
export default class ScreenA extends Component {
constructor(props) {
super(props);
this.state = {
onlineUsers = [];
}
}
componentDidMount() {
// Update list of online users when new user joins chat
this.socket.on('user joins', (payload) => {
this.setState({
onlineUsers: payload.users
})
})
}
}
Screen B:
export default class ScreenB extends Component {
constructor(props) {
super(props);
// I want to get the onlineUsers from ScreenA
this.state = {
onlineUsers = [];
}
}
}
Router:
export const Chat = TabNavigator({
ChatBox: {
screen: ScreenA
},
OnlineUsers: {
screen: ScreenB
},
})
PS: I'm using react-navigation to handle navigation
Best way is to handle events in the parent component and then passing it to their children components. So in your case, you should have a online user list in your router. Then pass the array to screen B. Here is how you should do
Router
state = {
online_users:[]
}
_update = (data) => {
this.setState({online_users:data});
};
export const Chat = TabNavigator({
ChatBox: {
screen: <ScreenA onUpdate={this._update}/>
},
OnlineUsers: {
screen: <ScreenB userList={this.state.online_users}>
},
})
Screen A
export default class ScreenA extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
// Update list of online users when new user joins chat
this.socket.on('user joins', (payload) => {
this.props.onUpdate(payload.users)
})
}
}
Screen B
export default class ScreenB extends Component {
constructor(props) {
super(props);
}
// You can access online user using this.props.userList
}
I came across this post when I ran into a similar issue earlier this year so I thought I'd post my solution.
I would say your best bet in this situation is to use a Custom Navigator to wrap your TabNavigator which will expose <TabNavigator /> in your custom navigator allowing you to pass any methods or state down to ScreenA and ScreenB as screenProps.
The custom navigator would look like:
import React from 'react'
import { ChatTabNavigator } from './ChatTabNavigator'
class StateManagingCustomNavigator extends React.Component {
static router = ChatTabNavigator.router
state = {
onlineStatus: [theStatus]
}
handleMessagePosted = () => {
// Handle message post, etc...
}
// ... SocketIO code and other logic to manage the state here? ...
render() {
const { navigation } = this.props
const { onlineStatus } = this.state
return (
<ChatTabNavigator
screenProps={{
theStatus: onlineStatus,
chatMessagePosted: this.handleMessagePosted
}}
navigation={navigation}
/>
)
}
}
export default StateManagingCustomNavigator
From here you could implement an event system as #Ashish Prakash suggested, or manage all of your state in the custom navigator and transform ScreenA and ScreenB into presentational components.
Use this.props.navigation.setParam({onlineUsers: onlineUsers: payload.users}) when you get user-list from server.
Then use it in Screen B like this.props.navigation.state.params.onlineUsers