Converting your reactjs site to react native app
Tags: react, reactnative, nodej, expo
Published: 2022-07-23 10:42:29

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](https://dev.to/tigawanna/socketio-react-chat-app-3)
[code repo] (https://github.com/tigawanna/sockets-rn)
[Built apk for android](https://drive.google.com/file/d/1GMfPdDjFU3Uepw8I6FrAUbkGQ-k_trOR/view?usp=drivesdk)

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](https://docs.expo.dev/get-started/create-a-new-app/)

```js
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](https://react-native-async-storage.github.io/async-storage/docs/install)

App.tsx
```js
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

```js
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

```js
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](https://www.npmjs.com/package/react-native-linear-gradient)

And there seems to be a way to use tailwindcss in react native [article link](https://www.npmjs.com/package/react-native-linear-gradient) 
I'll try it but let me know if you beat me to it .

[code repo] (https://github.com/tigawanna/sockets-rn)
[Built apk for android](https://drive.google.com/file/d/1GMfPdDjFU3Uepw8I6FrAUbkGQ-k_trOR/view?usp=drivesdk)

[reactjs client repo](https://github.com/tigawanna/sockets-client)
[server repo](https://github.com/tigawanna/sockets-server)