Adding an image to react-google-maps InfoWindow content - image

Note: I am using the "react-google-maps" api and this is how my current InfoWindow is set up
{showingInfoWindow && selectedPlace === spot._id && <InfoWindow
className="info-window"
onCloseClick={onInfoWindowClose}
position={{lat: spot.lat, lng: spot.lng}}
>
<div className="iw-container">
<strong className="iw-title">{spot.name}</strong>
<div className="iw-content">
{spot.location}
<div>Added By: {currentUser.displayName === spot.user ? "Me" : spot.user}</div>
<div>{spot.type}</div>
<div>{spot.desc}</div>
<div>{moment(spot.createdAt).format("MMM Do YYYY")}</div>
{/* <img src={`/server/uploads/${spot.createdAt.split('.')[0]+"Z"}.jpg`}> </img> */}
</div>
</div>
</InfoWindow>}
I was wondering how I add an image to the infowindow, I've seen it done with a content prop in other api's, and react-google-maps docs has a prop for updating the content, but I can't find how to set the content on their documentation. Any help is appreciated!

You can directly add an <img> tag as a child of the <infowindow>
Sample code snippet:
import React, { Component } from 'react';
import {
withGoogleMap,
GoogleMap,
Marker,
InfoWindow
} from 'react-google-maps';
class Map extends Component {
constructor(props) {
super(props);
this.state = {
isOpen: false
};
}
handleToggleOpen = () => {
this.setState({
isOpen: true
});
};
handleToggleClose = () => {
this.setState({
isOpen: false
});
};
render() {
const GoogleMapExample = withGoogleMap(props => (
<GoogleMap
defaultCenter={{ lat: -33.86882, lng: 151.209296 }}
defaultZoom={13}
>
<Marker
key={this.props.index}
position={{ lat: -33.86882, lng: 151.209296 }}
onClick={() => this.handleToggleOpen()}
>
{this.state.isOpen && (
<InfoWindow
onCloseClick={this.props.handleCloseCall}
>
<img src="https://www.australia.com/content/australia/en/places/sydney-and-surrounds/guide-to-sydney/jcr:content/mainParsys/imagecontainer/imageContainerParsys/imagehighlights_835593945/ImageTile/imageHighlightsSrc.adapt.740.medium.jpg" width="250px" height="250px"/>
</InfoWindow>
)}
</Marker>
</GoogleMap>
));
return (
<div>
<GoogleMapExample
containerElement={<div style={{ height: `500px`, width: '500px' }} />}
mapElement={<div style={{ height: `100%` }} />}
/>
</div>
);
}
}
export default Map;

I figured out the problem: I needed to use a self-closing img tag.
instead of
<img src="..."> </img>
it must be
<img src="..."/>

Related

Adding Leading Zero's in React 'duration' component

