How to pass values to components using dynamic import of NextJS - reactjs

I have a problem with dynamic import in Next.js. It would be great if someone could give me an answer or some advice to do this in a different way.
The thing is that I have a component that renders a leaflet-map, this map have a pointer so I could click the map and have longitude and latitude, this is the code:
import React from 'react'
import {MapContainer, Marker,TileLayer, useMapEvents } from 'react-leaflet'
import { iconMap } from '../../assets/customeIcon/iconMap';
import 'leaflet/dist/leaflet.css'
const MapView =({selectedPosition,setSelectedPosition}) =>{
const [initialPosition, setInitialPosition] = React.useState([38,-101]);
const Markers = () => {
const map = useMapEvents({
click(e) {
setSelectedPosition([
e.latlng.lat,
e.latlng.lng
]);
},
})
return (
selectedPosition ?
<Marker
key={selectedPosition[0]}
position={selectedPosition}
interactive={false}
icon={iconMap}
/>
: null
)
}
return <MapContainer center={selectedPosition || initialPosition} zoom={5} style={{height:"300px",width:"540px"}}>
<TileLayer url='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
></TileLayer>
<Markers />
</MapContainer>
}
export default MapView
As you can see this component has the parameters selectedPosition and setSelectedPosition. This is where I save the clicked position and return it to the parent component.
For example, the parent component used to call the map component this way:
const Parent = () => {
const [selectedPosition, setSelectedPosition] = React.useState(null);
...
<MapView selectedPosition={selectedPosition} setSelectedPosition={setSelectedPosition} > </MapView>
}
This used to work great, but now because of a problem with react-leaflet I have to call the map in a different way, using Next.js dynamic import, I had to create a new component that is like this:
import dynamic from 'next/dynamic';
function MapCaller() {
const Map = React.useMemo(() => dynamic(
() => import('./MapView'),
{ ssr: false, }
), [])
return <Map />
}
export default MapCaller
So now the parent component has to call the MapCaller instead of directly calling the MapView:
const Parent = () => {
const [selectedPosition, setSelectedPosition] = React.useState(null);
...
<MapCaller > </MapCaller>
}
With this I resolved the problem of react-leaflet, but I have other problem now, remember that I used to pass the position values to the map component, how could I do to pass that values with this new approach? How the parent component could communicate with the map to get the selected position? Is there another approach to do this?
Thanks!

Your <MapCaller> component is simply wrapping the existing <MapView>, so you could simply pass the props down to it.
const Map = dynamic(() => import('./MapView'), { ssr: false })
function MapCaller({ selectedPosition, setSelectedPosition }) {
return <Map selectedPosition={selectedPosition} setSelectedPosition={setSelectedPosition} />
}
Then use it in the parent component:
const Parent = () => {
const [selectedPosition, setSelectedPosition] = React.useState(null);
//...
<MapCaller selectedPosition={selectedPosition} setSelectedPosition={setSelectedPosition} />
}

Related

How to refer to other component in React?

I am trying to make a simple confirmation overlay. I have an overlay component that will overlay any parent div. I want to be able to call the confirm function from any place in my app. The code below works, but I feel like I am going against the React philosophy. My main questions are:
What is the alternative of using getElementById('main')? At the moment I want to overlay the same div, which I have given the 'main' id. This div is part of another React component. Can I use a ref in some way? Can I export this ref from the component I want to overlay and pass it to the confirm function?
My main App Component also uses the createRoot function. Is it wrong to use it again?
Am I using promises in the right way?
How to optimize below code?
import React from 'react'
import { createRoot } from 'react-dom/client'
import { Overlay } from './Overlay'
interface IConfirm {
text: string,
destroy: () => void,
resolve: (value: boolean | PromiseLike<boolean>) => void
}
export const Confirm = (props: IConfirm) => {
return (
<Overlay
text={props.text}
visible
>
<button onClick={() => {
props.resolve(true)
props.destroy()
}}>Yes</button>
<button onClick={() => {
props.resolve(false)
props.destroy()
}}>No</button>
</Overlay>
)
}
export default function confirm (text: string) {
const container = document.createElement('div')
const main = document.getElementById('main')
main!.appendChild(container)
const root = createRoot(container)
const destroy = () => {
root.unmount()
main?.removeChild(container)
}
const promise = new Promise<boolean>((resolve) => {
root.render(<Confirm text={text} destroy={destroy} resolve={resolve} />)
})
return promise
}
Thanks a lot.

