ワンクリックで
add-feature
// Add a new feature or screen to the Ecency mobile app following established patterns
// Add a new feature or screen to the Ecency mobile app following established patterns
Add a new blockchain mutation wrapper using @ecency/sdk in the mobile app
Use an @ecency/sdk query in the mobile app or create a new app-specific query
Add a new bottom sheet (action sheet) to the mobile app
Review code changes for bugs, pattern violations, and common pitfalls in ecency-mobile
Debug common issues in the Ecency mobile app with known solutions and investigation patterns
| name | add-feature |
| description | Add a new feature or screen to the Ecency mobile app following established patterns |
| argument-hint | ["feature-name"] |
| disable-model-invocation | true |
Guide for adding a new feature to the Ecency mobile app.
The app uses two patterns for screens:
Many existing screens use class components with container/view separation:
src/screens/<feature>/
screen/<feature>Screen.tsx # Class component with business logic
screen/<feature>Styles.ts # EStyleSheet styles
Example: src/screens/transfer/screen/delegateScreen.tsx
New screens should use functional components with hooks:
src/screens/<feature>/
screen/<feature>Screen.tsx # Functional component
children/ # Sub-components
hooks/ # Custom hooks
Location: src/screens/<feature>/screen/<feature>Screen.tsx
import React from 'react';
import { View, Text } from 'react-native';
import { useIntl } from 'react-intl';
import { useQuery } from '@tanstack/react-query';
import { getSomeQueryOptions } from '@ecency/sdk';
import { BasicHeader } from '../../../components';
import { useAppSelector } from '../../../hooks';
import { selectCurrentAccount } from '../../../redux/selectors';
import styles from './<feature>Styles';
const FeatureScreen = ({ route, navigation }) => {
const intl = useIntl();
const currentAccount = useAppSelector(selectCurrentAccount);
const { data, isLoading } = useQuery(getSomeQueryOptions(currentAccount?.name));
return (
<View style={styles.container}>
<BasicHeader title={intl.formatMessage({ id: 'feature.title' })} />
{/* Screen content */}
</View>
);
};
export default FeatureScreen;
In src/constants/routeNames.ts:
export default {
SCREENS: {
// ... existing
FEATURE: 'Feature',
},
// ...
};
In src/navigation/stackNavigator.tsx:
import FeatureScreen from '../screens/<feature>/screen/<feature>Screen';
// Inside the Stack.Navigator:
<Stack.Screen name={ROUTES.SCREENS.FEATURE} component={FeatureScreen} />
import { useNavigation } from '@react-navigation/native';
import ROUTES from '../../constants/routeNames';
const navigation = useNavigation();
navigation.navigate(ROUTES.SCREENS.FEATURE, { /* params */ });
Add strings to src/config/locales/en-US.json:
{
"feature.title": "Feature Title",
"feature.description": "Some description"
}
Use with react-intl:
import { useIntl } from 'react-intl';
const intl = useIntl();
intl.formatMessage({ id: 'feature.title' });
Use react-native-extended-stylesheet with theme variables:
import EStyleSheet from 'react-native-extended-stylesheet';
export default EStyleSheet.create({
container: {
flex: 1,
backgroundColor: '$primaryBackgroundColor',
},
title: {
color: '$primaryBlack',
fontSize: 18,
fontWeight: 'bold',
},
subtitle: {
color: '$primaryDarkGray',
fontSize: 14,
},
});
Key theme variables:
$primaryBackgroundColor — main background$primaryLightBackground — card/section background$primaryBlack — primary text$primaryDarkGray — secondary text$primaryBlue — accent/link color$iconColor — icon tint$primaryRed — error/destructive| State type | Where | When |
|---|---|---|
| Blockchain/API data | TanStack Query via SDK query options | Always for server data |
| Global app state | Redux (src/redux/reducers/) | Auth, settings, UI state |
| Optimistic updates | Redux cache reducer | Vote caching, post metadata |
| Local component state | useState/useReducer | Form inputs, toggles |
import { useAppSelector, useAppDispatch } from '../../hooks';
import { selectCurrentAccount } from '../../redux/selectors';
import { someAction } from '../../redux/actions/someAction';
const currentAccount = useAppSelector(selectCurrentAccount);
const dispatch = useAppDispatch();
dispatch(someAction(payload));
Common components from src/components/:
BasicHeader — screen header with back buttonMainButton — primary action buttonTextInput — styled text inputUserAvatar — user profile pictureIcon — icon componentModal — modal dialogPostCard — post list itemProfileSummary — user profile headerrouteNames.tsstackNavigator.tsxen-US.jsonyarn lint passes