Passing params from functional component to class component in React Native - react-native

I have functional component where I am passing params at onPress. Below is the code:
PAGE1
const onPress = (data) => {
navigation.goBack();
route.params.onPress(data);
};
I was able to pass this to a functional component, where I was getting the data, and was able to update my state in the functional component. Below is the code snippet from functional component:
PAGE2
function gotoSearch() {
navigation.navigate('SearchScreen',{
onPress:(data)=>{
console.log("Location Selected",data);
updateStateVar({
...stateVar,
address_line_1: data.address_line1,
address_line_2: data.address_line2,
area: data.area,
city: data.city,
country: data.country,
pincode: data.postCode,
lat:data.lat,
lng:data.lng,
});
}
});
}
I have another component which is a class component, I tried to do the same thing, but is showed me the error. Below is the code snippet from class component:
PAGE3
gotoSearch = () => {
this.props.navigation.navigate('SearchScreen'), {
onPress: (data) => {
console.log("Location Selected",data);
}
}
}
error:
cannot read property of 'onPress' of undefined
const onPress = (data) => {
navigation.goBack();
route.params.onPress(data);
^
};

you will need to export onPress function
export const onPress = (data) => {
navigation.goBack();
route.params.onPress(data);
};
then use it inside your class component
gotoSearch = () => {
this.props.navigation.navigate('SearchScreen'), {
onPress: (data) => {
console.log("Location Selected",data);
}
}
if your function exists in a different file and then first import it and then use
import {onPress} from './file.js'

Related

How to mock useRef hook with react-native using enzyme

I would like to write tests for my React-native app. My parent component will execute the methods within the child component.
My child component is using the Hooks forwardRef, useImperativeHandle, Ref as seen below
childs.tsx
export interface RefChild {
toggle: () => void,
close: () => void
}
const Child = forwardRef((props: ChildProps, ref: Ref<RefChild>) => {
const [isVisible, setIsVisible] = useState(false);
useImperativeHandle(ref, () => ({ toggle, close }));
const toggle = () => {
setIsVisible(!isVisible);
}
const close = () => {
setIsVisible(false)
}
return (...mycomponent)
}
My Parent component is catching the 'ref' call with
ref={(el: RefChild) => childRef.current = el}
Which allows me to call the 'toggle' and 'close' methods from within the Parent.
Now, I fail to understand how to do the same thing within my test
my parent-test.tsx:
describe('Parent', () => {
let wrapper: ShallowWrapper;
let props: any;
beforeEach(() => {
props = createTestProps({});
wrapper = shallow(<Parent {...props} />);
});
//this is what I am currently trying to do, but not working
//test 1 (not working)
it("useRef child", () => {
const useRefSpy = jest.spyOn(React, 'useRef').mockReturnValueOnce({ current: <Child/> });
expect(useRefSpy).toBeCalled();
useRefSpy.current.toggle();
})
//test 2 (not working)
it("useRef child2", () => {
const ref = {
current: {
toggle: jest.fn(),
close: jest.fn()
}
}
ref.current.toggle();
})
//test 3 (not working)
it("useRef child3", () => {
wrapper.instance().childref.current.toggle(); //failing as functional components don't have instance
})
})
My versions of React and RN are:
"react": "16.13.1",
"react-native": "0.63.3"
Could anyone explain me how should I achieve this?
As you mentioned in your question there is no instance in functional component, I think there is a better way to handle toggle and close functions from parent component using a boolean prop for each of them and the listen to changes in this value like this:
you have a state in parent component called isClose set to false and then in child component you use something like this:
useEffect(() => {
if(isClose){
//call close function
close()
}
}, [isClose])
But by the way in your current setup I think you need to mock the useRef hook something like this:
const useRefSpy = jest
.spyOn(React, "useRef")
.mockReturnValueOnce(() => ({ current: <Child /> }));

componentDidUpdate not being called

I have a react native app, and I am calling componentDidUpdate on App.js, but it doesn't fire.
I wonder if this is because I am calling from App.js?
Here is the App.js files:
class App extends Component {
componentDidUpdate = () => {
if (this.props.text && this.props.text.toString().trim()) {
Alert.alert(this.props.title || 'Mensagem', this.props.text.toString());
this.props.clearMessage();
}
}
render() {
return (
<NavigationContainer>
<Navigator />
</NavigationContainer>
)
}
}
const mapStateToProps = ({ message }) => {
return {
title: message.title,
text: message.text
}
}
const mapDispatchToProps = dispatch => {
return {
clearMessage: () => dispatch(setMessage({
title: '',
text: ''
}))
}
}
const connectDispatch = connect(mapStateToProps, mapDispatchToProps);
const connectApp = connectDispatch(App);
export default connectApp;
And here is where I am calling it.Inside a dispatch in posts action.
.then(res => {
dispatch(fetchPosts());
dispatch(postCreated());
dispatch(setMessage({
title: 'Sucesso',
text: 'Nova Postagem!'
}));
});
All other dispatchs are fired.
It's not the if that is preventing the alert to be fired, because I already put the alert outside of the if.
Change this
componentDidUpdate = () => { ... }
for this:
componentDidUpdate(prevProps, prevState, snapshot) { ... }
Keep in mind the componentDidUpdate does not trigger on first render
Thanks all!
I could fix it.
Instead of importing from '.ActionTypes' I was importing from 'Message'
import { SET_MESSAGE } from '../actions/ActionTypes';
I am new to Redux and it caught me offguard!

React-Redux Dispatch Action in Component

I am fairly new to react native, the issue I'm having is trying to get data from an action in componentdidmount but when I set my props the data is null. The props are being set if I access them in the render method. Can someone please look at the code and tell me what I am doing wrong.
Below is where I'm getting the action
export const Accountability = connect(
// inject states
(state: States) => ({
// props.loading -> modules.app.loading
loading: state.app.loading,
doctorName: state.doctor.doctorName
}),
// inject actions
dispatch => ({
doDoctors: () =>
dispatch(actions.doctor.getDoctors())
})
)(AccountabilityView)
This is where I'm calling it.
render() {
const {loading, doDoctors, doctorName } = this.props
// Getting the data here.
doDoctors()
}
One thing I notice is that I am getting a warning in the console
ExceptionsManager.js:82 Warning: Cannot update during an existing state transition (such as within render). Render methods should be a pure function of props and state.
UPDATE:
I currently have all my files separate(action, reducer, constants, index). My action gets data from an API call. Below is my reducer:
import { handleActions } from 'redux-actions'
import { LOAD_DOCTORS } from './constants'
export type DoctorState = {
doctorName: string
}
const initialState: DoctorState = {
doctorName: '',
}
export default handleActions(
{
[LOAD_DOCTORS]: (state: DoctorState = initialState, action): DoctorState
=> {
const p = action.payload
return {
doctorName: p.doctorName,
}
},
},
initialState
)
UPDATE:2
This is what the code shows in the console, note doDoctors which returns an array is empty on the first call. When called in ComponentDidMount it only shows the first and not the second.
ComponentDidMount
{screenProps: undefined, navigation: {…}, loading: true, doctorName: "",
doDoctors: ƒ}
render
{screenProps: undefined, navigation: {…}, loading: true, doctorName: "",
doDoctors: ƒ}
{screenProps: undefined, navigation: {…}, loading: true,
doctorName: Array(10), doDoctors: ƒ}
Any help would be appreciated.
Possible events where you could call your action are => componentDidMount and componentWillReceiveProps ... render method is only used for returning some jsx based on the update of your component props:
class YourComponent extends React.Component {
componentDidMount() {
// Here's where you call your action,
// first time your component is loaded: <<<===
this.props.doDoctors();
}
componentWillReceiveProps(nextProps) {
// Here's where you could call your action,
// if the component is already mounted: <<<===
this.props.doDoctors();
}
render() {
const {loading, doctorName } = this.props;
return (
<View>
...
</View>
);
}
}

I can only parse my mapStateToProps object to an extent inside of my react component

I am pretty new to redux and am having trouble parsing JSON data, when I mapStateToProps inside my react component. For instance, if I console.log(this.props.chartData[0]) in my react component, the console will display the array I am trying to access, however, when I try to access a specific element in the array by console logging (this.props.ChartData[0].title), I get an error:
[enter image description here][1]
class ChartContainer extends Component {
componentWillMount(){
this.props.chartChanged();
}
render(){
console.log(this.props.chartData[0]);
return(
<Text style={styles.textStyle}>
test
</Text>
);
}
}
const mapStateToProps = state => {
return {
chartData: state.chart
}
};
export default connect (mapStateToProps, {chartChanged}) (ChartContainer);
Interestingly, I have no problem accessing(this.props.ChartData[0].title) inside my reducer.
import {CHART_CHANGED} from '../actions/types';
const INITIAL_STATE = { chartData: [] };
export default (state = INITIAL_STATE, action) => {
console.log(action);
switch (action.type) {
case CHART_CHANGED:
console.log("action");
console.log(action.payload[0].title);
return{...state, chartData: action.payload};
default:
return state;
}
};
Here is the api call in my action file:
export const chartChanged = (chartData) => {
return (dispatch) => {
axios.get('https://rallycoding.herokuapp.com/api/music_albums')
.then((chartData) =>{
dispatch({type: CHART_CHANGED, payload: chartData.data});
});
};
};
If someone can explain why this is happening, I would be super grateful.
So the problem is that you shouldn't assign any value during fetching, what you need to do is use lodash and try doing something like this
import _ from 'lodash'
const title = _.get(this.props.ChartData, 'chartData', [])
if(!isFetching){
//do something
}

How to test arrow function in React ES6 class

I used arrow function inside of my React component to avoid binding this context, for example my component look like this;
class Comp extends Component {
_fn1 = () => {}
_fn2 = () => {}
render() {
return (<div></div>);
}
}
How do I test _fn1 and _fn2 function in my test cases? Because these kind of function did not associated with React component itself, so when I do
fnStub = sandbox.stub(Comp.prototype, "_fn1");
it is not going work, since _fn did not bind with Comp.prototype. Thus, how can I test those functions in React if I want to create function with arrow syntax? Thanks!
ES6 functions or arrow functions are not added to the class prototype.
However, there are a couple of ways to test them:-
Test that the functions themselves are called when a suitable event occurs
ES5 functions exist on the class prototype and something like this is possible:
import Component from 'path/to/component';
import { shallow } from 'enzyme';
describe(<Component>, () => {
it('should call handleSubmit', () => {
const spy = jest.spyOn(Component.prototype, 'handleSubmit');
const wrapper = shallow(<Component />);
...
//Invoke handleSubmit
...
expect(spy).toBeCalled()
});
});
whereas ES6 functions exist on the instance of the mounted component(you can also use shallow)
import Component from 'path/to/component';
import { mount } from 'enzyme';
describe(<Component>, () => {
it('should call handleSubmit', () => {
const wrapper = mount(<Component />);
...
const spy = jest.spyOn(wrapper.instance(), 'handleSubmit');
//update the instance with the new spy
wrapper.instance().forceUpdate();
...
//invoke handleSubmit
expect(spy).toBeCalled()
});
});
Test their functionality by simulating actions that will invoke these functions and test for the expected behavior
Assuming component content such as:
state = {
title: 'Current Title'
};
updateTitle = (event) => {
title = event.target.value;
this.setState({ title });
}
render() {
return (
<div>
<input type="text" value={this.state.title} onChange={this.updateTitle} />
<div>
)
}
Test
...
wrapper.find('input').simulate('change', {target: {value: 'New title'}});
expect(wrapper.state().title).toBe('New Title');
...
I hope this helps.
In general I find it easier to test that these functions have resulted in a correct component state, rather than test the function itself. For example, here is a component that toggles a state variable when a button is clicked:
class MyComponent extends Component {
state = {
toggle: false
}
_fn1 = () => {
this.setState(previousState => ({
toggle: !previousState.toggle
});
}
render() {
const { toggle } = this.state;
return (
<button onClick={this.clickHandler}>
Turn me {toggle ? 'on' : 'off'}
</button>
);
}
}
My preferred approach here would be to test the component as a whole, i.e. the "unit" of the unit test is the component. The test would therefore find the button, simulate a click, and ensure that the correct text is shown. This may not be a textbook unit test, but it achieves the goal of testing the component.
Using sinon/chai/mocha/enzyme:
describe('My Component', () => {
it('alternates text display when the button is clicked', () => {
const wrapper = shallow(<MyComponent />);
expect(wrapper).to.have.text('Turn me off');
wrapper.find('button').simulate('click');
expect(wrapper).to.have.text('Turn me on');
});
});