Render Item in Agenda using database data - React Native - javascript

I'm trying to show the items I get from my database in the calendar, everything is working fine (maybe not), but in short I got the data from the database with an array and then I converted it to an object (because the calendar only accepts objects), but it doesn't show anything and it doesn't give an error either
import React, { useEffect, useState } from 'react'
import { StyleSheet, View, Text } from 'react-native'
import { LocaleConfig, Agenda } from 'react-native-calendars'
import DateTimePicker from 'react-native-modal-datetime-picker';
import { getAuth } from 'firebase/auth';
import { getDatabase, ref, onValue, set, push, get, child } from 'firebase/database';
const Calendario = () => {
const dbRef = ref(getDatabase());
const data = []
var obj = {}
// getting data from the database
useEffect(() => {
getInDB()
} ,[])
const getInDB = () => {
get(child(dbRef, 'users/' + app.currentUser.uid)).then((snapshot) => {
snapshot.forEach(childsnap => {
let dateD = childsnap.child("date").val()
let titleD = childsnap.child("title").val()
let dtsD = childsnap.child("details").val()
// "yyyy-MM-dd": [{any: "whatever", any2: "whatever"}],
data.push({
[dateD] : [{ title: titleD, details: dtsD }],
});
})
obj = Object.assign({}, ...data)
console.log(obj)
})
}
const renderItem = (item) => {
return(
<View style={styles.itemContainer}>
<Text style={styles.textInf}>{item.title}</Text>
<Text style={styles.textInf}>{item.details}</Text>
</View>
)
}
return (
<>
<Agenda
items={obj}
renderEmptyDate={() => {
return <View />;
}}
renderEmptyData={() => {
return <View />;
}}
selected={new Date()}
minDate={null}
renderItem={renderItem}
markingType="custom"
/>
</>
}

You need to use state and set it or otherwise your component will not be rerendered with the new data.
Furthermore, the Agenda component expects an object. By using data as an array and the spread operator, we won't get the desired result.
You can implement this correctly as follows.
...
const [obj, setObj] = useState({});
...
const getInDB = () => {
get(child(dbRef, 'users/' + app.currentUser.uid)).then((snapshot) => {
const temp = {}
snapshot.forEach(childsnap => {
let dateD = childsnap.child("date").val()
let titleD = childsnap.child("title").val()
let dtsD = childsnap.child("details").val()
Object.assign(temp, {dateD: [{ title: titleD, details: dtsD }]})
})
setObj(temp)
})
}
I have implemented a little snack.

Related

How to i display only one chatcard for single room instead of multiple chatcard with same rooms?

How to i display only one chatcard for single room instead of multiple chatcard with same rooms? i am trying to develop a directs box like ig currently messages are displaying correctly with username and profilepic but I am facing a problem that if a user sends multiple messages to any user then multiple chatcard displaying in UI how can I fix this problem?
The only thing which is common is "RoomId" which is coming from backend how can I display that? i am getting RoomId from backend also I do have senderUsername that I want to display but how can I display each chatcard for one room?
i have added some images please check that from here you can see a sender name HelloWorld send 4 messages with 4 different chatcards how can display one chatcard for single room?
the RoomId is a room of the users who are chating
import { Text, View } from 'react-native'
import React, { useState, useEffect } from 'react'
import { ScrollView, TextInput } from 'react-native-gesture-handler'
import { Ionicons } from '#expo/vector-icons';
import { formHead } from '../../CommonCss/FormCss';
import ChatCard from '../../Cards/ChatCard';
import styles from './styles';
import AsyncStorage from '#react-native-async-storage/async-storage';
const MainChat = ({ navigation }) => {
const [keyword, setKeyword] = useState('')
const [chats, setChats] = useState({});
useEffect(() => {
const fetchData = async () => {
try {
const userDataString = await AsyncStorage.getItem('user');
const MyData = await JSON.parse(userDataString);
const response = await fetch(`http://10.0.2.2:3000/g`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
MyUserId: MyData.user._id
})
});
const data = await response.json();
let tempChats = {};
data.forEach((chat) => {
if (!tempChats[chat.username]) {
tempChats[chat.username] = {
username: chat.username,
messages: [chat.lastMessage],
};
} else {
tempChats[chat.username].messages.push(chat.lastMessage);
}
});
setChats(tempChats);
await AsyncStorage.setItem('chats', JSON.stringify(tempChats));
} catch (error) {
console.error(error);
}
};
(async () => {
const cachedChats = await AsyncStorage.getItem('chats');
if (cachedChats) {
setChats(JSON.parse(cachedChats));
} else {
fetchData();
}
})();
}, []);
return (
<ScrollView style={styles.container}>
<Ionicons name="arrow-back" size={24} color="grey" style={styles.backbtn}
onPress={() => navigation.navigate("home")}
/>
<View style={styles.searchSection}>
<Text style={formHead}>Your Chats</Text>
<TextInput placeholder='Search'
style={styles.searchbar}
onChangeText={(text) => setKeyword(text)}
/>
</View>
<View style={styles.ChatSection}>
{
chats
? Object.values(chats)
.filter((chat) => {
if (keyword === '') {
return chat;
} else if (
chat.username.toLowerCase().includes(keyword.toLowerCase())
|| chat.messages.some((message) => message.toLowerCase().includes(keyword.toLowerCase()))
) {
return chat;
}
return false;
})
.map((chat) => {
return <ChatCard key={chat.username} chat={chat} />;
})
: null
}
</View>
</ScrollView>
)
}
export default MainChat

