Rendering an object stored in state - ajax

I'm trying to render out my calendar's event summaries, using a .map function. I've stored my calendar events object in state, but can't find a way to .map out the different event summaries. Any suggestions?
export default class Container extends React.Component{
calendarID="xxx"
apiKey="zzz";
state = { events: [] };
setEvents = (a) => {
this.setState(a);
}
componentDidMount() {
ajax.get(`https://www.googleapis.com/calendar/v3/calendars/${this.calendarID}/events?fields=items(summary,id,location,start)&key=${this.apiKey}`)
.end((error, response) => {
if(!error && response ) {
this.setEvents({events: response.body});
console.log("success");
console.log(this.state.events);
} else {
console.log("Errors: ", error);
}
});
}
render(){
let lista = this.state.events;
let arr = Object.keys(lista).map(key => lista[key])
return (
<div class = "container">
{arr.map((event, index) => {
const summary = event.summary;
return (<div key={index}>{summary}</div>);
})}
</div>
);
}
}
EDIT:
Thanks for your answers! This is the data that the ajax call returns when I console log this.state.items:
Object {items: Array[1]}
items: Array[1]
0: Object
id: "cmkgsrcohfebl5isa79034h8a4"
start: Object
summary: "Stuff going down"
If I skip the ajax call and create my own state, the mapping works:
state = { items: [
{ items: { summary: "testing"} },
{ items: { summary: "12"} },
{ items: { summary: "3"} }
]};
To get this working, however, I change my render-function to:
render(){
let lista = this.state.items;
let arr = Object.keys(lista).map(key => lista[key])
return (
<div class = "container">
{arr.map((item, index) => {
const summary = item.items.summary;
return (<div key={index}>{summary}</div>);
})}
</div>
);
}
So maybe it has something to do with the object that this.state.items returns from the ajax call?
Edit2: #Andrea Korinski, you were right! I changed my render function to this, and now it works:
render(){
let list = this.state.items;
const arr = (list.items || []).map((item, index) => {
const summary = item.summary;
return (<div key={index}>{summary}</div>);
});
return (
<div class = "container">
{arr}
</div>
);
}
}
The whole component:
export default class Container extends React.Component{
calendarID="xxx";
apiKey="zzz";
state = {items: []};
setEvents = (a) => {
this.setState(a);
}
componentDidMount() {
ajax.get(`https://www.googleapis.com/calendar/v3/calendars/${this.calendarID}/events?fields=items(summary,id,location,start)&key=${this.apiKey}`)
.end((error, response) => {
if(!error && response ) {
this.setEvents({items: response.body});
console.log("success");
console.log(this.state.items);
} else {
console.log("Errors: ", error);
}
});
}
render(){
let list = this.state.items;
const irr = (list.items || []).map((item, index) => {
const summary = item.summary;
return (<div key={index}>{summary}</div>);
});
return (
<div class = "container">
{irr}
</div>
);
}
}

Related

Lost with useEffect Hooks - data now undefined

