↑の対応で@react-native-community/eslint-configを導入したら、
66:5 warning Do not use setState in componentDidMount react/no-did-mount-set-state 79:7 warning Do not use setState in componentDidUpdate react/no-did-update-set-state 51:9 warning Do not use setState in componentDidMount react/no-did-mount-set-state 60:9 warning Do not use setState in componentDidMount react/no-did-mount-set-state 43:5 warning Do not use setState in componentDidMount react/no-did-mount-set-state ✖ 25 problems (0 errors, 25 warnings)
ライフサイクルとsetState系のwarningが出るようになったので、今はReact Hooksに対応しつつwarningを減らしていっています。
React Hooksとは
React 16.8 で追加された新機能でクラスコンポーネントを書かなくても値管理ができるようになります。
置き換え
元のクラスコンポーネントが以下の通りです。。
class HomeScreen extends Component<Props, State> { static navigationOptions = ({ navigation, }: { navigation: NavigationScreenProp<NavigationRoute>; }) => { const { params = {} } = navigation.state; return { headerTitle: <LogoTitle />, headerStyle: { backgroundColor: theme().mode.header.backgroundColor, }, headerRight: ( <View style={styles.headerRight}> <Hint onPress={params.onPushCreatePlan} testID="ScheduleAdd"> <Feather name="plus" size={28} color={ darkMode() ? theme().color.highLightGray : theme().color.lightGreen } /> </Hint> </View> ), }; }; state = { refresh: '', mask: false, }; async componentDidMount() { this.props.navigation.setParams({ onPushCreatePlan: async () => { this.setState({ mask: false, }); await AsyncStorage.setItem('FIRST_CRAEATE_ITEM', 'true'); this.props.navigation.navigate('CreatePlan'); }, }); const mask = await AsyncStorage.getItem('FIRST_CRAEATE_ITEM'); this.setState({ mask: !mask, }); } render() { const refresh = this.props.navigation.getParam('refresh', ''); return ( <ItemsConsumer> {({ items, about, refreshData, itemsLoading }: ContextProps) => ( <ThemeConsumer> {({ rerendering, onFinishRerendering }: ThemeContextProps) => ( <> <HomeScreenPlan loading={Boolean(itemsLoading)} navigation={this.props.navigation} rerendering={rerendering} items={items} about={about} refresh={refresh} refreshData={refreshData} onFinishRerendering={onFinishRerendering} /> {this.state.mask && <View style={styles.mask} />} </> )} </ThemeConsumer> )} </ItemsConsumer> ); } }
Contextを書き換え
まずuseContextを使用してReactのContextを使用できるようにします。
この部分は
return ( <ItemsConsumer> {({ items, about, refreshData, itemsLoading }: ContextProps) => ( <ThemeConsumer> {({ rerendering, onFinishRerendering }: ThemeContextProps) => ( <HomeScreenPlan loading={Boolean(itemsLoading)} navigation={this.props.navigation} rerendering={rerendering} items={items} about={about} refresh={refresh} refreshData={refreshData} onFinishRerendering={onFinishRerendering} /> )} </ThemeConsumer> )} </ItemsConsumer> );
こんな感じに書き換えられます
const { items, about, refreshData, itemsLoading } = useContext(ItemsContext); const { rerendering, onFinishRerendering } = useContext(ThemeContext); return ( <HomeScreenPlan loading={Boolean(itemsLoading)} rerendering={rerendering} items={items} about={about} refresh={refresh} refreshData={refreshData} onFinishRerendering={onFinishRerendering} /> );
componentDidMountを書き換え
次はcomponentDidMountを書き換えます
async componentDidMount() { this.props.navigation.setParams({ onPushCreatePlan: async () => { this.setState({ mask: false, }); await AsyncStorage.setItem('FIRST_CRAEATE_ITEM', 'true'); this.props.navigation.navigate('CreatePlan'); }, }); const mask = await AsyncStorage.getItem('FIRST_CRAEATE_ITEM'); this.setState({ mask: !mask, }); }
ここではuseStateとuseEffectを使用します
こんな感じに書き換えられます
const [state, setState] = useState<HomeScreeState>({ mask: false }); useEffect(() => navigation.setParams({ onPushCreatePlan: async () => { setState({ mask: false }); await AsyncStorage.setItem('FIRST_CRAEATE_ITEM', 'true'); navigation.navigate('CreatePlan'); }, }); const checkMask = async () => { const m = await AsyncStorage.getItem('FIRST_CRAEATE_ITEM'); setState({ mask: !m }); }; checkMask(); , []);
navigationOptionsの置き換え
react-navigationのnavigationOptionsを置き換えていきます。 元はこんな感じ
class HomeScreen extends Component<Props, State> { static navigationOptions = ({ navigation, }: { navigation: NavigationScreenProp<NavigationRoute>; }) => { const { params = {} } = navigation.state; return { headerTitle: <LogoTitle />, headerStyle: { backgroundColor: theme().mode.header.backgroundColor, }, headerRight: ( <View style={styles.headerRight}> <Hint onPress={params.onPushCreatePlan} testID="ScheduleAdd"> <Feather name="plus" size={28} color={ darkMode() ? theme().color.highLightGray : theme().color.lightGreen } /> </Hint> </View> ), }; };
これは、ComponentのPropertyに追加すればOK
HomeScreen.navigationOptions = ({ navigation }: NavigationOptions) => { const { params = {} } = navigation.state; return { headerTitle: <LogoTitle />, headerStyle: { backgroundColor: theme().mode.header.backgroundColor, }, headerRight: ( <View style={styles.headerRight}> <Hint onPress={params.onPushCreatePlan} testID="ScheduleAdd"> <Feather name="plus" size={28} color={ darkMode() ? theme().color.highLightGray : theme().color.lightGreen } /> </Hint> </View> ), }; };
これでトップのコンポーネントの置き換えは完了です。 次にクリックなどのハンドラー関数を持つコンポーネントの置き換えについては、こちの通りです。
■ 元のコード
export class HomeScreenPlan extends Component<PlanProps, PlanState> { (略) onSchedule = (id: string, title: string) => { this.props.navigation.navigate('Schedule', { itemId: id, title }); }; render() { return ( <Page data={items} loading={this.props.loading} onSchedule={this.onSchedule} onDelete={this.onDelete} /> );
この場合は、memoとuseCallbackを使用します
■ 置き換え後のコード
const HomeScreenPlan = memo((props: PlanProps) => { (略) const onSchedule = useCallback( (id: string, title: string) => { navigate('Schedule', { itemId: id, title }); }, [navigate] ); return ( <Page data={items} loading={props.loading} onSchedule={onSchedule} onDelete={onDelete} /> ); });
こうすることで、レンダリングごとにファンクションを生成し直さなくて良くなります。
pull request
この方法で1ファイルを修正したpull request