Scroll to a component when a button is clicked in React JSX - reactjs

I've looked at way too many ways to scroll to a certain component when clicking a button, but it's always in a JS file. Can't seem to get it to work in my JSX files project.
I've tried several libraries and methods such as "scrollTo", "scrollIntoView", "scrollToComponent"..
They pretty much all try to reference a component, but I can't seem to get it to work.
In JS it usually works when they use the "useRef" from react but, again, can't seem to do it in JSX.
Does anyone have any idea why and how to do it?
Something like:
const MyComponent = () => {
const scrollSomewhere = () => {
// PSEUDO-CODE - something along these lines maybe?
// scrollToComponent("Somewhere");
}
return (
<>
<MyCustomButton onClick={scrollSomewhere}> Click me! </MyCustomButton >
(...)
<MyCustomComponent ref="Somewhere"> I'm somewhere </MyCustomComponent>
</>
}

The ref needs to hold a reference the DOM element to which you want to scroll. Here's an example:
.container {
font-family: sans-serif;
}
.target {
background-color: black;
color: white;
margin: 100rem 0;
padding: 1rem;
}
<script src="https://unpkg.com/react#17.0.2/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#17.0.2/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/#babel/standalone#7.16.3/babel.min.js"></script>
<div id="root"></div>
<script type="text/babel" data-type="module" data-presets="react">
const {useRef, useState} = React;
function Example () {
const elementRef = useRef(null);
const handleClick = () => {
if (elementRef.current) {
elementRef.current.scrollIntoView();
}
};
return (
<div className="container">
<div>The element you want to scroll to is below 👇</div>
<button onClick={handleClick}>Scroll to element</button>
<div className="target" ref={elementRef}>Here I am!</div>
</div>
);
}
ReactDOM.render(<Example />, document.getElementById('root'));
</script>

Related

React child component doesn't notice state change from async function inside of useEffect

I'm trying to pass a state that is generated inside of an async function that itself is inside of a useState. I learned that useRef might be the best way to go about this since it can reference a mutable state, and in the process learned about react-useStateRef, which finally solved another issue I had of state never updating inside my main component (would constantly get "too many renders" errors). So it essentially acts as useRef and useState in one.
But while my state does finally update, it still doesn't pass to my Canvas component. I'm trying to update the BG graphic of the canvas depending on the temperature I get from my dataset.
import { useEffect } from 'react';
import useState from 'react-usestateref';
import { getServerData } from './serviceData';
import "./App.css"
import Canvas from './components/Canvas';
function App() {
const [dataset, setDataset] = useState(null);
const [units, setUnits] = useState('metric');
// canvas background graphic stuff
var [currentBG, setCurrentBG, currentBGref] = useState(null);
useEffect(() => {
const fetchServerData = async () => {
const data = await getServerData(city, units);
setDataset(data);
function updateBG() {
const threshold = units === "metric" ? 20 : 60;
if (data.temp <= threshold) {
setCurrentBG('snow');
}
else {
setCurrentBG('sunny');
}
}
updateBG();
}
fetchServerData();
console.log(currentBG)
}, [city, units, currentBGref.current, currentFGref.current])
const isCelsius = currentUnit === "C";
button.innerText = isCelsius ? "°F" : "°C";
setUnits(isCelsius ? "metric" : "imperial");
};
return (
<div className="app">
{ dataset && ( <Canvas width={640} height={480} currentBG={currentBGref.current}></Canvas> )}
</div>
);
}
export default App;
I can only pass the initial value and it never updates past that, although the console.log inside of the useEffect shows that it definitely is updating. So why isn't it passing to my component?
useStateRef appears to be an anti-pattern. You decide what the new state is so in the off-chance you need another reference to it, you can always create one yourself. I would suggest minimizing properties on your canvas to prevent unnecessary re-renders.
function App({ width, height }) {
const canvas = React.useRef()
const [color, setColor] = React.useState("white")
// when color changes..
React.useEffect(() => {
if (canvas.current) {
const context = canvas.current.getContext('2d');
context.fillStyle = color
context.fillRect(0, 0, width, height)
}
}, [color, width, height])
return <div>
<canvas ref={canvas} width={width} height={height} />
<button onClick={_ => setColor("blue")} children="blue" />
<button onClick={_ => setColor("green")} children="green" />
<button onClick={_ => setColor("red")} children="red" />
</div>
}
ReactDOM.createRoot(document.querySelector("#app")).render(<App width={200} height={150} />)
canvas { display: block; border: 1px solid black; }
<script crossorigin src="https://unpkg.com/react#18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.development.js"></script>
<div id="app"></div>
Or maybe there's no reason to store the color as state. You could make your own setColor function and attach it as an event listener -
function App({ width, height }) {
const canvas = React.useRef()
const setColor = color => event => {
if (canvas.current) {
const context = canvas.current.getContext('2d');
context.fillStyle = color
context.fillRect(0, 0, width, height)
}
}
return <div>
<canvas ref={canvas} width={width} height={height} />
<button onClick={setColor("blue")} children="blue" />
<button onClick={setColor("green")} children="green" />
<button onClick={setColor("red")} children="red" />
</div>
}
ReactDOM.createRoot(document.querySelector("#app")).render(<App width={200} height={150} />)
canvas { display: block; border: 1px solid black; }
<script crossorigin src="https://unpkg.com/react#18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.development.js"></script>
<div id="app"></div>
I would also suggest you look at SVG. I find the API gels with the React pattern much better than the Canvas API. The capabilities of each differ, but it's worth considering if SVG handles your needs.

