I am trying to render content dynamically. I have an array of JSON data that I loop over, and, for now (testing purposes), create a text node "hey" for each element of that array,pushing it to another array called renderedData. I am trying to render the contents of this array, renderedData, but it is not working. I have no idea why - nothing is rendered, yet when I try a dummy array that is declared on the spot, such as "test", it works.
import * as React from 'react';
import { StyleSheet, Button, TextInput, Modal, Alert, TouchableHighlight, SafeAreaView, SectionList, FlatList, ListView } from 'react-native';
import EditScreenInfo from '../components/EditScreenInfo';
import { Text, View } from '../components/Themed';
import { NavigationContainer } from '#react-navigation/native';
import { createStackNavigator } from '#react-navigation/stack';
import AsyncStorage from '#react-native-community/async-storage';
import { useState } from 'react';
import { parse } from '#babel/core';
import { getInitialURL } from 'expo-linking';
const payments: any[] = []
export default function PaymentsScreen(){
const getData = async () => {
try {
const keys = await AsyncStorage.getAllKeys();
const result = await AsyncStorage.multiGet(keys)
return result
} catch(e) {
console.log(e)
}
}
React.useEffect(() => {
getData()
})
const data: (string | null)[] = []
const renderedData: string[] = []
async function parseData(){
const payments = await getData()
if (payments != null && payments != undefined) {
console.log(payments)
payments.map((eachPayment: any[]) => {
if (eachPayment[1] != null && eachPayment[1].includes("{"))
data.push(eachPayment[1])
})
}
data.map((eachPayment: any) => {
if (eachPayment != null) renderedData.push("hey")
})
return data
}
parseData()
return (
<View>
{data.map((info: any) => <Text>{info}</Text>)}
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
marginHorizontal: 16
},
item: {
backgroundColor: "#f9c2ff",
padding: 20,
marginVertical: 8
},
header: {
fontSize: 32,
backgroundColor: "#fff"
},
title: {
fontSize: 24
}
});
Since you have async call, here is how i would do it :
import React, { Component } from "react";
import { render } from "react-dom";
import "./style.css";
const App = () => {
const fakeApiData = [1, 2, 3];
const [data, setData] = React.useState([]);
const [renderedData, setRenderedData] = React.useState([]);
const getApiDataAsync = () => Promise.resolve(fakeApiData);
// Equal to componentDiMount()
React.useEffect(() => {
// This is your async call to get your data
const data = getApiDataAsync().then(data => setData(data));
}, []);
// Effect triggered after data changes, so when you get your data
React.useEffect(() => {
const futurRenderedData = [];
data.map(item => futurRenderedData.push(item));
setRenderedData(futurRenderedData);
}, [data]);
return (
<div>
{renderedData.map(item => <span>{item} </span>)}
</div>
);
};
render(<App />, document.getElementById("root"));
The idea is to get your data, be sure to have it, either through a promise, an await, or an observable, and then setting your renderedData.
Here is your code modified, tell me if there is still something wrong :
export default function PaymentsScreen() {
const [data, setData] = React.useState(new Array<string>());
const [renderedData, setRenderedData] = React.useState(new Array<string>());
const getData = async () => {
try {
const keys = await AsyncStorage.getAllKeys();
const result = await AsyncStorage.multiGet(keys);
return result;
} catch (e) {
console.log(e);
}
};
async function parseData() {
const payments = await getData();
if (payments) {
payments.foreach((eachPayment: any[]) => {
if (eachPayment[1] && eachPayment[1].includes('{'))
data.push(eachPayment[1]);
});
}
data.map((eachPayment: any) => {
if (eachPayment != null) setRenderedData('hey');
});
return data;
}
React.useEffect(async () => {
setData(await parseData());
}, []);
return (
<View>
<View>
{data.map((info: any) => (
<Text>{info}</Text>
))}
</View>
<View>
{renderedData.map(item => <span>{item} </span>)} {/*If this appear in the html then it works*/}
</View>
</View>
);
}
Related
I have custom hook which is catching data from dummyjson API. When I render products, it works fine and perfectly. When I try to catch only one product with this hook via parameter passed via url with useParams in the end it catch this one product, but it cannot render. It seems that a single product didn't manage to load with the help of the hook before it renders. So what is difference when all products are catched are rendered correctly
import axios, { Canceler } from 'axios';
import { useEffect, useState } from 'react';
import { dummyProductType } from '../types/types';
export const useFetch = ({ limit, id }: any) => {
const [products, setProducts] = useState<dummyProductType[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [hasMore, setHasMore] = useState(false);
const [error, setError] = useState(false);
useEffect(() => {
let cancel: Canceler;
const config =
id === null || id === undefined
? {
method: 'GET',
url: `https://dummyjson.com/products/`,
params: { limit: limit },
cancelToken: new axios.CancelToken((c) => (cancel = c)),
}
: {
method: 'GET',
url: `https://dummyjson.com/products/${id}`,
cancelToken: new axios.CancelToken((c) => (cancel = c)),
};
async function fetchData() {
setIsLoading(true);
{
await axios(config)
.then((response) => {
if (Object.hasOwn(config, 'params')) {
setProducts((prev) => {
return [...prev, ...response.data.products];
});
} else {
setProducts({ ...response.data });
}
if (products.length < response.data.total) setHasMore(true);
setIsLoading(false);
})
.catch((err) => {
if (axios.isCancel(err)) return;
setError(true);
});
}
}
fetchData();
return () => cancel();
}, [limit, id]);
return { products, isLoading, error, hasMore };
};
import React, { useCallback, useRef, useState } from 'react';
import { Link } from 'react-router-dom';
import { useFetch } from '../../hooks/useFetch';
import { CardProduct } from '../CardProduct';
import styles from './Cards.module.scss';
const { wrapperContainer } = styles;
const Cards = () => {
const [limit, setLimit] = useState(10);
const { products, isLoading, hasMore } = useFetch({ limit: limit });
const observer = useRef<IntersectionObserver | null>(null);
const lastProduct = useCallback(
(node: Element) => {
if (isLoading) {
return;
}
if (observer.current) {
observer.current.disconnect();
}
observer.current = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting && hasMore) {
setLimit((prev) => prev + 10);
}
});
if (node) observer.current.observe(node);
},
[isLoading, hasMore]
);
console.log(products);
return (
<div className={wrapperContainer}>
{products.map((product, index) => {
if (products.length === index + 1) {
return (
<Link to={`books/${index + 1}`}>
<CardProduct
key={`${index} ${product.title}`}
{...product}
innerRef={lastProduct}
/>
</Link>
);
} else {
return (
<Link to={`books/${index + 1}`}>
<CardProduct key={`${index} ${product.title}`} {...product} />
</Link>
);
}
})}
</div>
);
};
export default Cards;
import {
Button,
CardContent,
Card,
CardHeader,
CardMedia,
dividerClasses,
} from '#mui/material';
import { useParams } from 'react-router-dom';
import { useFetch } from '../../hooks/useFetch';
export const CardDetail = () => {
const { id } = useParams();
console.log(id);
const { products, isLoading, hasMore } = useFetch({
id: Number.parseInt(id),
});
console.log(products, isLoading, hasMore);
return (
<Card key={id}>
<CardHeader title={products[0].title}></CardHeader>
<CardMedia
component='img'
image={products[0].thumbnail}
sx={{ height: '150px' }}></CardMedia>
</Card>
);
};
What am I doing wrong? Or maybe it should be done different?
I tried to follow the examples on the Expo Docs about SQLite and also some more examples. Unfortunately I couldn't quite get it working. To debug I tried to add some callbacks and none of the callbacks were actually called. Neither success nor error callback.
This is my code for this small test:
import React, { useState, useEffect } from 'react';
import { Platform, ScrollView, StyleSheet, Text, TextInput, TouchableOpacity, View, Button, } from "react-native";
import * as SQLite from "expo-sqlite";
function App(props) {
function openDatabase() {
if (Platform.OS === "web") {
return {
transaction: () => {
return {
executeSql: () => {},
};
},
};
}
const db = SQLite.openDatabase("db.db");
return db;
};
const db = openDatabase();
const [text, setText] = useState(null);
const [forceUpdate, forceUpdateId] = useForceUpdate();
useEffect(() => {
console.log("Use Effect"); // this gets logged
db.transaction((tx) => {
tx.executeSql(
"create table if not exists items (id integer primary key not null, done int, value text);",
[],
(_, res) => {console.log("Error initializing database!");},
(_, err) => {console.log("Database was initialized");}
);
},
[],
(_, res) => {console.log("Error initializing database!");},
(_, err) => {console.log("Database was initialized");});
}, []);
return (
<View style={styles.container}>
<Text>Test</Text>
</View>
);
}
function useForceUpdate() {
const [value, setValue] = useState(0);
return [() => setValue(value + 1), value];
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center'
}
});
export default App;
Maybe someone can point to what I did wrong.
Thanks in advance.
Best,
Fred
There are a couple of steps to solve your problem.
Init your database only once. Replace your implementation with:
const openDatabase = () => {
if (Platform.OS === "web") {
return {
transaction: () => {
return {
executeSql: () => {},
};
},
};
}
const db = SQLite.openDatabase("db.db");
return db;
};
function App(props) {
const [db, setDB] = useState(null);
...
useEffect(() => {
if (!db) {
const instance = openDatabase();
setDB(db)
}
}, [])
useEffect(() => {
if (db) {
// do your transactions
}
}, [db])
}
Remove forceUpdate
pardon my ignorance but I'm still in the early days of learning react and firebase.
I'm trying to implement some functionality where a user can click on a category, then they will be navigated to this ViewItemPage component, where I'm attempting to get a collection from firebase based on the category that the user clicked. Once the collection has been retrieved I'm trying to select a random item from the collection and use that data to render another child component.
I'm assuming this is either a lifecycle issue and/or I'm using react hooks completely incorrectly, but I've been stuck trying to figure this out way too long
import { collection, query, where, getDocs } from 'firebase/firestore';
import { db } from '../firebase';
import { useLocation } from 'react-router';
import { AnotherComponent } from './AnotherComponent ';
export function ViewItemPage() {
const { state } = useLocation();
const { category } = state;
const [items, setItems] = useState([]);
const [loading, setLoading] = useState(true);
const [selectedItem, setSelectedItem] = useState();
const itemsRef = collection(db, 'items');
const getItems = async (e) => {
try {
const q = query(itemsRef, where('category', '==', category));
const querySnapshot = await getDocs(q);
querySnapshot.forEach((doc) => {
setItems({ data: doc.data() });
});
setLoading(false);
} catch (err) {
console.log(err.message);
}
};
const getRandomItem = () => {
const randomItem = items[Math.floor(Math.random() * items.length)];
setSelectedItem(randomItem);
};
useEffect(() => {
getItems();
// eslint-disable-next-line
}, [loading]);
useEffect(() => {
getRandomItem();
// eslint-disable-next-line
});
return <AnotherComponent category={category} item={selectedItem} />;
}
in the above example I'm attempting to use a forEach loop to update the state, but I've also tried mapping the response:
setItems(
querySnapshot.docs.map((doc) => ({ ...doc.data(), id: doc.id }))
);
and it has the same result, the child component seems to be passed and empty object, and throws an error when attempting to read any of the items properties
After a bit more learning, trial and error, and refactoring it seems to be working as I intended it to now. Here's the code in case someone else might find it useful
import React, { useState, useEffect, useRef } from 'react';
import { collection, query, where, getDocs } from 'firebase/firestore';
import { db } from '../firebase';
import { useLocation } from 'react-router';
import { AnotherComponent} from './AnotherComponent ';
export function ViewItemPage() {
const { state } = useLocation();
const { category } = state;
const [loading, setLoading] = useState(true);
const [randomItem, setRandomItem] = useState();
let itemArray = [];
const getItems = async () => {
try {
const q = query(
collection(db, 'items'),
where('category', '==', category)
);
const querySnapshot = await getDocs(q);
querySnapshot.forEach((doc) => {
itemArray = [...itemArray, doc.data()];
});
setRandomItem(
itemArray[Math.floor(Math.random() * itemArray.length)]
);
setLoading(false);
} catch (err) {
console.log(err.message);
}
};
useEffect(() => {
getItems();
// eslint-disable-next-line
}, [loading]);
if (loading) {
return (
<div
style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
height: '100vh',
}}
>
Loading the data
</div>
);
}
return <AnotherComponent item={randomItem} />;
}
I'm using socket.io along with react for a project. Here's my component
import axios from 'axios';
import React, { useEffect, useState } from 'react';
import { useLocation } from 'react-router-dom';
import { io } from 'socket.io-client';
import Button from '../../components/Button';
import { IProject } from '../../interfaces/projects';
import { IRun } from '../../interfaces/runs';
const socket = io(process.env.REACT_APP_SERVER_URL);
export default function RunAll() {
const { search } = useLocation();
// API State
const [project, setProject] = useState<IProject | undefined>(undefined);
const [runs, setRuns] = useState<IRun[]>([]);
// Query Params
const queryParams = new URLSearchParams(search);
const projectId = queryParams.get('projectId')!;
// Get Project
useEffect(() => {
(async () => {
const { data: project } = await axios.get(`${process.env.REACT_APP_SERVER_URL}/api/projects/${projectId}`);
setProject(project.data);
})();
}, [projectId]);
// Clear socket
useEffect(() => () => {
socket.close();
});
const runAllTests = async () => {
project?.tests.forEach((test) => {
console.log(test);
socket.emit('create run', { projectId, testId: test.id, url: process.env.REACT_APP_SERVER_URL });
});
socket.on('created run', (run: IRun) => {
console.log(run);
setRuns([...runs, run]);
});
};
console.log(runs);
const renderHeader = () => (
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', alignItems: 'center' }} className='mb-3'>
<h1 className='heading-primary mt-auto mb-auto'>Run Overview</h1>
<Button onClick={runAllTests}>Run All Tests</Button>
</div>
);
return (
<main>
{renderHeader()}
{runs?.map((run) => (
<div>{run.id}</div>
))}
</main>
);
}
When the button is click and runAllTests() is called, i can see the console.log(test) in the console and my server logs also show me that it has received the socket.emit('create run'). And when the server responds with socket.on('created run'), I can see the value of the created run. However, only the 2nd run(in case of the length of project.tests being 2), only the last run is being added to the state.
What am I missing here? Please help me out! Thanks in advance!
socket.on('created run', (run: IRun) => { console.log(run); setRuns([...runs, run]); });
use this code in useEffect and make sure it only runs once. Following should work for you.
import axios from 'axios';
import React, { useEffect, useState } from 'react';
import { useLocation } from 'react-router-dom';
import { io } from 'socket.io-client';
import Button from '../../components/Button';
import { IProject } from '../../interfaces/projects';
import { IRun } from '../../interfaces/runs';
const socket = io(process.env.REACT_APP_SERVER_URL);
export default function RunAll() {
const { search } = useLocation();
// API State
const [project, setProject] = useState<IProject | undefined>(undefined);
const [runs, setRuns] = useState<IRun[]>([]);
// Query Params
const queryParams = new URLSearchParams(search);
const projectId = queryParams.get('projectId')!;
// Get Project
useEffect(() => {
(async () => {
const { data: project } = await axios.get(`${process.env.REACT_APP_SERVER_URL}/api/projects/${projectId}`);
setProject(project.data);
})();
}, [projectId]);
// Clear socket
useEffect(() => () => {
socket.on('created run', (run: IRun) => {
console.log(run);
setRuns([...runs, run]);
});
return function cleanup () {
socket.close();
}
},[]);
const runAllTests = async () => {
project?.tests.forEach((test) => {
console.log(test);
socket.emit('create run', { projectId, testId: test.id, url: process.env.REACT_APP_SERVER_URL });
});
};
console.log(runs);
const renderHeader = () => (
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', alignItems: 'center' }} className='mb-3'>
<h1 className='heading-primary mt-auto mb-auto'>Run Overview</h1>
<Button onClick={runAllTests}>Run All Tests</Button>
</div>
);
return (
<main>
{renderHeader()}
{runs?.map((run) => (
<div>{run.id}</div>
))}
</main>
);
}
I'm creating a react-native app using react-navigation 5.
Let's say I have a screen component like this:
import {View, Text} from 'react-native';
function TextScreen({navigation}) {
const [text, setText] = useState(null);
useEffect(() => {
setText('Some text.');
navigation.addListener('focus', () => {
console.log('focus');
console.log(text); // this is always null :/
});
}, []);
return (
<View>
<Text>{text || 'No text'}</Text>
</View>
);
}
I have no idea why every console.log(text) displays null value on every focus. I expect text to be null only in the first focus but it happens all the time.
But when I changed this component into class component, everything worked as expected:
import {View, Text} from 'react-native';
class TextScreen extends React.Component {
state = {
text: null
}
componentDidMount() {
this.setState({text: 'Some text'});
this.props.navigation.addListener('focus', () => {
console.log('focus');
console.log(this.state.text); // this is null only in the first focus
});
}
render() {
return (
<View>
<Text>{this.state.text || 'No text'}</Text>
</View>
);
}
}
Is there something I'm doing wrong in the first version?
OK, I found the solution using useRef hook:
React useState hook event handler using initial state
So in my case should be:
import {View, Text} from 'react-native';
function TextScreen({navigation}) {
const [text, _setText] = useState(null);
const textRef = useRef(text);
const setText = newText => {
textRef.current = newText;
_setText(newText);
};
useEffect(() => {
setText('Some text.');
navigation.addListener('focus', () => {
console.log('focus');
console.log(textRef.current);
});
}, []);
return (
<View>
<Text>{text || 'No text'}</Text>
</View>
);
}
#erichio could you change from useEffect to useFocusEffect.
import { RouteProp, useFocusEffect } from '#react-navigation/native'
function TextScreen({navigation}) {
....
useFocusEffect(() => {
setText('Some text.');
navigation.addListener('focus', () => {
console.log('focus');
console.log(text); // this is always null :/
});
return () => {
navigation.removeEventListener('focus',() => /* YOUR_LOGIC */);
};
}, []);
...
}
You can do in this way
const onFocusScreen = useCallback(event => {
console.log(text);
}, []);
useEffect(() => {
navigation.addListener('focus', onFocusScreen);
return () => {
navigation.removeEventListener('focus', onFocusScreen);
};
}, [onFocusScreen]);
You can do something like this.
import { useFocusEffect } from '#react-navigation/native';
useFocusEffect (
React.useCallback(() =>
{
console.warn ("tabOrderStatus",text)
}, [text])
);