How to edit item in a Flatlist - javascript

I made a Flatlist and and edit button that can open a modal to a text input and track down the key of the specific item. How do I update this item in the Flatlist with the different text input. I tried doing something with the setJournal but I don't know how to return it with the edited entry.
export default function HomeScreen({ navigation }) {
const [journal, setJournal] = useState([
{ date: "12-dec22", entry: "good day", key: "1" },
{ date: "12-dec22", entry: "bad day", key: "2" },
]);
const [editModal, setEditModal] = useState(false);
const handleEditPress = (key) => {
const currentJournal = journal.find((journn) => {
return journn.key === key;
});
setEditModal(true);
console.log(key);
};
const updateEntry = (key, entry) => {
if (journal.key === key) {
setJournal((currentJournal) => {
return [entry, ...currentJournal];
});
} else {}
journal = journal.map((journn) =>
journn.key === key ? { ...journn, ...updateEntry } : journn
);
};
return (
<View>
<Modal visible={editModal}>
<TextInput onChangeText={() => updateEntry()} />
<MaterialIcons name="close" onPress={() => setEditModal(false)} />
</Modal>
<View>
<MaterialIcons onPress={() => setModalOpen(true)}/>
<MaterialIcons onPress={() => deleteAll()} />
</View>
<FlatList
data={journal}
renderItem={({ item }) => (
<View style={styles.flatlistView}>
<TouchableOpacity>
<View>
<MaterialIcons onPress={() => handleEditPress(item.key)}/>
</View>
</TouchableOpacity>
</View>
)}
/>
</View>
);
}

const handleEditPress = (editedField, itemKey)=>{
journal.filter((currentItem.key)=>{(currentItem.key) === itemKey})).text = editedField
setJournal(journal)
}

Related

How to prevent scroll to top after deleting an item or filtering data flatlist

I am building a flat list with infinite scrolling and a delete items feature.
But every time I delete an item (using a filter) my flat list scrolls to the top.
Delete Function
const deleteItemById = async entry => {
const filteredData = data.filter(item => {
return item.id != entry.id;
});
await setNotificationData(filteredData);
};
Delete Button
<IconButton
onPress={() => {
const notificationToken = globalVariable.token;
const deleteNotificationAPI = async () => {
try {
await axios.delete(`${BASE_URL}${data.id}`,
{
headers: {
Authorization: notificationToken,
},
}
);
} catch (err) {
console.error(err);
}
deleteItemById(data);
};
deleteNotificationAPI();
}}
style={IconButton7a33adf1}
icon={'Foundation/trash'}
size={25}
color={theme.colors.trashCan}
/>
FlatList
This is the flat list that I have been using with renderItem function. It is working fine, deleting the items, it is just scrolling to the top after the filter, I believe this is happening because is rerendering the flatList.
<FlatList
listKey={'s8qdyDKI'}
data={data}
initialNumToRender={30}
windowSize={40}
key={data.length}
ListFooterComponent={<LoadNotifications />}
keyExtractor={item => item?.id || item?.uuid || item}
renderItem={({ item }) => (
<Notifications
data={item}
style={notificationStyles.item}
/>
)}
onEndReached={oneMoreNotificationPage}
onEndReachedThreshold={0.15}
contentContainerStyle={
notificationStyles.FlatList21f2035aContent
}
numColumns={1}
renderItem
function Notifications({ data }) {
return (
<>
<View
style={[notificationStyles.Viewcd452035,
]}
>
<View
style={[notificationStyles.Viewb8b059f4,]}
>
<View
style={[
notificationStyles.Viewb34b4333,]}
>
<Text
style={[
notificationStyles.Text812c3e6d,]}
ellipsizeMode={'tail'}
numberOfLines={1}
>
{data?.title}
</Text>
</View>
<View style={notificationStyles.Viewdebd3207}>
<View style={notificationStyles.View3821c683}>
<Text
style={[
notificationStyles.Text13ddebce,]}
numberOfLines={1}
ellipsizeMode={'tail'}
>
{data?.created_at}
</Text>
</View>
<View style={notificationStyles.Viewc2817b4e}>
<IconButton
onPress={() => {
const notificationToken =
globalVariable.token;
const deleteNotificationAPI = async () =>
{
try {
await axios.delete(
`${BASE_URL}${data?.id}`,
{
headers: {
Authorization:
notificationToken,
},
}
);
} catch (err) {
console.error(err);
}
deleteItemById(data);
console.log('after call');
};
deleteNotificationAPI();
}}
style=
{notificationStyles.IconButton7a33adf1}
icon={'Foundation/trash'}
size={25}
color={theme.colors.trashCan}
/>
</View>
</View>
</View>
</View>
</View>
)}
</>
);
}