How to test interaction with Ant Design's Popover content?

I have a React Component that wraps an Ant Design Popover. This component gets a callback that is being called by user interaction (say click) in dynamically generated content. Something similar to this:
const { Popover, Button } = antd;
const PopoverExtended = ({ onWhatever, children }) => {
const handleClick = (event) => {
if (event.target.className === 'some-class') {
onWhatever(event.target.dataset.value);
}
};
const dynamic = () => '<span class="some-class" data-value="42">Click this text</span>';
const content = () => {
return (
<div>
<p>Some HTML</p>
<div dangerouslySetInnerHTML={{ __html: dynamic() }} onClick={handleClick}></div>
</div>
);
};
return (
<Popover content={content()} placement="right" trigger="click">
{children}
</Popover>
);
};
ReactDOM.render(
<PopoverExtended onWhatever={(x) => console.log(x)}>
<Button>Click me</Button>
</PopoverExtended>,
document.getElementById('root')
);
<link href="https://cdnjs.cloudflare.com/ajax/libs/antd/3.26.20/antd.css" rel="stylesheet"/>
<div id="root" style="margin: 2em 0 0 2em"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment-with-locales.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/antd/3.26.20/antd-with-locales.js" crossorigin="anonymous"></script>
Everything works as expected, but using Jest and Enzyme I'm trying to test that the onWhatever callback is being called, but so far I haven't been able to target the dynamic content neither as a ShallowWrapper nor ReactWrapper. I've tried:
describe(`<PopoverExtended /> interaction`, () => {
const mockChildren = <Button>Mock me</Button>;
const mockCallback = jest.fn();
const wrapper = mount(<PopoverExtended onWhatever={mockCallback}>{mockChildren}</PopoverExtended>);
// Try 1
const trigger = wrapper.find('.some-class[data-value="42"]'); // Nothing found.
// Try 2
const content = mount(<>{wrapper.find(Popover).prop('content')}</>);
console.log(content.html()); // Is apparently the correct Popover content HTML
const trigger = wrapper.find('.some-class[data-value="42"]'); // Nothing found.
// Try 3
const content = mount(<>{wrapper.find(Popover).prop('content')}</>);
const rendered = content.render();
const trigger = wrapper.find('.some-class[data-value="42"]'); // Node found, but
// it's a CheerioWrapper, so I cannot call trigger.simulate('click');
});
Any ideas on how to properly test that the callback is being called?
Enzyme does not see the dynamic content and hence there's no way for you to simulate click on the elements inside of dynamic content. You can verify this by doing console.log(wrapper.debug()) which will show you what Enzyme sees. After trying:
const mockCallback = jest.fn();
const wrapper = mount(<PopoverExtended onWhatever={mockCallback}>{<Button>Mock me</Button>}</PopoverExtended>);
const trigger = wrapper.find("button");
trigger.simulate("click");
Enzyme only goes as far as the hosting div:
...
<Content trigger={{...}} prefixCls="ant-popover" id={[undefined]} overlay={{...}}>
<div className="ant-popover-inner" id={[undefined]} role="tooltip">
<div>
<div className="ant-popover-inner-content">
<div>
<p>
Some HTML
</p>
<div dangerouslySetInnerHTML={{...}} onClick={[Function: handleClick]} />
</div>
</div>
</div>
</div>
</Content>
...
Now calling wrapper.html() actually returns the full DOM including the dynamic content, but that's pretty useless for our case as you've mentioned. On defense of Enzyme, the dynamic content uses html flavor instead of JSX (class instead of className) making it even more difficult to wrap.
With that out of the way, I don't see why you need to include the dynamic content in your test scenario. Simply simulate the click on the host div. In fact I'd argue that's the right way of doing it since you define the event handler on the host div and not inside the dynamic content:
it(`<PopoverExtended /> interaction`, () => {
const mockCallback = jest.fn();
const wrapper = mount(<PopoverExtended onWhatever={mockCallback}>{<Button>Mock me</Button>}</PopoverExtended>);
const mockEvent = {
type: "click",
target: {
dataset: { value: 42 },
className: "some-class"
}
};
const trigger = wrapper.find("button");
trigger.simulate("click");
const hostDiv = wrapper.find("div.trigger-wrapper");
hostDiv.simulate("click", mockEvent);
expect(mockCallback.mock.calls.length).toBe(1);
expect(mockCallback.mock.calls[0][0]).toBe(42);
});
Option 2
Interestingly the react testing library has no problem including your dynamic content so you may want to use it instead of Enzyme:
import React from "react";
import { render, fireEvent, screen } from '#testing-library/react'
import { Button } from "antd";
import PopoverExtended from "./PopOverExtended";
it(`<PopoverExtended /> interaction`, async () => {
const mockCallback = jest.fn();
render(<PopoverExtended onWhatever={mockCallback}>{<Button>Mock me</Button>}</PopoverExtended>);
fireEvent.click(screen.getByText('Mock me'))
fireEvent.click(screen.getByText('Click this text'))
expect(mockCallback.mock.calls.length).toBe(1);
expect(mockCallback.mock.calls[0][0]).toBe("42");
});
Argument for this is that the dataset is defined by the dynamic content and so you have to consider it in your test.

