how to add ref from forwardref component without rendering? - reactjs

I'm passing a dom node to a mapbox popup.
I've a component P.js which consists of forward ref as
import React from "react";
const P = React.forwardRef((props, ref) => {
return <p {...props} ref={ref}></p>
})
export default P;
I'm importing it in App.js as
import P from './P';
......
const pRef = useRef(null)
console.log(pRef)
const p = <P ref={pRef}/>
console.log(pREf)
const App = () => {
useEffect(() => {
..... //codes
marker.setPopup(new mapboxgl.Popup().setDOMContent(pRef.current))
})
}
How can i pass that ref rendering on another component and running in another?
In my case i tried to make another const to add value
const p = <P ref={pRef}/>
I think, this is not way to passing ref from another component, so it is not rendered.
Is there any methods in Marker component , that i can pass ref if instead to load dom content .
Thank you for helping.

I highly suggest using this react wrapper around the mapbox-gl named react-mapbox-gl
You can do the method mentioned in this tweet and put some style in your P tag to make it invisible by the way
<P class="hide">Hello world</P>
.hide { display: none } /* or any other that fits for you e.g. visibility hidden */
You can also use renderToString to convert your component to HTML string on the fly.
import * as React from 'react';
import { renderToString } from 'react-dom/server';
import mapboxgl from 'mapbox-gl';
const P = React.forwardRef((props, ref) => {
return <p {...props} ref={ref}></p>;
});
export default function App() {
React.useEffect(() => {
marker.setPopup(
new mapboxgl.Popup().setDOMContent(
// render your Paragraph component exactly to html
renderToString(<P>Hello world</P>),
),
);
});
return <div id="map" />;
}
Here is codesandbox for showing it works.
You can also pass the exact same string representation of your paragraph component suggested by a package doc
const popup = new mapboxgl.Popup()
.setHTML('<h1>Hello World!</h1>') // here is the string representation
.addTo(map);
If you want to work with the js package duo to legacy issue, there is also a package that helps you to add a custom tiny react wrapper around the package to work with called react-aptor

Related

Change function components method using forwardRef

I am trying to do:
Change child state as in this tutorial: Change child state from parent
Tutorial uses class component, I've tried to use function components.It gives an error like "Function components don't have instances so they can't have refs"
So I used forwardRef (for the first time) according to a stackoverflow answer here
Code:
import React,{useRef,forwardRef, useEffect} from "react";
const Component = (props) => {
function sayHello(){
console.log("hello")
}
return <h1>{props.children}</h1>
}
const ForwardComponent = forwardRef((props,ref)=> (<Component ref={ref}> {props.children} </Component>))
export default function App() {
const hello = useRef()
useEffect(()=>{
hello.current.sayHello()
})
return (
<div>
<ForwardComponent ref={hello}>Hello</ForwardComponent>
</div>
);
}
note: I could use context api or redux here, but I believe it should be simpler in my use case. Also I get to learn something new

How to position the react flatpickr inside a container instead of body

I am having a use case where i have to position the react flatpickr on the left or right side of the input element instead of above or below.
When using the flatpickr i noticed that the flatpickr gets added to the body of dom. Instead i want to add it to container div. so I am passing the appendTo option with container HTMLElement as value. But its not working as expected and still appending to the body tag.*
here is the code snippet i used.
import React, {useState, useRef} from 'react';
import Flatpickr from 'react-flatpickr';
const DateFilter = props => {
const {setDateTime} = props;
const containerRef = useRef() ;
const container = containerRef.current;
return (
<div ref={containerRef}>
<Flatpickr
appendTo={container}
onChange={date => setDateTime(date)}
enableTime={false}
dateFormat={"Y-m-d"}
/>
</div>
)
}
export default DateFilter;
The calendar still gets added to the body.
Contianer DOM post page load
Clearly i am doing something wrong. Any help is much appreciated. Thanks in Advance.
I found why it was not appending by inspecting the
1. React flatpicker Index.js
2. Core flatpicker.js files
The options (appendTo) wasn't received in react-flatpickr because it should be passed inside an object and not as a direct value.
import React, {useState, useRef} from 'react';
import Flatpickr from 'react-flatpickr';
const DateFilter = props => {
const {setDateTime} = props;
const containerRef = useRef() ;
const container = containerRef.current;
const getOptions = () => {
return {
appendTo : container,
enableTime : false,
dateFormat : "Y-m-d"
}
}
return (
<div ref={containerRef}>
<Flatpickr
onChange={date => setDateTime(date)}
options={getOptions()}
/>
</div>
)
}
export default DateFilter;
And also make sure you pass the correct HTMLElement on the first render. Else if you pass undefined it will still load it in Body.

Giving child functional component access to Ref defined in parent functional component. useRef()

