Architecting a Mobile React Application with Custom Routing and UI Integration
To begin configuring the build environment, note that standard Create React App setups hide webpack configurations within node_modules/react-scripts. To customize alias resolution and preprocessors, the configuration must be exposed.
Build Configuration and Aliases
After exposing the build scripts, modify config/webpack.config.js. Locate the resolve section and update the alias field to support absolute imports using the @ symbol.
resolve: {
alias: {
'@': path.resolve(__dirname, '../src'),
...(isEnvProductionProfile && {
'react-dom$': 'react-dom/profiling',
'scheduler/tracing': 'scheduler/tracing-profiling',
}),
...(modules.webpackAliases || {}),
},
}
Styling and Directory Structure
Reorganize the source directory to separate logic, styles, and views. Create a styles folder for global SCSS files and a pages folder for view components.
src/styles/global.scss
@import '@/styles/reset.scss';
html {
@include background-color(#ffffff);
font-size: 100px; /* Base for rem */
}
src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import '@/styles/global.scss';
import MainLayout from '@/MainLayout';
import * as serviceWorker from '@/serviceWorker';
ReactDOM.render(<MainLayout />, document.getElementById('root'));
serviceWorker.unregister();
Layout Architecture
Define the primary application shell. In JSX, remember to use className instead of class. The layout should consist of a header, scrollable content area, and a fixed footer.
src/MainLayout.js
import React from 'react';
import { Switch, Route, NavLink } from 'react-router-dom';
import HomeView from '@/pages/Home';
import CategoryView from '@/pages/Category';
import CartView from '@/pages/Cart';
import ProfileView from '@/pages/Profile';
const MainLayout = () => {
return (
<div className="root-wrapper">
<div className="main-body">
<header className="top-bar">Application Header</header>
<div className="scroll-content">
<Switch>
<Route path="/home" component={HomeView} />
<Route path="/category" component={CategoryView} />
<Route path="/cart" component={CartView} />
<Route path="/profile" component={ProfileView} />
</Switch>
</div>
</div>
<nav className="tab-bar">
<ul>
<li>
<NavLink to="/home" activeClassName="is-active">
<span className="icon-home"></span>
<p>Home</p>
</NavLink>
</li>
<li>
<NavLink to="/category" activeClassName="is-active">
<span className="icon-category"></span>
<p>Category</p>
</NavLink>
</li>
<li>
<NavLink to="/cart" activeClassName="is-active">
<span className="icon-cart"></span>
<p>Cart</p>
</NavLink>
</li>
<li>
<NavLink to="/profile" activeClassName="is-active">
<span className="icon-user"></span>
<p>Profile</p>
</NavLink>
</li>
</ul>
</nav>
</div>
);
};
export default MainLayout;
src/styles/global.scss (Layout Styles)
html, body, #root, .root-wrapper {
height: 100%;
width: 100%;
}
.root-wrapper {
display: flex;
flex-direction: column;
.main-body {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
.top-bar {
height: 0.44rem;
background-color: #ff6666;
}
.scroll-content {
flex: 1;
overflow-y: auto;
}
}
.tab-bar {
height: 0.5rem;
background-color: #f5f5f5;
ul {
display: flex;
height: 100%;
li {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
a {
color: #666;
text-decoration: none;
display: flex;
flex-direction: column;
align-items: center;
&.is-active {
color: #ff6666;
}
}
}
}
}
}
UI Library Integration
For mobile interfaces, integrate a component library such as antd-mobile. Update public/index.html to include necessary polyfills for older Android devices and FastClick for touch response.
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" />
<script src="https://as.alipayobjects.com/g/component/fastclick/1.0.6/fastclick.js"></script>
<script>
if ('addEventListener' in document) {
document.addEventListener('DOMContentLoaded', function() {
FastClick.attach(document.body);
}, false);
}
</script>
Install dependencies and configure babel for on-demand loading.
npm install antd-mobile --save
npm install babel-plugin-import --save-dev
package.json
"babel": {
"presets": ["react-app"],
"plugins": [
["import", { "libraryName": "antd-mobile", "style": "css" }]
]
}
Data Fetching and Proxy Setup
To handle Cross-Origin Resource Sharing (CORS) during development, configure a proxy middleware.
src/setupProxy.js
const proxy = require('http-proxy-middleware');
module.exports = function (app) {
app.use(proxy('/api', {
target: 'https://api.example.com',
changeOrigin: true,
pathRewrite: { '^/api': '' }
}));
};
Encapsulate HTTP requests using axios. Create a utility module to handle responses uniformly.
src/utils/request.js
import axios from 'axios';
const request = axios.create({
baseURL: '/api',
timeout: 5000
});
export default request;
Home Page Implementation
Utilize the UI lbirary's Carousel component for banners and map through product data for the list.
src/pages/Home/index.js
import React, { useState, useEffect } from 'react';
import { Carousel } from 'antd-mobile';
import ProductList from '@/components/ProductList';
import { fetchBanners, fetchProducts } from '@/utils/api';
const HomeView = () => {
const [slides, setSlides] = useState([]);
const [products, setProducts] = useState([]);
useEffect(() => {
fetchBanners().then(res => setSlides(res.data));
fetchProducts().then(res => setProducts(res.data));
}, []);
return (
<div className="page-container">
<header className="page-header">Home</header>
<div className="page-content">
<Carousel autoplay infinite>
{slides.map(item => (
<a key={item.id} href="#" style={{ display: 'inline-block', width: '100%' }}>
<img src={item.imageUrl} alt="banner" style={{ width: '100%' }} />
</a>
))}
</Carousel>
<ProductList items={products} />
</div>
</div>
);
};
export default HomeView;
Authentication and Separate Layouts
Certain pages, such as Login, should not display the main navigation bar. Create a separate layout wrapper for authentication routes.
src/AuthShell.js
import React from 'react';
import { Switch, Route } from 'react-router-dom';
import LoginView from '@/pages/Login';
const AuthShell = () => {
return (
<div className="auth-wrapper">
<Switch>
<Route path="/auth/signin" component={LoginView} />
</Switch>
</div>
);
};
export default AuthShell;
Update the entry point to handle multiple layout structures based on the route path.
src/index.js
import { HashRouter as Router, Switch, Route } from 'react-router-dom';
import MainLayout from '@/MainLayout';
import AuthShell from '@/AuthShell';
ReactDOM.render(
<Router>
<Switch>
<Route path="/auth" component={AuthShell} />
<Route path="/" component={MainLayout} />
</Switch>
</Router>,
document.getElementById('root')
);
Login Form and Validation
Implement form state management for phone number and password validation. Use programmatic navigation to redirect upon success.
src/pages/Login/index.js
import React, { useState } from 'react';
import { useHistory } from 'react-router-dom';
import { doLogin } from '@/utils/api';
import './style.scss';
const LoginView = () => {
const [phone, setPhone] = useState('');
const [passcode, setPasscode] = useState('');
const [errorMsg, setErrorMsg] = useState('');
const history = useHistory();
const validatePhone = (val) => {
if (val.length !== 11) return 'Invalid phone format';
return '';
};
const handleLogin = async () => {
const phoneError = validatePhone(phone);
if (phoneError || !passcode) {
setErrorMsg(phoneError || 'Password required');
return;
}
try {
const res = await doLogin(phone, passcode);
if (res.code === 200) {
localStorage.setItem('token', res.token);
localStorage.setItem('isLogin', 'ok');
history.goBack();
} else {
setErrorMsg(res.message);
}
} catch (err) {
setErrorMsg('Network error');
}
};
return (
<div className="login-box">
<header className="page-header">Sign In</header>
<div className="login-content">
<input
type="text"
placeholder="Phone Number"
value={phone}
onChange={e => setPhone(e.target.value)}
/>
<input
type="password"
placeholder="Password"
value={passcode}
onChange={e => setPasscode(e.target.value)}
/>
<p className="error-msg">{errorMsg}</p>
<button className="submit-btn" onClick={handleLogin}>Sign In</button>
</div>
</div>
);
};
export default LoginView;
Profile State Management
Check local storage upon mounting the Profile component to determine if the user is authenticated. Render conditional content based on the login flag.
src/pages/Profile/index.js
import React, { useState, useEffect } from 'react';
const ProfileView = () => {
const [isLoggedIn, setIsLoggedIn] = useState(false);
useEffect(() => {
const status = localStorage.getItem('isLogin') === 'ok';
setIsLoggedIn(status);
}, []);
return (
<div className="page-container">
<header className="page-header">Profile</header>
<div className="page-content">
{isLoggedIn ? (
<div>Welcome back, User</div>
) : (
<div>
<button onClick={() => window.location.href = '/auth/signin'}>Sign In</button>
<button>Register</button>
</div>
)}
</div>
</div>
);
};
export default ProfileView;
SMS Verification Integration
For enhanced security, integrate SMS verification using a cloud provider SDK. Install the core library and configure access credentials securely via environment variables rather than hardcoding them.
npm install @alicloud/pop-core --save
Ensure access keys and secrets are stored in .env files and never committed to version control.