Mastering React Component Design: Practical Tips for Scalable CRM Apps
This article shares practical strategies for designing, dividing, and managing React components in a large‑scale CRM system, covering component hierarchy, state and props handling, data communication, common pitfalls, and best practices to improve maintainability and performance.
Preface
React, created by Facebook, has quickly become a core component‑based front‑end framework, making component‑driven development the new norm.
Our team maintains an internal CRM system with complex business logic. To improve development and maintenance efficiency, we use React as the main view framework, split the application by business modules into separate components, and keep the app modular, traceable, and controllable.
Below we discuss how to practice component‑based development in this CRM system to truly boost productivity.
Key Points of React Component Design
The official React documentation (e.g., "Thinking in React") already covers component design principles. In real‑world projects we have identified additional important points and solutions, which are described below.
[1] Official documentation "Thinking in React": https://reactjs.org/docs/thinking-in-react.html
How to Divide Components
For an enterprise back‑office application, routing is designed according to business modules, and top‑level components are derived from routes. To keep top‑level components completely isolated, we confirm the independence of each business module with product owners, allowing a direct mapping from business module → route → top‑level component → backend controller without any branching.
We adopt two division methods:
Route‑based division creates business components (top‑level components) that align with the data model.
UI‑based division creates presentation components that focus on visual rendering.
Benefits of this approach include:
Quickly locate and respond to changes in any business module.
Business components remain tightly coupled to their specific logic, while presentation components can be abstracted into reusable shared components.
Maintain a consistent data model, facilitating the introduction of state‑management libraries such as Redux or MobX.
For a React + Redux system, the folder structure might look like this:
├── api
│ ├── apiA
│ └── apiB
├── action
│ ├── actionA
│ └── actionB
├── reducer
│ ├── reducerA
│ └── reducerB
├── components
│ ├── ComponentA
│ │ ├── index // business component, aligns with route and data model, composes presentation components
│ │ └── other child component // presentation component, UI‑driven
│ └── ComponentB
│ ├── index
│ └── other child component
└── ...Managing Component State
React provides two ways to obtain component state: internal state and external props. Proper usage includes:
Using state
State holds the component’s own data (its "blood"). Each state change triggers a re‑render, which can affect performance, so keep components as stateless as possible. Only place data that directly influences UI (e.g., a flag, className, or a boolean) into state, typically of type Number, String, or Boolean.
Common scenarios for state:
Updating UI, such as toggling a button’s disabled state.
Storing results of conditional checks for rendering.
Holding form values.
// good – update via state
const { disabled } = this.state;
<button disabled={disabled}></button>
// bad – direct DOM manipulation
this.refs.btn.disabled = true; const { conditionA, conditionB } = this.state;
<div>
{conditionA && <ComponentA />}
{conditionB ? <ComponentB /> : <ComponentC />}
</div> state = { username: '' }
render() {
const { username } = this.state;
return (<Input value={username} onChange={v => this.setState({username: v})} />);
}Using props
Props act as the component’s external "blood supply". To use them safely:
Define expected prop types with PropTypes to document the interface.
Provide default values via defaultProps to improve fault tolerance.
class Com extends Component {
static PropTypes = { xxx };
static defaultProps = { xxx };
}Component Data Communication
Data communication occurs in two scenarios: parent‑child and cross‑component.
Parent‑child communication
Props and callback functions handle data flow. Child‑to‑parent communication should convey "what happened" rather than "what to do".
class ParentCom extends Component {
render() {
return <ChildCom onChange={childState => function(childState)} />;
}
}
class ChildCom extends Component {
render() {
return <Com onChange={() => this.props.onChange(childState)} />;
}
}Cross‑component communication
For large applications, common solutions include:
Flux‑style unidirectional data flow with Redux (dispatch actions to update a central store).
Custom event emitters for ad‑hoc data passing.
Redux is usually preferred for traceable data flow, but performance‑sensitive updates may benefit from event‑based communication.
Challenges in React Component Design
Applying React in real systems reveals several pitfalls that affect maintainability and collaboration.
Component Reuse and Decomposition
Strictly adhering to the Single‑Responsibility Principle helps create reusable components, but over‑abstraction can lead to tangled dependencies and difficult maintenance.
Changing state in one component may cascade to many others.
Business‑specific logic scattered across a generic component makes modifications time‑consuming.
Balancing reuse with clear separation (e.g., abstracting a generic Table component and building business‑specific ListA and ListB on top) eases future changes.
Controlling Component Granularity
Too coarse components cause code duplication; too fine components hinder extensibility. Practical guidelines:
Keep component trees 3‑5 levels deep to avoid communication overhead.
Extract reusable pieces such as basic controls, shared styles, and stable‑logic components.
Direct DOM Manipulation
When integrating non‑React libraries (e.g., ECharts), avoid raw DOM queries; prefer React‑compatible wrappers like Recharts.
// good
const ele = this.refs.chart;
// bad
const ele = document.getElementById('chart');Avoiding Side‑Effect‑Inducing State Operations
Mutating original data structures (pop, push, splice, etc.) breaks immutability and makes state changes hard to track.
// good
const data = [...this.props.data];
const newState = data.pop();
// bad
const newState = this.props.data.pop();Common Misuses of State
Typical mistakes include storing derived data in state, placing React components themselves in state, and duplicating props in state. Instead, compute derived values in render and rely on PropTypes for validation.
// good – compute in render
render() {
const data = computeFrom(this.props.xxx);
return <Com data={data} />;
}
// bad – store derived data in state
componentDidMount() {
const data = computeFrom(this.props.xxx);
this.setState({ data });
}
render() {
const { data } = this.state;
return <Com data={data} />;
}Conclusion
Just as the classic riddle "How do you put an elephant into a refrigerator?" illustrates the need for a clear key idea when tackling complex problems, component design requires a balance between abstraction and concrete business rules. By iteratively splitting and recombining the application, developers improve not only coding skills but also overall architectural thinking.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Baixing.com Technical Team
A collection of the Baixing.com tech team's insights and learnings, featuring one weekly technical article worth following.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
