Update points Redux React Native - react-native

I'm trying to load points from Firebase in order to display it on the screen
I'm using Redux, because the points number can be updated but I can not put this.props.Points.updatePoint inside Firebase request
How can I update it?
Home.js :
class Home extends React.Component {
constructor(props) {
super(props);
this.state = {
};
}
componentDidMount = async () => {
const pointsRef=firebase.database().ref("Users").child(firebase.auth().currentUser.uid).orderByChild("Points").once('value',function(snapshot){
const Points=snapshot.val().Points
});
this.props.Points.updatePoints(Points)
render(){
return(
<Text>{this.props.Points}</Text>
)}
}
const mapStateToProps = (state) => {
return {
Points:state.Points};
};
const mapDispatchToProps = (dispatch) => {
return {
updatePoints:(Points)=>dispatch({ type: "UPDATE_POINTS", payload: Points }),
};
};
PointReducer.js :
const initialState = {
Points: 0,
};
const Points = (state = initialState, action) => {
switch (action.type) {
case "UPDATE_POINTS":
return {
...state,
Points: action.payload,
};
default:
return state;
}
};
export default Points;

Your method is correct. The problem is actually with the way you're trying to access to updatePoints function in mapDispatchToProps & the place you're run the statement.
class Home extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
componentDidMount = async () => {
const pointsRef = firebase
.database()
.ref("Users")
.child(firebase.auth().currentUser.uid)
.orderByChild("Points")
.once("value", (snapshot) => {
const Points = snapshot.val().Points;
this.props.updatePoints(Points); // You can directly access to `updatePoints` using prop object.
}); // convert this function to an arrow function. It will fix `this` related issues.
};
render() {
return <Text>{this.props.Points}</Text>;
}
}
const mapStateToProps = (state) => {
return {
Points: state.Points,
};
};
const mapDispatchToProps = (dispatch) => {
return {
updatePoints: (Points) =>
dispatch({ type: "UPDATE_POINTS", payload: Points }),
};
};
Let me know if you need further support.

Related

How to test a component that renders asynchronously after a call

