Pass a reference to a DOM element into React - reactjs

I have a reference to a DOM element (from a 3rd-party library) and need to render it it React. I can think of a couple ways do this, but I'm hoping to find a solution that's more straightforward and doesn't require converting the element to a string first. For example:
const el = document.createElement('div');
el.textContent = 'test';
// This works but requires converting the element to a string first.
const reactEl = (
<div dangerouslySetInnerHTML={{__html: el.innerHTML}} />
);
// Something like this should also work, but it seems overly complicated.
function MyComponent() {
const myRef = useRef();
useEffect(() => {
myRef.current.appendChild(el);
}, []);
return (
<div ref={myRef} />
);
}
Is there another, more straightforward way to insert a DOM element into jsx?

Your second method is the correct way to do it in React. It may seem complicated, but it's not.
function MyComponent() {
const myRef = useRef();
useEffect(() => {
myRef.current.appendChild(el);
}, []);
return (
<div ref={myRef} />
);
}

Related

.map is not a function (am I really not using an array here?)

I'm trying to learn some react hooks by practice and trying to change the state at the same time.
Here is what my useState hook looks like:
const [names, setNames] = useState([
"James",
"Jessica",
"Roger",
"Alfred"
])
I have used JSX to return the array items from "names" on the page as follows:
{names.map((name)=> {
return <p>{name}</p>
})}
This seems to display everything just fine, suggesting that the map function works correctly on the array called names.
However, when I create a function to update the state using setNames, I get the error "TypeError: names.map is not a function"
Here is what the function looks like:
const addName = () => {
setNames({
names: [...names, "Jessica"]
})
}
I am just running this in an onClick event through a button in the app:
<button onClick={addName}>Add</button>
Sorry in advanced if this is novice but I can't seem to understand why I'm getting this error. I understand that .map can only be used on an array, however that's what I thought names was.. an array. Also it displays names when I use the .map function so I'm just confused by the error itself.
Thanks in advance for any help.
setNames({
names: [...names, "Jessica"]
})
This is changing your state to no longer be an array, but rather to be an object with a .names property on it. Only in class components do you need to pass in an object when setting state.
Instead, you should do the following:
setNames([...names, "Jessica");
One slight improvement you could do is to use the function version of setNames. This will make sure you're always using the most recent version of the state, and thus eliminate the possibility of some bugs when setting state multiple times:
const addName = () => {
setNames(prev => {
return [...prev, "Jessica"];
});
}
You're using the old setState logic.
setNames accepts the new value as first param, not an object, to add a name, change the addName to the following:
const addName = () => {
setNames([...names, "Jessica"])
}
Correct modification of state arrays in React.js
Working example:
const {useState} = React;
const SameSet = () => {
const [names, setNames] = useState([
"James",
"Jessica",
"Roger",
"Alfred"
]);
const addName = () => {
setNames([...names, "Jessica"])
}
return (
<div>
{names.map((name) => <p>{name}</p>)}
<button onClick={addName}>Add</button>
</div>
)
}
ReactDOM.render(<SameSet />, document.getElementById("react"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>

Event Handling in React js

const [show, setShow] = useState(true)
const showDiv = () => {
if (show) {
document.getElementsByClassName("skills-hero")[0].style.display = "flex"
}
else if (!show) {
document.getElementsByClassName("skills-hero")[0].style.display = "none"
}
}
This is simple logic but it is not working and not giving any error too.
I just don't under stand why it doesn't working
There are two possibilities why your code is not working:
The skill-hero element is defined inside the 'root' element (where you are attaching your Reactjs app. assuming you are attaching your react app to the real DOM. Hence the element is getting overridden by the react app.
function showDiv is not returning anything, I presume react will complain if you call the function inside the return(jsx) function.
To make your example work:
Inside Index.html
<div class="skills-hero">
<div class="p">1</div>
<div class="p">2</div>
</div>
<div id="root"></div>
Inside Javascript
const MyFunc = () => {
const [show, setShow] = React.useState(true);
const showDiv = () => {
if (show) {
document.getElementsByClassName("skills-hero")[0].style.display = "flex";
} else if (!show) {
document.getElementsByClassName("skills-hero")[0].style.display = "none";
}
return "some dummy text";
};
return showDiv();
};
ReactDOM.render(<MyFunc />, document.getElementById("root"));
Note: This method frowned upon. ^^^
You should avoid direct DOM manipulation in ReactJs mainly because:
performance
security (XSS)
and ReactDOM does the dom-manipulation for you efficiently.
it makes out lives way easier 😼
etc...
const [show, setShow] = useState(true);
const showDiv = () => show ? <div className="skills-hero"> </div> : null
You can't select jsx elements with getElementsByClassName because they are not in the real dom. Your real DOM is empty in React.
You might want to do conditional rendering instead;
show && <div className="skills-hero"></div>
something like this.

What is the right place to call a function before render in React?

I have some issue on understandsing the lifecycle in React, so iam using useEffects() since i do understand that it was the right way to call a method before the component rendered (the replacement for componentDidMount ).
useEffect(() => {
tagSplit = tagArr.split(',');
});
And then i call tagSplit.map() function on the component, but it says that tagSplit.map is not a function
{tagSplit.map((item, index) => (
<div className="styles" key={index}>
{item}
</div>
))}
Is there something wrong that i need to fix or was it normal ?
useEffect runs AFTER a render and then subsequently as the dependencies change.
So yes, if you have tagSplit as something that doesn't support a map function initially, it'll give you an error from the first render.
If you want to control the number of times it runs, you should provide a dependency array.
From the docs,
Does useEffect run after every render? Yes! By default, it runs both after the first render and after every update. (We will later talk about how to customize this.) Instead of thinking in terms of “mounting” and “updating”, you might find it easier to think that effects happen “after render”. React guarantees the DOM has been updated by the time it runs the effects.
This article from Dan Abramov's blog should also help understand useEffect better
const React, { useState, useEffect } from 'react'
export default () => {
const [someState, setSomeState] = useState('')
// this will get reassigned on every render
let tagSplit = ''
useEffect(() => {
// no dependencies array,
// Runs AFTER EVERY render
tagSplit = tagArr.split(',');
})
useEffect(() => {
// empty dependencies array
// RUNS ONLY ONCE AFTER first render
}, [])
useEffect(() => {
// with non-empty dependency array
// RUNS on first render
// AND AFTER every render when `someState` changes
}, [someState])
return (
// Suggestion: add conditions or optional chaining
{tagSplit && tagSplit.map
? tagSplit.map((item, index) => (
<div className='styles' key={index}>
{item}
</div>
))
: null}
)
}
you can do something like this .
function App() {
const [arr, setArr] = useState([]);
useEffect(() => {
let tagSplit = tagArr.split(',');
setArr(tagSplit);
}, []);
return (
<>
{arr.map((item, index) => (
<div className="styles" key={index}>
{item}
</div>
))}
</>
)
}
Answering the question's title:
useEffect runs after the first render.
useMemo runs before the first render.
If you want to run some code once, you can put it inside useMemo:
const {useMemo, Fragment} = React
const getItemsFromString = items => items.split(',');
const Tags = ({items}) => {
console.log('rendered')
const itemsArr = useMemo(() => getItemsFromString(items), [items])
return itemsArr.map((item, index) => <mark style={{margin: '3px'}} key={index}>{item}</mark>)
}
// Render
ReactDOM.render(<Tags items='foo, bar, baz'/>, root)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="root"></div>
For your specific component, it's obvious there is no dilema at all, as you can directly split the string within the returned JSX:
return tagArr.split(',').map((item, index) =>
<div className="styles" key={index}>
{item}
</div>
)
But for more complex, performance-heavy transformations, it is best to run them only when needed, and use a cached result by utilizing useMemo

Use component function in React loop

I heard that () is not good at the rendering performance so it should not be used. However, in this case, it can not get the results when passing the function without ().
You can check the reseult in CodeSendbox: Link
const comp = () => {
return <div>This is Component</div>;
};
const elements = [comp(), comp]; // Please Attention This Line!
const items = [];
for (const [index, value] of elements.entries()) {
items.push(
<div>
{index}: {value} <br />
</div>
);
}
export default function Index() {
return <>{items}</>;
}
Result is:
0:
This is Component
1:
Why only comp() but not comp? What is the best practice in this case?
You can use JSX instead of calling the function:
const comp = () => {
return <div>This is Component</div>;
};
const elements = [comp, comp]; // Remove the function call, save only the reference
const items = [];
// need to use Value with V instead of v for JSX
for (const [index, Value] of elements.entries()) {
items.push(
<div>
{index}: <Value/> <br /> // render as JSX
</div>
);
}
export default function Index() {
return <>{items}</>;
}
Here's a sandbox link showing how it works: https://codesandbox.io/s/cranky-mclean-7ymzr
comp is an arrow function that returns a JSX literal. It's a "function" that is stored in a variable called comp. When called/invoked as comp(), notice the parenthesis, it is executed and the computed return value is returned in the array. When used without the parenthesis, like comp, this is simply the reference to the variable (that can be invoked like a function).
Functions aren't valid react children nor valid JSX.
Introducing JSX
const comp = () => {
return <div>This is Component</div>;
};
const elements = [comp(), comp]; // saves a JSX literal, and a function
const items = [];
for (const [index, value] of elements.entries()) {
items.push(
<div>
{index}: {value} <br /> // trying to render a function here in `value`
</div>
);
}
export default function Index() {
return <>{items}</>;
}
I assume you got the idea () is "not good at rendering performance" from some documentation about in-line anonymous arrow functions and event handlers and mapping an array of data. That is a different issue altogether. I believe your situation is just a misunderstanding about how arrow functions are invoked.

Using state setter as prop with react hooks

I'm trying to understand if passing the setter from useState is an issue or not.
In this example, my child component receives both the state and the setter to change it.
export const Search = () => {
const [keywords, setKeywords] = useState('');
return (
<Fragment>
<KeywordFilter
keywords={keywords}
setKeywords={setKeywords}
/>
</Fragment>
);
};
then on the child I have something like:
export const KeywordFilter: ({ keywords, setKeywords }) => {
const handleSearch = (newKeywords) => {
setKeywords(newKeywords)
};
return (
<div>
<span>{keywords}</span>
<input value={keywords} onChange={handleSearch} />
</div>
);
};
My question is, should I have a callback function on the parent to setKeywords or is it ok to pass setKeywords and call it from the child?
There's no need to create an addition function just to forward values to setKeywords, unless you want to do something with those values before hand. For example, maybe you're paranoid that the child components might send you bad data, you could do:
const [keywords, setKeywords] = useState('');
const gatedSetKeywords = useCallback((value) => {
if (typeof value !== 'string') {
console.error('Alex, you wrote another bug!');
return;
}
setKeywords(value);
}, []);
// ...
<KeywordFilter
keywords={keywords}
setKeywords={gatedSetKeywords}
/>
But most of the time you won't need to do anything like that, so passing setKeywords itself is fine.
why not?
A setter of state is just a function value from prop's view. And the call time can be anytime as long as the relative component is live.

Resources