I have a component with the following structure:
const _dbCall = () => {
const fooDb = SQLite.openDatabase(db);
return new Promise(resolve => {
fooDb.transaction(tx => {
tx.executeSql(`SOME SQL`, [], (tx, results) => {
resolve(results.rows._array);
}, null);
});
})
}
async function _renderSomething() {
const results = await _dbCall();
return <FlatList
data={results}
renderItem={_renderFunc}
keyExtractor={item => item} />
}
I use _renderSomething() in the render() function of the Component.
However, this gives me:
Error: Objects are not valid as a React child (found: object with keys {_U, _V, _W, _X}). If you meant to render a collection of children, use an array instead.
This {_U, _V, _W, _X} looks like an unresolved promise to me.
When I remove the async keyword from renderSomething(), comment the const results = ... and pass some dummy data to <FlatList ..., it renders without a problem.
Why does renderSomething() not return the <FlatList ... but an unresolved promise?
As #Yousaf pointed out:
const [resultsFromDb, setResultsFromDb] = useState([]);
const _dbCall = () => {
const foo = [];
const fooDb = SQLite.openDatabase(db);
fooDb.transaction(tx => {
tx.executeSql(`SOME SQL`, [], (tx, results) => {
// do something the results
for (let i = 0; i < results.rows.length; i++) {
foo.push(results.rows.item(i));
}
setResultsFromDb(foo)
}, null);
});
}
const _renderSomething = () => {
const results = _dbCall();
return <FlatList
data={resultsFromDb}
renderItem={_renderFunc}
keyExtractor={item => item} />
}
You can use in useEffect hook.
function _renderSomething() {
const [data,setData] = React.useState([])
React.useEffect(()=>{
(async () => {
const results = await _dbCall();
setData(results);
})()
}, []);
return <FlatList
data={data}
renderItem={_renderFunc}
keyExtractor={item => item} />
}
Related
I am trying to render a dynamic list but inside the jsx rendered, I can't display any item this is my code, I've also tried with useState const [orderList, setOrderList] = useState([]) and setOrderList(prev => [...prev, childData]) but it returns me an empty array so I've opted for the classic javascript way but it won't work, it also won't console log the data inside the render
const OrdiniModuloVideoAds = () => {
let orderList = [];
const ordiniRef = ref(realtimeDatabase, "ordinazioneVideoAds/modulo/ordini");
useEffect(() => {
onValue(ordiniRef, (snapshot) => {
snapshot.forEach((childSnapshot) => {
const childData = childSnapshot.val();
orderList.push(childData);
});
console.log(orderList);
});
}, []);
return (
<StyledOrdiniModuloVideoAds>
<div className='ordiniWrapper'>
{orderList.map((i) => {
return (
<span>{i.mail}</span>
);
})}
</div>
</StyledOrdiniModuloVideoAds>
);
};
EDIT
This is the snippet with the useState:
const OrdiniModuloVideoAds = () => {
const [orderList, setOrderList] = useState([])
const ordiniRef = ref(realtimeDatabase, "ordinazioneVideoAds/modulo/ordini");
useEffect(() => {
onValue(ordiniRef, (snapshot) => {
snapshot.forEach((childSnapshot) => {
const childData = childSnapshot.val();
setOrderList((prev) => [...prev, childData])
});
console.log(orderList);
});
}, []);
return (
<StyledOrdiniModuloVideoAds>
<div className='ordiniWrapper'>
{orderList.map((i) => {
return (
<span>{i.mail}</span>
);
})}
</div>
</StyledOrdiniModuloVideoAds>
);
};
The data is actually added because it logs to me the array on the useEffect Any suggestion?
Try this solution hopefully it will fix your issue.
const OrdiniModuloVideoAds = () => {
const [orderList, setOrderList] = React.useState([]);
const ordiniRef = ref(realtimeDatabase, "ordinazioneVideoAds/modulo/ordini");
useEffect(() => {
onValue(ordiniRef, (snapshot) => {
snapshot.forEach((childSnapshot) => {
const childData = childSnapshot.val();
setOrderList(prev => ([...prev, childData])); /// Order list array is empty because you're not returning the data properly that's why it just gives you the default empty array in the console.
});
});
}, []);
return (
<StyledOrdiniModuloVideoAds>
<div className='ordiniWrapper'>
{orderList.map((i) => <span key={i.mail}>{i.mail}</span>)}
</div>
</StyledOrdiniModuloVideoAds>
);
};
This is because your map callback does not return anything:
<div className='ordiniWrapper'>
{array.map((i) => {
return (
<span>{i.mail}</span>
);
})}
</div>
Or the short version:
<div className='ordiniWrapper'>
{array.map((i) => (
<span>{i.mail}</span>
))}
</div>
I'm trying to make react not load until after an axios get requests finishes. I'm pretty rough on react all around, so sorry in advance.
I'm getting an array of objects
const { dogBreedsTest } = useApplicationData()
And I need it to be the default value of one of my states
const [dogBreeds, updateDogBreeds] = useState(dogBreedsTest);
However, I'm getting an error that my value is coming up as null on the first iteration of my app starting. How can I ensure that my value has completed my request before my app tries to use it?
Here is how I am getting the data for useApplicationData()
const [dogBreedsTest, setDogBreeds] = useState(null);
const getDogBreeds = async () => {
try{
const { data } = await axios.get('https://dog.ceo/api/breeds/list/all')
if(data) {
const newDogList = generateDogsArray(data['message'])
const generatedDogs = selectedDogs(newDogList)
setDogBreeds(generatedDogs)
}
} catch(err) {
console.log(err);
}
}
useEffect(() => {
getDogBreeds()
}, []);
return {
dogBreedsTest,
setDogBreeds
}
And I am importing into my app and using:
import useApplicationData from "./hooks/useApplicationData";
const { dogBreedsTest } = useApplicationData()
const [dogBreeds, updateDogBreeds] = useState(dogBreedsTest[0]);
const [breedList1, updateBreedList1] = useState(dogBreedsTest[0])
function handleOnDragEnd(result) {
if (!result.destination) return;
const items = Array.from(dogBreeds);
const [reorderedItem] = items.splice(result.source.index, 1);
items.splice(result.destination.index, 0, reorderedItem);
for (const [index, item] of items.entries()) {
item['rank'] = index + 1
}
updateDogBreeds(dogBreedsTest[0]);
updateBreedList1(dogBreedsTest[0])
}
return (
<div className="flex-container">
<div className="App-header">
<h1>Dog Breeds 1</h1>
<DragDropContext onDragEnd={handleOnDragEnd}>
<Droppable droppableId="characters">
{(provided) => (
<ul className="dogBreeds" {...provided.droppableProps} ref={provided.innerRef}>
{breedList1?.map(({id, name, rank}, index) => {
return (
<Draggable key={id} draggableId={id} index={index}>
{(provided) => (
<li ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>
<p>
#{rank}: { name }
</p>
</li>
)}
</Draggable>
);
})}
{provided.placeholder}
</ul>
)}
</Droppable>
</DragDropContext>
</div>
)
error: TypeError: Cannot read property 'map' of null
(I am mapping the data later in the program)
const getDogBreeds = async () => {
try {
const { data } = await axios.get('https://dog.ceo/api/breeds/list/all')
if(data) {
const newDogList = generateDogsArray(data['message'])
const generatedDogs = selectedDogs(newDogList)
setDogBreeds(generatedDogs)
}
} catch(err) {
console.log(err);
}
}
useEffect(() => {
getDogBreeds() // -> you are not awaiting this
}, []);
Do this instead
useEffect(() => {
axios.get('https://dog.ceo/api/breeds/list/all')
.then(res => {
const newDogList = generateDogsArray(res.data['message']);
const generatedDogs = selectedDogs(newDogList);
setDogBreeds(generatedDogs);
})
.catch(err => console.log(err));
}, []);
I know this looks awful, but I don't think you should use async/await inside useEffect
Use this in your application
useEffect will update whenever dogBreedsTest is changed. In order to make it work, start with null values and update them to the correct initial values once your async operation is finished.
const { dogBreedsTest } = useApplicationData();
const [dogBreeds, updateDogBreeds] = useState(null);
const [breedList1, updateBreedList1] = useState(null);
useEffect(() => {
updateDogBreeds(dogBreedsTest[0]);
updateBreedList1(dogBreedsTest[0]);
}, [dogBreedsTest]);
The problem is, that react first render and then run useEffect(), so if you don't want to render nothing before the axios, you need to tell to react, that the first render is null.
Where is your map function, to see the code? to show you it?.
I suppose that your data first is null. So you can use something like.
if(!data) return null
2nd Option:
In your map try this:
{breedList1 === null
? null
: breedList1.map(({id, name, rank}, index) => (
<Draggable
key={id} draggableId={id} index={index}>
{(provided) => (
<li ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>
<p>
#{rank}: { name }
</p>
</li>
)}
</Draggable> ))}
You have null, because your axios is async and react try to render before any effect. So if you say to react that the list is null, react will render and load the data from the api in the second time.
Option 1 use the optional chaining operator
dogBreedsTest?.map()
Option 2 check in the return if dogBreedsTest is an array
retrun (<>
{Array.isArray(dogBreedsTest) && dogBreedsTest.map()}
</>)
Option 3 return early
if (!Array.isArray(dogBreedsTest)) return null
retrun (<>
{dogBreedsTest.map()}
</>)
Option 4 set initial state
const [dogBreedsTest, setDogBreeds] = useState([]);
You could also add a loading state and add a loading spinner or something like that:
const [dogBreedsTest, setDogBreeds] = useState(null);
const [loading, setLoading] = useState(true)
const getDogBreeds = async () => {
setLoading(true)
try{
const { data } = await axios.get('https://dog.ceo/api/breeds/list/all')
if(data) {
const newDogList = generateDogsArray(data['message'])
const generatedDogs = selectedDogs(newDogList)
setDogBreeds(generatedDogs)
}
} catch(err) {
console.log(err);
}
setLoading(false)
}
useEffect(() => {
getDogBreeds()
}, []);
return {
dogBreedsTest,
loading,
setDogBreeds
}
Edit
Try to use a useEffect hook to update the states when dogBreedsTest got set.
const { dogBreedsTest } = useApplicationData()
const [dogBreeds, updateDogBreeds] = useState(dogBreedsTest?.[0] ?? []);
const [breedList1, updateBreedList1] = useState(dogBreedsTest?.[0] ?? [])
useEffect(() => {
updateDogBreeds(dogBreedsTest?.[0] ?? [])
updateBreedList1(dogBreedsTest?.[0] ?? [])
}, [dogBreedsTest])
How can I put data into object values in an array in javaScript? I am taking data from the backend using axios and useEffect hook! taking data I need to push that data into an object which is inside of the array! code I wrote doesn't work and ignorant! there is a JS question!
const Articles = () => {
const [articleData, setArticleData] = useState([]);
useEffect(() => {
const fetchBlogs = async () => {
try {
const res = await axios.get(
`${process.env.REACT_APP_API_URL}/article/list/all`
);
setArticleData(res.data);
} catch (err) {}
};
fetchBlogs();
}, []);
for (let article in articleData) {
data: [
{
key: article._id,
title: article.title,
author_username: article.author_username,
category: article.category_name,
subcategory: article.subCategory_name,
publication_date: article.publication,
},
],
}
return (
<div className='articles'>
<Table
columns={columns}
dataSource={data}
size='large'
/>
</div>
)
};
You can use map to get a new array, then save it to the state and use the state variable in JSX. Something like this should work for you
const Articles = () => {
const [articleData, setArticleData] = useState([]);
useEffect(() => {
const fetchBlogs = async () => {
try {
const res = await axios.get(
`${process.env.REACT_APP_API_URL}/article/list/all`
);
setArticleData(res.data.map(article => ({
key: article._id,
title: article.title,
author_username: article.author_username,
category: article.category_name,
subcategory: article.subCategory_name,
publication_date: article.publication,
})));
} catch (err) {}
};
fetchBlogs();
}, []);
return (
<div className='articles'>
<Table
columns={columns}
dataSource={articleData}
size='large'
/>
</div>
)
};
I have a main screen where you can see multiple items displayed in card form you can access an item by pressing it to see its details, on the details screen I added a bookmark option that save the item using async storage, you can also check the saved items on a different screen called savedItems screen.
The problem is :
when i bookmark an item it get saved properly and i can go to the savedItems screen and find it there, but some times i have to reload the app for the item to appear on the savedItems screen why is that ?
if i book multiple items they all get saved ( on console.log ) but only the last one appears for some reason i never get more then one item displayed on the SavedItems screen
Bellow is snippets of the code used to book mark ( saved an item on its details screen )
Details.js
const DetailScreen = (props) => {
const [saved, setSaved] = useState([]);
const [items, setItems] = useState(props.route.params);
const onSave = (item) => {
const newItems = [...saved, item];
setSaved(newItems);
const items = JSON.stringify(newItems);
SaveItem("saved", items).then((res) => {
console.log("saved", res);
});
};
const goToDetails = () => {
setSaved([]);
props.navigation.navigate("SaveScreen");
};
const { width, height } = Dimensions.get("window");
const { data } = props.route.params; // this returns the data from each article
//ReadItem("saved").then((res) => console.log(res));
return (
<TouchableOpacity
...
onPress={() => {
onSave(data);
}}
>
<MaterialCommunityIcons
name="bookmark"
size={35}
color={colors.shade2}
/>
</TouchableOpacity>
)
SaveScreen.js
export default class Details extends Component {
state = {
saved: [],
};
removeItem = () => {
DeleteItem("saved")
.then((res) => {
this.setState({
saved: [],
});
console.log(res);
})
.catch((e) => console.log(e));
};
componentDidMount = () => {
ReadItem("saved")
.then((res) => {
if (res) {
const saved = JSON.parse(res);
this.setState({
saved: saved,
});
}
})
.catch((e) => console.warn(e));
};
render() {
return (
<View style={styles.container}>
<FlatList
keyExtractor={(item, index) => index.toString()}
data={this.state.saved}
renderItem={({ item }) => {
return (
<TouchableScale
activeScale={0.9}
tension={50}
friction={7}
useNativeDriver
onPress={() =>
this.props.navigation.navigate("DetailScreen", { data: item })
}
>
<Card item={item} />
</TouchableScale>
);
}}
/>
{this.state.saved.length > 0 && (
<TouchableOpacity onPress={this.removeItem} style={styles.button}>
<Text style={styles.save}>Remove Key</Text>
</TouchableOpacity>
)}
</View>
);
}
}
the code used to save data using async storage
Dbhelper.js
import { AsyncStorage } from "react-native";
export const SaveItem = async (key, value) => {
try {
await AsyncStorage.setItem(key, value);
console.log("saved");
} catch (e) {
console.log(e);
}
};
export const ReadItem = async (key) => {
try {
var result = await AsyncStorage.getItem(key);
return result;
} catch (e) {
return e;
}
};
export function MultiRead(key, onResponse, onFailure) {
try {
AsyncStorage.multiGet(key).then((values) => {
let responseMap = new Map();
values.map((result, i, data) => {
let key = data[i][0];
let value = data[i][1];
responseMap.set(key, value);
});
onResponse(responseMap);
});
} catch (error) {
onFailure(error);
}
}
export async function DeleteItem(key) {
try {
await AsyncStorage.removeItem(key);
return true;
} catch (exception) {
return false;
}
}
if i book multiple items they all get saved ( on console.log ) but only the last one appears for some reason i never get more then one item displayed on the SavedItems screen
I think the problem is when you save new item you remove old items. You have to read old items when you mount DetailScreen. Or you can read items inside onSave method. It's more safety. It depends your application architecture
const DetailScreen = (props) => {
const [items, setItems] = useState(props.route.params);
const onSave = (item) => {
let saved = [];
ReadItem("saved")
.then((res) => {
if (res) {
saved = JSON.parse(res);
}
const newItems = [...saved, item];
const items = JSON.stringify(newItems);
SaveItem("saved", items).then((res) => {
console.log("saved", res);
});
})
.catch((e) => console.warn(e));
};
const goToDetails = () => {
setSaved([]);
props.navigation.navigate("SaveScreen");
};
const { width, height } = Dimensions.get("window");
const { data } = props.route.params; // this returns the data from each article
//ReadItem("saved").then((res) => console.log(res));
return (
<TouchableOpacity
...
onPress={() => {
onSave(data);
}}
>
<MaterialCommunityIcons
name="bookmark"
size={35}
color={colors.shade2}
/>
</TouchableOpacity>
)
when i bookmark an item it get saved properly and i can go to the savedItems screen and find it there, but some times i have to reload the app for the item to appear on the savedItems screen why is that ?
I have only one idea why it may happens. In some cases, you don't unmount SaveScreen. When you return to the SaveScreen componentDidMount is not called. Just try to add console.log to componentDidMount and when you reproduce this behavior if console.log is missed it means you SaveScreen wasn't unmounted
To handle all cases you can try to do that:
export default class Details extends Component {
state = {
saved: [],
};
removeItem = () => {
DeleteItem("saved")
.then((res) => {
this.setState({
saved: [],
});
console.log(res);
})
.catch((e) => console.log(e));
};
componentDidMount = () => {
this.readItems();
this.unsubscribe = navigation.addListener('focus', this.readItems);
};
componentWillUnmount() {
this.unsubscribe();
}
readItems = () => {
ReadItem("saved")
.then((res) => {
if (res) {
const saved = JSON.parse(res);
this.setState({
saved: saved,
});
}
})
.catch((e) => console.warn(e));
}
render() { ... }
}
I'm trying to build a custom hook to fetch data.
This is my App.js:
const [loadData, mainCategories] = useCustomHook();
useEffect(() => {
loadData();
}, []);
return (
<FlatList
data={mainCategories}
renderItem={({ item }) => {
return (
<View>
<Text>{item.name}</Text>
</View>
);
}}
/>
);
};
This is my useCustomHook.js:
const useCats = () => {
const [mainCategories, setMainCategories] = useState();
const loadData = async () => {
let response = await fetch(
`URL/read.php`
);
let results = await response.json();
setMainCategories(results.data);
};
return [loadData, mainCategories];
};
export default useCustomHook;
It''s working fine. I'm returning and exporting loadData and mainCategories from my useCustomHook.js and I'm getting both in App.js to use it there.
Now comes the part I'm struggling with:
Let's say I need a constant from App.js in the useCustomHook.js. In my case I'm getting an ID in App.js (forwarded by react-navigation from the previous screen) which I need in the URL like so:
URL/read.php?id=${id}
Is there any way to get that ID in useCustomHook.js?
You can pass it as a parameter. Try below
const useCats = () => {
const [mainCategories, setMainCategories] = useState();
const loadData = async (id) => {
let response = await fetch(
`URL/read.php?id=${id} `
);
let results = await response.json();
setMainCategories(results.data);
};
return [loadData, mainCategories];
};