How can i get token in componentDidMount from redux? - react-native

I'm trying to add a props inside a componentDidMount from redux.
If i try to log in in to my app with componentDidUpdate i'm able to see the data loaded, but if i close the app and after i try to re open it, i can't see the data.
class Profile extends Component {
constructor(props) {
super(props);
this.state = {
results: []
};
}
componentDidUpdate = () => {
this.getMyWeather();
};
getMyWeather = () => {
const {
getUser: { userDetails }
} = this.props;
axios
.get(
settings.host +
'my_api_url',
{
headers: { Authorization: 'Token ' + userDetails.token },
}
)
.then(({ data }) => {
this.setState({
results: data.results
});
})
.catch(error => alert(error));
};
render() {
return (
<View style={styles.container}>
{this.state.results &&
this.state.results.map((data, index) => (
<Text key={index}>{data.title}</Text>
))}
</View>
);
}
}
let mapStateToProps;
mapStateToProps = state => ({
getUser: state.userReducer.getUser
});
let mapDispatchToProps;
mapDispatchToProps = dispatch => ({
dispatch
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(Profile);
How i can fetch the data also after closing and re-open the app?

Try this way
async componentDidMount() {
// GET request using axios with async/await
const {userDetails} = this.props.getUser; <-- Try this way -->
const data = await this.getMyWeather(userDetails);
this.setState({
results: data
});
}
getMyWeather = async (userDetails) => {
await axios
.get(
settings.host +
'my_api_url',
{
headers: { Authorization: 'Token ' + userDetails.token },
}
)
.then(({ data }) => {
return data.results;
})
.catch(error => alert(error));
};

Why to save to token in your redux in the first place?
personally I save it in local storage it's easy.
as you know redux is a state management of react this is mean when the you close the website the data store in redux die and because of this I think you should save in the local storage so you can get access to it really easy.
If you save the JWT in the DB you just need in the useEffect in the app.js call the action in redux that extract the JWT and save it

Related

How to implement splash screen properly in a component which have hooks running?

Inside App.js I have auth validation (i am using useState, useMemo, useEffect) but when tried to impement splash screen and following Splas screen Dos I am getting Rendered more hooks than during the previous render. So following Rules of Hooks I put at top level useEffect and useState but now I am getting a new error Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in %s.%s, a useEffect cleanup function, in App I see I need to cancel async functions but I need them to request the server and validate users.
This is how my code was before implementing Splash screen:
export default function App() {
const [auth, setAuth] = useState(undefined);
useEffect(() => {
(async () => {
const token = await getTokenApi();
if (token) {
setAuth({
token,
idUser: jwtDecode(token).id,
});
} else {
setAuth(null);
}
})();
}, []);
const login = (user) => {
setTokenApi(user.jwt);
setAuth({
token: user.jwt,
idUser: user.user.id,
});
};
const logout = () => {
if (auth) {
removeTokenApi();
setAuth(null);
}
};
const authData = useMemo(
() => ({
auth,
login,
logout,
}),
[auth]
);
if (auth === undefined) return null;
return (
<AuthContext.Provider value={authData}>
<PaperProvider>{auth ? <AppNavigation /> : <Auth />}</PaperProvider>
</AuthContext.Provider>
);
This is how i got it now
export default function App() {
const [auth, setAuth] = useState(undefined);
useEffect(() => {
(async () => {
const token = await getTokenApi();
if (token) {
setAuth({
token,
idUser: jwtDecode(token).id,
});
} else {
setAuth(null);
}
})();
}, []);
const [appIsReady, setAppIsReady] = useState(false);
useEffect(() => {
async function prepare() {
try {
await SplashScreen.preventAutoHideAsync();
await Font.loadAsync(Entypo.font);
await new Promise((resolve) => setTimeout(resolve, 4000));
} catch (e) {
console.warn(e);
} finally {
setAppIsReady(true);
}
}
prepare();
}, []);
const login = (user) => {
setTokenApi(user.jwt);
setAuth({
token: user.jwt,
idUser: user.user.id,
});
};
const logout = () => {
if (auth) {
removeTokenApi();
setAuth(null);
}
};
const authData = useMemo(
() => ({
auth,
login,
logout,
}),
[auth]
);
if (auth === undefined) return null;
const onLayoutRootView = useCallback(async () => {
if (appIsReady) {
await SplashScreen.hideAsync();
}
}, [appIsReady]);
if (!appIsReady) {
return null;
}
return (
<View onLayout={onLayoutRootView}>
<AuthContext.Provider value={authData}>
<PaperProvider>{auth ? <AppNavigation /> : <Auth />}</PaperProvider>
</AuthContext.Provider>
</View>
);
}

How to call a function declared in App.js

I have a react native project that uses react-navigation. I have these two screens that are part of a stack navigator. I want to call all API related functions in App.js or the stack navigator rather than directly on a screen. I would also like to use data in the two screens. How can I do this?
App.js
import fetchData from './Data';
export default function App() {
const [data, setData] = useState([]);
useEffect(() => {
const fetchAPI = async () => {
setData1WeekCases(await fetchData());
};
fetchAPI();
}, [setData1WeekCases]);
}
Data.tsx
export const fetchData = async () => {
try {
const {
data: { countries },
} = await axios.get("https://covid19.mathdro.id/api/countries");
return countries.map((country) => country.name);
} catch (error) {
console.log(error);
}
};
StackNavigator.tsx
const AppStack = createNativeStackNavigator();
const MainStackNavigator = () => {
return (
<AppStack.Navigator>
<AppStack.Screen
name="HomeScreen"
component={HomeScreen}
options={{
title: "Home",
}}
/>
<AppStack.Screen
name="DataScreen"
component={DataScreen}
options={{
headerBackTitle: "Summary",
title: "Data",
}}
/>
<AppStack.Navigator>
)
}
First of all to access data globally like in your case between screens, you have to use state management tool like Redux or Context. You can find many tutorials for this on youtube if you can't figure it out using the docs.
Secondly if you want to do all the fetching in a separate file then you can create an axios instance in a separate file like this :
import axios from "axios";
import AsyncStorage from "#react-native-async-storage/async-storage";
const axiosClient = axios.create();
axiosClient.defaults.baseURL = "API_URL";
axiosClient.defaults.headers = {
"Content-Type": "application/x-www-form-urlencoded",
Accept: "application/json",
};
//All request will wait 2 seconds before timeout
axiosClient.defaults.timeout = 2000;
axiosClient.defaults.withCredentials = true;
export default axiosClient;
axiosClient.interceptors.request.use(
async config => {
const token = await AsyncStorage.getItem('token');
if (token) {
config.headers.Authtoken = JSON.parse(token);
}
return config;
},
error => {
return console.log(error);
},
);
export function getRequest(URL) {
return axiosClient
.get(URL)
.then((response) => response)
.catch((err) => err);
}
export function postRequest(URL, payload) {
//! step for x-www-form-urlencoded data
const params = new URLSearchParams(payload);
return axiosClient.post(URL, params).then((response) => response);
}
export function patchRequest(URL, payload) {
return axiosClient.patch(URL, payload).then((response) => response);
}
export function deleteRequest(URL) {
return axiosClient.delete(URL).then((response) => response);
}
Fetch data from the api then change the state using redux to get the response in every screen.

UseEffect to run a function and that function set hook from another file

So what I want to do is throw my APIs in one file. This way it makes my app way more reusable.
Problem is that I don't know how to do what I'm doing.
My parent file holds all the Hooks I need for data.
I am trying to get the Parent file to call the API, run the call to get the data, then that data then calls back and sets the hook in the parent.
Parent File
import { handleDepartments } from './API/API';
export default function App() {
const [departments, setDepartments] = useState([]);
useEffect(() => {
handleDepartments;
}, []);
The API file..
export const handleDepartments = async () => {
console.log('getting Departments');
const data = await axios
.get(`URI`, {
headers: {
Authorization: 'API_KEY',
Accept: 'application/json',
},
})
.then((response) => {
setDepartments(response.data.departments);
})
.catch((err) => {
console.log(err);
});
};
You're on the right track but its not a great idea to pass down a setState function into the api to update the parent component. Instead, its better practice to make the api call only return data, then the parent can decide how to deal with it.
Api:
export const handleDepartmentsApi = async () => {
await axios
.get(`URI`, {
headers: {
Authorization: 'API_KEY',
Accept: 'application/json',
},
})
.then((response) => {
return data;
})
.catch((err) => {
return err;
});
};
Parent:
export default function App() {
const [departments, setDepartments] = useState([]);
const getDepartments = async () => {
try {
const response = await handleDepartmentsApi();
setDepartments(response.data.departments)
} catch (err) {
//handle error or do whatever
}
}
useEffect(() => {
getDepartments();
}, []);
return (<></>)
}

React Native accessing SecureStore

I have searched Google for a clear answer on this but cant find one. Below is my code.
In a previous screen I have stored the token to SecureStore
I'm now trying to access it from a different screen.
(async () => {
const token = await SecureStore.getItemAsync('token');
return token;
})();
export default class App extends React.Component {
constructor(){
super();
this.state = {
data: [],
loaded: true,
error: null,
token: token
}
}
Can anyone advise me how to get the value from SecureStore to my state inside the class?
Entire Code
import React, { Component} from 'react';
import { Text, Button, ScrollView } from 'react-native';
import { globalStyles } from '../styles/global';
import * as SecureStore from 'expo-secure-store';
(async () => {
const token = await SecureStore.getItemAsync('token');
//console.log('token output 1 ' + token);
return token;
})();
export default class App extends React.Component {
constructor() {
super();
this.state = {
data: [],
loaded: true,
error: null,
token: ""
};
}
baseURL = 'https://www.example.co.uk/api/auth';
getData = (ev)=>{
this.setState({loaded:false, error: null});
let url = this.baseURL + '/list';
let h = new Headers();
console.log('token output 2 = ' + this.state.token);
h.append('Authorization', 'Bearer tokenToBePutHere');
h.append('Content-Type', 'application/json');
h.append('X-Requested-With', 'XMLHttpRequest');
let req = new Request(url, {
headers: h,
method: 'GET'
});
fetch(req)
.then(response=>response.json())
.then(this.showData)
.catch(this.badStuff)
}
showData = (data)=>{
this.setState({loaded:true, data:data});
}
badStuff = (err) => {
this.setState({loaded: true, error: err.message});
}
componentDidMount() {
(async () => {
const token = await SecureStore.getItemAsync('token');
this.setState({ token });
})();
this.getData();
}
render() {
return (
<ScrollView style={globalStyles.container}>
{ !this.state.loaded && (
<Text>LOADING</Text>
)}
<Text>Your Lists Are:</Text>
{/*} <Button title="Get Data"
onPress={this.getData} /> */}
{ this.state.error && (
<Text style={styles.err}>{this.state.error}</Text>
)}
{ this.state.data && this.state.data.length > 0 && (
this.state.data.map( data => (
<Text key={data.id}>
{ data.lists.name }
</Text>
))
)}
</ScrollView>
);
}
}
I have added all of my code for this screen.
token Output 1 works and outputs the correct token
token Output 2 does not work and returns nothing.
I need to use the token in the api call but cant get it to pass there.
You can use the lifecycle method componentDidMount and callbacks like this:
export default class App extends React.Component {
constructor() {
super();
this.state = {
data: [],
loaded: true,
error: null,
token: ""
};
}
componentDidMount() {
SecureStore.getItemAsync("token").then(token => {
this.setState({ token });
});
}
// ...
}
Same solution using async/await:
class App extends React.Component {
constructor() {
super();
this.state = {
data: [],
loaded: true,
error: null,
token: '',
};
}
componentDidMount() {
(async () => {
const token = await SecureStore.getItemAsync('token');
this.setState({ token });
})();
}
// ...
}
Addressing updated question
If you need only need the token for the fetch request you don't even need to store the token in the state. You can use it directly after retrieving it from SecureStore:
componentDidMount() {
(async () => {
const token = await SecureStore.getItemAsync('token');
// Your fetch code
this.setState({loaded:false, error: null});
let url = this.baseURL + '/list';
let h = new Headers();
h.append('Authorization', `Bearer ${token}`);
h.append('Content-Type', 'application/json');
h.append('X-Requested-With', 'XMLHttpRequest');
let req = new Request(url, {
headers: h,
method: 'GET'
});
fetch(req)
.then(response=>response.json())
.then(() => this.setState({loaded:true, data:data}))
.catch(() => this.badStuff())
})();
}

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)