First time posting and have my Learner plates on.
Using axios with a json mock-server with gets through a ContextProvider to trying to map my data to present with Bootstrap cards.
(then) also click on the card to present on another page (not attempted yet)
My data was presenting fine in list form and in a card through Outlet - but I'm trying instead to present the whole array through cards.
I'd appreciate any help I can get. Apologies in advance for the lengthy code (I'm not sure where the problem is)
ProductList.js
import React from 'react'
import { ProductContext } from './ProductContext'
import ProductDetail from './ProductDetail'
function ProductList(props) {
function productList(products) {
if (products === null) return
return (
<div className='card-container'>
{
products.map(product => (<ProductDetail />))
}
</div>
)
}
return (
<div direction="horizontal" style={{ textAlign: 'center' }}>
<h1>Artworks</h1>
<ProductContext.Consumer>
{({ products }) => (
productList(products)
)}
</ProductContext.Consumer>
</div>
)
}
export default ProductList
ProductDetail.js
function ProductDetail(props) {
const hasFetchedData = useRef(false)
let params = useParams()
let navigate = useNavigate()
let { getProduct, deleteProduct } = useContext(ProductContext)
let [product, setProduct] = useState()
useEffect(() => {
if (!hasFetchedData.current) {
const res = axios.get("http://localhost:3002/products");
setProduct(res);
hasFetchedData.current = true;
}
}, [])
useEffect(() => {
async function fetch() {
await getProduct(params.productId)
.then((product) => setProduct(product))
}
fetch()
}, [params.productId]); // eslint-disable-line react-hooks/exhaustive-deps
let [error, setError] = useState()
useEffect(() => {
setError(null)
async function fetch() {
await getProduct(params.productId)
.then((product) => setProduct(product))
.catch((message) => setError(message))
}
fetch()
}, [params.productId, getProduct])
function errorMessage() {
return <Alert variant="danger">Stockroom is empty: {error}</Alert>
}
function handleDeleteProduct(id) {
deleteProduct(id)
navigate('/products')
}
function loading() {
return <div className="w-25 text-center"><Spinner animation="border" /></div>
}
function productCard() {
let { id, artistname, born, piecename, painted, imgurl, price } = product
return (
<Card className="w-25" key={product.id}>
<Card.Img variant="top" src={imgurl} />
<Card.Body>
<Card.Title>{artistname} {born}</Card.Title>
<Card.Subtitle className="mb-2 text-muted">{piecename}</Card.Subtitle>
<Card.Subtitle className="mb-2 text-muted">{painted}</Card.Subtitle>
<Card.Text>
<strong>Price:</strong> <span>${price}</span>
</Card.Text>
<Link to={`/products/${id}/edit`} className="btn btn-primary mx-3">Edit</Link>
<Button variant="danger" onClick={handleDeleteProduct.bind(this, id)}>Delete</Button>
</Card.Body>
</Card>
)
}
if (error) return errorMessage()
if (product === undefined) return loading()
return product.id !== parseInt(params.productId) ? loading() : productCard()
}
export default ProductDetail
ProductContext.js
export const ProductContext = createContext()
export const ProductProvider = (props) => {
const [products, setProducts] = useState([])
useEffect(() => {
async function getProducts() {
await refreshProducts()
}
getProducts()
}, []);
function refreshProducts() {
return axios.get("http://localhost:3002/products")
.then(response => {
setProducts(response.data)
})
}
function getProduct(id) {
return axios.get(`http://localhost:3002/products/${id}`)
.then(response =>
new Promise((resolve) => resolve(response.data))
)
.catch((error) =>
new Promise((_, reject) => reject(error.response.statusText))
)
}
function deleteProduct(id) {
axios.delete(`http://localhost:3002/products/${id}`).then(refreshProducts)
}
function addProduct(product) {
return axios.post("http://localhost:3002/products", product)
.then(response => {
refreshProducts()
return new Promise((resolve) => resolve(response.data))
})
}
function updateProduct(product) {
return axios.put(`http://localhost:3002/products/${product.id}`, product)
.then(response => {
refreshProducts()
return new Promise((resolve) => resolve(response.data))
})
}
return (
<ProductContext.Provider
value={{
products,
refreshProducts,
getProduct,
deleteProduct,
addProduct,
updateProduct
}}
>
{props.children}
</ProductContext.Provider>
)
}

Warning: Expected server HTML to contain a matching <div> in <div> when use localStorage as params