React 'useContext' hook not re-rendering after context updates with database data

I am using React's Context API to share data that most of my components need.
The Context is initially defined, but shortly receives data from the Firebase database (please see IdeaContext.tsx). I define the context in a functional component and the display component, which returns a small card based on the information received.
However, the component doesn't render when I start the development server with Yarn. Instead, in order to get it to render, I have to write console.log('something') inside the display component and then it suddenly re-renders. However, when I refresh the server, it again doesn't render.
How can I make my component render immediately (or at least after the context updates with the data from the database?)
Code:
Context Definition:
import React, { createContext, useEffect, useState } from "react";
import { IdeaContextType, Idea } from "../t";
import {ideasRef} from './firebase'
function getIdeas() {
var arr: Array<Idea> = [];
ideasRef.on('value', (snapshot) => {
let items = snapshot.val()
snapshot.forEach( (idea) => {
const obj = idea.val()
arr.push({
title: obj.title,
description: obj.description,
keyID: obj.keyID
})
console.log(arr)
})
})
return arr
}
const IdeaContextDefaultValues: IdeaContextType = {
ideas: [],
setIdeas: () => {},
};
const IdeaContext = createContext<IdeaContextType>(IdeaContextDefaultValues)
const IdeaContextProvider: React.FC = ({ children }) => {
const [ideas, setIdeas] = useState<Array<Idea>>(
IdeaContextDefaultValues.ideas);
useEffect( ()=> {
console.log('getting info')
setIdeas(getIdeas())
}, [])
useEffect( () => {
console.log('idea change: ', ideas)
}, [ideas])
return (
<IdeaContext.Provider value={{ ideas, setIdeas }}>
{children}
</IdeaContext.Provider>
);
};
Displayer and Card Component
import React, { FC, ReactElement, useContext } from "react";
import IdeaCreator from "./IdeaCreator";
import { IdeaContext } from "./IdeaContext";
import { Idea } from "../t";
import { Link } from "react-router-dom";
const IdeaPost:React.FC<Idea> = ({title, keyID, description}):ReactElement => {
console.log('Received',title,description,keyID)
return (
<div className="max-w-sm rounded overflow-hidden shadow-lg">
<img
className="w-full"
src="#"
alt="Oopsy daisy"
/>
<div className="px-6 py-4">
<div className="font-bold text-xl mb-2"> <Link to={"ideas/" + keyID} key= {keyID}> {title}</Link> </div>
<p className="text-gray-700 text-base">{description}</p>
</div>
</div>
);
};
const IdeaDisplay:FC<any> = (props:any):ReactElement => {
const { ideas, setIdeas } = useContext(IdeaContext)
console.log('Ideas in display: ', ideas)
console.log('test') //This is what I comment and uncommend to get it to show
return (
<div className="flex flex-wrap ">
{ideas.map((idea) => {
console.log(idea)
console.log('Sending',idea.title,idea.description,idea.keyID)
console.log(typeof idea.keyID)
return (
<IdeaPost
title={idea.title}
description={idea.description}
keyID = {idea.keyID}
key = {idea.keyID * 100}
/>
);
})}
</div>
);
};
export default IdeaDisplay;
Solution Code:
import React, { createContext, useEffect, useState } from "react";
import { IdeaContextType, Idea } from "../t";
import {ideasRef} from './firebase'
async function getIdeas() {
var arr: Array<Idea> = [];
const snapshot = await ideasRef.once("value");
snapshot.forEach((idea) => {
const obj = idea.val();
arr.push({
title: obj.title,
description: obj.description,
keyID: obj.keyID,
});
console.log(arr);
});
return arr
}
const IdeaContextDefaultValues: IdeaContextType = {
ideas: [],
setIdeas: () => {},
};
const IdeaContext = createContext<IdeaContextType>(IdeaContextDefaultValues)
const IdeaContextProvider: React.FC = ({ children }) => {
const [ideas, setIdeas] = useState<Array<Idea>>(
IdeaContextDefaultValues.ideas);
useEffect(() => {
console.log("getting info");
const setup = async () => {
const ideas = await getIdeas();
setIdeas(ideas);
};
setup()
}, []);
useEffect( () => {
console.log('idea change: ', ideas)
const updateDatabase = async () => {
await ideasRef.update(ideas)
console.log('updated database')
}
updateDatabase()
}, [ideas])
return (
<IdeaContext.Provider value={{ ideas, setIdeas }}>
{children}
</IdeaContext.Provider>
);
};
export {IdeaContext, IdeaContextProvider}
First of all you would need to use once and not on if you want to get the data only once. If you want to use a realtime listener you could send the setIdeas to your function. Also try to be carefull with async/away calls to the Firebase sdk. Your code could look like this:
import React, { createContext, useEffect, useState } from "react";
import { IdeaContextType, Idea } from "../t";
import { ideasRef } from "./firebase";
async function getIdeas() {
var arr: Array<Idea> = [];
const snapshot = await ideasRef.once("value");
let items = snapshot.val();
snapshot.forEach((idea) => {
const obj = idea.val();
arr.push({
title: obj.title,
description: obj.description,
keyID: obj.keyID,
});
console.log(arr);
});
return arr;
}
const IdeaContextDefaultValues: IdeaContextType = {
ideas: [],
setIdeas: () => {},
};
const IdeaContext = createContext < IdeaContextType > IdeaContextDefaultValues;
const IdeaContextProvider: React.FC = ({ children }) => {
const [ideas, setIdeas] =
useState < Array < Idea >> IdeaContextDefaultValues.ideas;
useEffect(() => {
console.log("getting info");
const getData = async () => {
const ideas = await getIdeas();
setIdeas(ideas);
};
}, []);
useEffect(() => {
console.log("idea change: ", ideas);
}, [ideas]);
return (
<IdeaContext.Provider value={{ ideas, setIdeas }}>
{children}
</IdeaContext.Provider>
);
};

