How can I use useMemo React Hook in this example - react-native

What am I doing wrong here? I want to utilise useMemo so that my RenderItems component doesn't keep flickering when the state (Data2) changes. The Data2 array is in place of an item in my apps state. In practice, Data2 is data fetched from an api, and thus is subject to change and update.
I'm not looking for an alternative in this case, I'd just like to know how to use useMemo in this example - thanks!
import React, { useMemo } from 'react';
import {
View,
Text
} from 'react-native';
const CoursesWidget = (props) => {
const Data2 = [{ id: '11' }, { id: '22' }, { id: '33' }];
const coursesArray = Data2;
const RenderItems = useMemo(() => {
return (
coursesArray
.map((course) => {
return (
<View key={course.id}>
<Text>{course.id}</Text>
</View>
);
}),
[coursesArray]
);
});
//const Finito = useMemo(() => RenderItems(), [])
return (
<View>
<RenderItems />
</View>
);
};
export default CoursesWidget;
Snack: https://snack.expo.dev/rr8toaABT

I would suggest that you use a state and a FlatList instead of creating the elements using map. There is no need to use useMemo at all in this scenario and it will not fix your issue.
import React, { useState } from 'react';
import {
View,
Text,
FlatList,
SafeAreaView
} from 'react-native';
const CoursesWidget = (props) => {
const [data, setData] = useState([{ id: '11' }, { id: '22' }, { id: '33' }])
return (
<SafeAreaView style={{margin: 20}}>
<FlatList
data={data}
renderItem={({ item }) => {
return <View>
<Text>{item.id}</Text>
</View>
}}
keyExtractor={item => item.id}
/>
</SafeAreaView>
);
};
export default CoursesWidget;
Here is an updated version of your snack.

All that needs to be changed is moving the dependency array that you pass to useMemo to be the last parameter, and instead of calling it in the return like a jsx component, you put the value in brackets since it's not really a function anymore:
import React, { useMemo } from 'react';
import {
View,
Text
} from 'react-native';
const CoursesWidget = (props) => {
const Data2 = [{ id: '11' }, { id: '22' }, { id: '33' }];
const coursesArray = Data2;
const RenderItems = useMemo(() => {
return (
coursesArray
.map((course) => {
return (
<View key={course.id}>
<Text>{course.id}</Text>
</View>
);
})
);
}, [coursesArray]);
//const Finito = useMemo(() => RenderItems(), [])
return (
<View>
{ RenderItems }
</View>
);
};
export default CoursesWidget;
Here's the snack: https://snack.expo.dev/5GbI-k8Pb

Related

React Native Datatable doesn't show after moving the fragment into Component

