Creating Group Chat Feature - javascript

I have been stunned on this problem for a bit now, so I've come to ask for help. I am creating a private messaging app with a group chat feature. I am using firebase 7.14.0 (yes I know that's old) when you click on a contact in my app you can create a groupchat which will create another chat with just you in it (I know its not optimal I just want to get functionality working first) then in that chat you can add people to the chat.
Here is the code I need help with.
import React, { useEffect, useState, useCallback } from 'react';
import firebase from "../firebase"
import { NavLink } from 'react-router-dom';
import { Avatar } from '#material-ui/core';
import './Gccontact.css';
const Gccontact = props => {
const [contact, setContact] = useState()
const [conversation, setConversation] = useState()
const getContact = useCallback(async () => {
const id = props.contact
const db = firebase.db
const unsubscribe = db.collection("users").doc(id).onSnapshot(snapshot => {
setContact(snapshot.data())
})
return unsubscribe
},[props.contact])
useEffect(() => {
getContact()
}, [props])
const addtogroup = useCallback(async uid => {
const members = [contact.uid];
await firebase.db.collection("conversations").doc('x8DGIeBhW96ziGh0MEGf').update({ members });
}, []);
/* db.collection("users").doc(currentUser.uid).update({
status
}) */
return (
<li className="contact">
{contact && <div className="wrap" onClick={addtogroup}>
<span className={`contact-status ${contact.status}`}></span>
<div className="img-container">
<Avatar src={contact.profilePicture} alt={contact.name + " Profile Picture"} />
</div>
<div style={{display: "inline-block"}} className="meta">
<p className="display-name">{contact.name}</p>
{props.recent && <p className="preview">
{props.recent?.sender === firebase?.auth?.currentUser?.uid && <span>You: </span>}
{props.recent?.attachments?.length === 0 ? props.recent?.body : "Picture"}
</p>}
</div>
</div>}
</li>
);
}
export default Gccontact;
This is the contact file, when you click on the contact I want it to add them to the groupchat. When I try to add the members to group im getting an error saying contact.uid is undefined, so I tried contact.name and it still didn't work. As far as the
await firebase.db.collection("conversations").doc('x8DGIeBhW96ziGh0MEGf').update({ members });
goes I don't know how to get each individuals documents id, so I have a set one just to test. and with .update I noticed it gets rid of all the members and just adds that one that a defined.

Related

react fetching and mapping data

import React,{useState, useEffect} from 'react'
import { useParams } from 'react-router-dom'
import Home from './Home'
import './detailpage.css'
function DetailPage({name,
info,
genre,
_id,
episodeNumber,
poster}) {
const [shows, setShows]= useState([{name:'',
info:'',
airingDate:'',
_id:'',
genre:'',
episodeNumber:'',
poster:''
}])
const params= useParams();
useEffect(()=>{
fetch("/home")
.then(res => res.json())
.then(jsonRes => setShows(jsonRes))
}, [])
const b = JSON.stringify(params);
const newShows = shows.filter(a=>a._id===b)
console.log(newShows)
return (
<div>
<h2>.</h2>
<h2>.</h2>
<h2>.</h2>
<h2>{JSON.stringify(params)}</h2>
<h2>{shows.genre}</h2>
{newShows.map(a=>
<div>
<div className='container'>
<img className='showImg' src={a.poster} alt=''></img>
<h2 className='showTitle'>{a.title}</h2>
<h3>{a.genre}</h3>
<p className='showInfo'>{a.info} </p>
</div>
</div>
)}
<h2>{episodeNumber}</h2>
<h2>{shows.info}</h2>
</div>
)
}
export default DetailPage
I have tv shows on my Home page and after clicking the image I want it to load the detail page about the clicked show however I couldn't manage to do it. I tried 'filter' method in the code but it didn't work I also tried like this
const newShows = shows.filter(a=>a.genre.length>5)
it works but this is not what I want. I would be really happy if someone could've helped. Thank you so much.
If I were you, I wouldn't use this fetch, as when you click on the image from your home you already know which tv show you want to display more details about.
I would use something like useLocation from react-router-dom, and while changing pages (home -> detail page about a tv show) carry a state variable with the specific tv show details.
https://v5.reactrouter.com/web/api/Hooks/usehistory
const handleClick = (state) => {
history.push({ pathname: "/detail-page", state })
}
<YourTvShowImage onClick={() => handleClick(TvShowData)} />
Then on your detail page class you use something like
https://v5.reactrouter.com/web/api/Hooks/uselocation
const location = useLocation()
const [tvShowData, setTvShowData] = useState()
useEffect(() => {
if (location.state) {
setTvShowData(location.state)
}
}, [location])