How to output only 5 elements in a FlatList from JSON? React-native

I have a JSON file where new news items are constantly stored and added, their number is constantly changing. I want to output only the last 3 elements from this JSON file to the FlatList. How to do it?
Here is my JSON receipt and my FlatList:
const [isLoading, setLoading] = useState(true);
const [data, setData] = useState([]);
const getNewsMain = async () => {
try {
const response = await fetch(`https://cdn.ertil-gorod.ru/json/ertnews.json?nocache`);
const json = await response.json();
setData(json.ertnews);
} catch (error) {
console.error(error);
} finally {
setLoading(false);
}
}
useEffect(() => {
getNewsMain();
}, []);
<FlatList
data={data}
initialNumToRender={5}
keyExtractor={({ id }, index) => id}
renderItem={ ( { item } ) => (
<TouchableOpacity
style={ styles.ert__news__item }
onPress={ ( ) => navigation.navigate( 'FullNews', item ) }>
<Image
style={ styles.ert__image__news }
source={ { uri: item.img } }
/>
<Text style={ styles.ert__title__news }>{ item.name }</Text>
<Text style={ styles.ert__anons__news }>{ item.anons }</Text>
</TouchableOpacity>
)} />
If data is an array you can use the Array.slice
<FlatList
data={data.slice(-3)}
initialNumToRender={5}
keyExtractor={({ id }, index) => id}
renderItem={ ( { item } ) => (
<TouchableOpacity
style={ styles.ert__news__item }
onPress={ ( ) => navigation.navigate( 'FullNews', item ) }>
<Image
style={ styles.ert__image__news }
source={ { uri: item.img } }
/>
<Text style={ styles.ert__title__news }>{ item.name }</Text>
<Text style={ styles.ert__anons__news }>{ item.anons }</Text>
</TouchableOpacity>
)}
/>

How to pass props to FlatList with renderScene in react-native-tab-view?

I'm a newbie in React Native and trying to pass props to ListHeaderComponent in FlatList
Here's the code:
const FirstRoute = (props) => {
const _renderHeader = () => {
return(
<View>
{props.isFollowed &&
<TouchableOpacity onPress={props.onSubmit}>
<Text>You have followed this person</Text>
</TouchableOpacity> }
</View>
)
}
return(
<View style={[styles.scene, { backgroundColor: '#ff4081' }]}>
<FlatList
data={data}
keyExtractor={item => item.id}
renderItem={itemData => ( <Image source={itemData.item.id} style={{height: WIDTH/3, width: WIDTH/3}} />)}
ListHeaderComponent={_renderHeader}
/>
</View>
)
};
const SecondRoute = () => (
<View style={[styles.scene, { backgroundColor: '#673ab7' }]} />
);
const initialLayout = { width: Dimensions.get('window').width };
export default function Parent() {
const [index, setIndex] = React.useState(0);
const [routes] = useState([
{ key: 'first', title: 'First' },
{ key: 'second', title: 'Second' },
]);
const [_isFollowed, setIsFollowed] = useState(false);
const _onSubmit = () => {
...
setIsfollowed(true)
}
const renderScene = ({route}) => {
switch(route.key){
case 'first': return <FirstRoute {...props} onSubmit={_onSubmit} isFollowed={_isFollowed} />
case 'second': return <SecondRoute {...props} />
}
};
return (
<TabView
navigationState={{ index, routes }}
renderScene={renderScene}
onIndexChange={setIndex}
initialLayout={initialLayout}
/>
);
}
But when I save it, the screen logs the error: Can't find the value of isFollowed
I think the problem is at the way I pass the props. I'm still learning it. Since when I delete the ListHeaderComponent, the FlatList still generates the list of images well. And I don't know if it has something to do with renderScene
I really don't understand why
Please help me. Thank you very much
Let me get this straight. You need to render _renderHeader dinamically based on _isFollowed state. So, you passed to the first route as props your _onSubmit function and _isFollowed state in order to get to access them at _renderHeader. Right?
As I see you actually doesn't need to do it once your _renderHeader has direct access to both _isFollowed state and _onSubmit function. Try it out as bellow:
export default function Parent() {
const [index, setIndex] = React.useState(0);
const [routes] = useState([
{ key: 'first', title: 'First' },
{ key: 'second', title: 'Second' },
]);
const [_isFollowed, setIsFollowed] = useState(false);
const initialLayout = { width: Dimensions.get('window').width };
function _onSubmit() {
setIsFollowed(true);
}
function _renderHeader() {
return (
<View>
{_isFollowed &&
<TouchableOpacity onPress={_onSubmit}>
<Text>You have followed this person</Text>
</TouchableOpacity> }
</View>
);
}
const FirstRoute = () => {
return(
<View style={[styles.scene, { backgroundColor: '#ff4081' }]}>
<FlatList
data={data}
keyExtractor={item => item.id}
renderItem={itemData => ( <Image source={itemData.item.id} style={{height: WIDTH/3, width: WIDTH/3}} />)}
ListHeaderComponent={_renderHeader}
/>
</View>
)
};
const SecondRoute = () => (
<View style={[styles.scene, { backgroundColor: '#673ab7' }]} />
);
const renderScene = ({route}) => {
switch(route.key){
case 'first': return <FirstRoute />
case 'second': return <SecondRoute />
}
};
return (
<TabView
navigationState={{ index, routes }}
renderScene={renderScene}
onIndexChange={setIndex}
initialLayout={initialLayout}
/>
);
}
Other point I don't understand in your code and couldn't check cause I didn't try to run it was the function bellow:
function _renderHeader() {
return (
<View>
{_isFollowed &&
<TouchableOpacity onPress={_onSubmit}>
<Text>You have followed this person</Text>
</TouchableOpacity> }
</View>
);
}
If you want to render TouchableOpacity just in case _isFollowed is true you should do it using ternary operator like so:
function _renderHeader() {
return (
<View>
{_isFollowed ?
<TouchableOpacity onPress={_onSubmit}>
<Text>You have followed this person</Text>
</TouchableOpacity> }
: <></>
}
</View>
);
}