I need to access "editor" which is a ref defined in my parent component (Editor.js). My child component (Toolbar.js) has function imgChangeHandler which requires editor ref. What is the best way to do this ?
Editor.js (parent):
import React, {useRef, useState} from "react"
const editor = useRef(null);
function Editor() {
...
return (
<>
<div className="center">
<div
className="editor"
style={editorStyle}
ref={editor}
contentEditable={true}
suppressContentEditableWarning={true}
>
<h1>introText</h1>
<p>subText</p>
</div>
</div>
</>
)
}
export default Editor
Toolbar.js (child):
import React, {useState} from "react"
function Toolbar() {
const dispatch = useDispatch();
const inputRef = useRef(null);
const [selectedFile, setSelectedFile] = useState(null);
const imgChangeHandler = e => {
e.preventDefault();
setSelectedFile(e.target.files[0]);
let reader = new FileReader();
let dataURI = reader.result;
const img = React.createElement("img",{src: dataURI});
editor.current.push(img); // need access to editor ref here
if(selectedFile) {
console.log("s");
reader.readAsDataURL(selectedFile)
}
};
...
}
export default Toolbar
In React (almost)everything is done using callbacks. Toolbar should say (in its interface) I need to have a onImgAdded callback so it makes it explicit and it is thought as best practices. (specially if you are using TypeScript to express what the inputs are and if there are any return types expected)
But if you still insist on using ref and passing the ref down for whatever reason it is; in React you can pass props just like you pass arguments to function. So you can do <Toolbar editor={this.editorRef} /> or <Toolbar editor={this.editorRef.current} />

How to use wavesurfer.js in React?

i'm trying to render audio files using wavesurfer.js in my react app. I'm a noob and I'm wondering why I'm getting a "Container element not found" message all the time.
This is what i need to know in a nutshell.
THank you in advance!
import React from "react";
import WaveSurfer from "wavesurfer.js";
export default function Chat(){
const wavesurfer = WaveSurfer.create({
container: "#waveform",
});
render(
<>
<div id="waveform">
</div>
</>
)
}
You are currently calling that in the render function, which would mean it is executed everytime the component refreshes. Instead you can add it in a useEffect hook so that the waveform function is called only once and the element you are binding it to is present.
Also, you can use a ref instead of the ID, which is what React uses to refer to DOM elements.
import React, { useRef, useEffect } from "react";
import WaveSurfer from "wavesurfer.js";
export default function Chat(){
const waveformRef = useRef();
useEffect(() => {
if(waveformRef.current) {
const wavesurfer = WaveSurfer.create({
container: waveformRef.current,
});
}
}, []);
return(
<>
<div ref={waveformRef}>
</div>
</>
)
}
useEffect
Refs

Testing react components that have JSS injected styles with enzyme

I'm having a react component. Let's say Todo
import React, { Component } from 'react';
import injectSheet from 'react-jss';
class Todo extends Component {
// methods that incl. state manipulation
render() {
const { classes } = this.props;
return (
<div className={classes.container}>
<WhateverElse />
</div>
);
}
}
export default injectSheet(Todo);
I want to test it with enzyme. And there are two problems with it.
1. Access to the state
(and other component specific features)
When I shallow or mount that composer in the suite I can't get access to its state of course because it's not my component anymore but something new around it.
E.g. this code will give me an error:
it('should have state updated on handleAddTodo', () => {
const todo = shallow(<Todo />);
const length = todo.state('todos').length;
});
It says of course TypeError: Cannot read property 'length' of undefined because the state is not what I expect but this: { theme: {}, dynamicSheet: undefined }
This won't also give me access to props, refs etc.
2. Problems with theme provider
To provide some default colouring to the project like this:
import React, { Component } from 'react';
import Colors from './whatever/Colors';
class App extends Component {
render() {
return (
<ThemeProvider theme={Colors}>
<WhateverInside />
</ThemeProvider>
);
}
}
And of course when running tests it gives me an error [undefined] Please use ThemeProvider to be able to use WithTheme.
So my question is the following. Is there a way to solve this problem in “one single place”. How can I make enzyme agnostic of what is my component wrapped with?
If not, then how do I solve the problem if passing the ThemeProvider features down to the component that I'm testing?
And how can I access the state, ref, props and other things of the wrapped component?
Thank you!
here's what I'd do to test the component,
import React, { Component } from 'react';
import injectSheet from 'react-jss';
const styles = {};
class Todo extends Component {
// methods that incl. state manipulation
render() {
const { classes } = this.props;
return (
<div className={classes.container}>
<WhateverElse />
</div>
);
}
}
export { styles, Todo as TodoCmp }
export default injectSheet(styles)(Todo);
and in the test file, I'd add the following:
import { theme } from 'your-theme-source';
const mockReducer = (prev, curr) => Object.assign({}, prev, { [curr]: curr });
const coerceStyles = styles =>
typeof styles === 'function' ? styles(theme) : styles;
const mockClasses = styles =>
Object.keys(coerceStyles(styles)).reduce(mockReducer, {});
import {TodoCmp, styles} from 'your-js-file';
// then test as you'd normally.
it('should blah blah', () => {
const classes = mockClasses(styles);
const todo = shallow(<Todo classes={classes} />);
const length = todo.state('todos').length;
})
Please read more about it in the nordnet-ui-kit library specifically in the test directory. Here's a quick example
It is not related to JSS specifically. Any HOC wraps your component. Ideally you don't test any internals of a component directly.
Components public api is props, use them to render your component with a specific state and verify the rendered output with shallow renderer.
For some edge cases if first and preferred way is impossible, you can access the inner component directly and access whatever you need directly. You will have to mock the props the HOC would pass otherwise for you.
const StyledComponent = injectSheet(styles)(InnerComponent)
console.log(StyledComponent.InnerComponent)
If your component relies on theming, you have to provide a theme provider, always.

Resources