react-nativerecoiljs

Create TodoList using react-native and recoil. a problem occurred


This is my code However, after entering a value as an input and passing the value to a button, this error occurred.

Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.

However, I deleted the changing state and it worked.

I couldn't figure out where or what I had missed. help me

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 *
 * @format
 * @flow strict-local
 */

import { Alert } from 'react-native';
import {
  SafeAreaView,
  ScrollView,
  StatusBar,
  StyleSheet,
  TextInput,
  Button,
  useColorScheme,
  View,
} from 'react-native';

import {
  Colors,
} from 'react-native/Libraries/NewAppScreen';
import { RecoilRoot, useRecoilValue, useRecoilState, useSetRecoilState } from 'recoil';
import { selectStatus, selectToDo, toDos } from './store/toDo';
import { useState } from 'react';

const Apps = () => {

  const toDoAtom = useRecoilValue(toDos);
  const setToDoAtom = useSetRecoilState(toDos)
  const [nowStatus, setStatus]= useRecoilState(selectStatus)
  const [toDo, onChangeToDo] = useState('')
  const [selectToDos, setNewToDos] = useRecoilState(selectToDo);
  const [id, setId] = useState(1)

  const isDarkMode = useColorScheme() === 'dark';

  const backgroundStyle = {
    backgroundColor: isDarkMode ? Colors.darker : Colors.lighter,
  };

  const onSetData = () => {
    const newToDoList = [
      ...toDoAtom,
      {
        id:id,
        contents:toDo,
        status: "DOING"
      }
    ]
    setToDoAtom(newToDoList)
    setId((id)=>id+1)
    onChangeToDo("")
    Alert.alert('submit')
  }

  const onChangeStatue = (data) => {

    const targetIndex = data.id
    var lists = [
      ...toDoAtom.slice(0, targetIndex),
      {id:data.id, contents:data.contents, status:"DONE"},
      ...toDoAtom.slice(targetIndex + 1)
    ]

    setToDoAtom(lists)

  }
  const onRest = () => {
    console.log(toDoAtom)
    setNewToDos([])
    setId(1)
  }

  const onStatus = () => {
    if(nowStatus == "DOING"){
      setStatus("DONE")
    }else if(nowStatus == "DONE"){
      setStatus("DOING")
    }
  }

  return (
    <RecoilRoot>
      <SafeAreaView style={backgroundStyle}>
        <StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} />
        <ScrollView
          contentInsetAdjustmentBehavior="automatic"
          style={backgroundStyle}>
          <View
            style={{
              backgroundColor: isDarkMode ? Colors.black : Colors.white,
            }}>
              <View>
              <TextInput
                style={styles.input}
                onChangeText={onChangeToDo}
                value={toDo}
              />
              <Button title='submit' color="blue" onPress={onSetData} ></Button>
              <Button
                title = "Reset"
                color = "red"
                onPress={onRest}
              />
              </View>
              <Button title={nowStatus} onPress={onStatus}/>
                {selectToDos?.map((hi)=>(
                  <View key={hi.id}>
                    <Button title={hi.contents} onPress={onChangeStatue(hi)} ></Button>
                  </View>
                ))} 
          </View>
        </ScrollView>
      </SafeAreaView>
    </RecoilRoot>
  );
};

export const App = () => {
  return(
    <RecoilRoot>
      <Apps/>
    </RecoilRoot>
  )
}

const styles = StyleSheet.create({
  input: {
    height: 40,
    margin: 12,
    borderWidth: 1,
    padding: 10,
  },
});

export default App;
import { selector, atom } from 'recoil'

export const status = atom({
    key:'status',
    default:'DOING'
})

export const toDos = atom({
    key:'toDos',
    default:[]
})

export const selectToDo = selector({
    key:'selectToDo',
    get: ({ get }) => {
        const originalToDos = get(toDos);
        const nowStatus = get(status)
        return originalToDos.filter((toDo) => toDo.status === nowStatus);
    },
})

export const selectStatus = selector({
    key:'selectStatus',
    get : ({get}) => {
        return get(status)
    },
    set: ({set}, selectStatus) => {
        set(status, selectStatus)
    }
})

After selecting the status with fliter and showing the value, I tried to change the Doing status to Done.


Solution

  • You have a functional component that renders on every state or prop change. Each time it renders, the button's onPress method is calling the state change function when it renders.

    <View key={hi.id}>
     <Button title={hi.contents} onPress={onChangeStatue(hi)} ></Button>
    </View>
    

    onPress should receive a callable function, not an invoked function:

    <View key={hi.id}>
     <Button title={hi.contents} onPress={() => onChangeStatue(hi)} ></Button>
    </View>