Render from a callback function - typescript

While using a graphql query, I am calling a showUsers function which is supposed to show all the users (the stying is done so that they can appear as boxes). However, currently nothing shows up.
I am using a functional component, not class component.
This function is called after my handleSubmitForm. Here I call showUsers.
const getFriendId = React.useCallback(
(data) => {
if (data) {
if (data.users.nodes.length == 0) {
Alert.alert('User Not Found');
} else {
const numberOfUsers = data.users.nodes.length;
showUsers(data, numberOfUsers);
addFriend(Number(data.users.nodes[0].id));
}
}
},
[addFriend],
);
showUsers():
const showUsers = React.useCallback(
(data: UsersLazyQueryHookResult, numberOfUsers: Number) => {
for (var i = 0; i < numberOfUsers; i++) {
const userId = data.users.nodes[i].id;
const userName = (data.users.nodes[i].firstName).concat((data.users.nodes[i].lastName));
return(
<View style={styles.friends}>
<View style={styles.item}>
<Text>{userName}</Text>
</View>
</View>
)
}
},
[createUserRelationMutation],
);
This is how my form looks like. I guess I have to make an edit here but I am not sure how.
return (
<Modal
visible={showAddFriendEmailPage}
animationType="slide"
transparent={true}>
<SafeAreaView>
<View style={styles.container}>
<View style={styles.searchTopContainer}>
<View>
<Formik
initialValues={initialValues}
onSubmit={handleSubmitForm}
validationSchema={validationSchema}>
{({ handleChange, handleBlur, handleSubmit, values }) => (
<View style={styles.searchFieldContainer}>
<View style={styles.form}>
<FieldInput
handleChange={handleChange}
handleBlur={handleBlur}
value={values.email}
fieldType="email"
/>
<ErrorMessage
name="email"
render={msg => (
<Text style={styles.errorText}>{msg}</Text>
)}
/>
</View>
<View style={styles.buttonContainer}>
<Button
onPress={handleSubmit}>
<Text >Add Friend </Text>
</Button>
</View>
</View>
)}
</Formik>
</View>
</View>
</View>
</SafeAreaView>
</Modal>
);
};
Note: I only want them to show up below the button, after I submit the form.
EDIT:
I am trying this but I have a few problems:
Even when there's only one user, I see the LOOPoutput on the console at least 4 times.
Once the query and mutation run successfully and a user is also rendered/displayed, I can no longer press the button again. Which means that I can no longer submit the form and re-run queries or mutations with a different email input.
export const AddFriendEmailPage: React.FunctionComponent<AddFriendEmailPageProps> = ({
toggleShowPage,
showAddFriendEmailPage,
}) => {
const initialValues: FormValues = {
email: '',
};
const [errorMessage, setErrorMessage] = useState('');
const [userData, setUserData] = useState<UsersLazyQueryHookResult>('');
const [numberOfUsers, setNumberOfUsers] = useState('');
const validationSchema = emailValidationSchema;
useEffect(() => {
setUserData(userData);
setNumberOfUsers(numberOfUsers);
}, [userData, numberOfUsers]);
const showAlert = () => {
Alert.alert('Friend Added');
};
useEffect(() => {
if (showAddFriendEmailPage) return;
initialValues.email = '';
}, [showAddFriendEmailPage]);
const _onLoadUserError = React.useCallback((error: ApolloError) => {
setErrorMessage(error.message);
Alert.alert('Unable to Add Friend');
}, []);
const [
createUserRelationMutation,
{
data: addingFriendData,
loading: addingFriendLoading,
error: addingFriendError,
called: isMutationCalled,
},
] = useCreateUserRelationMutation({
onCompleted: (data: CreateUserRelationMutationResult) => {
showAlert();
},
});
const showUsers = React.useCallback(
(data: UsersLazyQueryHookResult, numberOfUsers: Number) => {
console.log('Number of Users in Loop: ', numberOfUsers);
for (var i = 0; i < numberOfUsers; i++) {
const userId = data.users.nodes[i].id;
const userName = ((data.users.nodes[i].firstName).concat(' ')).concat(data.users.nodes[i].lastName);
console.log('Whats the Id', userId);
console.log('UserName', userName);
console.log('Loop');
return(
<View style={styles.friends}>
<View style={styles.item}>
<Text>{userName}</Text>
</View>
</View>
)
}
},
[createUserRelationMutation],
);
const addFriend = React.useCallback(
(id: Number) => {
console.log('Whats the Id', id);
createUserRelationMutation({
variables: {
input: { relatedUserId: id, type: RelationType.Friend, userId: 7 },
},
});
},
[createUserRelationMutation],
);
const getFriendId = React.useCallback(
(data: UsersLazyQueryHookResult) => {
if (data) {
if (data.users.nodes.length == 0) {
setErrorMessage('User Not Found');
Alert.alert('User Not Found');
} else {
setUserData(data);
//const numberOfUsers = data.users.nodes.length;
setNumberOfUsers(data.users.nodes.length);
showUsers(data, Number(numberOfUsers));
addFriend(Number(data.users.nodes[0].id));
}
}
},
[addFriend],
);
const [loadUsers] = useUsersLazyQuery({
onCompleted: getFriendId,
onError: _onLoadUserError,
});
const handleSubmitForm = React.useCallback(
(values: FormValues, helpers: FormikHelpers<FormValues>) => {
console.log('Submitted');
loadUsers({
variables: {
where: { email: values.email },
},
});
values.email = '';
},
[loadUsers],
);
if (!addingFriendLoading && isMutationCalled) {
if (addingFriendError) {
setErrorMessage(addingFriendError.message);
Alert.alert('Unable to Add Friend');
}
}
return (
<Modal
visible={showAddFriendEmailPage}
animationType="slide"
transparent={true}>
<SafeAreaView>
<View style={styles.container}>
<View style={styles.searchTopContainer}>
<View style={styles.searchTopTextContainer}>
<Text
style={styles.searchCancelDoneText}
onPress={toggleShowPage}>
Cancel
</Text>
<Text style={styles.searchTopMiddleText}>
Add Friend by Email
</Text>
<Text style={styles.searchCancelDoneText}>Done</Text>
</View>
<View>
<Formik
initialValues={initialValues}
onSubmit={handleSubmitForm}
validationSchema={validationSchema}>
{({ handleChange, handleBlur, handleSubmit, values }) => (
<View style={styles.searchFieldContainer}>
<View style={styles.form}>
<FieldInput
handleChange={handleChange}
handleBlur={handleBlur}
value={values.email}
fieldType="email"
/>
<ErrorMessage
name="email"
render={msg => (
<Text style={styles.errorText}>{msg}</Text>
)}
/>
</View>
<View style={styles.buttonContainer}>
<Button
rounded
style={styles.button}
onPress={handleSubmit}>
<Text style={styles.text}>Add Friend </Text>
</Button>
</View>
</View>
)}
</Formik>
</View>
{showUsers(userData, Number(numberOfUsers))}
</View>
</View>
</SafeAreaView>
</Modal>
);
};
While using a graphql query, I am calling a showUsers function which
is supposed to show all the users (the stying is done so that they can
appear as boxes). However, currently nothing shows up.
A function call showUsers(data, numberOfUsers); can't just show items. You have to render it somewhere.
<View style={styles.buttonContainer}>
<Button
rounded
style={styles.button}
onPress={handleSubmit}>
<Text style={styles.text}>Add Friend </Text>
</Button>
</View>
{showUsers(data, numberOfUsers)}
But this also don't work straight away because you don't have data variable in this context. You have to use useState. I don't know if you can use React.useCallback function to return component this way. I would change showUsers(data, numberOfUsers) to
separate functional component.

