DEV Community

Cover image for Converting your reactjs site to react native app
Dennis kinuthia
Dennis kinuthia

Posted on

Converting your reactjs site to react native app

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
Enter fullscreen mode Exit fullscreen mode

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%',
  },

});

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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,


 }

})
Enter fullscreen mode Exit fullscreen mode

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 .

code repo
Built apk for android

reactjs client repo
server repo

Top comments (1)

Collapse
 
willemj profile image
WillemJ

Thank you, it helped me a lot to start a little app from scratch!