react native : What is the way to fill in the table according to "Conteiners_Count" and also "Containers_Description"

In my example I tried to fill the contents of the table with the data Conteiners_Count and also Containers_Description according to Sampling_Request_ID but it does not display the data.
I would be happy to help..
import React, { useState, useEffect } from 'react';
import { useRoute, useNavigation } from '#react-navigation/native';
import { Table, Row, Rows } from 'react-native-table-component';
import { getExecutionDetail } from '../webservice_functions/tblSamplingExecution_Table';
const SikumHamechalim = () => {
const navigation = useNavigation();
const route = useRoute();
const params = route.params;
const { selectedItems } = params;
const [sikumVisible, setSikumVisible] = useState([]);
useEffect(() => {
(async () => {
try {
const sikum = await getExecutionDetail(selectedItems[0].Sampling_Request_ID);
const typesikum = sikum;
console.log('frog', typesikum);
setSikumVisible(typesikum);
} catch (err) {
console.warn('Error with getting sikum:', err);
}
})();
}, []);
const [tableHead, setTableHead] = useState(['Container type', 'Number of containers']);
let flatTable = {}
sikumVisible.forEach(e =>
flatTable[e.Containers_Description] = flatTable[e.Containers_Description] ? flatTable[e.Containers_Description] + e.Conteiners_Count :
e.Conteiners_Count)
const table = Object.keys(flatTable).map(key => [key, flatTable[key]])
const [tableData, setTableData] = useState(table);
return (
<>
<View style={styles.Secondary_title}>
<Text style={styles.secondaryTitleText}>
Concentration of containers for selected references
</Text>
</View>
<View style={styles.DividerLine}></View>
<View style={styles.container}>
<Table borderStyle={{ borderWidth: 2, borderColor: '#c8e1ff' }}>
<Row data={tableHead} style={styles.head} textStyle={styles.text} />
<Rows data={tableData} textStyle={styles.dataText} />
</Table>
</View>
</>
)
};
This is the data of "typesikum" :
[ {
"Containers_Count":1,
"Containers_Description":"DOG",
},
{
"Containers_Count":2,
"Containers_Description":"HORSE",
}
]
in react way you must consider that table first render would be empty until data comes from api, so change your code like this:
const [tableHead, setTableHead] = useState(['Container type', 'Number of containers']);
const [tableData, setTableData] = useState([]);
const arrangeData = ()=>{
let rows = [];
sikumVisible.forEach(e =>
{
let row = [e.Containers_Description,e.Conteiners_Count];
rows.push(row);
});
setTableData(rows);
}
useEffect(()=>{arrangeData ();},[sikumVisible]);
useEffect(() => {
(async () => {
try {
const sikum = await getExecutionDetail(selectedItems[0].Sampling_Request_ID);
const typesikum = sikum;
console.log('frog', typesikum);
setSikumVisible(typesikum);
} catch (err) {
console.warn('Error with getting sikum:', err);
}
})();
}, []);
in above code after "sikumVisible" changes by api call, second effect arrange data and fill out table, also you can arrange data after api call and pass data to be arrange and set to table