react-pixi is giving a error with useTick

Im having some trouble with the use of useTick in react with react-pixi
getting a error:
No Context found with PIXI.Application. Make sure to wrap component
with AppProvider
So i tried to wrap the AppProvider, but that did´nt do it.
So here is my code for the specific component that´s import react-pixi:
import React, {useState,useEffect} from 'react';
import { render } from 'react-dom'
import { ParticleContainer, useApp, useTick, Stage, Sprite, Container } from '#inlet/react-pixi'
import { Application } from 'pixi.js'
let scale = { x: 0.5, y: 0.5 };
const TryPixi = () => {
const app = useApp();
const [itemRotation, setItemRotation] = useState(0);
useTick(delta => {
setItemRotation(itemRotation + 1)
})
return (
<Stage width={139} height={140} options={{ transparent: true }}>
<Container
x={70}
y={70}
>
<Sprite
image="../../assets/ninja.png"
anchor={[0.5, 0.5]}
scale={scale}
rotation={0.5}
/>
</Container>
</Stage>
);
};
export default TryPixi;
And call the component like this from another component:
<AppProvider><TryPixi /></AppProvider>
I have tried with and without the AppProvider, and yes the AppProvideris imported :-) Hope anyone can help.
You can only use useTick on component that returns only the animated object.
const Ninja = () => {
const [itemRotation, setItemRotation] = useState(0);
useTick(delta => {
setItemRotation(itemRotation + 1)
})
return (
<Sprite
image="../../assets/ninja.png"
anchor={[0.5, 0.5]}
scale={scale}
rotation={0.5}
/>
);
};
const TryPixi = () => {
return (
<Stage width={139} height={140} options={{ transparent: true }}>
<Container
x={70}
y={70}
>
<Ninja />
</Container>
</Stage>
);
};
As far as I could understand from Stage component source code, it creates its own application in componentDidMount here [lines 115 - 121]:
this.app = new Application({
width,
height,
view: this._canvas,
...options,
autoDensity: options?.autoDensity !== false,
})
It then provides this app to its children via wrapping the children in AppProvider component [lines 216 - 217]:
const { children } = this.props
return <AppProvider value={this.app}>{children}</AppProvider>
This means you can use the hooks that depend on this context only inside the children components of Stage, not in the Stage component itself; because useApp hook uses this context, and useTick hook uses the useApp hook.
So, your wrapper component that renders TryPixi should be like this:
<Stage><TryPixi /></Stage>
And then you call the hooks from inside TryPixi component.

React: A service returning a ui component (like toast)?

