React native RNFS library - react-native

I have used react native RNFS library to access my mobile's file system . I am able to delete the file using file name. For example,
import React, { Component } from "react";
import { Text, View } from "react-native";
var RNFS = require("react-native-fs");
var path = RNFS.ExternalDirectoryPath + "/abc.png";
export default class HelloWorldApp extends Component {
render() {
return (
RNFS.unlink(path)
.then(() => {
console.log("FILE DELETED");
console.log(path);
})
.catch(err => {
console.log(err.message);
console.log(path);
})
);
}
}
Here, file with name abc.png will get deleted.
Question 1 - But suppose if want all files with a particular extension (like .txt,.png) to get deleted, then how can i achieve that ??
Question 2- using this code, Although I am able to delete the file but i am getting error in console .
Invariant Violation: Objects are not valid as a React child (found:
object with keys {_40, _65, _55, _72}). If you meant to render a
collection of children, use an array instead.
in HelloWorldApp (at renderApplication.js:34)
in RCTView (at View.js:45)
in View (at AppContainer.js:98)
in RCTView (at View.js:45)
in View (at AppContainer.js:115)
in AppContainer (at renderApplication.js:33)
I have used this documentation for writing code - https://github.com/itinance/react-native-fs

Delete files of a specific extension
It is possible to delete files with a specific extension it is a little involved but it is not too complicated. There are a few steps that we need to accomplish to make it happen.
Get a list of all the files in the directory
Filter the list so that only the files with the extension that we want to delete are left.
Unlink the files that are in our list.
So we could do something like this:
import React, {Component} from 'react';
import {Platform, StyleSheet, Text, View, Button} from 'react-native';
const RNFS = require('react-native-fs');
export default class App extends Component<Props> {
deleteFiles = async () => {
let ext = 'txt'; // extention that we want to delete
var re = /(?:\.([^.]+))?$/; // regex to get the file extension of the file
try {
let path = RNFS.DocumentDirectoryPath;
let fileList = await RNFS.readDir(path); // get all the files in the directory
if (fileList.length) {
let filesToDelete = fileList.filter(file => {
let extension = re.exec(file.name)[1] // gets the extension of the found file
return extension === ext; // check to make sure that each file has the extension that we are looking for
});
for (const file of filesToDelete) {
if (file.isFile()) { // only delete if it is a file
try {
await RNFS.unlink(file.path);
console.log(`Successfully deleted ${file.name}`);
} catch (deleteError) {
console.log(`Error deleting ${file.name}`);
}
}
}
}
} catch (err) {
console.warn(err);
}
}
render() {
return (
<View style={styles.container}>
<Button title={'delete files'} onPress={this.deleteFiles} />
</View>
);
}
}
This is a simple component that renders a button on screen. The deleteFiles function is called when the button is pressed. Currently it is set up to delete all files with the extension txt
Error: Invariant Violation: Objects are not valid as a React child
You are calling the function inside the return of your render method. You shouldn't be doing that. Either implement something like I have above where the files are deleted on the press of a button or the function to delete is called via a specific action, if you need to delete the file when the component is created then you should be doing that inside the componentDidMount rather than in the render function.
componentDidMount () {
RNFS.unlink(path)
.then(() => {
console.log("FILE DELETED");
console.log(path);
})
.catch(err => {
console.log(err.message);
console.log(path);
});
}

You can use readdir to get the file names and then pass the ones you want to delete to unlink
This error is because you are not returning a component. The render() method expects a react component as its return value.

When you return something from render(){} it need to beJSX, you cant render RNFS, because it's not JSX.
return (
RNFS.unlink(path)
.then(() => {
console.log("FILE DELETED");
console.log(path);
})
.catch(err => {
console.log(err.message);
console.log(path);
})
);
I'm not sure what you want to render, but that is why you are getting this error.
Objects are not valid as a React child means "you can't render an Object", to render something, it need to be a JSX
(e.g <Text>hey<Text>)

Related

How to load local audio files dynamically with expo av