I've created the following component. React Native Paper Datatable rows aren't showing after moving it into component and linking it to json loop.
If we comment " and uncommented the commented block below, you will see the Datatable is showing. What am I doing wrong with my two components? I've done all console.log. All data are showing correctly but JSX elements aren't rendering inside Datatable.
I've created the following code on Snack: https://snack.expo.dev/#everestster/datatable-component
import React, {useEffect} from 'react';
import type {Node} from 'react';
import {View, ScrollView, Text, StyleSheet, Dimensions} from 'react-native';
import {DataTable as PaperDataTable} from 'react-native-paper';
const DataTable = props => {
const optionsPerPage = [2, 3, 4];
const [page, setPage] = React.useState(0);
const [itemsPerPage, setItemsPerPage] = React.useState(optionsPerPage[0]);
useEffect(() => {
setPage(0);
}, [itemsPerPage]);
const HeaderSection = (): Node => {
console.log(props.items);
if (props.items.length === 0) {
return;
}
return (
<PaperDataTable.Header>
{Object.keys(props.items[0]).forEach(function (key) {
if (key !== 'Id') {
<PaperDataTable.Title style={[styles.allCell]}>
{key}
</PaperDataTable.Title>;
}
})}
</PaperDataTable.Header>
);
};
const BodySection = (): Node => {
return (
<PaperDataTable.Row>
{Object.keys(props.items[0]).forEach(function (key) {
if (key !== 'Id') {
<PaperDataTable.Cell style={[styles.allCell]}>
{key}
</PaperDataTable.Cell>;
}
})}
</PaperDataTable.Row>
);
};
return (
<ScrollView style={styles.tableHolder}>
<ScrollView horizontal={true}>
<View style={{alignItems: 'center'}}>
<PaperDataTable style={styles.table}>
<HeaderSection />
<BodySection />
{/*<PaperDataTable.Header>
<PaperDataTable.Title>Name</PaperDataTable.Title>
<PaperDataTable.Title>Email</PaperDataTable.Title>
</PaperDataTable.Header>
<PaperDataTable.Row>
<PaperDataTable.Cell>John</PaperDataTable.Cell>
<PaperDataTable.Cell>john#gmail.com</PaperDataTable.Cell>
</PaperDataTable.Row>
<PaperDataTable.Row>
<PaperDataTable.Cell>Harry</PaperDataTable.Cell>
<PaperDataTable.Cell>harr#gmail.com</PaperDataTable.Cell>
</PaperDataTable.Row>
<PaperDataTable.Row>
<PaperDataTable.Cell>Jessica</PaperDataTable.Cell>
<PaperDataTable.Cell>jessica#gmail.com</PaperDataTable.Cell>
</PaperDataTable.Row>*/}
<PaperDataTable.Pagination
page={page}
numberOfPages={1}
onPageChange={p => setPage(p)}
optionsPerPage={optionsPerPage}
itemsPerPage={itemsPerPage}
setItemsPerPage={setItemsPerPage}
showFastPagination
optionsLabel={'Rows per page'}
/>
</PaperDataTable>
</View>
</ScrollView>
</ScrollView>
);
};
const styles = StyleSheet.create({
tableHolder: {},
table: {
paddingLeft: 50,
paddingRight: 50,
flex: 1,
},
allCell: {
marginRight: 20,
},
});
export {DataTable};
Any help will be appreciated.
The problem is in your structure. Your current BodySection is not returning the correct structure react-native-paper wants. I rewrote the BodySection function. Here is the snack: https://snack.expo.dev/#truetiem/datatable-component
const BodySection = (): Node => {
return props.items.map(function (item) {
return (
<PaperDataTable.Row>
{Object.keys(item).map((key) => key === 'Id' ? null : (
<PaperDataTable.Cell>
{item[key]}
</PaperDataTable.Cell>
))}
</PaperDataTable.Row>
);
});
};

react native usememo renderitem not working why?

I want to prevent unneccessary rerender, so I use useMemo.
But I got this error message:
TypeError: renderItem is not a function. (In 'renderItem(props)', 'renderItem' is an instance of Object)
Code:
import * as React from 'react';
import { StyleSheet, Text, View, TouchableOpacity, Image, Dimensions, FlatList } from 'react-native';
import faker from 'faker';
const { width, height } = Dimensions.get('window');
const Advertising = () => {
const data = [
{ id: '1', name: 'Jens', image: faker.image.avatar() },
{ id: '2', name: 'Günther', image: faker.image.avatar() }
];
const renderItem = React.useMemo(() => {
return (
<View>
<Text>Hello</Text>
</View>
)
}, [data]);
return (
<FlatList
data={data}
keyExtractor={item => Math.random(100).toString()}
renderItem={renderItem}
/>
)
};
const styles = StyleSheet.create({
container: {
flex: 1,
}
});
export default React.memo(Advertising);
......................................................................................................................................................................................................
useMemo is a react hook and react hooks can't be used in that way.
I would advice you create a separate component for the this.
const MyComponent = React.memo(({item})=>{
return (<View></View>);
});
and then import like so
const renderItem = ({item}) => {
return <MyComponent />
}
...
<FlatList
data={data}
renderItem={renderItem}
keyExtractor={(_item, i)=>i.toString()}
/>
Also consider useCallBack
You have to return your renderItem function as a callback inside useMemo.
const renderItem = React.useMemo(() => () => (
<View>
<Text>Hello</Text>
</View>
), [data])
same as
const renderItem = () => (
<View>
<Text>Hello</Text>
</View>
)
const memoizedRenderItem = React.useMemo(renderItem, [data])

react native how can I use two items side by side in modal?