I think this warning appear when i work with localStorage, some answers in another page is use useEffect(), but I don't know how to use query or mutation in useEffect().How can I fix it?
export const productListForBill = () : GetProductForBill[] =>{
const returnEmtpyArray : GetProductForBill[] = []
if(typeof window !== "undefined"){
if(localStorage.getItem("products"))
{
const tempProduct = JSON.parse(localStorage.getItem("products") || "")
if(Array.isArray(tempProduct)){
return tempProduct
}
}
}
return returnEmtpyArray
}
const Cart = () => {
const { data } = useGetSomeProductQuery({
variables: { productList: productListForBill() },
});
return (
<>
<Navbar />
{data?.getSomeProduct ? (
data.getSomeProduct.map((product) => (
<div key={`${product.name}-${product.type}`}>
name: {product.name} --|-- type: {product.type} --|-- amount :{" "}
{product.amount} --|-- unitPrice : {product.unitPrice} --|-- total:{" "}
{product.totalPrice}
</div>
))
) : (
<div>Nothing in here</div>
)}
</>
);
};
export const getStaticProps: GetStaticProps = async () => {
const apolloClient = initializeApollo();
await apolloClient.query({
query: GetSomeProductDocument,
variables: { productList: productListForBill() },
});
return addApolloState(apolloClient, {
props: {},
});
};
I have to type something for text checker of Stackoverflow, have a nice day!
code of useGetSomeProductQuery, i'm working with graphql and use codegen to generate it at client
#Query((_return) => [ProductOfBill], { nullable: true })
async getSomeProduct(
#Arg("productList", (_type) => [GetProductForBill])
productList: GetProductForBill[]
): Promise<ProductOfBill[] | null | undefined> {
try {
const newList : ProductOfBill[] = await Promise.all(productList.map(async (product) => {
const price = await Price.findOne({
where: {
type: product.type,
product: product.productId,
}
});
const newProductOfBill = ProductOfBill.create({
name:product.name,
amount:product.amount,
type:product.type,
unitPrice:price?.price
})
return newProductOfBill
}))
.then(list => {
console.log(list)
return list
})
.catch(_ => {
const resultList : ProductOfBill[] = []
return resultList
})
return newList;
} catch (error) {
console.log(error);
return undefined;
}
}

Rendering logger output to component on Tesseract.js (with React) slows down