React state updates without a setState function - Unexpected

I have a few chat components, chat (parent), CreateMessage(child), and DisplayMessages(child). All three components will be shown below.
The user creates a message with the CreateMessage component. It saves it in the useState hook, indivMessages, stored in the parent Chat component.
The indivMessages are sent to the DisplayMessages component. It displays the messages as well as groups messages by the same author together based off the user id.
The problem is, the indivMessages state is getting set with the value from formattedMessages, which is only set inside the useEffect hook in DisplayMessages.
Why is indivMessages getting set with the value for formattedMessages??
For the sake of this example, I commented out all of the socket stuff to just work in a sterile environment - the same results will happen both ways.
Chat.js
import React, { useState, useEffect, useContext } from "react";
import { useSelector } from "react-redux";
import { SocketContext } from "src/SocketContext";
import CreateMessage from "./details/CreateMessage";
import DisplayMessages from "./details/DisplayMessages";
export default function Chat(props) {
const state = useSelector((state) => state);
const [indivMessages, setIndivMessages] = useState([]);
const socket = useContext(SocketContext);
// useEffect(() => {
// if (state.chatShow) {
// socket.emit("SUBSCRIBE_CHAT", state.chat.chatRoom);
// return () => {
// socket.emit("UNSUBSCRIBE", state.chat.chatRoom);
// };
// }
// });
// useEffect(() => {
// socket.on("new_message", (data) => {
// setIndivMessages([...indivMessages, data]);
// });
// return () => {
// socket.off("new_message");
// };
// }, [socket, indivMessages]);
return (
<div className="d-flex flex-column h-100 justify-content-end">
<DisplayMessages state={state} indivMessages={indivMessages} />
<CreateMessage
state={state}
indivMessages={indivMessages}
setIndivMessages={setIndivMessages}
/>
</div>
);
}
CreateMessage.js
import React, { useState, useContext } from "react";
import { CInputGroup, CInput, CInputGroupAppend, CButton } from "#coreui/react";
import CIcon from "#coreui/icons-react";
import { SocketContext } from "src/SocketContext";
export default function CreateMessage(props) {
const { indivMessages, setIndivMessages, state } = props;
const [newMessage, setNewMessage] = useState("");
const socket = useContext(SocketContext);
const sendMessage = () => {
let messageTemplate = {
messages: [{ msg: newMessage }],
username: state.user.username,
_id: indivMessages.length + 1,
ownerId: state.user._id,
picture: state.user.picture,
chatRoom: state.chat.chatRoom,
date: Date.now(),
};
// socket.emit("create_message", messageTemplate);
setIndivMessages((msgs) => [...msgs, messageTemplate]);
document.getElementById("msgInput").value = "";
};
return (
<CInputGroup style={{ position: "relative", bottom: 0 }}>
<CInput
type="text"
style={{ fontSize: "18px" }}
id="msgInput"
className="rounded-0"
placeholder="Type a message here..."
autoComplete="off"
onChange={(e) => setNewMessage(e.target.value)}
onKeyUp={(e) => e.code === "Enter" && sendMessage()}
/>
<CInputGroupAppend>
<CButton color="success" className="rounded-0" onClick={sendMessage}>
<CIcon name="cil-send" />
</CButton>
</CInputGroupAppend>
</CInputGroup>
);
}
DisplayMessages.js
import React, { useEffect, useState } from "react";
import { CContainer, CCard, CImg, CCol, CLabel } from "#coreui/react";
export default function DisplayMessages(props) {
const { indivMessages, state } = props;
const [formattedMessages, setFormattedMessages] = useState([]);
useEffect(() => {
//Create Grouped Messesges
let messagesArray = [...indivMessages];
let sortedArray = messagesArray.sort((a, b) => {
return new Date(a.date) - new Date(b.date);
});
let grouped = [];
for (let i = 0; i < sortedArray.length; i++) {
let index = grouped.length - 1;
if (sortedArray[i].ownerId === grouped[index]?.ownerId) {
let lastMessage = grouped.pop();
sortedArray[i].messages[0]._id = sortedArray[i]._id;
lastMessage.messages = [
...lastMessage.messages,
sortedArray[i].messages[0],
];
grouped.push(lastMessage);
} else {
console.log(i, grouped.length);
grouped.push(sortedArray[i]);
}
}
setFormattedMessages(grouped);
}, [indivMessages]);
useEffect(() => {
let msgContainer = document.getElementById("msgContainer");
msgContainer.scrollTop = msgContainer.scrollHeight;
}, [formattedMessages]);
return (
<CContainer
className="mt-2 no-scroll-bar"
style={{ overflow: "auto", maxHeight: "85vh" }}
id="msgContainer"
>
{formattedMessages.map((msg) => {
return (
<CCard
key={msg._id}
className="d-flex flex-row p-2"
color="secondary"
accentColor={state.user._id === msg.ownerId && "primary"}
>
<CImg
src={msg.picture}
alt={msg.owner}
className="w-25 align-self-start rounded"
/>
<CCol>
<CLabel>
<strong>{msg.username}</strong>
</CLabel>
{msg.messages.map((message) => {
return (
<p key={message._id ? message._id : msg._id}>{message.msg}</p>
);
})}
</CCol>
</CCard>
);
})}
</CContainer>
);
}
I am thinking that something is going on within the useEffect hook in the DisplayMessages component. This function executes every time the indivMessages array changes.
It creates grouped messages by checking if the newest message's author (ownerId), is the same as the previous message's author. If they are the same, it extracts all the messages from the message itself, and adds them to the previous message's message key in order to create grouped messages.
This result is what is being set in the indivMessages array, which is unexpected since I do not set indivMessages with grouped messages!
Thanks for your help and thanks for some pointers in the right direction!
In the useEffect code, you're modifying the objects in sortedArray, which are also held in indivMessages. For instance:
sortedArray[i].messages[0]._id = sortedArray[i]._id;
Your code setting up sortedArray just copies the array, not the objects within it. If you want to modify those objects, you need to make copies first. For instance, the example above becomes:
sortedArray[i] = {
...sortedArray[i],
messages: [{...sortedArray[i].messages[0], _id = sortedArray[i]._id}, ...sortedArray[i].messages.slice(1)],
};
...or similar.
You have the same sort of problem with:
lastMessage.messages = [
...lastMessage.messages,
sortedArray[i].messages[0],
];
...but probably want to solve it elsewhere (perhaps by changing grouped.push(sortedArray[i]); to grouped.push({...sortedArray[i]});, but I haven't done a really deep read of that code).

Last AsyncCallback item disappearing in React Native

I have an array of objects and this data should be stored in when new item is appended to the list. Also, when the application loads, this information have to be pushed back into the list. So, I use AsyncCallback, but it doesn't work properly. When I refresh application, all stored elements appear in the list except the last one. How can I return this item back to the list?
import React, { useState, useEffect } from 'react';
import { StyleSheet, View, Text, FlatList, Button, AsyncStorage } from 'react-native';
export default function HomeScreen() {
const [listItems, setListItems] = useState([
{
// example list item
id: '0',
text: 'Item 1'
},
]);
const [idx, incrIdx] = useState(1);
useEffect(() => {
getData();
}, []);
const pushItem = () => {
var data = new Object();
data.id = idx.toString();
data.text = "Item " + idx;
setListItems([...listItems, data]);
incrIdx(idx + 1);
storeData();
};
const storeData = async () => {
await AsyncStorage.setItem('listItems', JSON.stringify(listItems));
};
const getData = async () => {
const value = await AsyncStorage.getItem('listItems');
if(value !== null) {
setListItems([...listItems, JSON.parse(value)]);
}
};
return (
<View style={styles.container}>
<FlatList
data={listItems}
renderItem={({item}) =>
<View>
<Text>{item.text}</Text>
</View>
}
keyExtractor={item => item.id}
/>
<Button
title="Push data"
onPress={pushItem}
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
},
});
Did you look at what AsyncStorage.setItem is actually setting?
My guess is that it's not using the latest listItems state because hook setters are asynchronous (just like the old setState).
To express logic that should take effect when state is updated, use the useEffect api.
useEffect(() => {
// Instead of calling storeData() with stale state
AsyncStorage.setItem('listItems', JSON.stringify(listItems));
}, [listItems]);

Categories