i have a bunch of audio files local to my app and i want to load them dynamically based on a component's state, the only way i found to load the audio with expo av is to use "require", but this method keeps returning "invalid call" whenever i try to use a variable of any sort or any template literals in the path string in it.
i tried even storing the paths in a json file and then referrirng to the path directly there and still got the invalid call error.
const { sound } = await Audio.Sound.createAsync(require(audioPaths['paths'][fileKey]), {}, playbackStatusUpdate);
how do you guys go about this issue? my files are local so i can't take advantage of streaming/loading them from network. does expo av offer any alternative to the require method? i need any tips or advice you might have
PS: if you need any more details about the situation please ask me and i will fill you in
Edit: this is how my paths json looks like
{
"paths": [
"../assets/Records/1.mp3",
"../assets/Records/2.mp3",
"../assets/Records/3.mp3",
"../assets/Records/4.mp3"
]
}
The issue is related to audio paths not being declared as System.registerDynamic.
you should define paths in JSON like this
"paths": [
require('./assets/one.mp3'),
require('./assets/two.mp3'),
require('./assets/three.mp3'),
]
}
and call this without require,
const { sound } = await Audio.Sound.createAsync(audioPaths['paths'][fileKey], {}, playbackStatusUpdate);
here is a snack I used
Theoretically when you want to upload files in a react native app, you will use either formData, or fileupload or react-native-fs or expo-file-system.
I recommend you the expo-file-system since you use expo.
See complete implementation here
But saying i have a bunch of audio files local to my app means that your audio files are already uploaded into a directory in your project folder and just you want those audio to be played dynamically using the expo-av Audio.Sound.createAsync() with require(). This is how I would do that:
import * as React from 'react';
import { Text, View, StyleSheet, Button } from 'react-native';
import { Audio } from 'expo-av';
export default function App() {
const [sound, setSound] = React.useState();
async function playSound() {
console.log('Loading Sound');
const { sound } = await Audio.Sound.createAsync( require('./assets/Hello.mp3')
);
setSound(sound);
console.log('Playing Sound');
await sound.playAsync();
}
React.useEffect(() => {
return sound
? () => {
console.log('Unloading Sound');
sound.unloadAsync();
}
: undefined;
}, [sound]);
return (
<View style={styles.container}>
<Button title="Play Sound" onPress={playSound} />
</View>
);
}
This sample is for playing one audio, but in your question you want the audio to be played dynamically. For that you can only use react-native useEffect hook to create a kind of repeatable actions. I would first create a method playSound like this:
playSound = async () => {
await Audio.Sound.createAsync( require('' + source);
};
Here source is the path to an audio sent as variable and you may want to use function goToNext() and resumePlayList() to change the path of source variable like:
const goToNext = () => {
for(let i = 0; i < noGuest; i++){
source = JsonPath[i];
}

compiled application not loading in real device

For the life of me, I can't figure it out. All it shows is spinning without end and i am confused on the order of the life cycle happening. Basically, it goes to login or home screen and it works correctly on emulator but not on real device. I am on react 16.8.6 and react-native 0.60.5 environment.
I am getting started with RN and my debugging tools are not great. But for now just used Alert to see and the logic that was supposed to redirect to login/home screen is never reached. The Alerts shown are in the following order:
BS
mount2
render
mount1
My code is below: if the token exists, load home screen. else load auth screen is what I wanted to achieve but for now the line:
this.props.navigation.navigate(!goToLogin ? 'App' : 'Auth');
is never reached and so, spins a lot. Any help?
import React, {Component} from 'react';
import {StatusBar, View, Alert} from 'react-native';
import {
getUserToken,
loggedInToAssociation,
extractToken,
} from '../shared/loggedinUser';
import {setLanguage} from '../shared/localization';
import {appOptions} from '../config';
import Spinner from '../components/Spinner';
export default class AuthLoadingScreen extends Component {
constructor() {
super();
this.state = {
languageLoaded: false
};
}
componentDidMount() {
Alert.alert("mount1","oumnt1") // shown
loggedInToAssociation()
.then(details => {
// details is an array now
setLanguage(details['language']);
this.setState({languageLoaded: true});
Alert.alert("mount2","oumnt2") // SHOWN
})
.catch(err => {
setLanguage(appOptions.defaultLanguage);
this.setState({languageLoaded: true});
Alert.alert("mount3","oumnt3")
});
}
// Fetch the token from storage then navigate to our appropriate place
_bootstrapAsync = async () => {
const userToken = await getUserToken();
Alert.alert("bs","bs") // SHOWN
const tokenInfo = extractToken(userToken, 'both');
let goToLogin = true; // force user to go to the login page
if (tokenInfo.length == 2) {
goToLogin = false;
}
Alert.alert("bs2","bs2") // NEVER SHOWN
this.props.navigation.navigate(!goToLogin ? 'App' : 'Auth');
};
// Render any loading content that you like here
render() {
if (this.state.languageLoaded){
this._bootstrapAsync().then(s=>{
console.log(s)
}).catch(e=>{
console.log(e)
})
}
return (
<View>
<Spinner />
<StatusBar barStyle="default" />
</View>
);
}
}
did you check your debug console when running on device? There might be an unhandled promise rejection. The promise didn't go through but nowhere to handle the catch (consider try-catch scenario for this context).
It might be having a problem with this method.
extractToken(userToken, 'both')

Changing state in React native App.js from another component

I'm making authentication in an app, and I'm kind of stuck. I have 2 different navigations. One shows if the user is logged in and another one if not. Basically, a Sign in screen. It's working fine if I change the value manually upon the start. But I can't find a way to change a state when a user signs in, for example. Even though the value in auth module changes, it doesn't update in App.js So how can I update the App.js's state from Sign in screen, for example?
import React, { Component } from 'react';
import { AppRegistry, Platform, StyleSheet, Text, View } from 'react-native';
import DrawerNavigator from './components/DrawerNavigator'
import SignedOutNavigator from './components/SignedOutNavigator'
import auth from './auth'
type Props = {};
export default class App extends Component<Props> {
constructor(props) {
super(props)
this.state = {
isLoggedIn: auth.isLoggedIn
}
}
render() {
return (
(this.state.isLoggedIn) ? <DrawerNavigator /> : <SignedOutNavigator />
);
}
}
AppRegistry.registerComponent('App', () => App)
and my auth module, which is very simple
import { AsyncStorage } from 'react-native';
// try to read from a local file
let api_key
let isLoggedIn = false
function save_user_settings(settings) {
AsyncStorage.mergeItem('user', JSON.stringify(settings), () => {
AsyncStorage.getItem('user', (err, result) => {
isLoggedIn = result.isLoggedIn
api_key = result.api_key
});
isLoggedIn = true
});
}
module.exports.save_user_settings = save_user_settings
module.exports.api_key = api_key
module.exports.isLoggedIn = isLoggedIn
First off, there are loads of ways to approach this problem. Because of this I'm going to try explain to you why what you have now isn't working.
The reason this is happening is because when you assign auth.isLoggedIn to your isLoggedIn state, you are assigning the value once, kind of as a copy. It's not a reference that is stored.
In addition to this, remember, React state is generally only updated with setState(), and that is never being called here, so your state will not update.
The way I would approach this problem without bringing in elements like Redux, which is overkill for this problem by itself, is to look into building an authentication higher order component which handles all the authentication logic and wraps your entire application. From there you can control if you should render the children, or do a redirect.
Auth Component
componentDidMount() {
this._saveUserSettings(settings);
}
_saveUserSettings(settings) {
AsyncStorage.mergeItem('user', JSON.stringify(settings), () => {
AsyncStorage.getItem('user', (err, result) => {
isLoggedIn = result.isLoggedIn
api_key = result.api_key
});
this.setState({isLoggedIn: true});
});
}
render() {
const { isLoggedIn } = this.state;
return isLoggedIn ? this.props.children : null;
}
App.js
render() {
<AuthComponent>
//the rest of authenticated app goes here
</AuthComponent>
}
Here's a really quick, incomplete example. But it should showcase to you how you may want to lay your authentication out. You'll also want to consider error handling and such, however.

Packing js file to be inserted into React Native's WebView

I am creating a webview in a react native script, such as
<WebView source="https://www.google.com" />
However, I would like to inject a whole bunch of javascripts into the webview. The scripts to be included is located at let's say additionalScripts.js (which may import a whole bunch of dependencies such as jquery etc.
So intuitive I am trying to do
import additionalScripts from './additionalScripts.js'
<WebView source="https://www.google.com" injectedJavaScript={additionalScripts} />
Firstly it does not work.
Secondly, how do it make sure that the additionalScripts imported has been properly 'webpacked' and 'uglified' ?
Thanks
The problem is that injectedJavaScript needs a string, but the import actually executes the JS file and exports the result as an object.
To load the file as text, you can try something like React Native FS module, I learned about it from this answer.
// ...
import RNFS from require('react-native-fs');
// ...
class MyComponent extends Component {
constructor() {
super();
this.state = {
additionalScripts: null
};
}
componentDidMount() {
RNFS.readFile('./additionalScripts.js', 'utf8')
.then((contents) => {
this.setState({
additionalScripts: contents
});
});
}
render() {
if(!this.state.additionalScripts) {
return null;
}
return <WebView
source="https://www.google.com"
injectedJavaScript={additionalScripts} />;
}
}

React native: how to load AppRegistry component due to AsyncStorage

I want to load AppRegistry component due to AsyncStorage
this is the code in index.js:
import { AppRegistry } from 'react-native';
import { AsyncStorage } from 'react-native';
import index from './src/pages/LoginPage';
import HomeScreenRouter from './src/pages/CategoriesPage/index';
let page;
AsyncStorage.getItem("#token").then((value) => {
const token = value;
if(value === null) {
page = index;
} else {
page = HomeScreenRouter;
}
})
.then(res => {
});
AppRegistry.registerComponent('fatima2', () => page );
but I have an error that the app . doesn't found the AppRegistry component,
what should i do to solve this problem?
Corret usage of AsyncStorge is like that:
AsyncStorage.getItem('item', (err,result) => {
//Do your logic here
})
i dont know the main problem is this but, your usage may cause some issues.
Register a StactNavigator
in your AppRegistry. Add a new dummy page with empty view at the top of the navigator. So, when the app is opened the "DummyPage" will be displayed. At this stage, the StackNavigator will contain three pages i.e "DummyPage", "Index" and "HomeScreenRouter". Then read the value from Async Storage and on it's callback Reset your navigation stack
.
Then your StackNavigator will contain either "Index" or "HomeScreenRouter" screen.