I have created a 'working' duration slider, but I'm having trouble inserting 'leading zeros' into the hours and minutes:
const [hours, setHours] = useState([parseFloat(defaultValue.split(':')[0])])
const [minutes, setMinutes] = useState([parseFloat(defaultValue.split(':')[1])])
The above code is working, but will output (9hours & 5mins) as 9:5 (needs to be 09:05)
The code below (commented out in the full code) successfully puts in the leading zero's, but the code errors on line 61 and 97 where it's trying to handle the value
var leadingZeroHour = ('0000'+[parseFloat(defaultValue.split(':')[0])]).slice(-2)
const [hours, setHours] = useState(leadingZeroHour)
var leadingZeroMin = ('0000'+[parseFloat(defaultValue.split(':')[1])]).slice(-2)
const [minutes, setMinutes] = useState(leadingZeroMin)
The full code is below. If you can help me out and point me in the right direction, I'd be very grateful. Many thanks!
import React, { FunctionComponent, useState, useEffect } from 'react'
import { Range } from 'react-range'
type Props = {
className?: string,
defaultValue: string,
title?: string,
onUpdate: (value: string) => void;
[x: string]: any;
}
const defaultProps: Props = {
className: '',
defaultValue: '00:00',
title: '',
onUpdate: (value: any) => {},
}
const DurationInput: FunctionComponent<Props> = ({ className, defaultValue, title, onUpdate, ...rest }) => {
const [hours, setHours] = useState([parseFloat(defaultValue.split(':')[0])])
const [minutes, setMinutes] = useState([parseFloat(defaultValue.split(':')[1])])
// var leadingZeroHour = ('0000'+[parseFloat(defaultValue.split(':')[0])]).slice(-2)
// const [hours, setHours] = useState(leadingZeroHour)
// var leadingZeroMin = ('0000'+[parseFloat(defaultValue.split(':')[1])]).slice(-2)
// const [minutes, setMinutes] = useState(leadingZeroMin)
// console.log(hours)
useEffect(() => {
const duration = `${hours[0]}:${minutes[0]}`
onUpdate(duration)
}, [hours, minutes])
return (
<div className={`w-full ${className}`}>
{title ? <div className="text-base sm:text-xl mb-4 text-center">{title}</div> : <></>}
{/* <div className="grid grid-cols-3 gap-3 mb-8">
<div></div> */}
<div className="mx-auto w-40 sm:w-80 mb-8">
<div className="border border-orange rounded py-3 text-center text-4xl bg-white">
{hours}:{minutes}
</div>
<div></div>
</div>
<div className="mb-4 w-4/5 sm:w-3/5 mx-auto">
<div className="font-bold text-center mb-6">Hours</div>
<Range
step={1}
min={0}
max={23}
values={hours}
onChange={(values) => setHours(values)}
renderTrack={({ props, children }) => (
<div
{...props}
className="bg-orange rounded-full"
style={{
...props.style,
height: '6px',
width: '100%',
}}
>
{children}
</div>
)}
renderThumb={({ props }) => (
<div
{...props}
className="bg-white rounded-full border-4 border-orange"
style={{
...props.style,
height: '42px',
width: '42px',
}}
/>
)}
/>
</div>
<div className=" w-4/5 sm:w-3/5 mx-auto">
<div className="font-bold text-center mb-6">Minutes</div>
<Range
step={5}
min={0}
max={59}
values={minutes}
onChange={(values) => setMinutes(values)}
renderTrack={({ props, children }) => (
<div
{...props}
className="bg-orange rounded-full"
style={{
...props.style,
height: '6px',
width: '100%',
}}
>
{children}
</div>
)}
renderThumb={({ props }) => (
<div
{...props}
className="bg-white rounded-full border-4 border-orange"
style={{
...props.style,
height: '42px',
width: '42px',
}}
/>
)}
/>
</div>
</div>
)
}
DurationInput.defaultProps = defaultProps
export default DurationInput

How override the root app by Vuetify in Vuepress?

