Back to blogs
Blog
Writing in public
Longer posts — published here first, then cross-posted to Dev.to with a canonical URL back to this site.
Blog
Longer posts — published here first, then cross-posted to Dev.to with a canonical URL back to this site.
Back to blogs
Back to blogs
Postreact, reactnative, nodej, expo
One of React's best qualities is the ability to port it over to a react native app relatively quickly...
Date published

A guide to creating Android home screen widgets using Expo modules, complete with state management,...

This comprehensive guide will walk you through installing and configuring Android Studio on Linux...

Why You Might Still Pick Next.js Over TanStack Start From a TanStack Start...
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] (https://github.com/tigawanna/sockets-rn) 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
1create-expo-app -t expo-template-blank-typescript23cd 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
1import { StatusBar } from 'expo-status-bar';2import { StyleSheet, Text, View } from 'react-native';3import JoinRoom from './components/JoinRoom';4import { useState } from 'react';5import { getLocalStorageData } from './utils/storage';6import { useEffect } from 'react';7import UserContext from './utils/context';8import { User } from './utils/types';9import Chats from './components/Chats';10import Loading from './components/Loading';11import { useCountdownTimer } from 'use-countdown-timer';1213// let the_user:any14// const getUser = async()=>{15// the_user = await getLocalStorageData()16// }1718export default function App() {1920 const [user, setUser] = useState<User>({username:"",room:""});21 const updateUser = (new_user:User) => {setUser(new_user)};22 const [loading, setLoading] = useState(true);23 const [timeup, setTimeUp] = useState(true);24252627useEffect(()=>{28const timeout = setTimeout(() => {29setTimeUp (false);30}, 2000);3132getLocalStorageData()33 .then((res)=>{34 const local_user = res as User35 updateUser(local_user)36 if(!countdown){37 setLoading(false)38 }39 })404142 return () => {43 clearTimeout(timeout);44};4546 },[])474849const user_exists = user && user?.username !==""5051return (52 <View style={styles.container}>53 <View style={styles.status}>54 <StatusBar style="auto" />55 </View>5657 <View style={styles.chats}>58 {loading && timeup ?<Loading />:59 <UserContext.Provider value ={{user,updateUser}}>60 {user_exists?<Chats/>:<JoinRoom/>}61 </UserContext.Provider>}62 </View>63 </View>64 );65}6667const styles = StyleSheet.create({68 container: {69 flex: 1,70 backgroundColor: '#fff',71 alignItems: 'center',72 justifyContent: 'flex-end',73 height:'100%'7475 },76 status: {77 alignItems: 'center',78 justifyContent: 'flex-end',79 height:'5%',80 width:'100%',8182 },83 chats: {84 backgroundColor: '#fff',85 alignItems: 'center',86 justifyContent: 'flex-end',87 height:'95%',88 width:'100%',89 },9091});92
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
1import { Room, User } from "./types"2import { useRef,useState,useEffect } from 'react';3import socketIOClient,{ Socket } from 'socket.io-client';45const NEW_MESSAGE_ADDAED = "new_message_added";6const ROOM_DATA = "room_data";78const devUrl="http://localhost:4000"9const lanUrl="http://192.168.43.238:4000"10const prodUrl="https://sockets-server-ke.herokuapp.com/"11121314const useChats=(user:User)=>{1516const socketRef = useRef<Socket>();17const [messages, setMessages] = useState<any>([]);18const [room, setRoom] = useState<Room>({users:0,room:""});192021useEffect(() => {22socketRef.current = socketIOClient(prodUrl, {23query: { room:user.room,user:user.username },24transports: ["websocket"],25withCredentials: true,26extraHeaders:{"my-custom-header": "abcd"}2728})293031socketRef.current?.on(NEW_MESSAGE_ADDAED, (msg:any) => {32// //console.log("new message added==== ",msg)33setMessages((prev: any) => [msg,...prev]);34 });3536socketRef.current?.on(ROOM_DATA, (msg:any) => {37//console.log("room data ==== ",msg)38 setRoom(msg)});3940return () => {socketRef.current?.disconnect()};41}, [])4243const sendMessage = (message:any) => {44//console.log("sending message ..... === ",message)45 socketRef.current?.emit("new_message", message)46};4748495051return {room,messages,sendMessage}52}53export default useChats54
JoinRoom.tsx
1import { StyleSheet,View,Text} from 'react-native'2import React ,{useContext}from 'react'3import { useFormik } from 'formik';4import Button from './CustomButton';5import TextInput from './CustomInput';6import { storeLocalStorageData } from './../utils/storage';7import UserContext from './../utils/context';8import axios from 'axios';9import {LinearGradient} from 'expo-linear-gradient';1011import * as yup from 'yup'12import { useState } from 'react';13141516interface JoinRoomProps{1718}19const JoinRoom: React.FC<JoinRoomProps> = () => {202122 const devUrl="http://localhost:4000"23 const lanUrl="http://192.168.43.238:4000"24 const prodUrl="https://sockets-server-ke.herokuapp.com/"252627 const client = axios.create({ baseURL:prodUrl});28 const user = useContext(UserContext);2930const [error, setError] = useState({ name:"", message:"" });31const { handleChange, handleSubmit, values,errors,isSubmitting } = useFormik({3233 initialValues: { username:'',room:'general' },3435 onSubmit: values =>{36 const roomname = values.room?values.room.toLowerCase():"general"37 const username = values.username.toLowerCase()38 const room_data = {username,room:roomname}3940 client.post('/users', {user:room_data})41 .then( (response)=> {42 const user_exist =response.data.data43 console.log("user exists === ",user_exist,room_data)4445 if(user_exist){46 console.log("error block")47 setError({name:"username",message:"username exists"})48 errors.username = "username exists"49 }else{50 console.log("no error block")51 storeLocalStorageData(room_data)52 user.updateUser(room_data)53 }54555657 })58 .catch(function (error) {5960 });6162 }6364 })65 console.log("errors",errors.username)6667 const validationColor = "white"68 const textColor = "white"6970return (71 <View72 style={styles.container}>73 <LinearGradient colors={['#164e63', '#1b9999', '#851ea3']} style={styles.linearGradient}>74 <View style={styles.formbox}>7576 <TextInput onChangeText={handleChange('username')} value={values.username}77 validationColor={validationColor} textcolor={textColor}/>78 {/* {errors.username &&<Text style={{ fontSize: 15, color: 'yellow' }}>{errors.username}</Text>} */}79 {error.name==="username" &&<Text style={{ fontSize: 15, color: 'yellow' }}>{error.message}</Text>}80 <View style={styles.inputbuffer}></View>8182 <TextInput onChangeText={handleChange('room')} value={values.room}83 validationColor={validationColor} textcolor={textColor}84 />85 {error.name === "room" &&<Text style={{ fontSize: 15, color: 'yellow' }}>{error.message}</Text>}8687 <View style={styles.button}>88 <Button onPress={handleSubmit} label="JOIN" color={textColor} />89 </View>9091 </View>92 </LinearGradient>93</View>94 )95}9697export default JoinRoom9899const styles = StyleSheet.create({100 container:{101102 flex:1,103 width:'100%',104 height:"100%",105 marginTop:15106107108 },109 inputbuffer:{110 height:20,111 flexDirection:'column',112 justifyContent:'center',113 alignItems:'center',114 width:'100%'115},116linearGradient: {117 flex: 1,118 width:'100%',119 height:"100%",120 flexDirection:'column',121 justifyContent:'center',122 alignItems:'center',123124},125formbox:{126 flex:.5,127 height:100,128 backgroundColor:"#330033",129 flexDirection:'column',130 justifyContent:'center',131 alignItems:'center',132 width:'95%',133 borderRadius:10,134 elevation:7,135 shadowColor:'#00ff33',136 shadowOffset: {137 width: 5,138 height: 25,139 },140 shadowOpacity: .9,141 shadowRadius: 50.05,142 }143,144button:{145marginTop:20,146147148 }149150})
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] (https://github.com/tigawanna/sockets-rn) Built apk for android