I would like to add a progress indicator to Tesseract.js logging.
The example in docs works just fine, until setting a state hook into logger:
const worker = createWorker({
logger: (m) => {
setProgress(m) //new
}});
...
const [ocr, setOcr] = useState('Recognizing...');
const [progress, setProgress] = useState(null); //new
...
return (
<div className="App">
<p>
<LogComponent progress={progress}/> //new
</p>
</div>);
This causes the browser to slow down significantly (probably due React's way to re-render on each state update). Is there a way to get around this? Using React.memo perhaps?
You could design it into a hook so your entire component doesn't re-render. Here is a useTesseract hook you can use that I created: https://gist.github.com/KevinDanikowski/25cdcdda2ef4750bcf443f2027cc375a
Copy and Pasted:
import { useState, useEffect } from 'react'
import { createWorker } from 'tesseract.js'
export default function useTesseract({ tesseractLanguage = 'eng', log = false }) {
const [tesseractWorker, setTesseractWorker] = useState(null)
const [loadingModel, setLoadingModel] = useState(true)
const [modelError, setModelError] = useState(false)
const [imgResults, setImgResults] = useState({})
const [processing, setProcessing] = useState(false)
const [progress, setProgress] = useState(0)
const extractTextFromImage = (imageUrl) => {
const recognize = async () => {
const {
data: {
hocr: htmlOutput,
text,
// tsv, box, unlv
},
} = await tesseractWorker.recognize(imageUrl)
setProcessing(false)
setImgResults({ html: htmlOutput, text })
}
if (loadingModel) {
try {
setTimeout(recognize, 400)
} catch (e) {
console.error('Timeout Error:', e.message)
setImgResults({ error: true })
}
} else {
try {
setProcessing(true)
recognize()
} catch (e) {
console.error('Tesseract Error:', e.message)
setProcessing(false)
setImgResults({ error: true })
}
}
}
const logger = (m) => {
setProgress(m.progress)
if (log) {
console.info(m)
}
}
useEffect(() => {
const loadTesseract = async () => {
if (tesseractWorker) {
await tesseractWorker.loadLanguage(tesseractLanguage)
await tesseractWorker.initialize(tesseractLanguage)
console.info(`INFO: loaded ${tesseractLanguage} tesseract model`)
} else {
const tesseractWorker = createWorker({
logger,
// specify paths because sometimes the free CDN goes down
// corePath: '/static/tesseract-core.wasm.2.2.0.js',
// workerPath: '/static/tesseract-worker.v2.1.4.min.js',
})
setTesseractWorker(tesseractWorker)
await tesseractWorker.load()
await tesseractWorker.loadLanguage(tesseractLanguage)
await tesseractWorker.initialize(tesseractLanguage)
console.info(`INFO: loaded ${tesseractLanguage} tesseract model`)
setLoadingModel(false)
setModelError(true)
setLoadingModel(false)
}
}
loadTesseract().catch((e) => {
console.error(`ERROR: Failed to load tesseract model`, e.message)
setModelError(true)
setLoadingModel(false)
})
// TODO: Have to add a ref to reference the latest tesseractWorker in order to terminate
// return () => tesseractWorker.terminate()
}, [tesseractLanguage])
return {
imgResults,
loadingModel,
processing,
modelError,
progress,
extractTextFromImage,
}
}
I managed to implement and render the progress of the tesseract worker by putting my application render inside a class and using the setState method:
class App extends React.Component {
constructor(props){
super(props)
this.state = {
file: null
}
this.handleChange = this.handleChange.bind(this)
}
setProgress(m) {
if (m.progress !== 0 && m.progress !== 0.5 && m.progress !== 1){
var prog = "Progress: " + Math.round(m.progress*100) + "%"
this.setState({progress: prog})
}
}
worker = createWorker({
logger: m => this.setProgress(m),
});
doOCR = async () => {
await this.worker.load();
await this.worker.loadLanguage('eng');
await this.worker.initialize('eng');
const { data: { text } } = await this.worker.recognize(this.state.file);
this.setState({text: extractTotal(text),
progress: ""});
};
handleChange(event) {
this.setState({text: placeholder});
this.setState({
file: URL.createObjectURL(event.target.files[0]),
})
this.doOCR()
}
setText(input){
if (!input) {
return "Please select a receipt"
}
else {
return input
}
}
render() {
console.log("Text: " + this.state.text)
return (
<div className="container">
<p>{this.setText(this.state.text)}</p>
<p>{this.state.progress}</p>
<input type="file" onChange={this.handleChange}/>
<img src={this.state.file} className='logo' alt=""/>
</div>
);
}
}

Error when trying to update in react-redux

I am trying to update my data in redux but I get an error when I have more than one value in the state.
How I am transferring data into the AllPalletes component below:
<Route exact path='/' render ={(routeProps) => <AllPalletes data = {this.props.palleteNames} />} />
The AllPalletes component, where I am setting up the edit form:
class connectingPalletes extends Component {
render () {
console.log(this.props)
return (
<div>
<Menu inverted>
<Menu.Item header>Home</Menu.Item>
<Menu.Item as = {Link} to = '/createpalette'>Create a Palette</Menu.Item>
</Menu>
<Container>
<Card.Group itemsPerRow={4}>
{this.props.data.map((card) => {
let cardName = card.Name.replace(/\s+/g, '-').toLowerCase()
return (
<Card key = {card._id}>
<Image src = 'https://images.pexels.com/photos/1212406/pexels-photo-1212406.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500' wrapped ui={false}/>
<Card.Content>
<Grid>
<Grid.Column floated = 'left' width ={7}>
{card.edit? (
<PaletteEditForm {...card}/>
) : (
<Card.Header as = {Link} to = {`/palette/${cardName}`}>{card.Name}</Card.Header>
)}
</Grid.Column>
<Grid.Column floated = 'right' width = {5}>
<Icon name = 'pencil' />
<Icon name = 'trash' onClick = {() => this.props.dispatch(removePalette({id: card._id}))}/>
</Grid.Column>
</Grid>
</Card.Content>
</Card>
)
})}
</Card.Group>
<Divider></Divider>
<Divider hidden></Divider>
<Grid centered columns={1}>
<Button as = {Link} to = '/testing'>Go Back</Button>
</Grid>
</Container>
</div>
)
}
}
const AllPalletes = connect()(connectingPalletes)
export default AllPalletes
And here is the edit form:
class EditForm extends Component {
constructor(props) {
super(props)
this.state = {
paletteName: this.props.Name
}
}
handleChange = (e) => {
const val = e.target.value,
s_name = e.target.name
this.setState (() => {
return {
[s_name]: val,
}
})
}
handleSubmit = () => {
let updates = {Name: this.state.paletteName, edit: false}
this.props.dispatch(editPalette(this.props._id, updates))
}
render() {
console.log(this.props)
return (
<Form onSubmit = {this.handleSubmit}>
<Input type = 'text' name = 'paletteName' value = {this.state.paletteName} onChange={this.handleChange} />
</Form>
)
}
}
const PaletteEditForm = connect()(EditForm)
export default PaletteEditForm
My Reducer:
import uuid from 'uuid/v1'
const paletteDefault = [{
Name: "Material UI",
myArray: [],
_id: uuid(),
edit: false
}, {
Name: "Splash UI",
myArray: [],
_id: uuid(),
edit: true
}]
const PaletteReducers = (state = paletteDefault, action) => {
console.log(action)
switch(action.type) {
case 'ADD_PALETTE':
return [...state, action.palette]
case 'REMOVE_PALETTE':
return state.filter(x => x._id !== action.id)
case 'EDIT_PALETTE':
return state.map((palette) => {
if(palette._id === action.id) {
return {
...palette,
...action.updates
}
}
})
default:
return state
}
}
export default PaletteReducers
My Action
// EDIT_PALETTE
const editPalette = (id, updates) => ({
type: 'EDIT_PALETTE',
id,
updates
})
export {addPalette, removePalette, editPalette}
I have a feeling that the problem could be in how I have set up the reducer case.
The edit dispatch only works when I have one value in the state. Otherwise, I am getting this error:
Uncaught TypeError: Cannot read property 'Name' of undefined
at AllPalletes.js:23
Please help..
I found the error. I had not give a return value in the 'EDIT_PALETTE' case, after the if-statement. It was
case 'EDIT_PALETTE':
return state.map((palette) => {
if(palette._id === action.id) {
return {
...palette,
...action.updates
}
}
})
And instead should be:
case 'EDIT_PALETTE':
return state.map((palette) => {
if(palette._id === action.id) {
return {
...palette,
...action.updates
}
}
return palette
})

Double click and click on ReactJS Component

I have a ReactJS component that I want to have different behavior on a single click and on a double click.
I read this question.
<Component
onClick={this.onSingleClick}
onDoubleClick={this.onDoubleClick} />
And I tried it myself and it appears as though you cannot register both single click and double click on a ReactJS component.
I'm not sure of a good solution to this problem. I don't want to use a timer because I'm going to have 8 of these single components on my page.
Would it be a good solution to have another inner component inside this one to deal with the double click situation?
Edit:
I tried this approach but it doesn't work in the render function.
render (
let props = {};
if (doubleClick) {
props.onDoubleClick = function
} else {
props.onClick = function
}
<Component
{...props} />
);
Here is the fastest and shortest answer:
CLASS-BASED COMPONENT
class DoubleClick extends React.Component {
timer = null
onClickHandler = event => {
clearTimeout(this.timer);
if (event.detail === 1) {
this.timer = setTimeout(this.props.onClick, 200)
} else if (event.detail === 2) {
this.props.onDoubleClick()
}
}
render() {
return (
<div onClick={this.onClickHandler}>
{this.props.children}
</div>
)
}
}
FUNCTIONAL COMPONENT
const DoubleClick = ({ onClick = () => { }, onDoubleClick = () => { }, children }) => {
const timer = useRef()
const onClickHandler = event => {
clearTimeout(timer.current);
if (event.detail === 1) {
timer.current = setTimeout(onClick, 200)
} else if (event.detail === 2) {
onDoubleClick()
}
}
return (
<div onClick={onClickHandler}>
{children}
</div>
)
}
DEMO
var timer;
function onClick(event) {
clearTimeout(timer);
if (event.detail === 1) {
timer = setTimeout(() => {
console.log("SINGLE CLICK");
}, 200)
} else if (event.detail === 2) {
console.log("DOUBLE CLICK");
}
}
document.querySelector(".demo").onclick = onClick;
.demo {
padding: 20px 40px;
background-color: #eee;
user-select: none;
}
<div class="demo">
Click OR Double Click Here
</div>
I know this is an old question and i only shoot into the dark (did not test the code but i am sure enough it should work) but maybe this is of help to someone.
render() {
let clicks = [];
let timeout;
function singleClick(event) {
alert("single click");
}
function doubleClick(event) {
alert("doubleClick");
}
function clickHandler(event) {
event.preventDefault();
clicks.push(new Date().getTime());
window.clearTimeout(timeout);
timeout = window.setTimeout(() => {
if (clicks.length > 1 && clicks[clicks.length - 1] - clicks[clicks.length - 2] < 250) {
doubleClick(event.target);
} else {
singleClick(event.target);
}
}, 250);
}
return (
<a onClick={clickHandler}>
click me
</a>
);
}
I am going to test this soon and in case update or delete this answer.
The downside is without a doubt, that we have a defined "double-click speed" of 250ms, which the user needs to accomplish, so it is not a pretty solution and may prevent some persons from being able to use the double click.
Of course the single click does only work with a delay of 250ms but its not possible to do it otherwise, you have to wait for the doubleClick somehow...
All of the answers here are overcomplicated, you just need to use e.detail:
<button onClick={e => {
if (e.detail === 1) handleClick();
if (e.detail === 2) handleDoubleClick();
}}>
Click me
</button>
A simple example that I have been doing.
File: withSupportDoubleClick.js
let timer
let latestTouchTap = { time: 0, target: null }
export default function withSupportDoubleClick({ onDoubleClick = () => {}, onSingleClick = () => {} }, maxDelay = 300) {
return (event) => {
clearTimeout(timer)
const touchTap = { time: new Date().getTime(), target: event.currentTarget }
const isDoubleClick =
touchTap.target === latestTouchTap.target && touchTap.time - latestTouchTap.time < maxDelay
latestTouchTap = touchTap
timer = setTimeout(() => {
if (isDoubleClick) onDoubleClick(event)
else onSingleClick(event)
}, maxDelay)
}
}
File: YourComponent.js
import React from 'react'
import withSupportDoubleClick from './withSupportDoubleClick'
export default const YourComponent = () => {
const handleClick = withSupportDoubleClick({
onDoubleClick: (e) => {
console.log('double click', e)
},
onSingleClick: (e) => {
console.log('single click', e)
},
})
return (
<div
className="cursor-pointer"
onClick={handleClick}
onTouchStart={handleClick}
tabIndex="0"
role="button"
aria-pressed="false"
>
Your content/button...
</div>
)
}
onTouchStart start is a touch event that fires when the user touches the element.
Why do you describe these events handler inside a render function? Try this approach:
const Component = extends React.Component {
constructor(props) {
super(props);
}
handleSingleClick = () => {
console.log('single click');
}
handleDoubleClick = () => {
console.log('double click');
}
render() {
return (
<div onClick={this.handleSingleClick} onDoubleClick={this.handleDoubleClick}>
</div>
);
}
};

Resources