I write a documentation about a custom VueJS (v2) framework with Vuepress (version 1.7).
By default, the root element is the classic VueJs <app>. But I need it's v-app from Vuetify.
I created a custom theme with this main layout :
<template>
<v-app style="background: white;">
<div
class="theme-container"
:class="pageClasses"
#touchstart="onTouchStart"
#touchend="onTouchEnd"
>
<Navbar
v-if="shouldShowNavbar"
#toggle-sidebar="toggleSidebar"
/>
<div
class="sidebar-mask"
#click="toggleSidebar(false)"
/>
<Sidebar
:items="sidebarItems"
#toggle-sidebar="toggleSidebar"
>
<template #top>
<slot name="sidebar-top" />
</template>
<template #bottom>
<slot name="sidebar-bottom" />
</template>
</Sidebar>
<Home v-if="$page.frontmatter.home" />
<Page
v-else
:sidebar-items="sidebarItems"
>
<template #top>
<slot name="page-top" />
</template>
<template #bottom>
<slot name="page-bottom" />
</template>
</Page>
</div>
</v-app>
</template>
<script>
import Home from '#theme/components/Home.vue'
import Navbar from '#theme/components/Navbar.vue'
import Page from '#theme/components/Page.vue'
import Sidebar from '#theme/components/Sidebar.vue'
import { resolveSidebarItems } from '../util'
export default {
name: 'Layout',
components: {
Home,
Page,
Sidebar,
Navbar
},
data () {
return {
isSidebarOpen: false
}
},
computed: {
shouldShowNavbar () {
const { themeConfig } = this.$site
const { frontmatter } = this.$page
if (
frontmatter.navbar === false
|| themeConfig.navbar === false) {
return false
}
return (
this.$title
|| themeConfig.logo
|| themeConfig.repo
|| themeConfig.nav
|| this.$themeLocaleConfig.nav
)
},
shouldShowSidebar () {
const { frontmatter } = this.$page
return (
!frontmatter.home
&& frontmatter.sidebar !== false
&& this.sidebarItems.length
)
},
sidebarItems () {
return resolveSidebarItems(
this.$page,
this.$page.regularPath,
this.$site,
this.$localePath
)
},
pageClasses () {
const userPageClass = this.$page.frontmatter.pageClass
return [
{
'no-navbar': !this.shouldShowNavbar,
'sidebar-open': this.isSidebarOpen,
'no-sidebar': !this.shouldShowSidebar
},
userPageClass
]
}
},
mounted () {
this.$router.afterEach(() => {
this.isSidebarOpen = false
})
},
methods: {
toggleSidebar (to) {
this.isSidebarOpen = typeof to === 'boolean' ? to : !this.isSidebarOpen
this.$emit('toggle-sidebar', this.isSidebarOpen)
},
// side swipe
onTouchStart (e) {
this.touchStart = {
x: e.changedTouches[0].clientX,
y: e.changedTouches[0].clientY
}
},
onTouchEnd (e) {
const dx = e.changedTouches[0].clientX - this.touchStart.x
const dy = e.changedTouches[0].clientY - this.touchStart.y
if (Math.abs(dx) > Math.abs(dy) && Math.abs(dx) > 40) {
if (dx > 0 && this.touchStart.x <= 80) {
this.toggleSidebar(true)
} else {
this.toggleSidebar(false)
}
}
}
}
}
</script>
The generated html is :
<body>
<div id="app">
<div id="app" data-app="true" class="v-application v-application--is-ltr theme--light" style="background: white;">
...
</div>
</div>
</body>
As you see, it has two div with the id "app". The second is expected, but not the first. I don't know where it comes from. Maybe automatically added by Vuepress.
How can I remove the first div?

Issue with displaying Chartjs annotation elements in Nextjs