How to change input value using setState

i'm building a simple todo app, and i need to change the input value ( to edit tasks ). I tried to make it like in the react native docs :
export default function UselessTextInput() {
const [value, onChangeText] = React.useState('Useless Placeholder');
return (
<TextInput
style={{ height: 40, borderColor: 'gray', borderWidth: 1 }}
onChangeText={text => onChangeText(text)}
value={value}
/>
);
}
But in my code i have my input inside map function and it displays an error : "undefined is not a function ( near '...todos.tasks.map...')
Can anyone explain me why do I get this error and how to solve it ?
My code :
const App = () => {
const[todos,setTodos] = useState({
tasks: [],
task: '',
key: ''
})
const addItem = () => {
if(todos.task != '' && todos.task != null){
setTodos({
tasks: todos.tasks.concat(todos.task),
task: ''
})
console.log(todos.tasks)
}
else {
Alert.alert('OOPS!', 'You need to fill in input' , [{
text: 'Understood'
}])
}
}
const removeItem = arg => {
const list = todos.tasks;
list.splice(arg,1)
setTodos({tasks: list})
}
const handleInputTextChange = (newText) => {
setTodos({
tasks: newText
})
}
return (
<ScrollView keyboardShouldPersistTaps='handled'>
<View style={styles.container}>
<View style = {styles.header}>
<Text style = {styles.title}>Todos</Text>
</View>
<View style={styles.content}>
<TextInput
style = {styles.input}
placeholder = "Type new item"
value = {todos.task}
onChangeText = {e => setTodos({...todos, task: e, key: Date.now()})}
/>
<ButtonSubmit text = 'Submit' onPress = {addItem}/>
{
todos.tasks.map((item, index) => {
return(
<TouchableOpacity>
<View style = {styles.Wrapper} key = {todos.key}>
<View style = {styles.taskWrapper}>
<TextInput style = {styles.task} id = {todos.key} value = {item} onChangeText={handleInputTextChange} />
</View>
<ButtonRemove onPress = {() => removeItem(index)} />
</View>
</TouchableOpacity>
)
})
}
</View>
</View>
</ScrollView>
);
}
You are overwriting your tasks array with the input value and then you get an error when trying to map tasks that is a string and not an array anymore.
Try this:
const App = () => {
const[todos,setTodos] = useState({
tasks: [],
task: '',
key: ''
})
const addItem = () => {
if(todos.task != '' && todos.task != null){
setTodos({
tasks: todos.tasks.concat(todos.task),
task: ''
})
console.log(todos.tasks)
}
else {
Alert.alert('OOPS!', 'You need to fill in input' , [{
text: 'Understood'
}])
}
}
const removeItem = arg => {
const list = todos.tasks;
list.splice(arg,1)
setTodos({tasks: list})
}
const handleInputTextChange = (newText, index) => {
setTodos((s) => {
...s,
tasks: s.tasks.map((t, i) => i === index ? newText : t)
})
}
return (
<ScrollView keyboardShouldPersistTaps='handled'>
<View style={styles.container}>
<View style = {styles.header}>
<Text style = {styles.title}>Todos</Text>
</View>
<View style={styles.content}>
<TextInput
style = {styles.input}
placeholder = "Type new item"
value = {todos.task}
onChangeText = {e => setTodos({...todos, task: e, key: Date.now()})}
/>
<ButtonSubmit text = 'Submit' onPress = {addItem}/>
{
todos.tasks.map((item, index) => {
return(
<TouchableOpacity>
<View style = {styles.Wrapper} key = {todos.key}>
<View style = {styles.taskWrapper}>
<TextInput style = {styles.task} id = {todos.key} value = {item} onChangeText={value => handleInputTextChange(value, index)} />
</View>
<ButtonRemove onPress = {() => removeItem(index)} />
</View>
</TouchableOpacity>
)
})
}
</View>
</View>
</ScrollView>
);
}
Also, check your code for using key prop as it appears to be problematic. You should never use Date.now() as a key. Check React docs:
https://reactjs.org/docs/lists-and-keys.html

Categories

Resources