Converting your reactjs site to react native app
Dennis kinuthia
Posted on July 23, 2022
One of React's best qualities is the ability to port it over to a react native app relatively quickly since most of the business logic is the same .
Now should you build an app from the ground app using react native , i don’t know the bundle size might be off putting to a new user i. If your application is performance reliant then definitely consider the kotlin/swift native way.
I would go as far as not recommending it to anything but converting a react site to a native app in the case where a native app wasn’t on the budget to begin with.
So let’s convert this live chat react app react app
code repo
Built apk for android
to react native, this took me less than a day to do and it would probably take way longer if i tried it in kotlin.
i used expo docs
create-expo-app -t expo-template-blank-typescript
cd my-app
the only major change in the expo version is that local storage is not available so i used @react-native-async-storage/async-storage
App.tsx
import { StatusBar } from 'expo-status-bar';
import { StyleSheet, Text, View } from 'react-native';
import JoinRoom from './components/JoinRoom';
import { useState } from 'react';
import { getLocalStorageData } from './utils/storage';
import { useEffect } from 'react';
import UserContext from './utils/context';
import { User } from './utils/types';
import Chats from './components/Chats';
import Loading from './components/Loading';
import { useCountdownTimer } from 'use-countdown-timer';
// let the_user:any
// const getUser = async()=>{
// the_user = await getLocalStorageData()
// }
export default function App() {
const [user, setUser] = useState<User>({username:"",room:""});
const updateUser = (new_user:User) => {setUser(new_user)};
const [loading, setLoading] = useState(true);
const [timeup, setTimeUp] = useState(true);
useEffect(()=>{
const timeout = setTimeout(() => {
setTimeUp (false);
}, 2000);
getLocalStorageData()
.then((res)=>{
const local_user = res as User
updateUser(local_user)
if(!countdown){
setLoading(false)
}
})
return () => {
clearTimeout(timeout);
};
},[])
const user_exists = user && user?.username !==""
return (
<View style={styles.container}>
<View style={styles.status}>
<StatusBar style="auto" />
</View>
<View style={styles.chats}>
{loading && timeup ?<Loading />:
<UserContext.Provider value ={{user,updateUser}}>
{user_exists?<Chats/>:<JoinRoom/>}
</UserContext.Provider>}
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'flex-end',
height:'100%'
},
status: {
alignItems: 'center',
justifyContent: 'flex-end',
height:'5%',
width:'100%',
},
chats: {
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'flex-end',
height:'95%',
width:'100%',
},
});
we'll also add a timeout for 2 seconds because the async storage takes a few seconds to check the local storage.
the useChsts hook is completetly the same
import { Room, User } from "./types"
import { useRef,useState,useEffect } from 'react';
import socketIOClient,{ Socket } from 'socket.io-client';
const NEW_MESSAGE_ADDAED = "new_message_added";
const ROOM_DATA = "room_data";
const devUrl="http://localhost:4000"
const lanUrl="http://192.168.43.238:4000"
const prodUrl="https://sockets-server-ke.herokuapp.com/"
const useChats=(user:User)=>{
const socketRef = useRef<Socket>();
const [messages, setMessages] = useState<any>([]);
const [room, setRoom] = useState<Room>({users:0,room:""});
useEffect(() => {
socketRef.current = socketIOClient(prodUrl, {
query: { room:user.room,user:user.username },
transports: ["websocket"],
withCredentials: true,
extraHeaders:{"my-custom-header": "abcd"}
})
socketRef.current?.on(NEW_MESSAGE_ADDAED, (msg:any) => {
// //console.log("new message added==== ",msg)
setMessages((prev: any) => [msg,...prev]);
});
socketRef.current?.on(ROOM_DATA, (msg:any) => {
//console.log("room data ==== ",msg)
setRoom(msg)});
return () => {socketRef.current?.disconnect()};
}, [])
const sendMessage = (message:any) => {
//console.log("sending message ..... === ",message)
socketRef.current?.emit("new_message", message)
};
return {room,messages,sendMessage}
}
export default useChats
JoinRoom.tsx
import { StyleSheet,View,Text} from 'react-native'
import React ,{useContext}from 'react'
import { useFormik } from 'formik';
import Button from './CustomButton';
import TextInput from './CustomInput';
import { storeLocalStorageData } from './../utils/storage';
import UserContext from './../utils/context';
import axios from 'axios';
import {LinearGradient} from 'expo-linear-gradient';
import * as yup from 'yup'
import { useState } from 'react';
interface JoinRoomProps{
}
const JoinRoom: React.FC<JoinRoomProps> = () => {
const devUrl="http://localhost:4000"
const lanUrl="http://192.168.43.238:4000"
const prodUrl="https://sockets-server-ke.herokuapp.com/"
const client = axios.create({ baseURL:prodUrl});
const user = useContext(UserContext);
const [error, setError] = useState({ name:"", message:"" });
const { handleChange, handleSubmit, values,errors,isSubmitting } = useFormik({
initialValues: { username:'',room:'general' },
onSubmit: values =>{
const roomname = values.room?values.room.toLowerCase():"general"
const username = values.username.toLowerCase()
const room_data = {username,room:roomname}
client.post('/users', {user:room_data})
.then( (response)=> {
const user_exist =response.data.data
console.log("user exists === ",user_exist,room_data)
if(user_exist){
console.log("error block")
setError({name:"username",message:"username exists"})
errors.username = "username exists"
}else{
console.log("no error block")
storeLocalStorageData(room_data)
user.updateUser(room_data)
}
})
.catch(function (error) {
});
}
})
console.log("errors",errors.username)
const validationColor = "white"
const textColor = "white"
return (
<View
style={styles.container}>
<LinearGradient colors={['#164e63', '#1b9999', '#851ea3']} style={styles.linearGradient}>
<View style={styles.formbox}>
<TextInput onChangeText={handleChange('username')} value={values.username}
validationColor={validationColor} textcolor={textColor}/>
{/* {errors.username &&<Text style={{ fontSize: 15, color: 'yellow' }}>{errors.username}</Text>} */}
{error.name==="username" &&<Text style={{ fontSize: 15, color: 'yellow' }}>{error.message}</Text>}
<View style={styles.inputbuffer}></View>
<TextInput onChangeText={handleChange('room')} value={values.room}
validationColor={validationColor} textcolor={textColor}
/>
{error.name === "room" &&<Text style={{ fontSize: 15, color: 'yellow' }}>{error.message}</Text>}
<View style={styles.button}>
<Button onPress={handleSubmit} label="JOIN" color={textColor} />
</View>
</View>
</LinearGradient>
</View>
)
}
export default JoinRoom
const styles = StyleSheet.create({
container:{
flex:1,
width:'100%',
height:"100%",
marginTop:15
},
inputbuffer:{
height:20,
flexDirection:'column',
justifyContent:'center',
alignItems:'center',
width:'100%'
},
linearGradient: {
flex: 1,
width:'100%',
height:"100%",
flexDirection:'column',
justifyContent:'center',
alignItems:'center',
},
formbox:{
flex:.5,
height:100,
backgroundColor:"#330033",
flexDirection:'column',
justifyContent:'center',
alignItems:'center',
width:'95%',
borderRadius:10,
elevation:7,
shadowColor:'#00ff33',
shadowOffset: {
width: 5,
height: 25,
},
shadowOpacity: .9,
shadowRadius: 50.05,
}
,
button:{
marginTop:20,
}
})
the only big difference here is that we're using formik which is optonal and you can still use a plain regular form , you can also use formik on the website if you so desire
the other one is the linear gradient component to mimick the website's gradient color effect which was accomplished by a few tailwindcss classes
react linear gradient
And there seems to be a way to use tailwindcss in react native article link
I'll try it but let me know if you beat me to it .
Posted on July 23, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.