getting innerHTML from a div after changes through contentEditable in React

thanks for taking the time to look at this.
I am struggling to find out how to make this work specifically in react.
I have used contentEditable to get the div element to be editable and then i have used Refs to make the div reference its innerHTML. But the information does not seem to be put into the state of body.
The ultimate aim is to have it saved in a database, and then loaded to replace the div.
code:
import React, {useState, useRef} from "react";
import "./styles.css";
export default function App() {
let myRef= useRef()
const [body, setBody] = useState("");
let click = () => {
setBody(myRef.innerHTML)
}
return (
<div className="App">
<h1 ref={myRef}>Hello CodeSandbox</h1>
<div></div>
<h1 contentEditable={true}> rewrite me!</h1>
<button onClick={click}> CLICK!</button>
<h1>{body}</h1>
</div>
);
}
sandbox
https://codesandbox.io/s/wispy-glitter-nfym4?file=/src/App.js
Access the innerHTML using myRef.current.innerHTML.
From the docs
When a ref is passed to an element in render, a reference to the node becomes accessible at the current attribute of the ref.
<script src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/#babel/standalone/babel.min.js"></script>
<div id="root"></div>
<script type="text/babel">
function App() {
let myRef = React.useRef();
const [body, setBody] = React.useState("");
let click = () => {
setBody(myRef.current.innerHTML);
};
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<div></div>
{/* I think you misassigned your `myRef`, shouldn't it be on this h1? */}
{/* suppressContentEditableWarning=true, to suppress warning */}
<h1 ref={myRef} contentEditable={true} suppressContentEditableWarning={true}> rewrite me!</h1>
<button onClick={click}> CLICK!</button>
<h1>{body}</h1>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
</script>

Not Able to Toggle Class Using React Componnent

Can you please take a look at this demo and let me know why I am not able to toggle .green class for #root using onClick in react js?
function toggler(e){
var x = document.getElementById("root");
x.classList.toggle('green');
}
const Button = ({ styleClass, onClick, text }) => {
return (
<button
type="button"
onClick={e => onClick(e)}
className={`btn ${styleClass}`}
>
{text}
</button>
);
};
ReactDOM.render(
<div>
<Button styleClass="btn-primary" text='Primary Button' onClick={toggler} />
</div>
, window.root);
#root{
height:300px;
width:300px;
background:khaki;
}
.green{
background:green;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<div id="root"></div>
You should not touch the DOM directly when you're writing React components. React can help you manage your class name with state.
Do something like this:
import React, { Component } from 'react';
export default class Button extends Component {
constructor(props) {
super(props);
this.state = {
buttonStyleClass: 'bar'
}
}
toggler = (e) => {
this.setState({
buttonStyleClass: 'foo'
})
}
render() {
return (
<div
className={this.state.buttonStyleClass}
onClick={this.toggler}
>
Click me
</div>
);
}
}
The problem here is that id-selectors have higher priority over class-selectors in css. Since you have defined the base color with #root, you can't toggle it with just .green.
Many solutions here, but one of them could be #root.green, adding !important or selecting your root otherwise.
That being said, you should not mutate the DOM directly when using React. It voids one of its biggest advantages. See mxdi9i7's answer for more info.

can i use ternary operator in className of div?

can i do the following?
<div className={`${customerDetails.isCustomer} ? u-pb--sm : u-ph--sm u-pb--sm`}>
If not what is the best way to write this?
Thanks
That should work. Check customerDetails and try to console.log it.
const CustomerDetails = ({ customerDetails }) =>
<div className={customerDetails.isCustomer ? 'customer': 'notCustomer'}>
{`Customer: ${customerDetails.isCustomer}`}
</div>
class App extends React.Component {
render() {
const customerDetails1 = { isCustomer: true }
const customerDetails2 = { isCustomer: false }
return (
<div>
<CustomerDetails customerDetails={customerDetails1} />
<CustomerDetails customerDetails={customerDetails2} />
</div>
)
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
)
.customer {
background-color: green;
color: white;
}
.notCustomer {
background-color: red;
color: white;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
It works. I use it all the time. As others have asked, what does your render function look like? The true way of calling the render function again (incorporating changes in your DOM) is using this.setState. If you use a this.state.something as your ternary boolean, your className will dynamically update every single time you change it using this.setState.

Resources