I have a problem with the following exportHtml function, this must export the html content of a react component but I can't understand why it doesn't enter the function to extrapolate html, how can I solve it?
Editor.js
import EmailEditor.js
const Example = () => {
const emailEditorRef = useRef(null);
const [titolo, setTitolo] = useState('');
const [getHtml, setHtml] = useState('');
const [newsProfileValue, setNewsProfileSet] = useState('');
const [profileList, setNewsProfileList] = useState([]);
const [premiumValue, setPremium] = useState(false);
const [publicValue, setPublic] = useState(false);
const arr = [];
const newsCollectionRef = collection(db, 'news');
const saveDesign = () => {
emailEditorRef.current.editor.saveDesign((design) => {
exportData(design);
});
};
const getList = async () => {
try {
var id = await localStorage.getItem('uid');
console.log(id);
var data = [];
const querySnapshot = await getDocs(collection(db, 'news_profiles'));
querySnapshot.forEach((doc) => {
if (doc.data().about.uid == id) {
data.push({ title: doc.data().about.title, id: doc.id });
arr.push({ doc: doc.data().about.title, id: doc.id });
}
});
} catch (error) {
console.log('Error: ' + error);
}
var options = arr.map((e) => {
return `<option value="${e.id}">${e.doc}</option>`;
});
document.getElementById('selectNumber').innerHTML = options;
setNewsProfileList(data);
};
const exportData = (design) => {
const jsonString = `data:text/json;chatset=utf-8,${encodeURIComponent(
JSON.stringify(design)
)}`;
const link = document.createElement('a');
link.href = jsonString;
link.download = 'data.json';
link.click();
};
const exportHtml = () => {
console.log('Sono in html');
emailEditorRef.current.editor.exportHtml((data) => {
const { design, html } = data;
setHtml(html);
});
addElementToFireStoreNews(getHtml);
};
const addElementToFireStoreNews = async (html) => {
try {
console.log('Sono in addElementToFireStoreNews ');
var uuid = await localStorage.getItem('uid');
var jsonFile;
var templateid = await localStorage.getItem('templateid');
if (templateid == 1) {
jsonFile = sample;
} else if (templateid == 2) {
jsonFile = sample3;
} else {
jsonFile = sample2;
}
console.log('Setup options');
const options = {
method: 'POST',
url: 'http://link',
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer kjsdgu6523kjbnap!w',
},
data: {
internal: true,
online: publicValue,
premium: premiumValue,
html: html,
template: jsonFile,
subject: html,
email: uuid,
},
};
console.log('Chiamo il rest');
axios
.request(options)
.then(function (response) {
console.log('data: ' + response.data);
})
.catch(function (error) {
console.error('Errore axios: ' + error);
});
} catch (err) {
console.log('Errore inserimnento: ' + err);
}
};
const onDesignLoad = (data) => {
getList();
console.log('onDesignLoad', data);
};
const onLoad = async () => {
var templateid = await localStorage.getItem('templateid');
if (templateid == 1) {
emailEditorRef.current.editor.loadDesign(sample);
} else if (templateid == 2) {
emailEditorRef.current.editor.loadDesign(sample3);
} else {
emailEditorRef.current.editor.loadDesign(sample2);
}
emailEditorRef.current.editor.addEventListener(
'design:loaded',
onDesignLoad
);
};
const onReady = () => {};
const changeText = (text) => {
setTitolo(text);
};
const clickValue = (e) => {
setNewsProfileSet(e.target.value);
};
const changeValue = () => {
setPremium(!premium);
};
const changeValuePublic = () => {
setPublic(!publicValue);
};
return (
<Container>
<Bar>
<img src="firebase.png" width="100" height="50" />
<label style={{ color: 'white' }}>
<b>Titolo News: </b>
<input
type="text"
name="name"
onChange={(value) => changeText(value.target.value)}
style={{ marginRight: 200 }}
/>
Premium:{' '}
<Checkbox
{...label}
unchecked
onClick={() => changeValue()}
style={{ color: 'white' }}
/>
Online:{' '}
<Checkbox
{...label}
unchecked
onClick={() => changeValuePublic()}
style={{ color: 'white' }}
/>
</label>
<form id="myForm">
<select id="selectNumber" onClick={clickValue}>
<option>Scegli il news profile</option>
</select>
<button onClick={() => saveDesign}> Salva Design </button>
<button onClick={() => exportHtml()}> Salva Articolo </button>{' '}
</form>
</Bar>
<React.StrictMode>
<EmailEditor ref={emailEditorRef} onLoad={onLoad} onReady={onReady} />{' '}
</React.StrictMode>{' '}
</Container>
);
};
export default Example;
EmailEditor.js
import React, { Component } from 'react';
import { loadScript } from './loadScript';
import pkg from '../package.json';
let lastEditorId = 0;
export default class extends Component {
constructor(props) {
super(props);
this.editorId = props.editorId || `editor-${++lastEditorId}`;
}
componentDidMount() {
loadScript(this.loadEditor, this.props.scriptUrl);
}
render() {
let {
props: { minHeight = 500, style = {} },
} = this;
return (
<div
style={{
flex: 1,
display: 'flex',
minHeight: minHeight,
}}
>
<div id={this.editorId} style={{ ...style, flex: 1 }} />
</div>
);
}
loadEditor = () => {
if (this.loaded) return;
this.loaded = true;
const options = this.props.options || {};
if (this.props.projectId) {
options.projectId = this.props.projectId;
}
if (this.props.tools) {
options.tools = this.props.tools;
}
if (this.props.appearance) {
options.appearance = this.props.appearance;
}
if (this.props.locale) {
options.locale = this.props.locale;
}
this.editor = unlayer.createEditor({
...options,
id: this.editorId,
displayMode: 'email',
source: {
name: pkg.name,
version: pkg.version,
},
});
for (const [key, value] of Object.entries(this.props)) {
if (/^on/.test(key) && key !== 'onLoad' && key !== 'onReady') {
this.addEventListener(key, value);
}
}
const { onLoad, onReady } = this.props;
// #deprecated
onLoad && onLoad();
if (onReady) this.editor.addEventListener('editor:ready', onReady);
};
registerCallback = (type, callback) => {
this.editor.registerCallback(type, callback);
};
addEventListener = (type, callback) => {
this.editor.addEventListener(type, callback);
};
loadDesign = (design) => {
this.editor.loadDesign(design);
};
saveDesign = (callback) => {
this.editor.saveDesign(callback);
};
exportHtml = (callback) => {
this.editor.exportHtml(callback);
};
setMergeTags = (mergeTags) => {
this.editor.setMergeTags(mergeTags);
};
}
Related
hello i am not sure why i getting this error massage can some one correct my code i can not find key in my new post components
this is my newpost.js code
import React, { useContext } from 'react';
import { useHttpClient } from '../../hooks/useHttpClient';
import useForm from '../../hooks/useForm';
import { AuthContext } from '../../context/auth';
import { useHistory } from 'react-router-dom/cjs/react-router-dom.min';
import { newPostForm } from '../../utils/formConfig';
import { appendData, renderRepeatedSkeletons } from '../../utils';
import ErrorModal from '../../components/Modal/ErrorModal';
import SkeletonElement from '../../components/Skeleton/SkeletonElement';
const NewPost = () => {
const auth = useContext(AuthContext);
const history = useHistory();
const { currentUser } = auth;
const { isLoading, sendReq, error, clearError } = useHttpClient();
const { renderFormInputs, renderFormValues, isFormValid } =
useForm(newPostForm);
const formValues = renderFormValues();
const formInputs = renderFormInputs();
const postSubmitHandle = async (evt) => {
evt.preventDefault(); //otherwise, there will be a reload
const formData = appendData(formValues);
formData.append('author', currentUser.userId);
try {
await sendReq(
`${process.env.REACT_APP_BASE_URL}/posts`,
'POST',
formData,
{
Authorization: `Bearer ${currentUser.token}`,
}
);
history.push('/');
} catch (err) {}
};
return (
<>
<ErrorModal error={error} onClose={clearError} />
{isLoading ? (
renderRepeatedSkeletons(<SkeletonElement type='text' />, 20)
) : (
<div className='container-create-page'>
<form className='form form__create'>
<h2>Create a new post</h2>
{formInputs}
<button
onClick={postSubmitHandle}
className='btn'
disabled={!isFormValid()}
>
Submit <span>→</span>
</button>
</form>
</div>
)}
</>
);
};
export default NewPost;
and this is my useform.js code
import { useState, useCallback } from 'react';
//"signupForm" => "formObj" (name, email, password) => "form"
const useForm = (formObj) => {
const [form, setForm] = useState(formObj);
const renderFormInputs = () => {
//renders an [] of <Input> for all input fields
return Object.values(form).map((inputObj) => {
const { value, label, errorMessage, valid, renderInput } = inputObj;
return renderInput(
onInputChange,
value,
valid,
errorMessage,
label,
onCustomInputChange
);
});
};
const renderFormValues = () => {
let values = {};
Object.keys(form).forEach((inputObj) => {
values[inputObj] = form[inputObj].value;
});
return values;
};
const isInputFieldValid = useCallback(
(inputField) => {
for (const rule of inputField.validationRules) {
if (!rule.validate(inputField.value, form)) {
inputField.errorMessage = rule.message;
return false;
}
}
return true;
},
[form]
);
const onInputChange = useCallback(
(event) => {
const { name, value } = event.target;
let inputObj = { ...form[name], value };
const isValidInput = isInputFieldValid(inputObj);
if (isValidInput && !inputObj.valid) {
inputObj = { ...inputObj, valid: true };
} else if (!inputObj.touched && !isValidInput && inputObj.valid) {
inputObj = { ...inputObj, valid: false };
}
inputObj = { ...inputObj, touched: true };
setForm({ ...form, [name]: inputObj });
},
[form, isInputFieldValid]
);
const onCustomInputChange = useCallback(
(type, value, InputIsValid) => {
setForm({
...form,
[type]: { ...form[type], value, valid: InputIsValid },
});
},
[form]
);
const isFormValid = useCallback(
(customForm) => {
let isValid = true;
const arr = Object.values(customForm || form);
for (let i = 0; i < arr.length; i++) {
if (!arr[i].valid) {
isValid = false;
break;
}
}
return isValid;
},
[form]
);
return {
renderFormInputs,
renderFormValues,
isFormValid,
setForm,
};
};
export default useForm;
submit button does not work . it just a simple form with submit button and 4 input value and one image . if some one need more information . please ask in the comment section
index.js contain renderRepeatedSkeletons
export const checkInArray = (arr, elem) => {
return arr && arr.indexOf(elem) !== -1;
};
export const canModifyComment = (currentUserId, authorId) =>
currentUserId === authorId;
export const canReply = (currentUserId) => !!currentUserId;
export const isReplying = (activeComment, commentId) =>
activeComment &&
activeComment.type === 'replying' &&
activeComment.id === commentId;
export const isEditing = (activeComment, commentId) =>
activeComment &&
activeComment.type === 'editing' &&
activeComment.id === commentId;
export const readingTime = (body) => {
const wpm = 225;
const words = body.trim().split(/\s+/).length;
return `${Math.ceil(words / wpm)} min read`;
};
export const appendData = (data) => {
const formData = new FormData();
for (let [key, value] of Object.entries(data)) {
if (Array.isArray(value)) {
value = JSON.stringify(value);
}
formData.append(`${key}`, value);
}
return formData;
};
export const getReplies = (comments, commentId) => {
return (
comments &&
comments
.filter((comment) => comment && comment.parentId === commentId)
.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime())
);
};
export const formatDate = (date) => {
const options = { year: 'numeric', month: 'short', day: 'numeric' };
const today = new Date(date);
return today.toLocaleDateString('en-US', options);
};
export const getRandomColor = () => {
const letters = '0123456789ABCDEF';
let color = '#';
for (let i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
};
export const renderRepeatedSkeletons = (element, count) => {
let skeletons = [];
for (let i = 0; i < count; i++) {
skeletons.push(element);
}
return skeletons;
};
export const renderAlternateSkeletons = (elementOne, elementTwo, count) => {
let skeletons = [];
for (let i = 0; i < count; i++) {
if (i % 2 === 0) {
skeletons.push(elementOne);
} else {
skeletons.push(elementTwo);
}
}
return skeletons;
};
Your renderRepeatedSkeletons function have to add a key to elements
export const renderRepeatedSkeletons = (element, count) => {
let skeletons = [];
for (let i = 0; i < count; i++) {
skeletons.push(<React.Fragment key={i} />{element}</React.Fragment>);
}
return skeletons;
};
import firebase from './Firebase';
import { useState, useEffect } from 'react';
import { StyleSheet, Text, View, Button, FlatList } from 'react-native';
import { TextInput } from 'react-native-web';
import { setStatusBarNetworkActivityIndicatorVisible } from 'expo-status-bar';
import { getFirestore, collection, query, getDocs, getDoc, Firestore } from 'firebase/firestore'
import deepFreezeAndThrowOnMutationInDev from 'react-native/Libraries/Utilities/deepFreezeAndThrowOnMutationInDev';
function Users() {
const today = new Date();
const yyyy = today.getFullYear();
let mm = today.getMonth() + 1; // Months start at 0!
let dd = today.getDate();
if (dd < 10) dd = '0' + dd;
if (mm < 10) mm = '0' + mm;
var todayDate = dd + '/' + mm + '/' + yyyy;
const [error, setError] = useState(null);
const [isLoaded, setIsLoaded] = useState(false);
const [users, setUsers] = useState([]);
const [filteredUsers, setFilteredUsers] = useState([]);
const [search, setSearch] = useState('');
useEffect(() => {
firebase.firestore().collection('Testing').onSnapshot((snapshot) => {
setUsers(
snapshot.docs.map((doc) => ({
id: doc.id,
SMK: doc.data().SMK,
Name: doc.data().FullName,
todayStatus: getPresent(doc),
})));
setFilteredUsers(
snapshot.docs.map((doc) => ({
id: doc.id,
SMK: doc.data().SMK,
Name: doc.data().FullName,
todayStatus: getPresent(doc),
})));
setIsLoaded(true);
})
},[]);
const getPresent = (doc) => {
let temp = doc.data().Attendance[todayDate];
try {
if (temp.status == "P") {
return "P";
}
else {
return "A";
}
} catch (error) {
return "A";
}
}
const searchFilter = (text) => {
if (text) {
const newData = users.filter(item => {
const itemSMK = item.SMK;
const itemName = item.Name;
const textData = text;
return (
item.SMK.indexOf(textData) > -1 ||
item.Name.toLowerCase().indexOf(textData.toLowerCase()) > -1
)
});
setFilteredUsers(newData);
setSearch(text);
}
else {
setFilteredUsers(users);
setSearch(text);
}
}
//button press handelers
const markPresent = (id) => {
let currentDoc = firebase.firestore().collection('Testing').doc(id);
currentDoc.onSnapshot((snapshot) => {
let snapData = snapshot.data();
console.log('in present function');
try {
let tempObj = {
...snapData.Attendance,
[todayDate]: {
Time: new Date().toLocaleTimeString(),
Reason: '',
status: 'P'
},
};
snapData.Attendance = tempObj;
currentDoc.update(snapData);
} catch (error) {
let tempObj = {
Attendance: {
[todayDate]: {
Time: new Date().toLocaleTimeString(),
Reason: '',
status: 'P'
},
}
}
}
})
}
const markAbsent = (id) => {
let currentDocToDelete = firebase.firestore().collection('Testing').doc(id);
currentDocToDelete.onSnapshot((snapshot)=>{
let snapDeleteData = snapshot.data();
try {
let tempDeleteObj = {
...snapDeleteData.Attendance,
[todayDate]: {
status: 'A',
},
};
snapDeleteData.Attendance = tempDeleteObj;
currentDocToDelete.update(snapDeleteData);
} catch (error) {
console.log('Error in markAbsent function =>',error);
}
})
}
//returns view of particular list item of users
const itemView = ({ item }) => {
if (item.todayStatus == "P") {
return (
<View>
<Text>{item.SMK} : {item.Name} - present</Text>
<Button title='delete' onPress={() => markAbsent(item.id)} />
</View>
)
}
if(item.todayStatus=="A") {
return (
<View>
<Text>{item.SMK} : {item.Name} - absent</Text>
<Button title='present' onPress={() => markPresent(item.id)} />
</View>
)
}
}
if (!isLoaded) {
return <Text>Loading...</Text>
}
else {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<TextInput value={search} placeholder="search user here" onChangeText={searchFilter} />
<FlatList data={filteredUsers} renderItem={itemView} />
</View>
)
}
}
export default Users;
Firestore document of one user
Browser Console Screenshot
When I click button 2-3 time.. it works perfectly but if i click it more than that then it goes into infinite loop.
handler is called automatically for both markPresent and markAbsent. and page keeps re-rendering.
Here I'm trying to fetch list of users and I have to mark them as present or absent.
whenever I try to click present after 2-3 users it goes into infinite loops
Pretty sure you need to do a .get() after the .doc(id)
try this
const markPresent = async (id) => {
let currentDoc = await firebase.firestore().collection('Testing').doc(id).get();
How to map through two arrays and send data as props to other component? I want to send data from price and gamedet as a prop to Wishlist Component.As I am new to react, suggest me if this code is not good or unclean too, thanks in advance.
This is the dumb way I send whole array to Wishlist component :
price.map((game1) => {
let temp = {
steamAppID: game1.steamAppID,
storeID: game1.storeID,
normalPrice: game1.normalPrice,
salePrice: game1.salePrice,
};
console.log("temp sale", temp.salePrice);
return tempArr1.push(temp) && tempArr2.push(temp.steamAppID));
})
This is all of WishListData component:
import React, { useEffect, useState } from "react";
import Wishlist from "./Wishlist";
import "./Wishlist.css";
import "animate.css";
import axios from "axios";
const WishlistData = () => {
const [gamedet, setGameDet] = useState([]);
const [loaded, setLoaded] = useState(false);
const [stores, setStores] = useState([]);
const [price, setPrice] = useState([]);
const [wishlist, setWishlist] = useState([]);
useEffect(() => {
setWishlist(
localStorage.getItem("Wishlist")
? JSON.parse(localStorage.getItem("Wishlist"))
: []
);
}, []);
const RemoveFromWishlist = (id) => {
let newList = wishlist.filter((game) => game.gameID !== id);
setWishlist(newList)
localStorage.setItem("Wishlist", JSON.stringify(newList));
console.log("id", [wishlist.pop(id)]);
console.log("newlist", wishlist);
setGameDet([])
};
const DET_URL = `https://api.rawg.io/api/games`;
useEffect(() => {
let isCancelled = false;
const RAWGdet = () => {
wishlist &&
wishlist.map(async (game, index) => {
const res = await axios({
url: `https://cors-anywhere.herokuapp.com/${DET_URL}/${game.gameID}?key=${process.env.REACT_APP_RAWG_KEY}`,
headers: {
"X-Requested-With": "XMLHttpRequest",
},
method: "GET",
});
if (!isCancelled) {
setGameDet((gamedet) => gamedet.concat(res.data));
}
setLoaded(true);
});
};
RAWGdet();
return () => {
isCancelled = true;
};
}, [DET_URL, wishlist]);
useEffect(() => {
let isCancelled = false;
const CSPrice = () => {
wishlist &&
wishlist.map(async (game, index) => {
const res = await axios({
url: `https://cors-anywhere.herokuapp.com/${DET_URL}/${game.slug}/stores?key=${process.env.REACT_APP_RAWG_KEY}`,
headers: {
"X-Requested-With": "XMLHttpRequest",
},
method: "GET",
});
if (!isCancelled) {
setStores((stores) => stores.concat(res.data));
}
setLoaded(true);
});
};
CSPrice();
return () => {
isCancelled = true;
};
}, [DET_URL, wishlist]);
let stm = [];
stores
.map((steam) => {
return steam.results;
})
.filter((item) => {
return item.map((id) => {
return id.store_id === 1 ? stm.push(id.url) : <>{null}</>;
});
});
let idmain = [];
stm.map((steamid) => {
return steamid.split("/").map((item) => {
return idmain.push(item);
});
});
useEffect(() => {
let isCancelled = false
wishlist.length !==0 &&
wishlist.map((game, index) => {
return (
<div key={index}>
{axios
.get(
`https://www.cheapshark.com/api/1.0/deals?storeID=1&steamAppID=${game.steamID}`
)
.then((res) => {
if (!isCancelled){
setPrice((price) => price.concat(res.data));
setLoaded(true)
}
})
.catch((err) => {
console.log("ERR", err);
})}
</div>
);
});
return () => {
isCancelled = true
}
}, [wishlist]);
let tempArr1 = [];
let tempArr2 = [];
if (loaded ) {
return (
<div className="animate__animated animate__slideInDown">
<div className="wishlist_header">
<h3>Your Wishlist</h3>
</div>
{wishlist.length !== 0 ? (
price.map((game1) => {
let temp = {
steamAppID: game1.steamAppID,
storeID: game1.storeID,
normalPrice: game1.normalPrice,
salePrice: game1.salePrice,
};
console.log("temp sale", temp.salePrice);
return tempArr1.push(temp) && tempArr2.push(temp.steamAppID));
}) &&
gamedet.map((game, index) => {
// console.log("mad2", game.name);
return (
<div id="wishlist_ctn" key={index}>
<Wishlist
// key={index}
title={game.name}
steamRatingCount={game.id}
// steamRatingPercent={game[0].steamRatingPercent}
// savings={game[0].savings}
// normalPrice={}
// salePrice={salePrice}
steamAppID={tempArr2}
data={tempArr1}
image={game.background_image}
rem={() => RemoveFromWishlist(game.id)}
/>
</div>
);
})
) : (
<div className="wishlist_header">
<h3>Add Games!!</h3>
</div>
)}
</div>
);
} else {
return (
<div className="hmm">
<div className="wishlist_header">
<h3>Your Wishlist</h3>
</div>
<div className="wishlist_header">
<h3>Loading Games</h3>
</div>
);
</div>
);
}
};
export default WishlistData;
I don't understand why you need two extra arrays since you are mapping price to populate
tempArr1, which contain a copy of its items, and tempArr2, which contains a copy of its steamAppIDs.
I think you could just pass the price array as data, and a mapped version as steamAppID:
{wishlist.length !== 0 ?
(gamedet.map((game, index) => {
<Wishlist
// key={index}
title={game.name}
steamRatingCount={game.id}
// steamRatingPercent={game[0].steamRatingPercent}
// savings={game[0].savings}
// normalPrice={}
// salePrice={salePrice}
steamAppID={price.map(item => item.steamAppID)}
data={price}
image={game.background_image}
rem={() => RemoveFromWishlist(game.id)}
/>;
</div>
);
})
) : (
<div className="wishlist_header">
<h3>Add Games!!</h3>
</div>
)}
I'm converting my app from JS to TS. Everything has been working good under JS but when started conversion to TS I'm getting plenty of errors with handle functions like for example handleVideoAdd. Does anyone has idea what am I'm doing wrong? Tried many things without success...
Property 'handleVideoAdd' does not exist on type 'undefined'. TS2339 - and it's pointing out to this fragment of code:
const { handleVideoAdd, inputURL, handleInputURLChange } = useContext(Context)
My code looks like that:
Header.tsx
import { Context } from "../Context";
import React, { useContext } from "react";
import { Navbar, Button, Form, FormControl } from "react-bootstrap";
export default function Header() {
const { handleVideoAdd, inputURL, handleInputURLChange } =
useContext(Context);
return (
<Navbar bg="light" expand="lg">
<Navbar.Brand href="#home">Video App</Navbar.Brand>
<Form onSubmit={handleVideoAdd} inline>
<FormControl
type="text"
name="url"
placeholder="Paste url"
value={inputURL}
onChange={handleInputURLChange}
className="mr-sm-2"
/>
<Button type="submit" variant="outline-success">
Add
</Button>
</Form>
</Navbar>
);
}
Context.tsx
import { useEffect, useMemo, useState } from "react";
import { youtubeApi } from "./APIs/youtubeAPI";
import { vimeoApi } from "./APIs/vimeoAPI";
import React from "react";
import type { FormEvent } from "react";
const Context = React.createContext(undefined);
function ContextProvider({ children }) {
const [inputURL, setInputURL] = useState("");
const [videoData, setVideoData] = useState(() => {
const videoData = localStorage.getItem("videoData");
if (videoData) {
return JSON.parse(videoData);
}
return [];
});
const [filterType, setFilterType] = useState("");
const [videoSources, setVideoSources] = useState([""]);
const [wasSortedBy, setWasSortedBy] = useState(false);
const [showVideoModal, setShowVideoModal] = useState(false);
const [modalData, setModalData] = useState({});
const [showWrongUrlModal, setShowWrongUrlModal] = useState(false);
const createModalSrc = (videoItem) => {
if (checkVideoSource(videoItem.id) === "youtube") {
setModalData({
src: `http://www.youtube.com/embed/${videoItem.id}`,
name: videoItem.name,
});
} else {
setModalData({
src: `https://player.vimeo.com/video/${videoItem.id}`,
name: videoItem.name,
});
}
};
const handleVideoModalShow = (videoID) => {
createModalSrc(videoID);
setShowVideoModal(true);
};
const handleVideoModalClose = () => setShowVideoModal(false);
const handleWrongUrlModalShow = () => setShowWrongUrlModal(true);
const handleWrongUrlModalClose = () => setShowWrongUrlModal(false);
const handleInputURLChange = (e) => {
setInputURL(e.currentTarget.value);
};
const handleVideoAdd = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
const source = checkVideoSource(inputURL);
if (source === "youtube") {
handleYouTubeVideo(inputURL);
} else if (source === "vimeo") {
handleVimeoVideo(inputURL);
} else {
handleWrongUrlModalShow();
}
};
const checkVideoSource = (inputURL) => {
if (inputURL.includes("youtu") || inputURL.length === 11) {
return "youtube";
} else if (inputURL.includes("vimeo") || inputURL.length === 9) {
return "vimeo";
}
};
const checkURL = (inputURL) => {
if (!inputURL.includes("http")) {
const properURL = `https://${inputURL}`;
return properURL;
} else {
return inputURL;
}
};
const checkInputType = (inputURL) => {
if (!inputURL.includes("http") && inputURL.length === 11) {
return "id";
} else if (!inputURL.includes("http") && inputURL.length === 9) {
return "id";
} else {
return "url";
}
};
const fetchYouTubeData = async (videoID) => {
const data = await youtubeApi(videoID);
if (data.items.length === 0) {
handleWrongUrlModalShow();
} else {
setVideoData((state) => [
...state,
{
id: videoID,
key: `${videoID}${Math.random()}`,
name: data.items[0].snippet.title,
thumbnail: data.items[0].snippet.thumbnails.medium.url, //default, medium, high
viewCount: data.items[0].statistics.viewCount,
likeCount: data.items[0].statistics.likeCount,
savedDate: new Date(),
favourite: false,
source: "YouTube",
url: inputURL,
},
]);
setInputURL("");
}
};
const handleYouTubeVideo = (inputURL) => {
const inputType = checkInputType(inputURL);
if (inputType === "id") {
fetchYouTubeData(inputURL);
} else {
const checkedURL = checkURL(inputURL);
const url = new URL(checkedURL);
if (inputURL.includes("youtube.com")) {
const params = url.searchParams;
const videoID = params.get("v");
fetchYouTubeData(videoID);
} else {
const videoID = url.pathname.split("/");
fetchYouTubeData(videoID[1]);
}
}
};
const fetchVimeoData = async (videoID) => {
const data = await vimeoApi(videoID);
if (data.hasOwnProperty("error")) {
handleWrongUrlModalShow();
} else {
setVideoData((state) => [
...state,
{
id: videoID,
key: `${videoID}${Math.random()}`,
name: data.name,
thumbnail: data.pictures.sizes[2].link, //0-8
savedDate: new Date(),
viewCount: data.stats.plays,
likeCount: data.metadata.connections.likes.total,
savedDate: new Date(),
favourite: false,
source: "Vimeo",
url: inputURL,
},
]);
setInputURL("");
}
};
const handleVimeoVideo = (inputURL) => {
const inputType = checkInputType(inputURL);
if (inputType === "id") {
fetchVimeoData(inputURL);
} else {
const checkedURL = checkURL(inputURL);
const url = new URL(checkedURL);
const videoID = url.pathname.split("/");
fetchVimeoData(videoID[1]);
}
};
const deleteVideo = (key) => {
let newArray = [...videoData].filter((video) => video.key !== key);
setWasSortedBy(true);
setVideoData(newArray);
};
const deleteAllData = () => {
setVideoData([]);
};
const toggleFavourite = (key) => {
let newArray = [...videoData];
newArray.map((item) => {
if (item.key === key) {
item.favourite = !item.favourite;
}
});
setVideoData(newArray);
};
const handleFilterChange = (type) => {
setFilterType(type);
};
const sourceFiltering = useMemo(() => {
return filterType
? videoData.filter((item) => item.source === filterType)
: videoData;
}, [videoData, filterType]);
const sortDataBy = (sortBy) => {
if (wasSortedBy) {
const reversedArr = [...videoData].reverse();
setVideoData(reversedArr);
} else {
const sortedArr = [...videoData].sort((a, b) => b[sortBy] - a[sortBy]);
setWasSortedBy(true);
setVideoData(sortedArr);
}
};
const exportToJsonFile = () => {
let dataStr = JSON.stringify(videoData);
let dataUri =
"data:application/json;charset=utf-8," + encodeURIComponent(dataStr);
let exportFileDefaultName = "videoData.json";
let linkElement = document.createElement("a");
linkElement.setAttribute("href", dataUri);
linkElement.setAttribute("download", exportFileDefaultName);
linkElement.click();
};
const handleJsonImport = (e) => {
e.preventDefault();
const fileReader = new FileReader();
fileReader.readAsText(e.target.files[0], "UTF-8");
fileReader.onload = (e) => {
const convertedData = JSON.parse(e.target.result);
setVideoData([...convertedData]);
};
};
useEffect(() => {
localStorage.setItem("videoData", JSON.stringify(videoData));
}, [videoData]);
return (
<Context.Provider
value={{
inputURL,
videoData: sourceFiltering,
handleInputURLChange,
handleVideoAdd,
deleteVideo,
toggleFavourite,
handleFilterChange,
videoSources,
sortDataBy,
deleteAllData,
exportToJsonFile,
handleJsonImport,
handleVideoModalClose,
handleVideoModalShow,
showVideoModal,
modalData,
showWrongUrlModal,
handleWrongUrlModalShow,
handleWrongUrlModalClose,
}}
>
{children}
</Context.Provider>
);
}
export { ContextProvider, Context };
App.js (not converted to TS yet)
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import "bootstrap/dist/css/bootstrap.min.css";
import reportWebVitals from "./reportWebVitals";
import { ContextProvider } from "./Context.tsx";
ReactDOM.render(
<React.StrictMode>
<ContextProvider>
<App />
</ContextProvider>
</React.StrictMode>,
document.getElementById("root")
);
reportWebVitals();
This is because when you created your context you defaulted it to undefined.
This happens here: const Context = React.createContext(undefined)
You can't say undefined.handleVideoAdd. But you could theoretically say {}.handleVideoAdd.
So if you default your context to {} at the start like this: const Context = React.createContext({})
Your app shouldn't crash up front anymore.
EDIT: I see you're using TypeScript, in that case you're going to need to create an interface for your context. Something like this:
interface MyContext {
inputURL?: string,
videoData?: any,
handleInputURLChange?: () => void,
handleVideoAdd?: () => void,
deleteVideo?: () => void,
// and all the rest of your keys
}
Then when creating your context do this:
const Context = React.createContext<MyContext>(undefined);
CodeSandbox: https://codesandbox.io/s/stupefied-heisenberg-fery5
HeaderMenu.js
import {Component} from 'react';
import {
Menu,
Container,
Button,
Label,
Loader,
List,
Image,
Icon,
Dropdown
} from 'semantic-ui-react';
import Head from 'next/head';
import web3 from '../ethereum/web3';
import Constant from '../support/Constant';
import Config from '../support/Config';
import appDispatcher from '../core/AppDispatcher';
import contractManager from '../core/ContractManager';
class HeaderMenu extends Component {
constructor(props) {
super(props);
this.account = props.account;
this.contractManager = contractManager;
// console.log(contractManager);
this.transactionDispatcher = props.transactionDispatcher;
this.state = {address: "", balance: "", name: "",
avatarUrl: "", isLoading: true, isJoinButtonLoading: false,
isJoined: false, numPendingTx: 0};
this.reloadCount = 0;
}
clearAllData = () => {
window.localStorage.clear();
}
componentDidMount() {
if (this.account) {
this.getAccountInfo();
appDispatcher.register((payload) => {
if (payload.action == Constant.EVENT.ACCOUNT_BALANCE_UPDATED) {
this.setState({balance: this.account.balance});
} else if (payload.action == Constant.EVENT.ACCOUNT_INFO_UPDATED) {
this.setState({name: payload.profile.name, avatarUrl: payload.profile.avatarUrl, isJoined: payload.profile.isJoined});
}
});
this.transactionDispatcher.register((payload) => {
if (payload.action == Constant.EVENT.PENDING_TRANSACTION_UPDATED) {
this.setState({numPendingTx: payload.numPendingTx});
}
});
}
}
getAccountInfo = () => {
var address = this.account.getAddress();
if (address) {
this.setState({address: address, balance: this.account.balance, isLoading: false, isJoined: this.account.isJoined});
} else {
if (this.reloadCount == 1) {
this.setState({isLoading: false});
} else {
this.reloadCount++;
setTimeout(this.getAccountInfo, 800);
}
}
}
handleDropdownClicked = (event, data) => {
if (data.name == 'updateProfile') {
appDispatcher.dispatch({
action: Constant.ACTION.OPEN_UPDATE_PROFILE
});
} else if (data.name == 'logOutItem') {
this.clearAllData();
window.location.reload();
} else if (data.name == 'settingsItem') {
appDispatcher.dispatch({
action: Constant.ACTION.OPEN_SETTINGS_MODAL
})
}
else if (data.name == 'changeEthNetwork') {
if (data.networkid != Config.ENV.EthNetworkId) {
Config.ENV.EthNetworkId = data.networkid;
this.removeNetworkDependentData();
window.location.reload();
}
}
}
removeNetworkDependentData = () => {
this.account.storageManager.removeNetworkDependentData();
}
handleJoinClicked = () => {
var publicKeyBuffer = this.account.getPublicKeyBuffer();
this.contractManager.joinContract(publicKeyBuffer, (resultEvent) => {
if (resultEvent == Constant.EVENT.ON_REJECTED || resultEvent == Constant.EVENT.ON_ERROR) {
this.setState({isJoinButtonLoading: false});
} else if (resultEvent == Constant.EVENT.ON_RECEIPT) {
window.location.reload();
}
});
this.setState({isJoinButtonLoading: true});
}
handleImportPrivateKeyClicked = () => {
appDispatcher.dispatch({
action: Constant.ACTION.OPEN_PRIVATE_KEY_MODAL
});
}
render() {
var accountInfo = (<div></div>);
if (this.account) {
if (this.state.isLoading == false) {
if (this.state.address) {
var addressExplorerUrl = Config.ENV.ExplorerUrl + 'address/' + this.state.address;
var dropdownTrigger;
if (this.state.avatarUrl) {
dropdownTrigger = (
<span><Image src={this.state.avatarUrl} avatar/>{ this.state.name ? this.state.name : this.state.address.substr(0,10)}</span>
);
} else {
dropdownTrigger = (
<span><Icon name='user' size='large'/>{ this.state.name ? this.state.name : this.state.address.substr(0,10)}</span>
);
}
var networkItems = [];
for (var i=0;i<Config.NETWORK_LIST.length;i++) {
networkItems.push(
<Dropdown.Item key={'networkItem' + i} networkid={Config.NETWORK_LIST[i].id} name='changeEthNetwork' onClick={this.handleDropdownClicked}>
{Config.NETWORK_LIST[i].name}
</Dropdown.Item>
);
}
var memberInfo;
if (this.account.isJoined) {
memberInfo = (
<Dropdown item trigger={dropdownTrigger}>
<Dropdown.Menu>
<Dropdown.Item name='updateProfile' onClick={this.handleDropdownClicked}>
<Icon name='write'/>Update profile
</Dropdown.Item>
<Dropdown.Item name='settingsItem' onClick={this.handleDropdownClicked}>
<Icon name='settings'/>Settings
</Dropdown.Item>
<Dropdown.Item name='logOutItem' onClick={this.handleDropdownClicked}>
<Icon name='log out'/>Log out
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
);
} else {
memberInfo = (
<Button color='orange' onClick={this.handleJoinClicked}
loading={this.state.isJoinButtonLoading}
disabled={this.state.isJoinButtonLoading}>Join {Constant.APP_NAME}</Button>
);
}
var pendingTxItem;
if (this.state.numPendingTx > 0) {
pendingTxItem = (
<Label as='a' color='yellow' href={addressExplorerUrl} target='_blank'>
<Icon name='spinner' loading/>
{this.state.numPendingTx} pending tx
</Label>
);
}
accountInfo = (
<Menu.Menu position='right'>
<Menu.Item>
<Dropdown item text={Config.ENV.NetworkName}>
<Dropdown.Menu>
{networkItems}
</Dropdown.Menu>
</Dropdown>
</Menu.Item>
<Menu.Item>
<List>
<List.Item>
<a href={addressExplorerUrl} target='_blank'>
{this.state.address}
</a>
</List.Item>
<List.Item>
Balance: <Label as='a' href={addressExplorerUrl} target='_blank' color='orange'>{parseFloat(web3.utils.fromWei("" +this.state.balance, 'ether')).toFixed(8) + ' ETH' }</Label>
{pendingTxItem}
</List.Item>
</List>
</Menu.Item>
<Menu.Item>
{memberInfo}
</Menu.Item>
</Menu.Menu>
);
} else {
accountInfo = (
<Menu.Menu position='right'>
<Menu.Item>
<Button onClick={this.handleImportPrivateKeyClicked} color='blue'>Import private key</Button>
</Menu.Item>
</Menu.Menu>
);
}
} else {
accountInfo = (<Loader inverted active />);
}
}
return (
<Menu fixed='top' color='grey' inverted>
<Head>
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.12/semantic.min.css"></link>
</Head>
<Container>
<Menu.Item>
<a href='/'><Image src='static/images/blockchat.png' height={55} /></a>
</Menu.Item>
{this.account ? accountInfo: (<div></div>)}
</Container>
</Menu>
);
}
}
export default HeaderMenu;
ContractManager.js
import web3 from '../ethereum/web3';
import compiledContract from '../ethereum/build/EtherChat.json';
import TransactionsManager from './TransactionManager';
import appDispatcher from './AppDispatcher';
import Config from '../support/Config';
import Constant from '../support/Constant';
import utils from '../support/Utils';
import crypto from 'crypto';
/**
* Responsible for interacting with the Ethereum smart contract
*/
export class ContractManager {
constructor(accountManager, storageManager) {
this.getContract();
this.accountManager = accountManager;
this.storageManager = storageManager;
this.transactionManager = new TransactionsManager(accountManager);
}
// Create a web3 contract object that represent the ethereum smart contract
getContract = async () => {
this.contract = await new web3.eth.Contract(JSON.parse(compiledContract.interface),
Config.ENV.ContractAddress);
appDispatcher.dispatch({
action: Constant.EVENT.CONTRACT_READY
})
}
// Get current account profile from EtherChat contract's storage
getProfile = async (address) => {
var result = await this.contract.methods.members(this.accountManager.getAddress()).call();
var profile = {};
if (result.isMember == 1) {
profile.isJoined = true;
profile.avatarUrl = utils.hexStringToAsciiString(result.avatarUrl);
profile.name = utils.hexStringToAsciiString(result.name);
this.storageManager.setJoinedStatus(true);
this.storageManager.setName(this.name);
this.storageManager.setAvatarUrl(this.avatarUrl);
appDispatcher.dispatch({
action: Constant.EVENT.ACCOUNT_INFO_UPDATED,
profile: profile
})
}
return profile;
}
getMemberInfo = async (address, relationship) => {
var memberInfo = await this.contract.methods.members(address).call();
if (memberInfo.isMember) {
var publicKey = '04' + memberInfo.publicKeyLeft.substr(2) + memberInfo.publicKeyRight.substr(2);
var name = utils.hexStringToAsciiString(memberInfo.name);
var avatarUrl = utils.hexStringToAsciiString(memberInfo.avatarUrl);
this.storageManager.updateContact(address, publicKey, name, avatarUrl, relationship);
}
}
getPastEvents = async (eventName, filters) => {
return await this.contract.getPastEvents(eventName, filters);
}
joinContract = async(publicKeyBuffer, callback) => {
var publicKeyLeft = '0x' + publicKeyBuffer.toString('hex', 0, 32);
var publicKeyRight = '0x' + publicKeyBuffer.toString('hex', 32, 64);
this.transactionManager.executeMethod(this.contract.methods.join(publicKeyLeft, publicKeyRight))
.on(Constant.EVENT.ON_APPROVED, (txHash) => {
if (callback) callback(Constant.EVENT.ON_APPROVED);
})
.on(Constant.EVENT.ON_REJECTED, (txHash) => {
if (callback) callback(Constant.EVENT.ON_REJECTED);
})
.on(Constant.EVENT.ON_RECEIPT, (receipt) => {
if (callback) callback(Constant.EVENT.ON_RECEIPT);
})
.on(Constant.EVENT.ON_ERROR, (error, txHash) => {
appDispatcher.dispatch({
action: Constant.EVENT.ENCOUNTERED_ERROR,
message: error.message,
title: "Error"
});
if (callback) callback(Constant.EVENT.ON_ERROR);
});
}
// joinContract = async (publicKeyBuffer, callback) => {
addContact = async (address, callback) => {
console.log(address);
var method = this.contract.methods.addContact(address);
this.transactionManager.executeMethod(method)
.on(Constant.EVENT.ON_APPROVED, (txHash) => {
if (callback) callback(Constant.EVENT.ON_APPROVED);
})
.on(Constant.EVENT.ON_RECEIPT, (receipt) => {
if (callback) callback(Constant.EVENT.ON_RECEIPT);
})
.on(Constant.EVENT.ON_ERROR, (error, txHash) => {
appDispatcher.dispatch({
action: Constant.EVENT.ENCOUNTERED_ERROR,
message: error.message,
title: "Error"
});
if (callback) callback(Constant.EVENT.ON_ERROR);
});
}
acceptContactRequest = async (address, callback) => {
var method = this.contract.methods.acceptContactRequest(address);
this.transactionManager.executeMethod(method)
.on(Constant.EVENT.ON_APPROVED, (txHash) => {
if (callback) callback(Constant.EVENT.ON_APPROVED);
})
.on(Constant.EVENT.ON_RECEIPT, (receipt) => {
if (callback) callback(Constant.EVENT.ON_RECEIPT);
})
.on(Constant.EVENT.ON_ERROR, (error, txHash) => {
appDispatcher.dispatch({
action: Constant.EVENT.ENCOUNTERED_ERROR,
message: error.message,
title: "Error"
});
if (callback) callback(Constant.EVENT.ON_ERROR);
});
}
updateProfile = async (name, avatarUrl, callback) => {
var nameHex = '0x' + Buffer.from(name, 'ascii').toString('hex');
var avatarUrlHex = '0x' + Buffer.from(avatarUrl, 'ascii').toString('hex');
var method = this.contract.methods.updateProfile(nameHex, avatarUrlHex);
this.transactionManager.executeMethod(method)
.on(Constant.EVENT.ON_APPROVED, (txHash) => {
if (callback) callback(Constant.EVENT.ON_APPROVED);
})
.on(Constant.EVENT.ON_RECEIPT, (receipt) => {
if (callback) callback(Constant.EVENT.ON_RECEIPT);
})
.on(Constant.EVENT.ON_ERROR, (error, txHash) => {
appDispatcher.dispatch({
action: Constant.EVENT.ENCOUNTERED_ERROR,
message: error.message,
title: "Error"
});
if (callback) callback(Constant.EVENT.ON_ERROR);
});
}
// A message will be encrypted locally before sending to the smart contract
sendMessage = async (toAddress, publicKey, message) => {
var publicKeyBuffer = Buffer.from(publicKey, 'hex');
var encryptedRaw = utils.encrypt(message, this.accountManager.computeSecret(publicKeyBuffer));
var encryptedMessage = '0x' + encryptedRaw.toString('hex');
var method = this.contract.methods.sendMessage(toAddress, encryptedMessage, utils.getEncryptAlgorithmInHex());
this.transactionManager.executeMethod(method)
.on(Constant.EVENT.ON_APPROVED, (txHash) => {
this.storageManager.addMyLocalMessage(encryptedMessage, toAddress, utils.getEncryptAlgorithm(), txHash);
appDispatcher.dispatch({
action: Constant.EVENT.MESSAGES_UPDATED,
data: toAddress
});
})
.on(Constant.EVENT.ON_REJECTED, (data) => {
// do nothing
})
.on(Constant.EVENT.ON_RECEIPT, (receipt, ) => {
this.storageManager.updateLocalMessage(toAddress, receipt.transactionHash, Constant.SENT_STATUS.SUCCESS);
appDispatcher.dispatch({
action: Constant.EVENT.MESSAGES_UPDATED,
data: toAddress
});
})
.on(Constant.EVENT.ON_ERROR, (error, txHash) => {
this.storageManager.updateLocalMessage(toAddress, txHash, Constant.SENT_STATUS.FAILED);
appDispatcher.dispatch({
action: Constant.EVENT.MESSAGES_UPDATED,
data: toAddress
});
});
}
}
export default ContractManager;
enter image description here
Here, I'm trying to call the function 'joinContract' {the function of ContractManager class} in HeaderMenu.js using the function handleJoinClicked().
And,boom... code is getting crash after clicking the join button. It's showing the error. This[coontractManager.joinContract] is not a function.
Please help.