I have the following React Native modules:
_localStorage.js
import AsyncStorage from '#react-native-community/async-storage';
const _storeData = async (key, value) => {
try {
await AsyncStorage.setItem(key, value);
} catch (error) {
console.log(error);
}
}
const _retrieveData = async (key) => {
try {
await AsyncStorage.getItem(key);
} catch (error) {
console.log(error);
}
}
export {_storeData, _retrieveData};
AppHeader.js
import React from 'react';
import {Button} from 'react-native-paper';
import {_retrieveData, _storeData} from '../../utils/_localStorage'
const LoginButton = () => {
return (
<Button icon='login' color='yellow' onPress={() => navigation.navigate('Login')}>
Login
</Button>
)
}
const UserButton = (user) => {
return (
<Button color='yellow' onPress={() => console.log('Botón usuario presionado...')}>
text
</Button>
)
}
const AppHeader = ({navigation, route}) => {
const user = _retrieveData('user');
console.log(user);
return user === ''? <LoginButton />: <UserButton user={user} />;
}
export default AppHeader;
I expect _retrieveData() to return the value of the key parameter, or null if it doesn't exits, but what I am getting in the console is this: {"_U": 0, "_V": 0, "_W": null, "_X": null}.
This is not how documentation of AsyncStorage indicates it works.
It's because you're not waiting for _retrieveData to finish. You're just setting user to the async function instead of waiting for its returned value.
Try something like this:
const AppHeader = ({navigation, route}) => {
const [user, setUser] = useState();
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
fetchUser();
}, [])
const fetchUser = async () => {
setIsLoading(true);
const userData = await _retrieveData('user');
setUser(userData);
setIsLoading(false);
}
if (isLoading) return <LoadingIndicator />
if (!!user) return <UserButton user={user} />
return <LoginButton />;
}
I've called fetchUser in the initial useEffect that gets called when the AppHeader component is first loaded. It sets a loading boolean to true and then requests the user data. When the userData is returned it sets it in state and sets loading to false.
You don't need the loading bit but I included it otherwise your app would show the login button while it's fetching the data. You'll have to create the LoadingIndicator component yourself.
_retrieveData is returning promise here. You need to await for that promise to resolve. Try writing it like this:
const _retrieveData = async (key) => {
try {
const data = await AsyncStorage.getItem(key);
return data;
} catch (error) {
console.log(error);
}
}
AppHeader.js
const AppHeader = ({navigation, route}) => {
_retrieveData('user').then((user)=>{
console.log(user);
return user === ''? <LoginButton />: <UserButton user={user} />;
});
}
Read this for more clarity : https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Async_await
You're not returning anything from your _retrieveData function. Try writing it like so:
const _retrieveData = async (key) => {
try {
const data = await AsyncStorage.getItem(key);
return data;
} catch (error) {
console.log(error);
}
}
Related
I am trying to make a membership login system but I get an error. I couldn't understand much because I had just started. What's the problem?
export default async () => {
const [isLoading, setIsLoading] = React.useState(true);
const [userToken, setUserToken] = React.useState(null);
const AsyncUserValue = await AsyncStorage.getItem('userid');
console.log(AsyncUserValue); // (userid 15)
if(AsyncUserValue != null){
console.log('AsyncStorageParse: ' + AsyncUserValue); // (userid 15)
setUserToken(AsyncUserValue);
console.log('Tokken: ' + userToken); // NULL
}
React.useEffect(() => {
setTimeout(() =>{
setIsLoading(false);
}, 1000);
}, []);
if(isLoading) { return <SplashScreen /> }
return(
<NavigationContainer>
{userToken ? (
<AppTabs />
) : (
<LoginStack />
) }
</NavigationContainer>
)
}
You returned functional component as asynchronous (note top export default async ()).
You can't do that - components are required to return React elements (so they have to be synchronous).
What you can do instead is to create a inner async function and do all your async logic there:
export default () => {
const [state, updateState] = useState();
async function myWork() {
const data = await getDataAsyncWay();
updateState(data);
}
useEffect(() => {
myWork()
}, [])
return <View>{...}</View>
}
Note: avoid exporting anonymous function as components - this way, their name won't be visible in stack trace (see your screenshot). What you can do instead is:
function MyComponent() {...};
export default MyComponent
I am new to React Native and trying to build an App with login function. Here is the overview of my App.
In my root app.js
const rootReducer = combineReducers({
login: loginReducer,
preload: preloadReducer,
});
const store = createStore(rootReducer, applyMiddleware(ReduxThunk));
export default function App() {
return (
<Provider store={store}>
<MyNavigator/>
</Provider>
);
};
In my LoginScreen.js, I have
import * as authActions from '../store/actions/auth';
const LoginScreen = props => {
const loginHandler = async () => {
try {
// login here
await dispatch(authActions.login(username, password));
// I want to get login result and navigate to different screens
if (alreadyLogin) {
props.navigation.navigate('MainScreen');
} else {
props.navigation.navigate('StartupScreen');
}
}
catch (err) {
console.log(err);
}
}
return (<View style={styles.login}>
<Button title='Login' onPress={loginHandler}
</View>);
}
In the ../store/action/auth.js
export const login = (username, password) ={
return async dispatch => {
const response = await fetch(url);
const resData = await response.json();
let results = JSON.parse(resData.results);
// if login success, I dispatch the token for later use
if (resData.errno===0){
dispatch({ type: LOGIN , result: results });
} else {
dispatch({ type: RESET , result: null });
}
}
}
Can I achieve what I want with current structure? I want to get login result and navigate to different screens in my LoginScreen.js above. Thank you.
React-admin version 2 used redux-form. Together with the documentation "Altering the Form Values before Submitting" i created a custom button that returns a form error from a custom function, or does a save.
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { crudCreate, SaveButton, Toolbar } from 'react-admin';
import { SubmissionError } from 'redux-form';
const myValidate = (values) => {
const errors = {};
if (values.files < 1) errors.files = 'resources.Order.errors.files';
return errors;
};
// A custom action creator which modifies the values before calling the default crudCreate action creator
const saveWithNote = (values, basePath, redirectTo) =>
crudCreate('posts', { ...values, average_note: 10 }, basePath, redirectTo);
class SaveWithNoteButtonView extends Component {
handleClick = () => {
const { basePath, handleSubmit, redirect, saveWithNote } = this.props;
return handleSubmit(values => {
return new Promise((resolve, reject) => {
const errors = myValidate(values);
if (errors) {
reject(new SubmissionError(errors))
} else {
resolve( saveWithNote(values, basePath, redirect) );
};
}
});
});
};
render() {
const { handleSubmitWithRedirect, saveWithNote, ...props } = this.props;
return (
<SaveButton
handleSubmitWithRedirect={this.handleClick}
{...props}
/>
);
}
}
const SaveWithNoteButton = connect(
undefined,
{ saveWithNote }
)(SaveWithNoteButtonView);
Now in React-admin version 3 "Altering the Form Values before Submitting" Can't I do this anymore?
This code does not work:(
import React, { useCallback } from 'react';
import ProcessIcon from '#material-ui/icons/HeadsetMic';
import { useForm, useFormState } from 'react-final-form';
import { SaveButton } from 'react-admin';
const validateProcess = (values) => {
const errors = {};
if (values.files < 1) errors.files = ['resources.Order.errors.files'];
return errors
};
const SaveWithProcessButton = ({ handleSubmitWithRedirect, ...props }) => {
const form = useForm();
const formState = useFormState();
const handleClick = useCallback(() => {
return new Promise((resolve, reject) => {
const errors = validateProcess(formState.values);
if (errors) {
reject(errors)
} else {
form.change('status', "DRAFT");
resolve( handleSubmitWithRedirect('show') );
};
});
}, [form]);
return <SaveButton {...props} handleSubmitWithRedirect={handleClick} label="ra.action.process" icon={<ProcessIcon />} />;
};
export default SaveWithProcessButton;
I manage to do this. Try my code below, I hope it will works for also for you.
const handleClick = useCallback(() => {
if (!formState.valid) {
form.submit();
return;
} else { ... your code goes here }
}, [form]);
So I'm trying to figure out the best way to display a Toast error and success function when the API call fires from redux.
My line of thinking: Create action for the API call. If successful, then I want the screen to change to the home screen. If it fails, then display the message in a Toast.
Here's what some of my actions look like:
export function getTokenAPI(username, password) {
return async function action(dispatch) {
try {
dispatch({ type: t.AUTH_GET_TOKEN });
dispatch(setLoading(true));
const { data } = await API.authGetToken(username, password);
const { success } = data;
if (success) {
const { access_token, refresh_token } = data;
dispatch(setAccessToken(access_token));
dispatch(setRefreshToken(refresh_token));
await dispatch(setLoading(false));
} else if (!success) {
const { errorMessage } = data;
throw Error(errorMessage);
}
} catch (e) {
dispatch(setError(e.message));
dispatch(setLoading(false));
}
};
}
The setError action sets the error key to true and sets the errorMessage. Here's what my screen looks like:
import React from 'react';
import { Container, View, Toast } from 'native-base';
import styles from './styles';
import { connect } from 'react-redux';
import { authActions } from '_ducks/auth';
const LoginScreen = props => {
const { getToken, navigation } = props;
const { navigate } = navigation;
const navigateToHome = () => navigate('Home');
const handleLogin = async () => {
const { error, errorMessage } = props;
await getToken('sample', 'pass123');
if (error) {
Toast.show({
text: errorMessage,
buttonText: 'kay',
});
} else {
navigateToHome();
}
};
return (
<Container>
<View style={styles.container}>
<LoginButton onPress={handleLogin} />
</View>
</Container>
);
};
const mapDispatchToProps = dispatch => ({
getToken: () => dispatch(authActions.getTokenAPI()),
});
const mapStateToProps = state => ({
isLoading: state.authReducer.isLoading,
error: state.authReducer.error,
errorMessage: state.authReducer.errorMessage,
});
export default connect(
mapStateToProps,
mapDispatchToProps,
)(LoginScreen);
So if there's an error, then display the toast. If it's successful, navigate to the home screen. Essentially, error will not be true quick enough to make the check within handleLogin work appropriately.
Any recommendations on the pattern or process? Should I be using a useEffect hook here?
I have started redux with react-native. I am trying to fetch data from API listing that data using map. Following is my code to fetch data from API.
App.js
const store = createStore(
rootReducer,
applyMiddleware(thunk)
);
const App = () => {
return (
<Provider store={store}>
<ProductList />
</Provider>
);
};
export default App;
productList.js
class ProductList extends React.Component {
componentDidMount() {
this.props.dispatch(fetchProducts());
}
render() {
const { products } = this.props;
return (
<View>
{products.map(product => (
<Text key={product.title}>{product.title}</Text>
))}
</View>
);
}
}
const mapStateToProps = state => ({
products: state.products.items,
});
export default connect(mapStateToProps)(ProductList);
productAction.js
async function getProducts() {
return fetch("https://rallycoding.herokuapp.com/api/music_albums")
.then(res => res.json());
}
export function fetchProducts() {
return async dispatch => {
return getProducts()
.then(json => {
dispatch(fetchProductsSuccess(json));
return json;
})
.catch(error =>
console.log(error)
);
};
}
export const FETCH_PRODUCTS_SUCCESS = "FETCH_PRODUCTS_SUCCESS";
export const fetchProductsSuccess = products => ({
type: FETCH_PRODUCTS_SUCCESS,
payload: { products }
});
productReducer.js
const initialState = {
items: [],
};
export default function productReducer(
state = initialState,
action
) {
switch (action.type) {
case FETCH_PRODUCTS_SUCCESS:
return {
...state,
items: action.payload.products
};
default:
return state;
}
}
rootReducer.js
export default combineReducers({
products
});
It is working perfectly. It is showing the list as well.
But can anyone please tell me is it a correct way if I will use this method in big projects then will it be useful or should I follow some other method? Thanks in advance.
I've not used fetch with react-native, but I think this should work fine. I've used axis though. And it is easy to use
import * as axios from 'axios';
import Constant from './../utilities/constants';
axios.defaults.baseURL = Constant.api_base_url;;
axios.defaults.headers.post['Content-Type'] = 'application/json';
// Axios interceptor for handling common HTTP errors
// Need to use it with reducers
axios.interceptors.response.use(res => res, err => Promise.reject(error));
/**
* HTTP request to search item in The MovieDB
*
* #returns {object | Promise}
*/
const getConfiguration = () => {
return axios.get(`/configuration?${Constant.api_key}`)
}
export { getConfiguration }
You can view complete code https://github.com/SandipNirmal/React-Native-MovieDB.