I am transferring my CRA to a Nextjs and I am having a bit of an issue with anything that uses the <canvas> element. The charts and data are mostly there, but my annotations are now missing from the charts. I have tried importing everything with the dynamic function for the parent element, but it still seems to not show the missing features.
I am also seeing some weird things happening on an arcgis map which is not visualizing 3d elements on a <canvas>. So my guess is that this has something with the way that canvas or d3 interact with the browser.
// parent component
import moment from 'moment-timezone';
import React, { useRef } from 'react';
import {
Chart as ChartJS,
LinearScale,
CategoryScale,
BarElement,
PointElement,
LineElement,
Legend,
Tooltip,
} from 'chart.js';
// import { Chart } from 'react-chartjs-2';
import { ArrowRight } from '../../icons/ArrowRight';
import Link from 'next/link';
import { chartOptions } from '../../lib/chartOptions';
import dynamic from 'next/dynamic';
const Chart = dynamic((): any => import('react-chartjs-2').then((m: any) => m.Chart), {
ssr: false,
});
ChartJS.register(
LinearScale,
CategoryScale,
BarElement,
PointElement,
LineElement,
Legend,
Tooltip,
);
export const MarkupCard = ({ item }: any) => {
const chartRef = useRef();
const userName = item.user_id.split('#')[0];
return (
<div className="w-2/3 pb-10 mx-auto border-2 border-blue items-center rounded-lg my-4 py-4 flex flex-row justify-between">
<div className="w-full text-left pl-4 pb-6 h-72">
<div className="w-full flex flex-row justify-between">
<h2 className="text-lg font-bold">{userName} Marked up a chart</h2>
<div className=" w-1/3 text-right pr-4">
<h2>
{moment(item.created_at)
.tz(process.env.NEXT_PUBLIC_TIMEZONE ?? '')
.format('MM-DD-YYYY hh:mm:ss a')}
</h2>
</div>
</div>
<h2>Route: {item.routeLongName}</h2>
<Chart
style={{ height: '100px', width: '99%' }}
ref={chartRef}
plugins={item.details.options.plugins}
className="trips-chart"
type="line"
options={chartOptions(item.details.options, item.details.annotations)}
data={item.details.chartData}
/>
</div>
<Link href={`/app/markupDetail/${item.id}`}>
<button className="mx-6 h-full flex">
<ArrowRight />
</button>
</Link>
</div>
);
};
// chart component
import React, { useEffect, useState } from 'react';
import { useDispatch } from 'react-redux';
import { supabase } from '../../client';
import { actions } from '../../store';
import { SocialNote } from '../../types';
import { Card } from './Card';
const SocialFeed = () => {
const [feed, setFeed] = useState<SocialNote[]>([]);
const dispatch = useDispatch();
const loadPage = async () => {
dispatch(actions.setLoaded(true));
const { data, error } = await supabase
.from('notes')
.select('*')
.order('last_update', { ascending: false });
if (data) {
setFeed(data);
console.log(data);
return data;
} else {
return error;
}
};
useEffect((): (() => void) => {
loadPage();
return () => supabase.removeAllSubscriptions();
}, []);
return (
<div className="w-full mx-auto overflow-y-auto">
{feed.map((item, key) => (
<Card key={key} item={item} />
))}
</div>
);
};
export default SocialFeed;
// chartoptions.js
export const chartOptions: any = (options: any, annotations: any) => {
const { title } = options;
const { tooltip } = options.plugins;
return {
title,
responsive: true,
maintainAspectRatio: false,
interaction: {
mode: 'index' as const,
intersect: false,
},
plugins: {
annotation: { annotations: annotations },
tooltip,
legend: {
position: 'top' as const,
},
title,
},
};
};
// next.config.js
/** #type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
}
module.exports = nextConfig
CRA
Nextjs
Turns out I just needed to register Annotations in ChartJS.register()
This was not the case for React, but was required for Nextjs
import Annotation from 'chartjs-plugin-annotation';
ChartJS.register(
Annotation,
LinearScale,
CategoryScale,
BarElement,
PointElement,
LineElement,
Legend,
Tooltip,
);

Form is not rendered

I'm making a todo app and using useState to pass value to the form then submit the todo but for some reasons my todo form is not render and i don't know what is missing in my codes, please help me to check! Thank you so much!
import React, { useState } from "react";
function Todo({ todo, index }) {
console.log("hiiii");
return (
<div>
<p>{todo.text}</p>
</div>
);
}
function todoForm(addTodo) {
const [value, setValue] = useState("");
handleSubmit = (e) => {
e.preventDefault();
if (!value) return;
addTodo(value);
setValue("");
};
return (
<div>
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="add new todo"
value={value}
onChange={(e) => {
setValue(e.target.value);
}}
/>
</form>
</div>
);
}
function App() {
const [todos, setTodos] = useState([
{
text: "eat lunch",
isCompleted: false
},
{
text: "do homework",
isCompleted: false
},
{
text: "go to school",
isCompleted: false
}
]);
addTodo = (text) => {
console.log("hey");
const newTodos = [...todos, { text }];
setTodos(newTodos);
};
return (
<div>
<div>
{todos.map((todo, index) => {
return <Todo key={index} index={index} todo={todo} />;
})}
</div>
<div>
<todoForm addTodo={addTodo} />
</div>
</div>
);
}
export default App;
Link sandbox: https://codesandbox.io/s/serverless-bash-ef4hk?file=/src/App.js
JSX tags must be uppercased in order to be properly parsed by the compiler as a React component.
Instead of todoForm, use TodoForm.
Capitalized types indicate that the JSX tag is referring to a React component. These tags get compiled into a direct reference to the named variable, so if you use the JSX expression, Foo must be in scope.
From: https://reactjs.org/docs/jsx-in-depth.html#specifying-the-react-element-type
Also, you need to destructure props inside TodoForm in order to gain access to addTodo:
// Bad
function TodoForm(addTodo) {...}
// Good
function TodoForm({addTodo}) {...}
You should also assign you handlers to consts:
// Bad
addTodo = (text) => {...};
// Good
const addTodo = (text) => {...};
your problem is solved it
APP.JS
import React, { useState } from "react";
function Todo({ todo, index }) {
console.log("hiiii");
return (
<div>
<p>{todo.text}</p>
</div>
);
}
function todoForm(addTodo) {
const [value, setValue] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
if (!value) return;
addTodo(value);
setValue("");
};
return (
<div>
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="add new todo"
value={value}
onChange={(e) => {
setValue(e.target.value);
}}
/>
</form>
</div>
);
}
function App() {
const [todos, setTodos] = useState([
{
text: "eat lunch",
isCompleted: false
},
{
text: "do homework",
isCompleted: false
},
{
text: "go to school",
isCompleted: false
}
]);
const addTodo = (text) => {
console.log("hey");
const newTodos = [...todos, { text }];
setTodos(newTodos);
};
return (
<div>
<div>
{todos.map((todo, index) => {
return <Todo key={index} index={index} todo={todo} />;
})}
</div>
<div>
{todoForm(addTodo)}
</div>
</div>
);
}
export default App;

reducer case set value delayed response

When I dispatch "REMOVE_TODO" on button click it does what I want it to do, the problem I'm having is that when it executes. It doesn't return the correct current array length.
Now when I click an item, it will dispatch "TOGGLE_TODO" which will change the font color and put a line-through the text.
Now while toggled and I click the "Clear Completed" button, it toggles "REMOVE_TODO" and works fine. It removes the items toggled. The problem I'm having is that The number doesn't reflex the current amount of items left in the list when I click the button once..
However if I click the button once more (or however many more times) the number updates to the correct total
This is my app code
import React, { useState, useReducer } from 'react';
import { Reducer } from './reducers/reducer';
import './App.css';
function App() {
const [{ todos, todoCount }, dispatch] = useReducer(Reducer, {
todos: [],
todoCount: 0,
completedCount: 0
});
const [text, setText] = useState("");
return (
<div className="App">
<header className="App-header">
<div>ToDo List [ <span style={{color: '#61dafb', margin: '0px', padding: '0px'}}>{ todoCount }</span> ]</div>
<div>
{ todos.map((todo, index) => (
<div
key={index}
onClick={() => dispatch(
{ type: "TOGGLE_TODO", index }
)}
style={{
fontFamily: 'Tahoma',
fontSize: '1.5rem',
textDecoration: todo.completed ? 'line-through' : "",
color: todo.completed ? '#61dafb' : 'dimgray',
cursor: 'pointer'
}}
>
{ todo.text }
</div>
))
}
<form
onSubmit={e => {
e.preventDefault();
text.length === 0 ? alert("No Task To Add!") : dispatch({ type: "ADD_TODO", text });
setText("");
}}
>
<input
type="text"
name="input"
value={ text }
onChange={e => setText(e.target.value)}
/><br />
<button>
Add
</button>
</form>
<button onClick={() => {dispatch({ type: "REMOVE_TODO" })}}>
Clear Completed
</button>
</div>
</header>
</div>
);
}
export default App;
and this is my reducer code
export const Reducer = (state, action) => {
switch (action.type) {
case 'ADD_TODO':
return {
todos: [...state.todos, { text: action.text, completed: false, id: Date.now() }],
todoCount: state.todoCount + 1,
completedCount: 0
};
case 'TOGGLE_TODO':
return {
todos: state.todos.map((todo, index) => index === action.index ? { ...todo, completed: !todo.completed } : todo),
todoCount: state.todoCount,
completedCount: 0
};
case 'REMOVE_TODO':
return {
todos: state.todos.filter(t => !t.completed),
todoCount: state.todos.length
}
default:
return state;
};
};
Does anyone have any idea what I'm doing wrong, or what I'm not doing? Thanks in advance!
Remove "todoCount" from reducer, then derive count using "todos":
<div>
ToDo List [{" "}
<span style={{ color: "#61dafb", margin: "0px", padding: "0px" }}>
{todos.filter((todo) => !todo.completed).length}
</span>{" "}
]
</div>
View in CodeSandbox here

Resources