dva: Elm-style React/Redux Framework
Dva is a lightweight, Elm-style front-end framework designed for building single-page applications by integrating React, Redux, React Router, and Redux Saga into a cohesive development experience. It simplifies state management and side effects through a declarative model system and streamlined data flow, aiming to reduce boilerplate typically associated with Redux setups. The package's last stable release, version 2.4.1, dates back to 2018, with subsequent beta versions for 2.6.0 released in late 2019. The project currently exhibits an abandoned release cadence, with no further stable releases or active development beyond 2019. Its key differentiators include an opinionated, "batteries-included" approach that pre-configures major React ecosystem libraries and its model-centric architecture for managing application state and logic.
Common errors
-
Invariant Violation: Dva requires React and React-DOM to be installed as peer dependencies.
cause `react` or `react-dom` are missing from your project's `package.json` dependencies, or their installed versions do not satisfy Dva's peer dependency range.fixInstall compatible versions of React and ReactDOM: `npm install react@^16.0.0 react-dom@^16.0.0` or `yarn add react@^16.0.0 react-dom@^16.0.0`. -
Error: Invariant failed: Models require a `namespace` property.
cause A Dva model object was registered without a required `namespace` string property, which uniquely identifies the model in the Redux store.fixAdd a unique `namespace` string property to your Dva model definition, e.g., `app.model({ namespace: 'myModel', ... });`. -
TypeError: Cannot read properties of undefined (reading 'dispatch') OR dispatch is not a function
cause You are attempting to call `dispatch` in a React component that has not been properly connected to the Dva/Redux store, or in a context where `dispatch` is not implicitly available.fixEnsure your component is wrapped with Dva's `connect` higher-order component, which injects `dispatch` as a prop: `export default connect((state) => ({ ...state }))(MyComponent);`.
Warnings
- breaking The `dva` project is no longer actively maintained with stable releases since 2018. New features, bug fixes, or security patches are unlikely to be released. This impacts long-term viability and security.
- gotcha Dva has strict peer dependencies on `react@15.x || ^16.0.0-0` and `react-dom@15.x || ^16.0.0-0`. Using React 17 or 18 with Dva is not officially supported and can lead to unexpected runtime issues or compatibility problems due to changes in React's lifecycle and rendering mechanisms.
- gotcha Dva's internal router (exposed via `dva/router`) is based on `react-router-dom` v4.x or v5.x. It is not compatible with `react-router-dom` v6.x, which introduced significant API changes. Direct integration with `react-router-dom` v6 will break Dva's routing functionality.
Install
-
npm install dva -
yarn add dva -
pnpm add dva
Imports
- dva
const dva = require('dva'); // CommonJS is also correct for older projects or Node, but ESM is preferred in modern frontend.import dva from 'dva';
- connect
import { connect } from 'react-redux'; // While re-exported, dva exports it directly for convenience.import { connect } from 'dva'; - routerRedux
import { routerRedux } from 'dva'; - Route, Router, Switch
import { Router, Route, Switch } from 'dva/router';
Quickstart
import dva, { connect } from 'dva';
import React from 'react';
import ReactDOM from 'react-dom';
import { Router, Route, Switch } from 'dva/router';
// 1. Initialize
const app = dva();
// 2. Model
app.model({
namespace: 'count',
state: { current: 0 },
reducers: {
add(state) { return { current: state.current + 1 }; },
minus(state) { return { current: state.current - 1 }; },
},
effects: {
*addAsync(action, { call, put }) {
yield call(() => new Promise(resolve => setTimeout(resolve, 1000)));
yield put({ type: 'add' });
},
},
subscriptions: {
setup({ dispatch, history }) {
history.listen(({ pathname }) => {
if (pathname === '/count') {
console.log('Entered count page');
dispatch({ type: 'add' }); // Initial increment when entering
}
});
},
},
});
// 3. View
function CountPage({ count, dispatch }) {
return (
<div>
<h2>Count: {count.current}</h2>
<button onClick={() => dispatch({ type: 'add' })}>+</button>
<button onClick={() => dispatch({ type: 'minus' })}>-</button>
<button onClick={() => dispatch({ type: 'addAsync' })}>Add Async</button>
</div>
);
}
const ConnectedCountPage = connect(({ count }) => ({ count }))(CountPage);
// 4. Router
app.router(({ history }) => (
<Router history={history}>
<Switch>
<Route path="/" exact component={() => <div>Home Page <br/><a href="/count">Go to Count</a></div>} />
<Route path="/count" component={ConnectedCountPage} />
</Switch>
</Router>
));
// 5. Start
// Ensure you have a div with id="root" in your HTML
app.start('#root');