I am trying to render Leaflet maps in Next.js using Typescript. I read that ssr needed to be disabled to avoid the 'window not defined' problem, but when trying this to generate the map:
import React from "react";
import { MapContainer, TileLayer } from "react-leaflet";
export const Leaflet: React.FC = () => {
return (
<MapContainer center={{ lat: 48.71291, lng: 44.52693 }} zoom={13}>
<TileLayer
attribution='© <a href="http://osm.org/copyright%22%3EOpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
</MapContainer>
);
};
and this to render it:
const Home: NextPage = () => {
const MapWithNoSSR = dynamic(() => import("../components/Leaflet"), {
ssr: false,
});
return (
<>
<MapWithNoSSR/>
</>
);
};
export default Home
TypesSript gives me this error:
Argument of type '() => Promise<typeof
import("/src/components/Leaflet")>' is not assignable to parameter of
type 'DynamicOptions<{}> | Loader<{}>'. Type '() => Promise<typeof
import("/src/components/Leaflet")>' is not assignable to type '() =>
LoaderComponent<{}>'.
And the browser gives this error:
Error: Element type is invalid. Received a promise that resolves to:
[object Module]. Lazy element type must resolve to a class or
function.
Has anyone here experienced something similar and have some advice regarding how to solve it?
You are getting those errors because your Leaflet component is a named module, and you are trying to access it as if it was a default one. Change your code to as the doc says:
import { NextPage } from "next";
import dynamic from "next/dynamic";
// ℹ️: this then is needed because the component is not exported with the default keyword
const MapWithNoSSR = dynamic(
() => import("../components/Leaflet").then((module) => module.Leaflet),
{
ssr: false,
}
);
const Home: NextPage = () => {
return (
<>
<MapWithNoSSR />
</>
);
};
export default Home;
Also, notice I pushed the dynamic import outside of Home.
Related
I'm currently making a simple web frontend with react using react-autosuggest to search a specified user from a list. I want to try and use the Autosuggest to give suggestion when the user's type in the query in the search field; the suggestion will be based on username of github profiles taken from github user API.
What I want to do is to separate the AutoSuggest.jsx and then import it into Main.jsx then render the Main.jsx in App.js, however it keeps giving me 'TypeError: _ref2 is undefined' and always refer to my onChange function of AutoSuggest.jsx as the problem.
Below is my App.js code:
import './App.css';
import 'bootstrap/dist/css/bootstrap.min.css';
import Header from './views/header/Header';
import Main from './views/main/Main';
import Footer from './views/footer/Footer';
const App = () => {
return (
<>
<Header/>
<Main/> <- the autosuggest is imported in here
<Footer/>
</>
);
}
export default App;
Below is my Main.jsx code:
import React, { useState } from 'react';
import Container from 'react-bootstrap/Container';
import Row from 'react-bootstrap/Row';
import axios from 'axios';
import { useEffect } from 'react';
import AutoSuggest from '../../components/AutoSuggest';
const Main = () => {
const [userList, setUserList] = useState([]);
useEffect(() => {
axios.get('https://api.github.com/users?per_page=100')
.then((res) => setUserList(res.data))
.catch((err) => console.log(err));
}, [])
return (
<Container>
<br/>
<Row>
<AutoSuggest userList={userList} placeHolderText={'wow'} />
</Row>
</Container>
);
}
export default Main;
Below is my AutoSuggest.jsx code:
import React, { useState } from "react";
import Autosuggest from 'react-autosuggest';
function escapeRegexCharacters(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
function getSuggestions(value, userList) {
const escapedValue = escapeRegexCharacters(value.trim());
if (escapedValue === '') {
return [];
}
const regex = new RegExp('^' + escapedValue, 'i');
return userList.filter(user => regex.test(user.login));
}
function getSuggestionValue(suggestion) {
return suggestion.name;
}
function renderSuggestion(suggestion) {
return (
<span>{suggestion.name}</span>
);
}
const AutoSuggest = ({userList, placeHolderText}) => {
const [value, setValue] = useState('');
const [suggestions, setSuggestions] = useState([]);
const onChange = (event, { newValue, method }) => { <- error from console always refer here, I'm not quite sure how to handle it..
setValue(newValue);
};
const onSuggestionsFetchRequested = ({ value }) => {
setValue(getSuggestions(value, userList))
};
const onSuggestionsClearRequested = () => {
setSuggestions([]);
};
const inputProps = {
placeholder: placeHolderText,
value,
onChange: () => onChange()
};
return (
<Autosuggest
suggestions={suggestions}
onSuggestionsFetchRequested={() => onSuggestionsFetchRequested()}
onSuggestionsClearRequested={() => onSuggestionsClearRequested()}
getSuggestionValue={() => getSuggestionValue()}
renderSuggestion={() => renderSuggestion()}
inputProps={inputProps} />
);
}
export default AutoSuggest;
The error on browser (Firefox) console:
I have no idea what does the error mean or how it happened and therefore unable to do any workaround.. I also want to ask if what I do here is already considered a good practice or not and maybe some inputs on what I can improve as well to make my code cleaner and web faster. Any input is highly appreciated, thank you in advance!
you have to write it like this... do not use the arrow function in inputProps
onChange: onChange
I am new to TypeScript and am trying to render a page by getting data from getStaticProps using the following code:
import React, {FormEvent, useState} from "react";
import { InferGetStaticPropsType } from "next";
import AddPost from '../components/AddPost';
import Post from "../components/Post";
import { IPost } from "../types";
const API_URL: string = 'https://jsonplaceholder.typicode.com/posts'
export default function IndexPage ( {
posts
}) : InferGetStaticPropsType<typeof getStaticProps> {
const [postList, setpostList] = useState(posts);
if(!postList) {
return <h1>Loadin....</h1>
}
return (
<div>
<h1>My posts</h1>
{postList.map((post : IPost) => {
<div>
<h1>post{post.id}</h1>
<Post key={post.id} post={post}
</div>
})}
</div>
)
}
export async function getStaticProps() {
const res = await fetch(API_URL)
const posts : IPost[] = await res.json()
return {
props: {
posts
}
}
}
My post type is defined as:
export interface IPost {
id: number
title: string
body: string
}
The code executes fine but I get a ts error saying when trying to return the jsx to render on the screen:
Property 'posts' is missing in type 'ReactElement<any, any>' but required in type '{ posts: IPost[]; }'.
Could You please help me with what's wrong here?
You seem to be typing incorrectly. I would suggest you to type IndexPage with FC from React as the page is a React Functional Component, like this:
import { FC } from "react";
const IndexPage: FC<{ posts: IPost[] | null }> = ({ posts }) => {
return <div></div>;
};
export default IndexPage;
Also your map part is not correct as you are not returning anything and also not adding the key:
{postList.map((post: IPost) => {
return (
<div key={post.id}>
<h1>post{post.id}</h1>
<Post key={post.id} post={post}
</div>
);
})}
I am trying to implement a searchbox feature in my react app. But getting this error "Attempted import error: 'MapControl' is not exported from 'react-leaflet'" in the new version of react-leaflet
import { MapContainer, TileLayer, Polygon, Marker, Popup } from 'react-leaflet';
import "./index.css";
// Cordinates of Marcillac
const center = [45.269169177925754, -0.5231516014256281]
const purpleOptions = { color: 'white' }
class MapWrapper extends React.Component {
render() {
return (
<div id="mapid">
<MapContainer center={center} zoom={13} scrollWheelZoom={true}>
<TileLayer
attribution='© OpenStreetMap © CartoDB'
url='https://cartodb-basemaps-{s}.global.ssl.fastly.net/dark_all/{z}/{x}/{y}.png'
/>
</MapContainer>
</div>
)
}
}
export default MapWrapper;
The implementation given here https://stackoverflow.com/questions/48290555/react-leaflet-search-box-implementation doesnt work as MapControl is depricted.
Tried 2nd solution as well.
import { Map, useLeaflet } from 'react-leaflet'
import { OpenStreetMapProvider, GeoSearchControl } from 'leaflet-geosearch'
// make new leaflet element
const Search = (props) => {
const { map } = useLeaflet() // access to leaflet map
const { provider } = props
useEffect(() => {
const searchControl = new GeoSearchControl({
provider,
})
map.addControl(searchControl) // this is how you add a control in vanilla leaflet
return () => map.removeControl(searchControl)
}, [props])
return null // don't want anything to show up from this comp
}
export default function Map() {
return (
<Map {...otherProps}>
{...otherChildren}
<Search provider={new OpenStreetMapProvider()} />
</Map>
)
}
Here I get map.addControl is not defined
Your approach is correct. You have just confused react-leaflet versions.
The way you are doing it would be correct in react-leaflet version 2.x
For react-leaflet v.3.x your custom comp should look like this:
function LeafletgeoSearch() {
const map = useMap(); //here use useMap hook
useEffect(() => {
const provider = new OpenStreetMapProvider();
const searchControl = new GeoSearchControl({
provider,
marker: {
icon
}
});
map.addControl(searchControl);
return () => map.removeControl(searchControl)
}, []);
return null;
}
You can take the map reference from useMap hook instead of useLeaflet.
Demo
I'm trying to test a connected TSX component. I have tested connected components before and I exactly know how to implement it, but seems like there is some issue in the way that jest and typescript interact.
What I have tried ?
I have exported an unconnected component for testing purposes
I have created a mock store and wrapper the component around a provider in the test file
I have modified jest.config.js as suggest by the error
I keep getting the same error!
Cannot find module 'react' from 'Provider.js'
However, Jest was able to find:
'components/Provider.js'
You might want to include a file extension in your import, or update your 'moduleFileExtensions', which is currently ['web.js', 'js', 'web.ts', 'ts', 'web.tsx', 'tsx', 'json', 'web.jsx', 'jsx', 'node'].
See https://jestjs.io/docs/en/configuration#modulefileextensions-array-string
However, Jest was able to find:
'./App.test.tsx'
'./App.tsx'
You might want to include a file extension in your import, or update your 'moduleFileExtensions', which is currently ['web.js', 'js', 'web.ts', 'ts', 'web.tsx', 'tsx', 'json', 'web.jsx', 'jsx', 'node'].
See https://jestjs.io/docs/en/configuration#modulefileextensions-array-string
at Resolver.resolveModule (node_modules/jest-resolve/build/index.js:259:17)
at Object.<anonymous> (../node_modules/react-redux/lib/components/Provider.js:10:38)
My component is as below (App.tsx):
import React from "react";
import { connect } from "react-redux";
import { Album, Photo, fetchAlbums, fetchPhotos } from "../actions";
import { StoreState } from "../reducers";
// *Notice: in this file we have used React.UseEffect and React.UseState instead of importing
// hooks directly from React. That's for the reasons of testing and how Enzyme has not yet adopted
// very well with hooks.
// the type of your action creators has been intentionally set to "any", as typescript does not play well with redux-thunk
interface AppProps {
albums?: Album[];
photos?: Photo[];
fetchAlbums?(): any;
fetchPhotos?(id: number): any;
}
export const _App = ({
albums,
photos,
fetchAlbums,
fetchPhotos
}: AppProps) => {
// setting the initial state of the loader and thmbnail
const [fetching, setFetching] = React.useState(false);
const [image, setImage] = React.useState();
// setting the state back to false once our data updates
React.useEffect(() => {
setFetching(false);
}, [albums, photos]);
// click evnet handler
const ClickHandler = (): void => {
fetchAlbums();
setFetching(true);
};
// album entry event handler
const AlbumClickHandler = (id: number): void => {
fetchPhotos(id);
};
const display = (id: number): JSX.Element[] => {
const relevantThumbs = photos.filter(photo => photo.albumId === id);
return relevantThumbs.map((thumb, idx) => {
return (
<img
onClick={() => setImage(thumb.id)}
key={idx}
alt={thumb.title}
src={image === thumb.id ? thumb.url : thumb.thumbnailUrl}
></img>
);
});
};
// helper function to render jsx elements
const renderList = (): JSX.Element[] =>
albums.map(album => (
<div className="albums" key={album.id}>
<h2 onClick={() => AlbumClickHandler(album.id)}>{album.title}</h2>
{display(album.id).length !== 0 ? (
<div className="albums__thumbnails">{display(album.id)}</div>
) : null}
</div>
));
return (
<section className="container">
<button className="container__button" onClick={() => ClickHandler()}>
Fetch Albums
</button>
{/* conditionally rendering the loader */}
{fetching ? "loading" : null}
{renderList()}
</section>
);
};
const mapStateToProps = ({
albums,
photos
}: StoreState): { albums: Album[]; photos: Photo[] } => {
return { albums, photos };
};
export default connect(mapStateToProps, { fetchAlbums, fetchPhotos })(_App);
and here is my test file (App.test.tsx):
import React from "react";
import Enzyme, { mount } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
import { findByClass } from "../test/Utils";
import App from "./App";
Enzyme.configure({ adapter: new Adapter() });
// setting our initial mount, we use mount here bcause of the hooks
const setup = () => mount(<App />);
describe("app", () => {
it("renders succesfully", () => {
// Arrange
const wrapper = setup();
const component = findByClass(wrapper, "container");
// Assert & Act
expect(component.length).toBe(1);
});
});
What am I missing ?
I am trying to convert a video that I have successfully loaded up to an Electron with React project. I did not have a problem adding the videos, but when I try to convert the video to a different file type I get the error below:
Uncaught Exception: TypeError: Cannot read property 'path' of
undefined
at EventEmitter.ipcMain.on (/Users/danale/Projects/ElectronCode/boilerplates/convert/index.js:37:32)
at emitTwo (events.js:106:13)
at EventEmitter.emit (events.js:191:7)
at WebContents. (/Users/danale/Projects/ElectronCode/boilerplates/convert/node_modules/electron/dist/Electron.app/Contents/Resources/electron.asar/browser/api/web-contents.js:247:37)
at emitTwo (events.js:106:13)
at WebContents.emit (events.js:191:7)
Its referencing this code below:
ipcMain.on("conversion:start", (event, videos) => {
const video = videos[0];
const outputDirectory = video.path.split(video.name)[0];
const outputName = video.name.split(".")[0];
const outputPath = `${outputDirectory}${outputName}.${video.format}`;
console.log(outputPath);
// ffmpeg(video.path).output();
});
but I do not see anything wrong with the code. Why is videos undefined now? I have been able to add them successfully.
Here is my action creator:
export const convertVideos = videos => (dispatch, getState) => {
ipcRenderer.send("conversion:start", videos);
};
This is my reducer:
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case VIDEO_COMPLETE:
return { ...state, [action.payload.path]: { ...action.payload, complete: true } };
case VIDEO_PROGRESS:
return { ...state, [action.payload.path]: action.payload };
case ADD_VIDEOS:
return { ...state, ..._.mapKeys(action.payload, 'path')}
case ADD_VIDEO:
return { ...state, [action.payload.path]: action.payload };
case REMOVE_VIDEO:
return _.omit(state, action.payload.path);
case REMOVE_ALL_VIDEOS:
return INITIAL_STATE
default:
return state;
}
}
convertVideos is being called from src/components/ConvertPanel.js:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { withRouter } from 'react-router'
import * as actions from '../actions';
class ConvertPanel extends Component {
onCancelPressed = () => {
this.props.removeAllVideos();
this.props.history.push('/')
}
render() {
return (
<div className="convert-panel">
<button className="btn red" onClick={this.onCancelPressed}>
Cancel
</button>
<button className="btn" onClick={this.props.convertVideos}>
Convert!
</button>
</div>
);
};
}
export default withRouter(
connect(null, actions)(ConvertPanel)
);
So when DanStarns asked for me to show where convertVideos is being called and I posted the ConvertPanel.js file, I felt something was amiss there, this did not seem right:
export default withRouter(
connect(null, actions)(ConvertPanel)
);
No need for a mapStateToProps there? The videos object was defined when adding videos but it was not being set right by the Redux back-end when it came time to convert the file type of said object.
So for the convert button I decided to use an arrow function and then passed convertVideos the videos prop. That in itself was not enough and I believed I also needed a mapStateToProps and after lots of painful wrangling, this is what I came up with that worked:
render() {
return (
<div className="convert-panel">
<button className="btn red" onClick={this.onCancelPressed}>
Cancel
</button>
<button
className="btn"
onClick={() => this.props.convertVideos(this.props.videos)}
>
Convert!
</button>
</div>
);
}
}
const mapStateToProps = state => {
return { videos: _.at(state.videos, _.keys(state.videos)) };
};
export default withRouter(
connect(
mapStateToProps,
actions
)(ConvertPanel)
);
For the above to work I had to import lodash library and I do not like the way that mapStateToProps looks, if anyone has a more elegant version, I would be willing to adopt it.