Suppose I have a component that loads its content when an asynchronous call returns succesfuly:
const MyScreen = () => {
let userData: userDataResponse;
const [email, setEmail] = useState("");
const [firstTime, setFirstTime] = useState(true);
async function localGetUserData() {
userData = await getUserData();
setEmail(userData.email);
setFirstTime(false);
}
useEffect(() => {
localGetUserData();
}, []);
if (firstTime) {
return <Text>Cargando...</Text>;
}
return (
<SafeAreaView style={styles.formStyling}>
When the data is available, it sets a state variable so the real content renders
If I want to test it, I think I should mock the getUserData so the mocked function returns a mocked email, say {email: a#b.c}
What would be a good approach to achieve this?
Assuming following component setup (as I cannot see whole component):
myScreenUtils.js
export const getUserData = async () => {
return Promise.resolve('original implementation')
}
MyScreen.jsx
import { useState, useEffect } from "react";
import { getUserData } from './myScreenUtils.js'
const MyScreen = () => {
let userData;
const [email, setEmail] = useState("");
const [firstTime, setFirstTime] = useState(true);
async function localGetUserData() {
userData = await getUserData();
setEmail(userData.email);
setFirstTime(false);
}
useEffect(() => {
localGetUserData();
}, []);
if (firstTime) {
return <div>Cargando...</div>;
}
return (
<div>{email}</div>
)
};
export default MyScreen;
You can write following tests:
import { screen, render, waitFor, waitForElementToBeRemoved } from '#testing-library/react';
import MyScreen from "../MyScreen";
import * as utils from '../myScreenUtils';
describe('MyScreen', () => {
it('the text is displayed and then removed', async () => {
jest.spyOn(utils, 'getUserData').mockResolvedValue({ email: 'mocked value' });
render(<MyScreen />);
expect(screen.getByText('Cargando...')).toBeInTheDocument();
await waitForElementToBeRemoved(() => screen.queryByText('Cargando...'))
})
it('the text email is fetched and displayed', async () => {
jest.spyOn(utils, 'getUserData').mockResolvedValue({ email: 'mocked value' });
render(<MyScreen />);
await waitFor(() => {
expect(screen.getByText('mocked value')).toBeInTheDocument()
})
})
})

How to call a functional component in a class component?

I have a functional component to check if fonts have been loaded or not, I want to call it before the app render in App.js and if it's loaded give it a state called loaded but i don't know how can I do that because it's a class component, it's quite confusing to me:
Here's the functional component code:
export default function Cache() {
const [isLoadingComplete, setLoadingComplete] = React.useState(false);
React.useEffect(() => {
async function loadResourcesAndDataAsync() {
try {
SplashScreen.preventAutoHideAsync();
await Font.loadAsync({
/// fonts
});
finally {
setLoadingComplete(true);
SplashScreen.hideAsync();
}
}
loadResourcesAndDataAsync();
}, []);
return isLoadingComplete;
}
And here's my app.js component in which I want to call the Cache component:
export class App extends Component {
constructor(props) {
super(props);
this.state = {
loaded: false,
};
}
componentDidMount() {
firebase.auth().onAuthStateChanged((user) => {
if (!user) {
this.setState({
loggedIn: false,
});
} else {
this.setState({
loggedIn: true,
});
}
});
}
render() {
const { loggedIn, loaded } = this.state;
if (!loaded) {
return null;
} else {}
You can just put your fonts in the componentDidMount like that
async componentDidMount(){
await Font.loadAsync({
'Montserrat': require('../assets/fonts/Montserrat.ttf'),
}).then(() => {
this.setState({fontLoaded: true})
})

React native mapDispatchToProps not working

I can't get my mapDispatchToProps to work properly.
I export a combineReducers:
export default combineReducers({
auth: AuthReducer,
tenants: TenantsReducer
});
The tenants reducer:
const INITIAL_STATE = {
error: false,
data: [],
tenantData: {},
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case GET_TENANTS_DATA:
return { ...state, error: false, data: action.payload };
case GET_TENANT_DATA:
return { ...state, error: false, tenantData: action.payload };
default:
return state;
}
};
Then I have getTenantByID method in my action
export const getTenantByID = ({ tenantID }) => {
return (dispatch) => {
const getTenant = {
FirstName: 'Jonh', LastName: 'Doe', Email: 'jonh#test.com', Phone: 'xxx-xxx-xxxx',
Unit: '101', MiddleName: '',
};
dispatch({
type: GET_TENANT_DATA,
payload: getTenant
});
};
};
Finally, I tried to use it in my component.
import { connect } from 'react-redux';
import { getTenantByID } from '../actions';
...
componentDidMount() {
const { navigation } = this.props;
const tenantID = navigation.getParam('tenantID', '0');
this.props.getTenantByID(tenantID);
console.log(this.props);
this.state = {
tenantData: this.props.tenantData
};
}
const mapStateToProps = ({ tenants }) => {
return {
error: tenants.error,
tenantData: tenants.tenantData
};
};
const mapDispatchToProps = () => {
return {
getTenantByID
};
};
export default connect(mapStateToProps, mapDispatchToProps)(TenantDetails);
In my componentDidMount, the console.log(this.props) is returning a empty object for tenantData. What am I doing wrong?
Initial state is showing as the component already mounted, which is empty object {}
this.props.getTenantByID(tenantId);
this action triggers actually, but the data is not available in componentDidMount lifecycle.
try putting log in render like this
componentDidMount(){
this.props.getTenantByID(2);
}
render() {
console.log(this.props.tenantData); // 1st render => {}, 2nd render=> desired data
return (
<div/>
);
}
use componentDidUpdate to check if value is changed,
componentDidUpdate(prevProps){
if(prevProps.tenantData !== this.props.tenantData){ console.log(prevProps.tenantData, this.props.tenantData) }
}
remember to receive the dispatch parameter in your mapDispatchToProps method
const mapDispatchToProps = (dispatch) => {
return {
getTenantByID: (tenantID ) => {
dispatch(getTenantByID({tenantID }));
};
};
};
call for
this.props.getTenantByID({ tenantID: 10 })

React Native Reducer data not being sent to my page

I have an action data that is being sending to the reducer, but not ti my page constructor.
Action Method:
export const getTenantByID = ({ tenantID }) => {
return (dispatch) => {
const getTenant = {
FirstName: 'Jonh', LastName: 'Doe', Email: 'jonh#test.com', Phone: 'xxx-xxx-xxxx',
Unit: '101', MiddleName: '',
};
dispatch({
type: GET_TENANT_DATA,
payload: getTenant
});
};
};
Then, in my reducer
const INITIAL_STATE = {
error: false,
data: [],
tenantData: {},
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case GET_TENANTS_DATA:
return { ...state, error: false, data: action.payload };
case GET_TENANT_DATA:
return { ...state, error: false, tenantData: action.payload };
default:
return state;
}
};
If I do a console.log(action) after the case GET_TENANT_DATA, I can see that data for the payload, so it is working in the reducer.
My page:
constructor(props) {
super(props);
const { navigation } = this.props;
const tenantID = navigation.getParam('tenantID', '0');
this.props.getTenantByID(tenantID);
console.log(this.props); // this show tenantData as a empty object
this.state = {
tenantData: this.props.tenantData
};
}
...
const mapStateToProps = ({ tenants }) => {
const { error, tenantData } = tenants;
return { error, tenantData };
};
export default connect(mapStateToProps, {
getTenantByID
})(TenantDetails);
Seems like you are using thunk and it's asynchronous, so you need to await your action so that you can get the updated state after you fire the action. Otherwise, you can remove thunk if it's not necessary. You may want to fire the action in componentDidMount instead of constructor too
componentDidMount() {
this.getTenant();
}
getTenant = async () => {
const { navigation } = this.props;
const tenantID = navigation.getParam('tenantID', '0');
await this.props.getTenantByID(tenantID); // Wait for action to complete
console.log(this.props); // Get updated props here
this.state = {
tenantData: this.props.tenantData
};
}
const mapStateToProps = ({ tenants }) => {
const { error, tenantData } = tenants;
return { error, tenantData };
};
export default connect(mapStateToProps, {
getTenantByID
})(TenantDetails);
Or you can capture the update via componentDidUpdate
componentDidMount() {
this.getTenant();
}
componentDidUpdate(previousProps) {
if (this.props.tenantData !== previousProps.tenantData) {
console.log(this.props); // Get updated props here
this.state = {
tenantData: this.props.tenantData
};
}
}
getTenant = async () => {
const { navigation } = this.props;
const tenantID = navigation.getParam('tenantID', '0');
this.props.getTenantByID(tenantID);
}
const mapStateToProps = ({ tenants }) => {
const { error, tenantData } = tenants;
return { error, tenantData };
};
export default connect(mapStateToProps, {
getTenantByID
})(TenantDetails);

React-redux: Why is the state undefined in my Home component?

I am having troubles with getting the state in my HomeComponent.js . Every time I try to print it, it return "undefined" .
I've tried different ways to call onPress in my Home component (e.g. onPress={this.printState()}, but none work)
This is my HomeComponent.js
//import statements
const mapStateToProps = state => {
return {
jobTitles: state.jobTitles
}
}
const mapDispatchToProps = dispatch => ({
fetchJobTitles: () => dispatch(fetchJobTitles())
});
class Home extends Component {
constructor(props) {
super(props);
this.state = {
jobInputValue: '',
addressInputValue: ''
};
}
componentDidMount() {
this.props.fetchJobTitles();
}
printState = () => {
console.log('State is: ' +
JSON.stringify(this.state.jobTitles));
}
render() {
return (
<ImageBackground style={styles.bkgImage} source={require('../assets/homepage_background.jpg')}>
//JSX goes here
<Button
title="CAUTÄ‚"
type="outline"
underlayColor={colors.red}
titleStyle={styles.buttonTitleStyle}
color={colors.red}
style={styles.buttonStyle}
onPress={this.printState}
/>
</ImageBackground>
);
}
}
//some styles
export default connect(mapStateToProps, mapDispatchToProps)(Home);
This is my reducer (jobTitles.js):
import * as ActionTypes from '../ActionTypes';
export const jobTitles = (state = { errMess: null,
jobTitles:[]}, action) => {
switch (action.type) {
case ActionTypes.GET_JOB_TITLES:
return {...state, errMess: null, jobTitles: action.payload};
case ActionTypes.JOB_TITLES_FAILED:
return {...state, errMess: action.payload};
default:
return state;
}
};
And this is my Action Creator:
import * as ActionTypes from './ActionTypes';
import { baseUrl } from '../shared/baseUrl';
export const fetchJobTitles = () => (dispatch) => {
return fetch(baseUrl + 'api/jobs/job_keywords')
.then(response => {
if (response.ok) {
return response;
} else {
var error = new Error('Error ' + response.status + ': ' +
response.statusText);
error.response = response;
throw error;
}
},
error => {
var errmess = new Error(error.message);
throw errmess;
})
.then(response => response.json())
.then(jobTitles => dispatch(addJobTitles(jobTitles)))
.catch(error => dispatch(jobTitlesFailed(error.message)));
};
export const jobTitlesFailed = (errmess) => ({
type: ActionTypes.JOB_TITLES_FAILED,
payload: errmess
});
export const addJobTitles = (jobTitles) => ({
type: ActionTypes.GET_JOB_TITLES,
payload: jobTitles
});
This is how the response from the API looks like:
"jobTitles": Object {
"results": Array [
"Engineer",
"Software",
"Software Architect",
"Software Consultant",
"Solution Architect",
"System Architect"
]
}
I expected the console.log() statement from the print() function in the HomeComponent.js to print the JSON response from the API, but instead it returns "undefined". Any ideas why?
Any help will be greatly appreaciated!
In your code :
this.state = {
jobInputValue: '',
addressInputValue: ''
};
What you try to print :
this.state.jobTitles
Of course it's undefined ! Either log this.props.jobTitles or set state jobTitles to print what you want.
You should use this.props.jobTitles
The mapStateToProps puts data from the redux state into the props of the component. this.state only holds the local state of the component. So jobInputValue and addressInputValue in this case. Everything from mapStateToProps and mapDispatchToProps will end up in the props. (As the name of the function indicates)