Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Architecting a Mobile React Application with Custom Routing and UI Integration

Tech May 19 1

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.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.