Draft-js JSON to EditorState does not update

So, I used Draft-js to create blog posts. When a user creates a post, the data is converted to a string and sent to the server to be saved. I converted the draft-js EditorState like this:
JSON.stringify(convertToRaw(editorState.getCurrentContent())).
Now, I want to add an edit post function. To do this, I get the string data from the server (in the exact same format as described above), and I try to create an editorState from it like this:
let data = convertFromRaw(res.data['postContent'])
setEditorState(EditorState.createWithContent(data))
This seems to work as when I run console.log(convertToRaw(editorState.getCurrentContent())) I can see that the server data is in the editorState. However, nothing is displayed inside of the editor. My question: How do I show the data to the user inside the editor, and make it editable? As in, the user can see the post and modify parts of it.
Here is a screenshot of what I get (as you can see, the title is there but the editor is empty when it should say "Test data!"):
Here is my full code:
import React, {useEffect, useState} from 'react'
import { EditorState, convertToRaw, convertFromRaw } from 'draft-js'
import { useLocation } from 'react-router-dom';
import { Editor } from 'react-draft-wysiwyg';
import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css';
import './css/CreateBlog.css'
import ApiClient from '../util/ApiClient';
// submit the blog post
const submitPage = async (data, postTitle, setPosted, setPostFailed) => {
const content = JSON.stringify(convertToRaw(data));
ApiClient.post("/blog/handle-blog", {"postTitle": postTitle,"postContent": content})
.then((res) => {
console.log(res)
// there was no error
if(res.status === 201) {
setPosted("Your blog has been posted.")
setPostFailed(false)
} else {
setPosted("There was an error.")
setPostFailed(true)
}
})
}
// if this is an edit
const getPostData = async (postID, setEditorState, setPostTitle) => {
const res = await ApiClient.get(`/blog/get-blog/${postID}`)
// set the title and data
setPostTitle(res.data['postTitle'])
setEditorState(EditorState.createWithContent(convertFromRaw(res.data['postContent']))) // set the editorState data
}
export default function CreateBlogpost() {
const location = useLocation();
const postID = location.state?.id; // get the ID if there is one
const [editorState, setEditorState] = useState(null);
const [postTitle, setPostTitle] = useState("");
const [posted, setPosted] = useState(""); // the error/success message
const [postFailed, setPostFailed] = useState(false); // has the blog successfully been posted?
// handle the post button click
const handleSubmit = async (e) => {
e.preventDefault();
// check the post contains data
if(editorState === EditorState.createEmpty() || postTitle == "") {
setPosted("Please add some content to your post.")
setPostFailed(true)
return;
}
const data = editorState.getCurrentContent()
await submitPage(data, postTitle, setPosted, setPostFailed);
}
useEffect(() => {
// if the state has an ID, then you're editing a post
if(postID) {
getPostData(postID, setEditorState, setPostTitle)
}
}, [])
return(
<div className="create-blog">
<div className="create-blog-text m-2 mb-3">
<h1>{postID ? "Edit" : "Create"} a blog post</h1>
</div>
<div className="d-flex">
<h3 className="m-2">Title: </h3>
<input value={postTitle} type="text" id="fname" className="m-2" onChange={e=>setPostTitle(e.target.value)} />
<p className="m-2">Please note: if you want to upload a photo, upload it to an image sharing site like imgur and then link the image.</p>
</div>
<div className="editor-container mb-3 ml-2 mr-2"
style={{ border: "1px solid black", minHeight: "6em", cursor: "text" }}
>
<Editor value={editorState} onEditorStateChange={setEditorState} />
</div>
<button className="button-3 m-2" onClick={(e) => handleSubmit(e)}>
{postID ? "Edit" : // if there is a postID, then you are editing the post
"Post" }
</button>
{/* <button className="button-3 m-2" onClick={(e) => handleSubmit(e)}>Save as draft</button> */}
<h4 style={{ color: postFailed ? 'red' : 'green'}}>{posted}</h4>
</div>
)
}
change
<Editor
value={editorState}
onEditorStateChange={setEditorState}
/>
to
<Editor
editorState={editorState}
onEditorStateChange={setEditorState}
/>
Here is the working version in sandbox. I commented the useLocation and ApiClient calls so perhaps those are the culprit. Also your res.data['postContent'] looks like a JSON. If so, then you need to JSON.parse(res.data['postContent']).

