how to covert document.querySelector to user using react hooks - javascript

how to convert getting element by querySeletor to add div with ref document.querySelector to using react hooks
import ReactDOM from "react-dom";
import h337 from "heatmap.js";
import "./styles.css";
function App() {
useEffect(() => {
var heatmapInstance = h337.create({
// only container is required, the rest will be defaults
container: document.querySelector('.App')
});
// now generate some random data
var points = [];
var max = 0;
var width = 840;
var height = 400;
var len = 200;
while (len--) {
var val = Math.floor(Math.random()*100);
max = Math.max(max, val);
var point = {
x: Math.floor(Math.random()*width),
y: Math.floor(Math.random()*height),
value: val
};
points.push(point);
}
// heatmap data format
var data = {
max: max,
data: points
};
// if you have a set of datapoints always use setData instead of addData
// for data initialization
heatmapInstance.setData(data);
})
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
how to convert getting element by querySeletor to add div with ref document.querySelector to using react hooks

const appRef = React.useRef();
...
<div className="App" ref={appRef}>
And then to can get DOM element (the same as document.querySelector('.App')):
appRef.current
Your app renders faster that you trying to access it. You need to add small adjustments to useEffect hook:
useEffect(() => {
if(appRef.current) {
...your code here
}
}, [appRef])
On that case your code runs only after DOM element will be mounted.

Related

How do you grab an element and dynamically add or remove styles from it in react native

I'm coming from the world of React and trying to translate a component into React Native. In react we can easily grab an element with useRef and then add or remove classes.
Is there a way to easily translate this to react native as I see when I inspect the current property of the <Text> element it looks nothing like an html element.
Here is the file I'm trying to convert:
import React, {useState, useEffect, useRef} from 'react';
// styles
import styles from './whiteTextReveal.module.css';
export const WhiteTextReveal = props => {
// props
const {text, duration, callback} = props;
// local
const leftMask = useRef(null);
const rightMask = useRef(null);
const textRef = useRef(null);
const animationTimeouts = useRef([]);
useEffect(() => {
reveal();
return () => {
animationTimeouts.current.map(val => clearTimeout(val));
}
}, [text]);
function reveal() {
let time = 0;
// reveal first white masks
const timeout1 = setTimeout(() => {
// cleanup if called successively
textRef.current.classList.remove(styles.shrink);
leftMask.current.classList.remove(styles.moveRight);
rightMask.current.classList.remove(styles.moveLeft);
leftMask.current.classList.add(styles.moveLeft);
rightMask.current.classList.add(styles.moveRight);
}, 1000*time);
animationTimeouts.current = [...animationTimeouts.current, timeout1];
// reveal text behind first white mask
time = time + .8; // come from the css file .mask.left.moveLeft
const timeout2 = setTimeout(() => {
textRef.current.classList.remove(styles.hide);
leftMask.current.classList.remove(styles.moveLeft);
rightMask.current.classList.remove(styles.moveRight);
leftMask.current.classList.add(styles.moveRight);
rightMask.current.classList.add(styles.moveLeft);
}, 1000*time);
animationTimeouts.current = [...animationTimeouts.current, timeout2];
// move mask to cover text again
time = time + .5 + duration; // come from the css file .mask.left.moveRight
const timeout3 = setTimeout(() => {
textRef.current.classList.add(styles.shrink);
const timeout4 = setTimeout(() => {
textRef.current.classList.add(styles.hide);
callback()
}, .7*1000);
animationTimeouts.current = [...animationTimeouts.current, timeout4];
}, time*1000);
animationTimeouts.current = [...animationTimeouts.current, timeout3];
}
return (
<div className={styles.container}>
<span ref={textRef} className={`${styles.text} ${styles.hide}`}>{text}</span>
<div ref={leftMask} className={`${styles.mask} ${styles.left}`}/>
<div ref={rightMask} className={`${styles.mask} ${styles.right}`}/>
</div>
)
};
This is a text animation reveal that paints a white strip over text, slides back, and the scales the text to 0 before telling the parent component it finished.
As you can see from these lines:
textRef.current.classList.remove(styles.shrink);
leftMask.current.classList.remove(styles.moveRight);
rightMask.current.classList.remove(styles.moveLeft);
I am grabbing the ref and then removing classes. And the lines that follow after add those classes back in specific ways.

Conditionally render a class to a grid cell, then remove it on a state change

Ok, so first time question-asker here, but I'm having trouble rendering a class to a grid cell, then removing it. The idea is the grid is the floor, and the class represents a roomba moving around it. I want to render the roomba where the cell id matches the position saved in state. Here's my code for the grid component:
import React, { useState, useEffect } from "react";
function Grid(props) {
const [position, setPosition] = useState(props.location);
const [roomba, setRoomba] = useState({
location: "1, 1",
});
const theRoomba = `${position[0]}, ${position[1]}`;
const renderRoomba = document.getElementById(theRoomba);
useEffect(() => {
if (roomba) {
setPosition(props.location);
setRoomba(position);
console.log("Im the current location", props.location);
}
}, [props.location]);
const grid = [];
for (let i = 10; i >= 1; i--) {
for (let j = 10; j >= 1; j--) {
grid.push(
<div className="square" id={`${i}, ${j}`} key={`${i}, ${j}`}>
{`${i}, ${j}`}
</div>
);
}
}
const updatedGrid = [];
grid.map((cell) => {
if (renderRoomba) {
renderRoomba.classList.add("roomba");
updatedGrid.push(cell);
} else {
updatedGrid.push(cell);
}
return updatedGrid;
});
return <div className="grid">{updatedGrid}</div>;
}
export default Grid;
I'm a bit of a noob and know this is probably bloated, but it currently renders my 10x10 grid, and I can move my roomba around but can't ever remove the className='roomba' from the previous cell. I've tried a few DOM manipulation methods but nothing seems very 'React-ty'. Appreciate any help/feedback, this has been wracking my brain all week!

Simple Javascript animation in Gatsby

I'm new to learning React and Gatsby, and am trying to find the best way to apply simple a Javascript animation to a DOM element within a component. I know how to handle component events with onClick etc, but say for example I want to continuously change the colour of a <span> in my Header.js component every 2 seconds.
import React from 'react';
export default function Header() {
return (
<header>
<p>This is a <span>test!</span></p>
</header>
)
}
I'd then want to use some JS like:
const spanEl = document.querySelector('header span');
let counter = 0;
const changeColor = () => {
if (counter % 2 == 0) {
spanEl.style.color = "red";
} else {
spanEl.style.color = "blue";
}
counter++;
if (counter == 10) counter = 0;
}
setInterval(changeColor, 2000);
I found that I could put this inside a script tag in html.js before the closing body tag, but is there a way to keep this functionality within the component? Do I need to completely rethink my approach when working within this framework?
If you want to approach this with idiomatic React, then I would recommend expressing this behavior using hooks, component lifecycles, and effects.
The official React docs for hooks and effects are very good, I would start there.
import React from 'react';
const noop = () => null;
// Encapsulate the interval behavior
const useInterval = (callback, delay) => {
const savedCallback = useRef(noop);
useEffect(() => {
savedCallback.current = callback;
savedCallback.current();
}, [callback]);
useEffect(() => {
const id = setInterval(savedCallback.current, delay);
return () => clearInterval(id);
}, [delay]);
};
export default function Header() {
const [color, setColor] = useState("blue");
// setColor causes a re-render of the component
const updateColor = setColor(color === "blue" ? "red" : "blue");
useInterval(updateColor, 2000);
// Use the jsx to change the color instead of reaching into the dom
return (
<header>
<p>This is a <span style={{ color }}>test!</span></p>
</header>
)
}
[EDIT: I've just seen the answer from #windowsill, which I think is better than mine; I would recommend going with that solution.]
In a React functional component, you need to use the useReference hook to target an element (rather than selecting it with document.querySelector()) and the useEffecet hook to set and clear the timeout when the component mounts/unmounts:
import React, {
useEffect,
useRef,
useCallback
} from 'react';
export function Header() {
const animatedText = useRef(null);
const runAnimation = useCallback(elem => {
const currColour = elem.style.color;
elem.style.color = (currColour === 'red' && 'blue') || 'red';
}, []);
useEffect(() => {
const animationInterval = setInterval(() => {
runAnimation(animatedText.current);
}, 2000);
return () => {
clearInterval(animationInterval);
}
}, [runAnimation]);
return (
<header>
<p>This is a <span ref={animatedText}>test!</span></p>
</header>
);
}
The useCallback hook is used for optimization purposes and prevent the function runAnimation from being re-defined and initialized every time the component re-renders.

Is there a way to avoid using forceupdate in my code?

I have a block of code like this:
randomArray(arrayOfImage){
//algorithm goes here
return shuffledArray;
}
shuffle(){
this.randomArray(images);
this.forceUpdate();
}
render(){
let array = this.randomArray(images); //images is a declared array
let squares = array.map((item,index) => <Squares/> )
return(
<div>
<div id='square-container'>
{squares}
</div>
<button className='btn' onClick = {this.shuffle.bind(this)}>Shuffle</button>
</div>
)
}
Basically, I have an array images declared. The function randomArray() return a shuffled version of images. And then, for each of the item inside the shuffled array array the browser will render a div with the given props.
Now I have a button so that the user can shuffle the array themselves. Here, I use forceupdate() because even though the array is shuffled when the button is clicked, the DOM won't update because there is no changes in the state.
It works! But since using forceupdate() is not encouraged, how should I make this less... say, amateur?
Sure, React will render an element again as soon as it's state gets changed, or it receives new props from it's parent component (or a state library).
You can read more about how react handles the rendering in their documentation
So, to handle this, just create a new array based on your original one, and then set it to the state again.
In the olden days, you would require a class for setting the state on component level
const target = document.getElementById('container');
class ArrayOfImages extends React.Component {
constructor() {
// don't forget to call super
super();
// set the initial state
this.state = {
images: [
'https://c402277.ssl.cf1.rackcdn.com/photos/18357/images/featured_story/Medium_WW252652.jpg?1576698319',
'https://c402277.ssl.cf1.rackcdn.com/photos/882/images/circle/African_Elephant_7.27.2012_hero_and_circle_HI_53941.jpg?1345532748',
'https://c402277.ssl.cf1.rackcdn.com/photos/1732/images/circle/Asian_Elephant_8.13.2012_Hero_And_Circle_HI_247511.jpg?1345551842'
]
};
// bind the function we are going to call from the render function
this.shuffle = this.shuffle.bind( this );
}
shuffle() {
// create a shallow copy of the state object
const copyOfState = this.state.images.slice();
// shuffle it a bit (Durstenfeld shuffle: http://en.wikipedia.org/wiki/Fisher-Yates_shuffle#The_modern_algorithm)
for (let i = copyOfState.length; i--;) {
let target = Math.floor( Math.random() * (i+1) );
[copyOfState[i], copyOfState[target]] = [copyOfState[target], copyOfState[i]];
}
// update the existing state with the new array
this.setState({ images: copyOfState });
}
render() {
const { images } = this.state;
return (
<div>
<h1>Some elephants</h1>
{ images.map( img => <img key={ img } alt={ img } src={ img } /> ) }
<div><button type="button" onClick={ this.shuffle }>Shuffle images</button></div>
</div>
);
}
}
ReactDOM.render( <ArrayOfImages />, target );
<script id="react" src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.2/react.js"></script>
<script id="react-dom" src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/15.6.2/react-dom.js"></script>
<div id="container"></div>
But now, you could do it by using the useState helper for that
// this would be an import in normal code
const { useState } = React;
const target = document.getElementById('container');
const shuffle = arr => {
// create a shallow copy of the array
const copy = arr.slice();
// shuffle it a bit (Durstenfeld shuffle: http://en.wikipedia.org/wiki/Fisher-Yates_shuffle#The_modern_algorithm)
for (let i = copy.length; i--;) {
let target = Math.floor( Math.random() * (i+1) );
[copy[i], copy[target]] = [copy[target], copy[i]];
}
return copy;
}
const ArrayOfImages = () => {
const [images, updateImages] = useState([
'https://c402277.ssl.cf1.rackcdn.com/photos/18357/images/featured_story/Medium_WW252652.jpg?1576698319',
'https://c402277.ssl.cf1.rackcdn.com/photos/882/images/circle/African_Elephant_7.27.2012_hero_and_circle_HI_53941.jpg?1345532748',
'https://c402277.ssl.cf1.rackcdn.com/photos/1732/images/circle/Asian_Elephant_8.13.2012_Hero_And_Circle_HI_247511.jpg?1345551842'
]);
return (
<div>
<h1>Some elephants</h1>
{ images.map( img => <img key={ img } alt={ img } src={ img } /> ) }
<div><button type="button" onClick={ () => updateImages( shuffle( images ) ) }>Shuffle images</button></div>
</div>
);
}
ReactDOM.render( <ArrayOfImages />, target );
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.1/umd/react-dom.production.min.js"></script>
<div id="container"></div>
The shuffle algorithm comes from this answer

How to change a innerHtml of an div in React?

I'm a newbie in React. I have 6 divs and whenever I call foo() I want to add a number to the first div that's empty.
For example, let's say that the values of the six divs are 1,2,0,0,0,0 and when I call foo(), I want to have 1,2,3,0,0,0.
Here is what I've tried:
var index = 1;
function foo() {
let var x = document.getElementsByClassName("square") // square is the class of my div
x[index-1].innerHTML = index.toString()
index++;
}
I don't know when I should call foo(), and I don't know how should I write foo().
The "React way" is to think about this is:
What should the UI look like for the given data?
How to update the data?
Converting your problem description to this kind of thinking, we would start with an array with six values. For each of these values we are going to render a div:
const data = [0,0,0,0,0,0];
function MyComponent() {
return (
<div>
{data.map((value, i) => <div key={i}>{value}</div>)}
</div>
);
}
ReactDOM.render(<MyComponent />, document.body);
<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>
Now that we can render the data, how are we going to change it? From your description it sounds like every time a function is called, you want change the first 0 value in the array to another value. This can easily be done with:
// Find the index of the first 0 value
const index = data.indexOf(0);
if (index > -1) {
// if it exists, update the value
data[index] = index + 1;
}
To make this work properly with React we have to do two things: Keep track of the updated data in state, so that React rerenders the component when it changes, and update the data in a way that creates a new array instead of mutating the existing array.
You are not explaining how/when the function is called, so I'm going to add a button that would trigger such a function. If the function is triggered differently then the component needs to be adjusted accordingly of course.
function update(data) {
const index = data.indexOf(0);
if (index > -1) {
data = Array.from(data); // create a copy of the array
data[index] = index + 1;
return data;
}
return data;
}
function MyComponent() {
var [data, setData] = React.useState([0,0,0,0,0,0]);
return (
<div>
{data.map((value, i) => <div key={i}>{value}</div>)}
<button onClick={() => setData(update(data))}>Update</button>
</div>
);
}
ReactDOM.render(<MyComponent />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
You would use state to hold the value and then display the value of that variable.
If you're using functional components:
const App = () => {
const [values, setValues] = React.useState([0, 0, 0, 0, 0, 0]);
const [index, setIndex] = React.useState(0);
const foo = () => {
const tempValues = [...values];
tempValues[index] = index;
setValues(tempValues);
setIndex((index + 1) % values.length);
}
return (
<div>
{ values.map((value) => <div key={`square-${value}`}>{value}</div>) }
<button onClick={ foo }>Click me</button>
</div>
);
};
In class-based components:
constructor(props) {
super(props);
this.state = {
values: [0, 0, 0, 0, 0, 0],
index: 0
};
this.foo = this.foo.bind(this);
}
foo() {
const tempValues = [...values];
const newIndex = index + 1;
tempValues[newIndex] = newIndex;
this.setState({
values: tempValues,
index: newIndex
});
}
render() {
return (
<div>
{ values.map((value) => <div key={`square-${value}`>value</div>) }
<button onClick={ this.foo}>Click me</button>
</div>
);
}
If you need to set the innerHTML of a React component, you can try this:
return <div dangerouslySetInnerHTML={foo()} />;
the foo() here returns the value you want to post in the div.
But in my opinion, your way of thinking on this problem is wrong.
React is cool, but the logic is a bit different of common programming :D
The ideal approach would be to have the divs created by React (using its render method). Then you can pass a variable from array, which is stored in your state. You then just need to change this array within the state and it'll reflect in your view. If you need a working example, just let me know.
However, if you want to update the divs that are not created using react, then you need to use a dirty approach. I would suggest not to use react if you can't generate the view from react.
React is good to separate the concerns between the view and the data.
So the concept of state for this example is useful to store the data.
And the JSX, the React "template" language, to display the view.
I propose this solution:
import React from "react";
class Boxes extends React.Component {
state = {
divs: [1, 2, 3, 0, 0, 0]
};
add() {
// get the index of the first element equals to the condition
const index = this.state.divs.findIndex(elt => elt === 0);
// clone the array (best practice)
const newArray = [...this.state.divs];
// splice, i.e. remove the element at index AND add the new character
newArray.splice(index, 1, "X");
// update the state
// this is responsible, under the hood, to call the render method
this.setState({ divs: newArray });
}
render() {
return (
<div>
<h1>Boxes</h1>
{/* iterate over the state.divs array */}
{this.state.divs.map(function(elt, index) {
return (
<div
key={index}
style={{ border: "1px solid gray", marginBottom: 10 }}
>
{elt}
</div>
);
})}
<button onClick={() => this.add()}>Add a value</button>
</div>
);
}
}
export default Boxes;

Categories