how can I use two items side by side in modal ?
Normally I make it like this:
flex-direction: 'row'
but in modal with flatlist, how I make it ? look at the bottom the picture. I want 2 names side by side.
Code:
import React, { useRef, forwardRef, useEffect } from 'react';
import { StyleSheet, Text, View, Image, TouchableOpacity, FlatList } from 'react-native';
import { Modalize } from 'react-native-modalize';
import faker from 'faker';
import { useCombinedRefs } from '../../utils/use-combined-refs';
export const SnappingList = forwardRef((_, ref) => {
const modalizeRef = useRef(null);
const contentRef = useRef(null);
const combinedRef = useCombinedRefs(ref, modalizeRef);
const getData = () =>
Array(10)
.fill(0)
.map(_ => ({
name: faker.name.findName(),
email: faker.internet.email(),
image: faker.image.avatar()
}));
const profile_image = faker.image.avatar();
const getHeader = () => {
return (
<View style={s.modalContainer}>
<Text>I AM A HEADER</Text>
</View>)
};
const renderItem = ({ item }) => (
<TouchableOpacity style={s.item}>
<Text style={s.item__name}>{item.name}</Text>
</TouchableOpacity>
);
return (
<Modalize
ref={combinedRef}
contentRef={contentRef}
snapPoint={350}
flatListProps={{
data: getData(),
renderItem: renderItem,
ListHeaderComponent: getHeader(),
keyExtractor: item => item.email,
showsVerticalScrollIndicator: false,
removeClippedSubviews: true,
initialNumToRender: 5
}}
/>
);
});
..............................

Objects are not valid as a React child (found: object with keys ..)

I am trying to build a basic app where I fetch some restaurants from yelp api.
I get the error below on iOS and I can't seem to fix it.
Objects are not valid as a React child (found: object with keys {id,
alias, name, image_url, is_closed, url, review_count, categories,
rating, coordinates, transactions, price, location, phone,
display_phone, distance}). If you meant to render a collection of
children, use an array instead.
When I remove the part results={filterResultsByPrice('$')} from <ResultsList> the app works again.
Would appreciate a lot if someone could help.
This is my main screen:
import React, {useState} from 'react';
import { View, Text, StyleSheet } from 'react-native';
import SearchBar from '../component/SearchBar';
import useResults from '../hooks/useResults';
import ResultsList from '../component/ResultsList';
const SearchScreen = () => {
const [term, setTerm] = useState('');
const [searchApi, results, errorMessage] = useResults();
const filterResultsByPrice = (price) => {
return results.filter( result => {
return result.price === price;
});
};
return (
<View>
<SearchBar
term={term}
onTermChange={(newTerm)=> setTerm(newTerm)}
onTermSubmit={searchApi}
/>
{errorMessage ? <Text>{errorMessage}</Text> : null }
<Text>We have found {results.length} results</Text>
<ResultsList results={filterResultsByPrice('$')} title="Cost Effective"/>
<ResultsList results={filterResultsByPrice('$$')} title="Bit Pricier"/>
<ResultsList results={filterResultsByPrice('$$$')} title="Big Spender"/>
</View>
);
};
const styles = StyleSheet.create({});
export default SearchScreen;
This is the component I want to place on the screen:
import React from 'react';
import { View, Text, StyleSheet} from 'react-native';
const ResultsList = ({ title, results }) => {
return (
<View>
<Text style={styles.title}> {title}</Text>
<Text> Results: {results.length} </Text>
</View>
);
};
const styles = StyleSheet.create({
title:
{
fontSize: 18,
fontWeight: 'bold'
}
});
export default ResultsList;
And this is my useResults hook:
import {useEffect, useState } from 'react';
import yelp from '../api/yelp';
export default () => {
const [results, setResults] = useState([]); //default is empty array
const [errorMessage, setErrorMessage] = useState('');
const searchApi = async searchTerm=> {
console.log('Hi there');
try {
const response = await yelp.get('/search', {
params: {
limit: 50,
term: searchTerm,
location: 'san jose'
}
});
setResults(response.data.businesses);
} catch (err) {
setErrorMessage('Something went wrong.');
}
};
useEffect(()=> {
searchApi('pasta');
}, []);
return [searchApi, results, errorMessage];
};
You need to update your ResultsList component to this one, hopefully it will fix your issue permanently:
import React from "react";
import { View, Text, StyleSheet } from "react-native";
const ResultsList = ({ title, results }) => {
return (
<View>
<Text style={styles.title}> {title}</Text>
{results.map(result => (
<Text>Results: {result.length}</Text>
))}
</View>
);
};
const styles = StyleSheet.create({
title: {
fontSize: 18,
fontWeight: "bold"
}
});
export default ResultsList;

Saving list with AsyncStorage