actions are dispatching every time i render a child component

I am new to react and kind of experimenting with the hooks and i am stuck in a problem where i am using useEffect to dispatch an action to redux store. so everything works fine but the problem i am facing is every time i render a child component in my main component it dispatch the action. Like there are cards that are basically child components and whenever i click on of them to show more details it dispatch the actions that are in parent components same if i close the component so my question how can i prevent that from happening and only render the items once. Let me know if you need any other code..
Parent Component
import React, { useEffect } from "react";
//ANIMATION AND STYLED
import styled from "styled-components";
import { motion, AnimatePresence, AnimateSharedLayout } from "framer-motion";
//REDUX and ROUTER
import {
AllPopularGame,
NextPage,
PrevPage,
} from "../Actions/popularGameActions";
import { useSelector, useDispatch } from "react-redux";
import { Link, useLocation, useHistory } from "react-router-dom";
//COMPONENTS
import Game from "./games";
import GameDetail from "./gameDetail";
const PopularGames = () => {
//GETTNG PATH
const Location = useLocation();
const History = useHistory();
const pathId = Location.pathname.split("/")[4];
//Redux store
const { allPopularGame, gameCount, currentPage, gameLoading } = useSelector(
(state) => state.popular
);
//No of pages
const totalPage = Math.ceil(gameCount / 36);
//SCROLL TO TOP
useEffect(() => {
window.scrollTo(0, 0);
}, [currentPage]);
//Handlers
const PrevHandler = () => {
if (currentPage <= 1) {
return;
} else {
dispatch(PrevPage());
History.push(`/popular/games?page=${currentPage - 1}`);
}
};
const NextHandler = () => {
if (currentPage >= totalPage) {
console.log("Hello");
return;
} else {
dispatch(NextPage());
History.push(`/popular/games?page=${currentPage + 1}`);
}
};
//Fetch all popular games
const dispatch = useDispatch();
useEffect(() => {
async function fetchGames(page) {
const games = dispatch(AllPopularGame(page));
return games;
}
fetchGames(currentPage);
}, [dispatch, currentPage]);
// {`${currentPage} /popular/games/${popularGames.id}`}
return (
<Popular>
<h2>Popular Games </h2>
<AnimateSharedLayout type="crossfade">
<AnimatePresence>
{pathId && <GameDetail pathId={pathId} curPage={currentPage} />} //child component
</AnimatePresence>
{gameLoading ? (
<h2>Loading</h2>
) : (
<Games>
{allPopularGame.map((popularGames) => (
<Link
to={`/popular/games/${currentPage}/${popularGames.id}`}
key={popularGames.id}
>
<Game
name={popularGames.name}
img={popularGames.background_image}
rating={popularGames.rating}
id={popularGames.id}
key={popularGames.id}
released={popularGames.released}
/>
</Link>
))}
</Games>
)}
</AnimateSharedLayout>
<Page>
<Button onClick={PrevHandler}>
<span>Prev</span>
</Button>
<p>{currentPage}</p>
<Button onClick={NextHandler}>
<span>Next</span>
</Button>
</Page>
</Popular>
);
};
Github repo
Current code
Thanks for sharing the repository! It's quite clear now. You're using a link and re-routing the page - so although the components are the same and there isn't a re-paint on the UI, the entire page still mounts again. That's why your useEffect gets triggered everytime you click on a card (and even when you close one!).
Your UI state is managed by the path in the URL - this is really bad practice - especially considering that you aren't making any API calls to fetch data based on the URL.
Here's what I would suggest -
Replace the pathID variable that you are currently reading from the URL with a useState hook:
const [activeGameID, setActiveGameID]=useState(null);
Replace the Link component that you use to wrap the Game with a regular div and pass it an onClick handler to setActiveGameID:
<Games>
{popular.map((popularGames) => (
<div
onClick={() => setActiveGameID(popularGames.id)}
key={popularGames.id}
>
<Game
name={popularGames.name}
img={popularGames.background_image}
rating={popularGames.rating}
id={popularGames.id}
key={popularGames.id}
released={popularGames.released}
/>
</div>
))}
</Games>```
I think the problem you may be facing is that your animations are dependent on URL changes - in that case I would urge you to use some other way to animate your cards.

TypeError: Cannot read property 'condition' of undefined

So I'm trying to learn React and I got stuck trying to do a weather app.
Those are my component and my app.js files.
import React from 'react'
const WeatherCard = (props) => {
return (
<div className="card d-flex align-content-center align-items-center flex-wrap">
<div className= 'd-flex flex-column'>
<img className = 'd-flex' alt='Icona' src={props.icon}></img>
<p className ='d-flex'> {props.location} </p>
</div>
<div className= 'd-flex justify-content-between'>
<span className = "">{props.condition}</span><span className = ''>{props.temperature}°C</span>
</div>
</div>
)
}
export default WeatherCard;
import './App.css';
import React, {useEffect, useState} from 'react';
import WeatherCard from './components/WeatherCard';
const App = () => {
const APP_KEY = "My_key(yes, I put my key here)";
const [weatherDatas, setWeather] = useState({});
const [search, setSearch] = useState("");
const [query, setQuery] = useState('Iglesias');
useEffect(() => {
getWeather();
},[query]);
const getWeather = async () => {
const response = await fetch(`http://api.weatherapi.com/v1/current.json?key=${APP_KEY}&q=${query}`);
const data = await response.json()
setWeather(data);
}
const updateSearch = e => {
setSearch(e.target.value);
}
const getSearch = e => {
e.preventDefault();
setQuery(search);
}
return(
<div className="App">
<form onSubmit={getSearch} className="SearchForm d-flex justify-content-center">
<input type='text' value = {search} onChange = {updateSearch} />
<button type='submit'>Search</button>
</form>
<div className='d-flex justify-content-center'>
<WeatherCard
icon = {weatherDatas.current.condition.icon}
location = {weatherDatas.location.name}
condition = {weatherDatas.current.condition.text}
temperature = {weatherDatas.current.temp_c}
/>
</div>
</div>
)
}
export default App;
The problem is that when I start the "TypeError: Cannot read property 'condition' of undefined" error pops up.
But if I remove this part of the code while it's running:
icon = {weatherDatas.current.condition.icon}
location = {weatherDatas.location.name}
condition = {weatherDatas.current.condition.text}
temperature = {weatherDatas.current.temp_c}
refresh and past it in again it runs smoothly, I'm so confused, It looks like it tries to get the props before getting the data from the API? can someone help me understand what's going on?
It is a classic error, as you are running an async function, for some moments when your components mount, the props received is just "{}" and that's why you receive an error. I changed your snippet, and I think now it is going to work.
import './App.css';
import React, {useEffect, useState} from 'react';
import WeatherCard from './components/WeatherCard';
const App = () => {
const APP_KEY = "My_key(yes, I put my key here)";
const [weatherDatas, setWeather] = useState(null); //CHANGE HERE
const [search, setSearch] = useState("");
const [query, setQuery] = useState('Iglesias');
useEffect(() => {
getWeather();
},[query]);
const getWeather = async () => {
const response = await fetch(`http://api.weatherapi.com/v1/current.json?key=${APP_KEY}&q=${query}`);
const data = await response.json()
setWeather(data);
}
const updateSearch = e => {
setSearch(e.target.value);
}
const getSearch = e => {
e.preventDefault();
setQuery(search);
}
return(
<div className="App">
<form onSubmit={getSearch} className="SearchForm d-flex justify-content-center">
<input type='text' value = {search} onChange = {updateSearch} />
<button type='submit'>Search</button>
</form>
<div className='d-flex justify-content-center'>
{weatherDatas && ( //CHANGE HERE
<WeatherCard
icon = {weatherDatas.current.condition.icon}
location = {weatherDatas.location.name}
condition = {weatherDatas.current.condition.text}
temperature = {weatherDatas.current.temp_c}
/>)}
</div>
</div>
)
}
export default App;
Now your code just gonna try to render the component, when it's loaded
This is a common problem caused by threading. Your variable isn't assigned a value, so it's null, and you can't pull something out of the variable so it throws the "Cannot read property 'xxxx' of undefined" error.
I can understand why you think the weatherDatas variable should be populated, due to the "await" keyword, but you have that embedded in an "async" method. In this case, "async" trumps "await".
const getWeather = async () => {
const response = await fetch(`http://api.weatherapi.com/v1/current.json?key=${APP_KEY}&q=${query}`);
const data = await response.json()
setWeather(data);
}
You will need to do null checking when using the weatherDatas variable. You should look into optional chaining, which means that you'll call your object with question marks before the . so that it verifies data exists before trying to use the data.
There's a couple ways to do it, and they can work well or badly depending on what exactly you expect. Here's some examples, but they aren't necessarily what you want:
icon = {weatherDatas?.current?.condition?.icon}
location = {weatherDatas?.location.name}
condition = {weatherDatas.current.condition?.text}
The example for your icon variable is likely want you want. It prevents icon from being assigned anything beyond null unless that whole object path has data, so it won't throw the null reference error for attempting to access weatherDatas, current, or condition. However, icon can still be null if weatherDatas.current.condition.icon is null. This is likely the situation you want, since it checks all objects for null before accessing properties of the object. This might not be necessary for every object usage, but it's good for a single use or the use you have. Doing a regular null check would normally be better, though.
For the location variable, it won't throw the null reference error for trying to access weatherDatas, but it could for accessing weatherDatas.location if it's null.
In the condition variable example, you can still get the null reference error on weatherDatas and current, but not condition. Since your weatherDatas is null, it'll still fail in your current setup, so this isn't what you want right now.

