I have a google map approach that map itself isn't showing, I have 2 pieces of code the first is Map component itself, that's the code for it:
import React, {
useState,
useEffect,
useRef
} from 'react';
interface MapProps extends google.maps.MapOptions {
style: { [key: string]: string };
onClick?: (e: google.maps.MapMouseEvent) => void;
onIdle?: (map: google.maps.Map) => void;
children?: React.ReactNode;
}
const Map: React.FC<MapProps> = ({
onClick,
onIdle,
children,
style,
...options
}) => {
const MyStyle = {
width: '100%',
height: '600px'
}
const ref = useRef<HTMLDivElement>(null);
const [map, setMap] = useState<google.maps.Map>();
return (
<>
<div ref={ref} />
</>
)
}
export default Map;
The second component is App component, the one which Map is being rendered, its code is:
import Map from './components/mapComponent2';
import {
Wrapper,
Status
} from '#googlemaps/react-wrapper';
function App = () => {
const center = { lat: 30.033333, lng: 31.233334 };
const zoom = 5;
useEffect(() => {
if (ref.current && !map) {
setMap(new window.google.maps.Map(ref.current, {}));
}
if (map) {
["click", "idle"].forEach((eventName) =>
google.maps.event.clearListeners(map, eventName)
);
if (onClick) {
map.addListener("click", onClick);
}
if (onIdle) {
map.addListener("idle", () => onIdle(map));
}
}
}, [ref, map, onClick, onIdle]);
return(
<div className='App'>
<Wrapper apiKey={API_KEY} libraries={['places', 'drawing', 'geometry']}>
<Map
center={center}
onClick={onMapClick}
onIdle={onIdle}
zoom={zoom}
style={{ flexGrow: "1", height: '600px' }}
/>
</Wrapper>
</div>
)
}
export default App;
after giving the inner div the proper height and width the map element appears inside inner div but the element itself isn't showing, although all click handlers related to the map are working. Can any anyone help me to determine where's the problem?
Related
We upgraded MUI from v4 to v5 and we have UI tests which started failing. Error is:
TypeError: Cannot read property 'secondary' of undefined (I added comment to which line in code this refers)
Test example:
describe('<AnonDragNDropFileUpload />', () => {
it('should render', () => {
const blob = () => {
return new File(['Test string'], 'Test file.txt');
};
const fileSet: AnonFileSet = {
originalFile: { get: blob, set: () => undefined },
compressedFile: { get: () => undefined, set: () => undefined },
};
const result = render(<AnonDragNDropFileUpload fileSet={fileSet} downloadFileAction={jest.fn()} clearFileAction={jest.fn()} />);
expect(result).toBeTruthy();
});
});
Code:
import { Paper } from '#mui/material';
import { green, red } from '#mui/material/colors';
import { lighten, Theme } from '#mui/material/styles';
import makeStyles from '#mui/styles/makeStyles';
import { JobInputFileTypeEnum } from 'app/api';
import React, { useCallback, useEffect, useState } from 'react';
import { useDropzone } from 'react-dropzone';
import { AnonFileSet } from '.';
const useDropZoneStyles = makeStyles((theme: Theme) => ({
dragndropZone: {
backgroundColor: lighten(theme.palette.secondary.light, 0.8), // <-- this line fails
width: '100%',
},
info: {
backgroundColor: green[100],
width: '100%',
},
}));
interface Props {
fileSet: AnonFileSet;
clearFileAction: (fileType?: JobInputFileTypeEnum) => void;
downloadFileAction: () => void;
}
export const AnonDragNDropFileUpload: React.FC<Props> = ({ fileSet, clearFileAction, downloadFileAction }) => {
const classes = useDropZoneStyles();
const [fileLabel, setFileLabel] = useState('');
const onDrop = useCallback(async (acceptedFiles: File[]) => {
setFileLabel(fileSet.originalFile.get()?.name ?? '');
fileSet.originalFile.set(acceptedFiles[0]);
}, []);
const { acceptedFiles, getRootProps, getInputProps } = useDropzone({ onDrop, multiple: false, accept: '.csv' });
const { ref, ...rootProps } = getRootProps();
const handleDeleteFile = () => {
acceptedFiles.splice(
acceptedFiles.findIndex((x) => x.name === fileSet.originalFile.get()?.name),
1,
);
clearFileAction();
};
useEffect(() => {
setFileLabel(fileSet.originalFile.get()?.name ?? '');
}, [fileSet.originalFile.get()]);
if (fileSet.originalFile.get())
return (
<Paper variant="outlined">
<div className="flex px-8 py-32 justify-center">
<div className="flex">
<a style={{ color: '#888888', textDecoration: 'underline', cursor: 'default' }}>{fileLabel}</a>
<p className="mx-4"> </p>
<a onClick={handleDeleteFile} style={{ color: red[600], cursor: 'pointer' }} role="link">
{'[Clear File]'}
</a>
<p className="mx-4"> </p>
{fileSet.compressedFile?.get() && (
<a onClick={downloadFileAction} style={{ color: green[600], cursor: 'pointer' }} role="link">
{'[Download File]'}
</a>
)}
</div>
</div>
</Paper>
);
return (
<Paper {...rootProps} className={classes.dragndropZone} variant="outlined">
<div className="flex px-8 py-32 justify-center">
<input {...getInputProps()} name="customerCSVFilename" placeholder="CSV File"/>
<p>{fileLabel}</p>
</div>
</Paper>
);
};
What I've tried so far:
Checked if ThemeProvider is available
Added custom theme just to the code block which fails
All other tests which are testing hooks or custom logic (like pure TypeScript) are working without any issues, but it seems that somehow using styles from MUI is not working. When I remove these lines, test is passing, so my guess it has something with MUI makeStyles.
Any ideas? Thanks for helping me out.
Try to use a mocked component, wrapped inside a ThemeProvider instance:
import theme from './path/to/your/theme'
const MockAnonDragNDropFileUpload = (props: any) => {
return (
<ThemeProvider theme={theme}>
<AnonDragNDropFileUpload {...props} />
</ThemeProvider>
);
}
To mock the component using the existing theme you could separate its declaration into a distinct file:
const theme = createTheme({
...
});
export default theme;
Then use the mocked instance in the tests:
describe('<AnonDragNDropFileUpload />', () => {
it('should render', () => {
...
const result = render(
<MockAnonDragNDropFileUpload
fileSet={fileSet}
downloadFileAction={jest.fn()}
clearFileAction={jest.fn()}
/>
);
expect(result).toBeTruthy();
});
});
I am trying to understand the useContext hook a little bit better. I am playing around with this codesandbox which adds items from the left side component to the right side.
Now, I would like to count the number of times they are added to the list (how many times their resp. add to cart button has been clicked on) and display that next to them.
Do I need to create a completely new context and state hook to have access to the resp. items' values all over?
I tried implementing a counter in the addToItemList(item) function without success. I also tried to implement a counter function outside of it and then implement a global handleClick function in vain as well.
Thanks in advance for any tips and hints!
The code for the list of items:
import { useContext } from "react";
import { data, Item } from "./data";
import { ItemListContext } from "./ItemList.context";
const items: Item[] = data;
export default function ItemList() {
const { itemList, setItemList } = useContext(ItemListContext); // get and set list for context
const addItemToItemList = (item: Item) => {
//you are using the itemList to see if item is already in the itemList
if (!itemList.includes(item)) setItemList((prev) => [...prev, item]);
};
return (
<div className="itemlist">
{items.map((item, index) => (
<div style={{ marginBottom: 15 }} key={index}>
<div style={{ fontWeight: 800 }}>{item.name}</div>
<div>{item.description}</div>
<button onClick={() => addItemToItemList(item)}>
Add to sidebar
</button>
</div>
))}
</div>
);
}
And the code for the container which contains the added items:
import { useContext } from "react";
import { ItemListContext } from "./ItemList.context";
import { Link, useHistory } from "react-router-dom";
export default function ItemContainer() {
const history = useHistory(); //useHistory hooks doc: https://reactrouter.com/web/api/Hooks
const { itemList } = useContext(ItemListContext);
const onNavigate = () => {
history.push("/selectedItemList");
};
return (
<div style={{ flexGrow: 4 }}>
<h1 style={{ textAlign: "center" }}>List of items</h1>
<p>Number of items: {itemList.length}</p>
{itemList.length > 0 && (
<ul>
{itemList.map((item, i) => (
<li key={i}>{item.name}</li>
))}
</ul>
)}
<div>
<button>
{" "}
<Link to="/selectedItemList"> selected list details</Link>
</button>
<div>
<button type="button" onClick={onNavigate}>
selected list with useHistory hook
</button>
</div>
</div>
</div>
);
}
ItemList.context.tsx
import React, {
createContext,
Dispatch,
FunctionComponent,
useState
} from "react";
import { Item } from "./data";
type ItemListContextType = {
itemList: Item[]; // type of your items thata I declare in data.ts
setItemList: Dispatch<React.SetStateAction<Item[]>>; //React setState type
};
export const ItemListContext = createContext<ItemListContextType>(
{} as ItemListContextType
);
export const ItemListContextProvider: FunctionComponent = ({ children }) => {
const [itemList, setItemList] = useState<Item[]>([]);
return (
<ItemListContext.Provider
value={{ itemList: itemList, setItemList: setItemList }}
>
{children}
</ItemListContext.Provider>
);
};
Add wrapper methods to the context.
This assumes names are unique.
type ItemListContextType = {
itemList: Item[]; // type of your items thata I declare in data.ts
addItem: (item: Item) => void;
removeItem: (item: Item) => void;
counter: { [itemName: string]: number };
};
export const ItemListContext = createContext<ItemListContextType>(
{} as ItemListContextType
);
export const ItemListContextProvider: FunctionComponent = ({ children }) => {
const [itemList, setItemList] = useState<Item[]>([]);
const [counter, setCounter] = useState<{ [itemName: string]: number }>({});
const addItem = useCallback((item) => {
setCounter((prev) => ({
...prev,
[item.name]: (prev[item.name] || 0) + 1,
}));
setItemList((prev) => [...prev, item]);
}, []);
const removeItem = useCallback((itemName) =>
setItemList((prev) => prev.filter((it) => it.name !== itemName)), []
);
return (
<ItemListContext.Provider
value={{ itemList: itemList, addItem, removeItem, counter }}
>
{children}
</ItemListContext.Provider>
);
};
I am new to react. I have a task for importing a component inside another one dynamically.
as you can see I have an AutoComplete component that uses react-select; what I want is when user searches for a value and the value is not provided by the list, the user clicks on Create ... option and a dialog opens containing the needed component ( the component need to by imported at that time). Now I am passing the component as child but I want it more dynamic like passing the path of Component as prop and the AutoComplete loads by the path. any solution?
sample component uses AutoComplete:
<Autocomplete
apiUrl="/coreums/api/provinces"
onChange={(field, value) => {
console.log(`${field} ${value}`);
}}
onCreate={(field) => {
console.log(field);
}}
name="province"
componentName=""
entity={null}
>
{/* <ProvinceUpdate></ProvinceUpdate> */}
<h1>Hello</h1>
</Autocomplete>
as you see I am passing a h1 and it renders but for another component like ProvinceUpdater just want to pass my components path and it will be rendered.
which changes need my AutoComplete?
here is my AutoComplete:
import React, {
Component,
FC,
Suspense,
lazy,
useEffect,
useState,
} from 'react';
import Axios from 'axios';
import CreatableSelect from 'react-select/creatable';
import Select from 'react-select';
import { AvForm } from 'availity-reactstrap-validation';
import { Button } from 'primereact/button';
import { Dialog } from 'primereact/dialog';
interface Props {
name: string;
apiUrl: string;
entity: any;
filterBy?: string;
onChange: (field: string, value: any) => void;
onCreate: (field: string) => void;
componentName?: string;
}
const Autocomplete: FC<Props> = ({
name,
apiUrl,
entity,
onChange,
children,
onCreate,
filterBy = 'name',
componentName,
}) => {
const [myOptions, setMyOptions] = useState([]);
const [childDialogVisible, setChildDialogVisible] = useState(false);
useEffect(() => {
Axios.get(`services/${apiUrl}?cacheBuster=${new Date().getTime()}`).then(
(res) => {
// console.log(res)
setMyOptions(
res.data.map((obj) => {
return { value: obj.id, label: obj[filterBy] };
})
);
}
);
}, []);
const handleChange = (newValue: any, actionMeta: any) => {
if (newValue) onChange(name, newValue.value);
};
const handleInputChange = (inputValue: any, actionMeta: any) => {
Axios.get(
`services/${apiUrl}?name.contains=${inputValue}&cacheBuster=${new Date().getTime()}`
).then((res) => {
setMyOptions(
res.data.map((obj) => {
return { value: obj.id, label: obj.name };
})
);
});
};
const handleCreate = (inputValue) => {
setChildDialogVisible(true);
onCreate(inputValue);
};
const renderFooter = () => {
return (
<div>
<Button
label="No"
icon="pi pi-times"
onClick={() => setChildDialogVisible(false)}
className="p-button-text"
/>
<Button
label="Yes"
icon="pi pi-check"
onClick={() => setChildDialogVisible(false)}
autoFocus
/>
</div>
);
};
return (
<>
<CreatableSelect
options={myOptions}
onChange={handleChange}
onCreateOption={handleCreate}
onInputChange={handleInputChange}
menuPortalTarget={document.body}
/>
<Dialog
header="Header"
visible={childDialogVisible}
style={{ width: '50vw' }}
footer={renderFooter()}
onHide={() => setChildDialogVisible(false)}
>
<Suspense fallback={() => <p>Loading</p>}>{children}</Suspense>
</Dialog>
</>
);
};
export default Autocomplete;
I'm stucking on a problem with React-Router and querySelector.
I have a Navbar component which contains all the CustomLink components for navigation and a line animation which listens to those components and displays animation according to the current active component.
// Navbar.tsx
import React, { useCallback, useEffect, useState, useRef } from "react";
import { Link, useLocation } from "react-router-dom";
import CustomLink from "./Link";
const Layout: React.FC = ({ children }) => {
const location = useLocation();
const navbarRef = useRef<HTMLDivElement>(null);
const [pos, setPos] = useState({ left: 0, width: 0 });
const handleActiveLine = useCallback((): void => {
if (navbarRef && navbarRef.current) {
const activeNavbarLink = navbarRef.current.querySelector<HTMLElement>(
".tdp-navbar__item.active"
);
console.log(activeNavbarLink);
if (activeNavbarLink) {
setPos({
left: activeNavbarLink.offsetLeft,
width: activeNavbarLink.offsetWidth,
});
}
}
}, []);
useEffect(() => {
handleActiveLine();
}, [location]);
return (
<>
<div className="tdp-navbar-content shadow">
<div ref={navbarRef} className="tdp-navbar">
<div className="tdp-navbar__left">
<p>Todo+</p>
<CustomLink to="/">About</CustomLink>
<CustomLink to="/login">Login</CustomLink>
</div>
<div className="tdp-navbar__right">
<button className="tdp-button tdp-button--primary tdp-button--border">
<div className="tdp-button__content">
<Link to="/register">Register</Link>
</div>
</button>
<button className="tdp-button tdp-button--primary tdp-button--default">
<div className="tdp-button__content">
<Link to="/login">Login</Link>
</div>
</button>
</div>
<div
className="tdp-navbar__line"
style={{ left: pos.left, width: pos.width }}
/>
</div>
</div>
<main className="page">{children}</main>
</>
);
};
export default Layout;
// CustomLink.tsx
import React, { useEffect, useState } from "react";
import { useLocation, useHistory } from "react-router-dom";
interface Props {
to: string;
}
const CustomLink: React.FC<Props> = ({ to, children }) => {
const location = useLocation();
const history = useHistory();
const [active, setActive] = useState(false);
useEffect(() => {
if (location.pathname === to) {
setActive(true);
} else {
setActive(false);
}
}, [location, to]);
return (
// eslint-disable-next-line react/button-has-type
<button
className={`tdp-navbar__item ${active ? "active" : ""}`}
onClick={(): void => {
history.push(to);
}}
>
{children}
</button>
);
};
export default CustomLink;
But it doesn't work as I want. So I opened Chrome Devtool and debugged, I realized that when I clicked on a CustomLink first, the querySelector() from Navbar would return null. But if I clicked on the same CustomLink multiple times, it would return properly, like the screenshot below:
Error from Chrome Console
How can I get the correct return from querySelector() from the first time? Thank you!
It's because handleActiveLine will trigger before setActive(true) of CustomLink.tsx
Add a callback in CustomLink.tsx:
const CustomLink: React.FC<Props> = ({ onActive }) => {
useEffect(() => {
if (active) {
onActive();
}
}, [active]);
}
In Navbar.tsx:
const Layout: React.FC = ({ children }) => {
function handleOnActive() {
// do your query selector here
}
// add onActive={handleOnActive} to each CustomLink
return <CustomLink onActive={handleOnActive} />
}
I've trying to use addEventListener in the Functional component in order to attach the onclick event to its parent.
So when its parent(the red box) is clicked the console.log("Test") is should prints.
At first I should get a ref of it to access the its parent.
So I tried:
https://codesandbox.io/s/red-framework-vv9j7
import React, { useRef, useEffect } from "react";
interface ContextMenuProps {
isVisible: boolean;
}
const ContextMenu: React.FC<ContextMenuProps> = props => {
const thisComponent = useRef<any>(this);
useEffect(() => {
if (thisComponent && thisComponent.current) {
thisComponent.current.addEventListener("click", test);
}
}, []);
const test = () => {
console.log("Test");
};
return <></>;
};
export default ContextMenu;
////////////////////////////////////
function App() {
return (
<div
className="App"
style={{ width: "200px", height: "200px", backgroundColor: "red" }}
>
<Test />
</div>
);
}
But, thisComponent is undefined.