I’m writing a small web application, hopefully it looks aesthetic on phone. On a mobile phone, the font showing the selected clinics are center justified, I want to know how to insert a line break into the code, so that the options appear consistently justified left in the mobile version, regardless of the name of the options.
Update: I still maintain a spaced-out view on the computer version, after the change.
The problem can be reproduced if 670150 is keyed as postal code, age 22, Singapore, eligible, CHAS Orange, and just select the first option of GP and polyclinics.
Phone view
Computer view
Can someone help me out here? Here's the relevant code.
ResultsTab.js
import React from "react";
import PropTypes from "prop-types";
import SwipeableViews from "react-swipeable-views";
import { makeStyles, useTheme } from "#material-ui/core/styles";
import AppBar from "#material-ui/core/AppBar";
import Tabs from "#material-ui/core/Tabs";
import Tab from "#material-ui/core/Tab";
import Typography from "#material-ui/core/Typography";
// import MyMap from "./myMap";
import TestMap from "./TestMap";
import PcDialog from "./PcDialog";
import GpDialog from "./GpDialog";
// import { display } from "#material-ui/system";
import CompareDialog from "./CompareDialog";
import Grid from "#material-ui/core/Grid";
import Paper from "#material-ui/core/Paper";
function TabContainer({ children, dir }) {
return (
<Typography component="div" dir={dir} style={{ padding: 8 * 3 }}>
{children}
</Typography>
);
}
TabContainer.propTypes = {
children: PropTypes.node.isRequired,
dir: PropTypes.string.isRequired
};
const useStyles = makeStyles(theme => ({
root: {
backgroundColor: theme.palette.background.paper,
width: "100%"
}
}));
const ResultTabs = props => {
const classes = useStyles();
const theme = useTheme();
const [value, setValue] = React.useState(0);
const [state, setState] = React.useState({
sortByLoc: true
});
function handleChange(event, newValue) {
setValue(newValue);
}
function handleChangeIndex(index) {
setValue(index);
}
const [open, setOpen] = React.useState(false);
const [selectedGP, setSelectedGP] = React.useState({
properties: { HCI_NAME: "Please Choose a GP" },
distance: "x",
price: "x",
rating: "x"
});
const [selectedPC, setSelectedPC] = React.useState({
Name: "Please choose a Polyclinic",
distance: "x",
price: "x",
rating: "x"
});
const [GPName, setGPName] = React.useState("none");
const [PCName, setPCName] = React.useState("none");
function handleClickOpen() {
setOpen(true);
}
const handleGPClose = (clinic, name) => {
setOpen(false);
clinic.price = "$$";
clinic.rating = "4.3";
setSelectedGP(clinic);
setGPName(name);
};
const handlePCClose = (clinic, name) => {
setOpen(false);
clinic.price = "$";
clinic.rating = "4.0";
setSelectedPC(clinic);
setPCName(name);
};
return (
<div className={classes.root}>
<Grid style={{ flexGrow: 1 }} direction="row">
<Grid container justify="space-evenly">
<Grid item>Selected GP: {GPName}</Grid>
<Grid item>
<p style={{ fontSize: "1em" }}>Selected PolyClinic: {PCName}</p>
{/* {console.log(selectedGP)} */}
</Grid>
</Grid>
</Grid>
<Grid style={{ flexGrow: 1 }} direction="row">
<Grid container justify="center">
<CompareDialog
GP={selectedGP}
PC={selectedPC}
formData={props.formData}
/>
</Grid>
</Grid>
<hr />
<AppBar position="static" color="default">
<Tabs
value={value}
onChange={handleChange}
indicatorColor="primary"
textColor="primary"
variant="fullWidth"
>
<Tab label="GP" />
<Tab label="Polyclinic" />
<Tab label="Map View" />
</Tabs>
</AppBar>
<SwipeableViews
axis={theme.direction === "rtl" ? "x-reverse" : "x"}
index={value}
onChangeIndex={handleChangeIndex}
>
<TabContainer dir={theme.direction}>
{props.GP.map(clinic => {
return (
<div key={clinic.properties.id}>
<GpDialog
clinic={clinic}
selectedGP={selectedGP}
open={open}
onClose={handleGPClose}
/>
<hr />
</div>
);
})}
</TabContainer>
<TabContainer dir={theme.direction}>
{props.PC.map(clinic => {
return (
<div key={clinic.id}>
<PcDialog
clinic={clinic}
selectedPC={selectedGP}
open={open}
onClose={handlePCClose}
/>
<hr />
</div>
);
})}
</TabContainer>
<TabContainer dir={theme.direction}>
{props.currentLoc[0] !== 0 && (
<TestMap coord={props.currentLoc} GP={props.GP} PC={props.PC} />
)}
</TabContainer>
</SwipeableViews>
</div>
);
};
export default ResultTabs;
FilteredResults.js
import React from "react";
import GP from "./chas.json";
import * as turf from "#turf/turf";
import ResultTabs from "./ResultTabs.js";
import PC from "./polyclinics.json";
import Button from "#material-ui/core/Button";
import Switch from "#material-ui/core/Switch";
import Grid from "#material-ui/core/Grid";
const API_KEY = "";
// this component aims to display the filtered clinic after they fill in the form
//try not to abuse the API call, im using some kind of free credits from google for this
//api to be able to consistenly make the api call
//api in use here are: google geocode & turf
//everything works except for styling, but the content from the json file abit lacking,
// no opening hrs etc
class FilteredResult extends React.Component {
constructor(props) {
super(props);
this.state = {
formData: this.props.location.state, //this gets the info from react router from Form.js
userLng: 0,
userLat: 0,
sortByLoc: true
};
this.goBack = this.goBack.bind(this);
}
componentDidMount() {
fetch(
`https://maps.googleapis.com/maps/api/geocode/json?address=${
this.state.formData.postalCode
}®ion=sg&key=${API_KEY}`
)
.then(res => res.json())
.then(json => {
this.setState({
userLng: json.results[0].geometry.location.lng,
userLat: json.results[0].geometry.location.lat
});
});
}
goBack() {
this.props.history.goBack();
}
render(props) {
const { userLat, userLng, formData } = this.state;
const filteredGP = GP.features.filter(clinic => {
const from = turf.point([userLng, userLat]);
const to = turf.point([
clinic.geometry.coordinates[0],
clinic.geometry.coordinates[1]
]);
const options = { units: "kilometers" };
const dist = turf.distance(from, to, options);
clinic.distance = dist;
if (formData.hasSubsidy === "Yes") {
return (
dist <= 3
// && clinic.properties.CLINIC_PROGRAMME_CODE.includes(formData.subsidyType)
);
}
return dist <= 3;
});
const filteredPC = PC.clinics.filter(clinic => {
const from = turf.point([userLng, userLat]);
const to = turf.point([clinic.coord[0], clinic.coord[1]]);
const options = { units: "kilometers" };
const dist = turf.distance(from, to, options);
clinic.distance = dist;
return dist <= 100;
});
function sortDist(a, b) {
if (a.distance < b.distance) {
return -1;
} else {
return 1;
}
}
const handleSwitch = name => event => {
this.setState({ [name]: event.target.checked });
};
const sortedGP = filteredGP.sort(sortDist);
const sortedPC = filteredPC.sort(sortDist);
//note: dangerouslySetInnerHTML cos the json is in string, but its actually HTML
return (
<div>
<Grid container justify="center">
<h2>
Filtered clinics for{" "}
<span style={{ fongWeight: "bold", textDecoration: "underline" }}>
S{formData.postalCode}
</span> {" "}
{formData.subsidyType === ""
? ""
: `with ${formData.subsidyType} subsidy`}
</h2>
</Grid>
{/* <Switch
checked={this.state.sortByLoc}
onChange={handleSwitch('sortByLoc')}
value="sortByLoc"
inputProps={{ 'aria-label': 'secondary checkbox' }}
/> */}
<div>
<hr />
<ResultTabs
GP={sortedGP}
PC={sortedPC}
formData={formData}
currentLoc={[this.state.userLng, this.state.userLat]}
/>
<Button onClick={this.goBack}>Go Back</Button>
</div>
</div>
);
}
}
export default FilteredResult;
In the ResultTab.js inside the return give justify="left" instead of "space-evenly" for the Grid and I hope you will achieve what you wanted for phone application
<Grid container justify="left">
<Grid item>Selected GP: {GPName}</Grid>
<Grid item>
<p style={{ fontSize: "1em" }}>Selected PolyClinic: {PCName}</p>
{/* {console.log(selectedGP)} */}
</Grid>
</Grid>
</Grid>
Related
I have found a code for a material table that accepts a list as input and applies pagination, sorting and filtering on it. The thing is I need to find a way to extract the data from the row onClick and redirect the page to a new route along with those data. How can I do that?
In the component, I call the table as follows:
export default function ViewAllUsers() {
const [filterFn, setFilterFn] = useState({ fn: items => { return items; } })
const records = ....//List of records
const {
TblContainer,
TblHead,
TblPagination,
recordsAfterPagingAndSorting
} = useTable(records, headCells, filterFn);
const handleSearch = e => {
let target = e.target;
//Handle search
}
return (
<>
<Paper className={classes.pageContent}>
<Toolbar>
<Controls.Input onChange={handleSearch}/>
</Toolbar>
<TblContainer>
<TblHead />
<TableBody>
{
recordsAfterPagingAndSorting().map(item =>
(<TableRow key={item.id}>
<TableCell>{item.id}</TableCell>
<TableCell>{item.fullName}</TableCell>
</TableRow>)
)
}
</TableBody>
</TblContainer>
<TblPagination/>
</Paper>
}
and the useTable hook is:
export default function useTable(records, headCells, filterFn) {
const pages = [5, 10, 25]
const [page, setPage] = useState(0)
const [rowsPerPage, setRowsPerPage] = useState(pages[page])
const [order, setOrder] = useState()
const [orderBy, setOrderBy] = useState()
const TblContainer = props => (
<Table className={classes.table}>
{props.children}
</Table>
)
const TblHead = props => {
const handleSortRequest = cellId => {
//Some code
}
return (<TableHead>
<TableRow>
{
headCells.map(headCell => (
<TableCell key={headCell.id}
sortDirection={orderBy === headCell.id ? order : false}>
{headCell.disableSorting ? headCell.label :
<TableSortLabel
active={orderBy === headCell.id}
direction={orderBy === headCell.id ? order : 'asc'}
onClick={() => { handleSortRequest(headCell.id) }}>
{headCell.label}
</TableSortLabel>
}
</TableCell>))
}
</TableRow>
</TableHead>)
}
const TblPagination = () => (
<TablePagination
component="div"
page={page}
rowsPerPageOptions={pages}
rowsPerPage={rowsPerPage}
count={records.length}
onChangePage={handleChangePage}
onChangeRowsPerPage={handleChangeRowsPerPage}
id="TablePagination"
/>
)
return {
TblContainer,
TblHead,
TblPagination,
recordsAfterPagingAndSorting
}
}
You can simply use an onClick handler to pass the item data through it:
export default function ViewAllUsers() {
const [filterFn, setFilterFn] = useState({ fn: items => { return items; } })
const records = ....//List of records
const {
TblContainer,
TblHead,
TblPagination,
recordsAfterPagingAndSorting
} = useTable(records, headCells, filterFn);
const handleSearch = e => {
let target = e.target;
//Handle search
}
const handleItemClick = item => {
//Redirect to new route from here with the item data
}
return (
<>
<Paper className={classes.pageContent}>
<Toolbar>
<Controls.Input onChange={handleSearch}/>
</Toolbar>
<TblContainer>
<TblHead />
<TableBody>
{
recordsAfterPagingAndSorting().map(item =>
(<TableRow key={item.id} onClick={() => handleItemClick(item)}>
<TableCell>{item.id}</TableCell>
<TableCell>{item.fullName}</TableCell>
</TableRow>)
)
}
</TableBody>
</TblContainer>
<TblPagination/>
</Paper>
</>
)
}
I'm implementing a Like and Dislike Button, and I wanna that when I click them will be with other colors, but just the clicked component, when I click all buttons change the state, can anybody help me?
`
const indexPost = async () => {
const data = await api.get('/api/posts')
if(data.data.length !=0){
const dataArray = data.data
if(dataArray.length === 0) {
return
}else{
return(
setPost(dataArray.map( data => (
<Post key={data._id} id={data._id} title={data.title} text={data.text}>
<Like id={data._id}></Like>
</Post>
)))
)
}
}
}
export default function Like({itemId}) {
const context = useContext(notificationContext)
const {isLoved, Like, Loved, Unlike, isLike, isUnlike, setIsLike, setIsUnlike, setIsLoved } = context
return(
<div className={styles.likeContainer} key={itemId}>
{isLike ? (
<button className={styles.likeContent} onClick={() => setIsLike(false)}><Icon.ThumbsUp className={styles.Icon} fill="#5CB0BB" ></Icon.ThumbsUp></button>) :
(<button className={styles.likeContent} onClick={() => Like() }><Icon.ThumbsUp className={styles.Icon} ></Icon.ThumbsUp></button>)}
{isLoved ?
(<button className={styles.likeContent} onClick={() => setIsLoved(false)}><Icon.Heart className={styles.Icon} fill="red" ></Icon.Heart> </button>) :
(<button className={styles.likeContent} onClick={() => Loved() }><Icon.Heart className={styles.Icon} ></Icon.Heart></button>)}
{isUnlike ? (
<button className={styles.likeContent} onClick={() => setIsUnlike(false)}><Icon.ThumbsDown className={styles.Icon} fill="#702BA6" ></Icon.ThumbsDown> </button>) :
(<button className={styles.likeContent} onClick={() => Unlike()}><Icon.ThumbsDown className={styles.Icon} ></Icon.ThumbsDown></button>
)}
</div>
)
};
I have implemented the similar one in my project, it is very basic , it shows how to update the likes , you need to handle the cases of user authentication and stuff
App.js
import { useState, useEffect, createContext, useReducer } from "react";
import { updateArrayOfObj } from "./utils";
import AllPosts from "./AllPosts";
export const PostsContext = createContext();
const initialState = {
posts: [
{
_id: "1",
name: "Browny",
image: "http://placekitten.com/200/310",
likes: 0,
love: 0,
dislikes: 0
},
{
_id: "2",
name: "Blacky",
image: "http://placekitten.com/200/320",
likes: 0,
love: 0,
dislikes: 0
},
{
_id: "3",
name: "SnowWhite",
image: "http://placekitten.com/200/300",
likes: 0,
love: 0,
dislikes: 0
}
]
};
const reducer = (state, action) => {
switch (action.type) {
case "UPDATE_POST":
return {
...state,
posts: updateArrayOfObj(
state.posts,
action.payload.obj,
"_id",
action.payload._id
)
};
case "CREATE_POST":
return {
...state,
posts: [...state.posts, ...action.payload.data]
};
case "DELETE_POST":
return {
...state,
posts: state.posts.filter((ele) => ele._id !== action.payload._id)
};
default:
return state;
}
};
export default function App() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<PostsContext.Provider
value={{
state,
dispatch
}}
>
<div className="App">
<AllPosts />
</div>
</PostsContext.Provider>
);
}
PostsAll.js
import Post from "./Post";
import { PostsContext } from "./App";
import { useContext } from "react";
export default function AllPosts() {
const { state } = useContext(PostsContext);
return (
<div className="allPosts">
{state.posts.map((item) => {
return (
<Post
name={item.name}
image={item.image}
likes={item.likes}
love={item.love}
dislikes={item.dislikes}
id={item._id}
key={item._id}
/>
);
})}
</div>
);
}
Post.js
import { PostsContext } from "./App";
import { useContext } from "react";
export default function Post(props) {
const { state, dispatch } = useContext(PostsContext);
const handleUserInteraction = (type, id) => {
dispatch({
type: "UPDATE_POST",
payload: {
obj: { [type]: props[type] + 1 },
_id: id
}
});
};
return (
<div className="post">
<h3>{props.name}</h3>
<img src={props.image} alt="cat" />
<br />
<button onClick={() => handleUserInteraction("likes", props.id)}>
{props.likes} Like
</button>{" "}
<button onClick={() => handleUserInteraction("love", props.id)}>
{props.love} Love
</button>{" "}
<button onClick={() => handleUserInteraction("dislikes", props.id)}>
{props.dislikes} Dislike
</button>
</div>
);
}
You can refer to this codesandbox to implement the same
You can use onClick() on each like button and attach it with a function, then you can get the value of that particular like with e.currentTarget.id and change its css/style the way you want.
const handleClick=(e)=>
{
console.log(e.currentTarget.id);
}
I am having problems calling a function in React Native. I simply want to change the value of 'Context'. Here is some code, first the script for 'context':
//LogContext.js
import React, { useState } from 'react'
export const LogContext = React.createContext({
set: "en",
login: "false"
})
export const LogContextProvider = (props) => {
const setLog = (login) => {
setState({set: "jp", login: login})
}
const initState = {
set: "en",
login: "false"
}
const [state, setState] = useState(initState)
return (
<LogContext.Provider value={state}>
{props.children}
</LogContext.Provider>
)
}
and the 'app.js' code:
//app.js
import React, { useState, useContext } from 'react';
import { Button, Text, TextInput, View } from 'react-native';
import { NavigationContainer } from '#react-navigation/native';
import { createStackNavigator } from '#react-navigation/stack';
import { LogContextProvider, LogContext } from './LogContext'
function HomeScreen({ navigation }) {
const state = useContext(LogContext);
return (
<>
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Passed config: {JSON.stringify({state})}</Text>
<Text>Home Screen</Text>
</View>
{state.login === 'false' ? (
<Button
title="Go to Login"
onPress={() => navigation.navigate('Login')}
/>
) : (
<Button title="Stuff" onPress={() => navigation.navigate('DoStuff')} />
)}
</>
);
}
function LoginScreen({ navigation }) {
const state = useContext(LogContext);
//do stuff to login here...
state.setLog('true'); //not functional...
return (
<LogContext.Provider value={'true'}> //value={'true'} also not functional...
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Login Screen</Text>
<Button title="Go to Home" onPress={() => navigation.navigate('Home')} />
</View>
</LogContext.Provider>
);
}
function StuffScreen({ navigation }) {
//do other stuff here...
}
const Stack = createStackNavigator();
function App() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Login" component={LoginScreen} />
<Stack.Screen name="DoStuff" component={StuffScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}
export default App;
Obviously I am not too familiar with React Native. Any advice on how to call the "setLog()" function as to enable an update of the value for the 'Context' global variable would be greatly appreciated. I thank you in advance.
I am trying to modify my "App()" function to wrap the Navigator within the provider as suggested by another user...however this following is completely non-functional...suggestions appreciated:
const Stack = createStackNavigator();
function App() {
const [data, setData] = useState({
set: 'en',
login: 'false',
});
const state = { data, setData };
return (
<LogContext.Provider value={state}>
<NavigationContainer>
{state.data.login === 'true' ? (
<Stack.Navigator>
<Stack.Screen name="BroadCast" component={VideoScreen} />
<Stack.Screen name="Logout" component={LogoutScreen} />
</Stack.Navigator>
) : (
<Stack.Navigator>
<Stack.Screen name="Login" component={LoginScreen} />
<Stack.Screen name="Details" component={DetailsScreen} />
<Stack.Screen name="Home" component={HomeScreen} />
</Stack.Navigator>
)}
</NavigationContainer>
</LogContext.Provider>
);
}
The issue you are having is not having a set function in your context and i dont see a need for a separate LogContext provider function.
You can simply do that part in your app.js or whatever the root function. The below example does that. You can see how a state value is passed along with a function to set the values and this can be modified from teh Login component which is inside the provider. If you use a separate provider its a bit confusing. The below is a working example without the navigation part to give you an idea.
const LogContext = createContext({
data: {
set: 'en',
login: 'false',
},
});
export default function App() {
const [data, setData] = useState({
set: 'en',
login: 'false',
});
const state = { data, setData };
return (
<LogContext.Provider value={state}>
<View style={{ flex: 1 }}>
<Text>{JSON.stringify(state.data)}</Text>
<Login />
</View>
</LogContext.Provider>
);
}
const Login = () => {
const state = React.useContext(LogContext);
return (
<View>
<Button
onPress={() => state.setData({ set: 'bb', login: 'true' })}
title="Update"
/>
</View>
);
};
To modify your code, you should wrap the main navigator inside the LogContext.Provider and maintain the state there which will help you do the rest.
Feel free to ask any further clarification :)
I have set of components where it would consist of input fields along with text rows.
As given in the image the users should be able to add categories and description. After adding them they will be rendered as a list of components. like this
Inside a category there will be tags as given in the above image and to add them i have to add a input component. This input component should be available only when the user clicks on the Add tag button below each category row. When a user clicks on it,it should enable the input(should render a input component inside the selected category row) and should be able to type the tag name on it and save it. I need to make this input field enable only when i click on the add tag button. and it should enable only in the selected category row. This is the code that i have tried.
import React, { Component, Fragment } from "react";
import { Button, Header, Input } from "semantic-ui-react";
import "semantic-ui-css/semantic.min.css";
import ReactDOM from "react-dom";
class App extends Component {
state = {
category: "",
description: "",
categories: []
};
onChange = (e, { name, value }) => {
this.setState({ [name]: value });
};
addCategory = () => {
let { category, description } = this.state;
this.setState(prevState => ({
categories: [
...prevState.categories,
{
id: Math.random(),
title: category,
description: description,
tags: []
}
]
}));
};
addTag = id => {
let { tag, categories } = this.state;
let category = categories.find(cat => cat.id === id);
let index = categories.findIndex(cat => cat.id === id);
category.tags = [...category.tags, { name: tag }];
this.setState({
categories: [
...categories.slice(0, index),
category,
...categories.slice(++index)
]
});
};
onKeyDown = e => {
if (e.key === "Enter" && !e.shiftKey) {
console.log(e.target.value);
}
};
tags = tags => {
if (tags && tags.length > 0) {
return tags.map((tag, i) => {
return <Header key={i}>{tag.name}</Header>;
});
}
};
enableTagIn = id => {};
categories = () => {
let { categories } = this.state;
return categories.map(cat => {
return (
<Fragment key={cat.id}>
<Header>
<p>
{cat.title}
<br />
{cat.description}
</p>
</Header>
<Input
name="tag"
onKeyDown={e => {
this.onKeyDown(e);
}}
onChange={this.onChange}
/>
<Button
onClick={e => {
this.addTag(cat.id);
}}
>
Add
</Button>
{this.tags(cat.tags)}
</Fragment>
);
});
};
render() {
return (
<Fragment>
{this.categories()}
<div>
<Input name="category" onChange={this.onChange} />
<Input name="description" onChange={this.onChange} />
<Button onClick={this.addCategory}>Save</Button>
</div>
</Fragment>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
This is the codesandbox url.
Any idea on how to achieve this?.
I changed your code by using function components and react hooks and i created category component which has it own state like this:
import React, { Fragment } from "react";
import { Button, Header, Input } from "semantic-ui-react";
import "semantic-ui-css/semantic.min.css";
import ReactDOM from "react-dom";
const App = () => {
const [Category, setCategory] = React.useState({
title: "",
description: ""
});
const [Categories, setCategories] = React.useState([]);
return (
<div>
{console.log(Categories)}
<Input
value={Category.title}
onChange={e => setCategory({ ...Category, title: e.target.value })}
/>
<Input
value={Category.description}
onChange={e =>
setCategory({ ...Category, description: e.target.value })
}
/>
<Button onClick={() => setCategories([...Categories, Category])}>
Save
</Button>
<div>
{Categories.length > 0
? Categories.map(cat => <CategoryItem cat={cat} />)
: null}
</div>
</div>
);
};
const CategoryItem = ({ cat }) => {
const [value, setvalue] = React.useState("");
const [tag, addtag] = React.useState([]);
const [clicked, setclicked] = React.useState(false);
const add = () => {
setclicked(false);
addtag([...tag, value]);
};
return (
<Fragment>
<Header>
<p>
{cat.title}
<br />
{cat.description}
</p>
</Header>
<Input
name="tag"
value={value}
style={{ display: clicked ? "initial" : "none" }}
onChange={e => setvalue(e.target.value)}
/>
<Button onClick={() => (clicked ? add() : setclicked(true))}>Add</Button>
<div>{tag.length > 0 ? tag.map(tagname => <p>{tagname}</p>) : null}</div>
</Fragment>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
and here a sandbox
I would like to display InfoWindow on Marker click. I followed some tutorials and I used react-google-maps for my project. I would like my app to work like this: "https://tomchentw.github.io/react-google-maps/basics/pop-up-window" but my code is a little bit different.
class Map extends React.Component {
handleMarkerClick(){
console.log("Clicked");
}
handleMarkerClose(){
console.log("CLOSE");
}
render(){
const mapContainer= <div style={{height:'100%',width:'100%'}}></div>
//fetch markers
const markers = this.props.markers.map((marker,i) => {
return (
<Marker key={i} position={marker.location} showTime={false} time={marker.time} onClick={this.handleMarkerClick} >
{
<InfoWindow onCloseClick={this.handleMarkerClose}>
<div>{marker.time}</div>
</InfoWindow>
}
</Marker>
)
})
/* set center equals to last marker's position */
var centerPos;
if(markers[markers.length-1]!== undefined)
{
centerPos=markers[markers.length-1].props.position;
}
else {
centerPos={};
}
return (
<GoogleMapLoader
containerElement={mapContainer}
googleMapElement={
<GoogleMap
defaultZoom={17}
center={centerPos}
>
{markers}
</GoogleMap>
}/>
);
}
}
export default Map;
I got "this.props.markers" from another class component, which fetching data from URL. I am almost sure, that it is easy problem to solve. Currently on marker click in console I got "Clicked" and on Marker close "CLOSE" as you can guess from above code it is because of handleMarkerClick() and handleMarkerClose(). I want to have pop-window with InfoWindow.
What should I do to make it work?
Here is heroku link : App on heroku
Hi I came across the same requirement. I did this (I am using redux and redux-thunk) :
GoogleMap.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import {
withGoogleMap,
GoogleMap,
Marker,
InfoWindow
} from 'react-google-maps';
import { onMarkerClose } from '../actions/Cabs';
const GettingStartedGoogleMap = withGoogleMap(props => (
<GoogleMap
defaultZoom={12}
defaultCenter={{ lat: 12.9716, lng: 77.5946 }}
>
{props.markers.map( (marker, index) => (
<Marker {...marker} onClick={() => props.onMarkerClose(marker.key)}>
{marker.showInfo &&(
<InfoWindow onCloseClick={() => props.onMarkerClose(marker.key)}>
<div>
<h1>Popover Window</h1>
</div>
</InfoWindow>
)}
</Marker>
))}
</GoogleMap>
));
class CabType extends Component{
constructor(props){
super(props);
}
render(){
if(this.props.cabs.length === 0){
return <div>loading...</div>
}
return(
<div className="map-wrapper">
<GettingStartedGoogleMap
containerElement={
<div style={{ height: '100%' }} />
}
mapElement={
<div style={{ height: '100%' }} />
}
onMarkerClose = {this.props.onMarkerClose}
markers={this.props.showMap ? this.props.markers : []}
/>
</div>
)
}
}
export default connect(store => {return {
cabs : store.cabs,
markers: store.markers
}}, {
onMarkerClose
})(CabType);
Action.js
const getMarkers = (cabs , name) => dispatch => {
let markers = [];
let data = {};
cabs.map(cab => {
if(cab.showMap){
data = {
position: {
lat : cab.currentPosition.latitude,
lng : cab.currentPosition.longitude
},
showInfo: false,
key: cab.cabName,
icon: "/images/car-top.png",
driver: cab.driver,
contact: cab.driverContact,
};
markers.push(data);
}
});
dispatch(emitMarker(markers));
};
function emitSetMarker(payload){
return{
type: SET_MARKER,
payload
}
}
export const onMarkerClose = (key) => dispatch => {
dispatch(emitSetMarker(key))
};
RootReducer.js
import { combineReducers } from 'redux';
import { cabs } from "./Cabs";
import { markers } from "./Markers";
const rootReducer = combineReducers({
cabs,
markers,
});
export default rootReducer;
MarkerReducer.js
import { GET_MARKERS, SET_MARKER } from "../types"
export const markers = (state = [], action) => {
switch (action.type){
case GET_MARKERS:
return action.payload;
case SET_MARKER:
let newMarker = state.map(m => {
if(m.key === action.payload){
m.showInfo = !m.showInfo;
}
return m;
});
return newMarker;
default: return state;
}
};
Sorry for a long post but this is code which is tested and running. Cheers!