React problem with getting image from API

I am currently practicing React, and my goal is to build a simple app that searches movies and display some short info about them as results. I managed to pull data from API and store em in React hooks. I can access any data, but when I try to pull images I get error:
TypeError: Cannot read property 'medium' of null.
Here are the API results:
http://api.tvmaze.com/search/shows?q=$girls
I find an image that I want to use stored in {show.image.medium}
Here is my React code:
import React, {useState, useEffect} from 'react';
import Movie from './Movie';
const App = () => {
const [movies, setMovies] = useState([]);
useEffect(() => {
getMovies();
}, []);
const getMovies = async () => {
const response = await fetch(`http://api.tvmaze.com/search/shows?q=$girls`);
const data = await response.json();
setMovies(data);
console.log(data)
;}
return (
<div>
<form className='search-form'>
<input type='text' className='search-bar' placeholder='search movie'>
</input>
<button type='submit' className='search-button'>
Search
</button>
</form>
{movies.map(movie => (
<Movie title={movie.show.name} image={movie.show.image.medium} />
))}
</div>
);
};
export default App;
and Movie.js file:
import React from 'react';
const Movie = ({title, image}) => {
return(
<div>
<h1>{title}</h1>
<img src={image} alt=''/>
</div>
);
}
export default Movie;
so I basically mapped the results in movie array, but {movie.show.image.medium} just won't work, while pulling any other data work just fine.
I know that this is probably an easy fix, but I tried everything and searched for an answer for hours and still, nothing worked. I would really appreciate it if someone can explain to me what I am doing wrong. Thanks in advance!
In the API call there is one value where movie.show.image is technically null. For null you could not get any properties, even medium.
What you can do as a solution is the following:
{
movies.map(movie =>
movie.show.image ?
<Movie title={movie.show.name} image={movie.show.image.medium} /> :
null)
}
Additionally you need to return from Array.prototype.map().
Iteration from the API on my console:
I hope that helps!

Categories

Resources