Requirement: Show toast on bottom-right corner of the screen on success/error/warning/info.
I can create a toast component and place it on any component where I want to show toasts, but this requires me to put Toast component on every component where I intend to show toasts. Alternatively I can place it on the root component and somehow manage show/hide (maintain state).
What I am wondering is having something similar to following
export class NotificationService {
public notify = ({message, notificationType, timeout=5, autoClose=true, icon=''}: Notification) => {
let show: boolean = true;
let onClose = () => {//do something};
if(autoClose) {
//set timeout
}
return show ? <Toast {...{message, notificationType, onClose, icon}} /> : </>;
}
}
And call this service where ever I need to show toasts.
Would this be the correct way to achieve the required functionality?
You can use AppContext to manage the state of your toast and a hook to trigger it whenever you want.
ToastContext:
import React, { createContext, useContext, useState } from 'react';
export const ToastContext = createContext();
export const useToastState = () => {
return useContext(ToastContext);
};
export default ({ children }) => {
const [toastState, setToastState] = useState(false);
const toastContext = { toastState, setToastState };
return <ToastContext.Provider value={toastContext}>{children}</ToastContext.Provider>;
};
App:
<ToastProvider>
<App/>
<Toast show={toastState}/>
</ToastProvider>
Then anywhere within your app you can do:
import {useToastState} from 'toastContext'
const {toastState, setToastState} = useToastState();
setToastState(!toastState);

Define a functional component inside storybook preview

I have a custom modal component as functional component and in typescript. This modal component exposes api's through context providers and to access them, I'm using useContext hook.
const { openModal, closeModal } = useContext(ModalContext);
Example code on how I use this api's:
const TestComponent = () => {
const { openModal, closeModal } = useContext(ModalContext);
const modalProps = {}; //define some props
const open = () => {
openModal({...modalProps});
}
return (
<div>
<Button onClick={open}>Open Modal</Button>
</div>
)
}
And I wrap the component inside my ModalManager
<ModalManager>
<TestComponent />
</ModalManager>
This example works absolutely fine in my Modal.stories.tsx
Problem:
But this doesn't work inside my Modal.mdx. It says I cannot access react hooks outside functional component. So, I need to define a TestComponent like component to access my modal api's from context. How to define it and where to define it so that below code for preview works?
import {
Props, Preview, Meta
} from '#storybook/addon-docs/blocks';
<Meta title='Modal' />
<Preview
isExpanded
mdxSource={`
/* source of the component like in stories.tsx */
`}
>
<ModalManager><TestComponent /></ModalManager>
</Preview>
I'm not sure if this is a hack or the only way. I created the TestComponent in different tsx file and then imported it in mdx. It worked.
You may have a utility HOC to render it inside a MDX file as below
HOCComp.tsx in some Utils folder
import React, { FunctionComponent, PropsWithChildren } from 'react';
export interface HOCCompProps {
render(): React.ReactElement;
}
const HOCComp: FunctionComponent<HOCCompProps> = (props: PropsWithChildren<HOCCompProps>) => {
const { render } = props;
return render();
};
export default HOCComp;
Inside MDX File
import HOCComp from './HOC';
<HOCComp render={()=> {
function HOCImpl(){
const [count,setCount] = React.useState(180);
React.useEffect(() => {
const intId = setInterval(() => {
const newCount = count+1;
setCount(newCount);
},1000)
return () => {
clearInterval(intId);
}
})
return <Text>{count}</Text>
}
return <HOCImpl />
}}
/>

On click returns null instead of an object

It's really basic I guess. I'm trying to add onClick callback to my script & I believe I'm missing a value that would be responsible for finding the actual item.
Main script
import React from 'react';
import { CSVLink } from 'react-csv';
import { data } from 'constants/data';
import GetAppIcon from '#material-ui/icons/GetApp';
import PropTypes from 'prop-types';
const handleClick = (callback) => {
callback(callback);
};
const DownloadData = (props) => {
const { callback } = props;
return (
<>
<CSVLink
data={data}
onClick={() => handleClick(callback)}
>
<GetAppIcon />
</CSVLink>
</>
);
};
DownloadData.propTypes = {
callback: PropTypes.func.isRequired,
};
export default DownloadData;
Storybook code
import React from 'react';
import DownloadData from 'common/components/DownloadData';
import { data } from 'constants/data';
import { action } from '#storybook/addon-actions';
export default {
title: 'DownloadData',
component: DownloadData,
};
export const download = () => (
<DownloadData
data={data}
callback={action('icon-clicked')}
/>
);
So right now with this code on click in the storybook I'd get null and I'm looking for an object.
One of the potential issues I can see is that your handleClick function is stored as it is in-memory, when you import the component. That means you're keeping reference of something that doesn't exists and expects to use it when rendering the component with the callback prop.
Each instance of a component should have its own function. To fix it, move the function declaration inside the component. Like this:
const Foo = ({ callback }) => {
// handleClick needs to be inside here
const handleClick = callback => {
console.log("clicked");
callback(callback);
};
return <div onClick={() => handleClick(callback)}>Click me!</div>;
};
Check this example.
If this doesn't fix your problem, then there is something wrong with how you're implementing Storybook. Like a missing context.

Resources