Passing props to a component to use in testing a SnapShot - react-native

Screen-test.js
it('renders the Engagement Detail modal', () => {
const tree = renderer.create(<EngagementDetailModal details={engagementData}/>).toJSON();
expect(tree).toMatchSnapshot();
})
the component I am trying to test
class CompanyDetailView extends React.Component {
render() {
const data = this.props.navigation.getParam('details');
return (
// rest of the code
)
}
}
My data variable is just a bunch of static data. Using jest I am getting this error TypeError: Cannot read property 'getParam' of undefined and it is pointing to this line const data = this.props.navigation.getParam('details');
I know the component works as i run the app, just the test is failing.
I thought that by just providing the prop to the component in my Screen-test.js file it would work the same but i get undefined. How can i test this?
Also I am passing the prop using react navigation like this onPress={() => this.props.navigation.navigate('EngagementDetailModal', {details: this.props.data})

It cannot read the property getParamof navigation because you haven't mocked the navigation. So when the test case runs, it doesn't know what this.props.navigation does.
Add the following after imports section in your Screen-test.js .
const testProps = props => ({
navigation: {navigate: jest.fn(), getParam : jest.fn()},
...props,
})
Hope this helps!!

Related

Re-render component everytime screen is opened react native

I'm kinda new to React Native. I'm using the getFamily() on my screen MyFamily but when I go to another screen there change the value of the Family and come back to my MyFamily screen then I don't see the changes.
I tried doing it with the useEffect but still nothing happens, also the log doesn't happen. How can I solve this?
export default function MyFamily({ navigation, props, person, inheritors }) {
console.log(getFamily());
let [family, setFamily] = useState(getFamily());
useEffect(() => {
console.log(getFamily());
setFamily(getFamily());
}, [getFamily]);
In the screen where I set the Family again I do this:
And I know that's correct because the Json that is shown shows the updated value.
import { setFamily } from '../../utilities/family';
setFamily(responseJson.family);
This is the way family is formulated:
let family = '';
export default family;
export function getFamily() {
return family;
}
export function setFamily(f) {
family = f;
}
React doesn't actually know that the value returned from the getFamily function changes each render. In the useState function, it's only used in the initial state, and the useEffect function never gets re-run because the getFamily function itself doesn't ever change and re-trigger the useEffect. You have to change the getFamily() function to use a state that's stored in a parent component and pass it into the MyFamily component as a prop.
e.g.
// the parent component that renders the MyFamily screen
function Router() {
const [family, setFamily] = useState('')
return (
<Navigator>
<Screen component={<MyFamily family={family} setFamily={setFamily} />
<Screen component={<OtherComponent family={family} setFamily={setFamily} />
</Navigator>
}
)
}
And then from MyFamily:
function MyFamily({ family }) {
console.log(family); // this should be updated
}
and from OtherComponent:
function OtherComponent({ setFamily }) {
return (<Button onClick={() => setFamily('newFamily')>Change family</Button>)
}

How to mock React Functionnal Component method?

I don't found a lot of jest&enzyme mock/spy example when react components are functions instead of class...
Since it looks impossible to spy on functionnal components method with "jest.spyOn" like it's possible to do with class components, is there a better or other way than passing method reference in props like my example below ?
it('click on connect button', () => {
let handleConnect = jest.fn();
let shallowLogin = shallow(<Login handleConnect={handleConnect} />);
//const spy = jest.spyOn(shallowLogin.instance(), 'handleConnect');
//Impossible to spyOn like this ?
let button = shallowLogin.find('Styled(Button)');
button.simulate('press');
expect(handleConnect).toHaveBeenCalledTimes(1);
});
I hate to do this because it's intrusive on the component code and it force the change stuff inside for the tests...
Component example :
import useForm from 'react-hook-form';
export default function Login(props) {
const {register, handleSubmit, setValue} = useForm();
const onSubmit = data => {
console.log('data: ', data);
if (FormValidation(data, "login")) {
props.navigation.navigate('App');
}
};
return (
...
<Button style={styles.button} rounded large onPress={handleSubmit(onSubmit)}><Text
style={styles.buttonText}>{locale.t('loginScreen.CONNECT')}</Text></Button>
...
);
}

how to call fetch in a sub component in react native?

I saw some tutorials online and majority of the examples are using
a class component e.g
export default class App extends Component {
constructor() {
super();
//set state here and data source
}
componentDidMount() {
//get json data here
}
render() {
return(
blah blah blah
)
}
}
what if I want a sub component to have a functionality that calls fetch , how to do it ?
//NewComponent.js
//this is my sub component
const NewComponent = () => {
return(
<FlatList />
//I want a list of data here
)
}
const getData = () => {
//call fetch here
}
export default NewComponent
so in my second code snippet how to call the getData in order for me to display the data inside the NewComponent?
You have several options. Personally I would go for #3
You can make your sub component a class and use React's lifecycle method such as componentDidMount
You can move getData function to another javascript module. Then import and call it from parent component. Pass the result as props down into the sub component.
Use React's Effect Hook. Pay attention to the section Optimizing Performance by Skipping Effects also.
Hope that helps.

How to navigate in mobx store using react navigation?

I can use this.props.navigation from screen component to navigate. How should I do the similar in mobx store file? Or should I perform navigation in store?
I read the Navigating without the navigation prop article, but it seems only works for screen components, right?
Someone says use global variable to store a this.props.navigation reference and use it anywhere, but I don't like the idea...
Yes either:
forward the navigation class to the store when calling the method:
// add nivagation parameter to store fucntion:
this.props.store.handleSomething(data, this.props.navigation);
Or you can singleton the navigator (warning only works for one ofc):
return <Navigator ref={(nav) => this.props.store.navigator = nav} />;
after this is rendered it will set the navigator property in the store.
But I would suggest to store all your routing state also in a routing store like this: https://github.com/alisd23/mobx-react-router.
This way you always have easy access to the navigation state and you can be sure everything properly re-renders. (when in render function in components also depends on navigation changes!)
You can keep all your states including navigation state in mobx store.
For example:
// sourced and modified from https://github.com/react-community/react-navigation/issues/34#issuecomment-281651328
class NavigationStore {
#observable headerTitle = "Index"
#observable.ref navigationState = {
index: 0,
routes: [
{ key: "Index", routeName: "Index" },
],
}
// NOTE: the second param, is to avoid stacking and reset the nav state
#action dispatch = (action, stackNavState = true) => {
const previousNavState = stackNavState ? this.navigationState : null;
return this.navigationState = AppNavigator
.router
.getStateForAction(action, previousNavState);
}
}
// NOTE: the top level component must be a reactive component
#observer
class App extends React.Component {
constructor(props, context) {
super(props, context)
// initialize the navigation store
this.store = new NavigationStore()
}
render() {
// patch over the navigation property with the new dispatch and mobx observed state
return (
<AppNavigator navigation={addNavigationHelpers({
dispatch: this.store.dispatch,
state: this.store.navigationState,
addListener: () => { /* left blank */ }
})}/>
)
}
};
Then you can directly call the dispatch action of the store to navigate to a new screen.
Send this one this.props.navigation as a parameter to the store. Then use as you use on the component side.
LoginStore.login(this.props.navigation)
in the LoginStore
#action login = (navigation) => { navigation.navigate('Page');}

Way to dynamically set a React Navigation screen title using MobX store value?

On my default React Navigation tab screen I'd like to set the screen's title to a value from a MobX store. It's my understanding that the only way to do this is to pass the value via a param--so I can't just put the MobX value in the 'title: ' field... but as this is the 'default' screen I'm not passing it anything.
Default screen:
export default class HomeScreen extends Component {
static navigationOptions = ({ navigation, screenProps }) => ({
title: `This is ${navigation.state.params.title}`,
I've attempted to make use of setParams during componentWillMount, but console.log shows me it must be happening too late, so I get an empty object in the title.
Any idea how to do this?
Had the same problem today and figured it out.
Simply change your navigationOptions to a function instead of an object, that way it will re-evaluate on state changes. Here's the fix for you, as described by the manual at https://reactnavigation.org/docs/intro/headers#Header-interaction-with-screen-component:
static navigationOptions = ({ navigation }) => {
const { params = {} } = navigation.state;
const title = `This is ${params.title}`;
return { title };
};