So I made a "notepad" app and I want to do so the text that the user wrote etc it should be saved, so the text doesn't get reset when user quits the app.
I'm new to react-native, after a few google searches I need AsyncStorage? to make this happen.
but really dunno on how to do it.
import React, { useState } from 'react';
import {
StyleSheet,
Text,
View,
FlatList,
TouchableWithoutFeedback,
TouchableOpacity,
Keyboard,
AsyncStorage
} from 'react-native';
import Header from './components/header';
import ListItem from './components/listitem';
import AddList from './components/addlist';
export default function App() {
const [todos, setTodos] = useState([
]);
const pressHandler = (key) => {
setTodos((prevTodos) => {
return prevTodos.filter(todo => todo.key != key);
});
}
const submitHandler = (text) => {
if(text.length > 0) {
setTodos((prevTodos) => {
return [
{ text: text, key: Math.random().toString() },
...prevTodos
];
})
}
}
return (
<TouchableWithoutFeedback onPress={() => {
Keyboard.dismiss();
}}>
<View style={styles.container}>
<Header />
<View style={styles.content}>
<AddList submitHandler={submitHandler} />
<View style={styles.todoList}>
<FlatList
data={todos}
renderItem={({ item }) => (
<ListItem item={item} pressHandler={pressHandler} />
)}
/>
</View>
</View>
</View>
</TouchableWithoutFeedback>
);
}
new problem out of nowhere worked great before now broken without touching the code
const pressHandler = key =>
setTodos(prevTodos => {
const newTodos = prevTodos.filter(todo => todo.key !== key);
storeTodosInAsync(newTodos);
console.log(prevTodos);
return newTodos;
});
const submitHandler = text => {
if (text.length > 0) {
const key = Math.random().toString();
setTodos(prevTodos => {
const newTodos = [{ text, key }, ...prevTodos];
storeTodosInAsync(newTodos);
console.log(newTodos);
return newTodos;
});
}
};
You can use AsyncStorage to store and load data to/from local storage. One thing to note is data MUST be a string, so anything like an object that is not a string needs to be stringified. You can use JSON.stringify(...) to do this. And then when you get the string back you can use JSON.parse(...) to convert it back into an object.
So to convert your current code into something that automatically loads saved todos and always saves the latest, you could write this:
import React, { useState, useEffect } from 'react';
import {
StyleSheet,
Text,
View,
FlatList,
TouchableWithoutFeedback,
TouchableOpacity,
Keyboard,
AsyncStorage,
Button
} from 'react-native';
import Header from './components/header';
import ListItem from './components/listitem';
import AddList from './components/addlist';
export default function App() {
const [todos, setTodos] = useState([]);
useEffect(() => {
restoreTodosFromAsync();
}, []);
const pressHandler = key => {
console.log('Todos BEFORE delete');
console.log(todos);
const newTodos = todos.filter(todo => todo.key !== key);
console.log('Todos AFTER delete');
console.log(todos);
setTodos(newTodos);
storeTodosInAsync(newTodos);
};
const submitHandler = text => {
if (text.length === 0) return;
const key = Math.random().toString();
console.log('Todos BEFORE submit');
console.log(todos);
const newTodos = [{ text, key }, ...todos];
console.log('Todos AFTER submit');
console.log(todos);
setTodos(newTodos);
storeTodosInAsync(newTodos);
};
const asyncStorageKey = '#todos';
const storeTodosInAsync = newTodos => {
const stringifiedTodos = JSON.stringify(newTodos);
AsyncStorage.setItem(asyncStorageKey, stringifiedTodos).catch(err => {
console.warn('Error storing todos in Async');
console.warn(err);
});
};
const restoreTodosFromAsync = () => {
AsyncStorage.getItem(asyncStorageKey)
.then(stringifiedTodos => {
console.log('Restored Todos:');
console.log(stringifiedTodos);
const parsedTodos = JSON.parse(stringifiedTodos);
if (!parsedTodos || typeof parsedTodos !== 'object') return;
setTodos(parsedTodos);
})
.catch(err => {
console.warn('Error restoring todos from async');
console.warn(err);
});
};
return (
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
<View style={styles.container}>
<Header />
<View style={styles.content}>
<AddList submitHandler={submitHandler} />
<View style={styles.todoList}>
<FlatList
data={todos}
renderItem={({ item }) => <ListItem item={item} pressHandler={pressHandler} />}
/>
</View>
</View>
</View>
</TouchableWithoutFeedback>
);
}