Can react hooks be used inside JSX? - reactjs

I've recently come across the following code:
import React, { useContext } from "react";
import ReactDOM from "react-dom";
import UserContext from 'UserContext';
const useUserName = () => {
const context = useContext(UserContext);
return context.userName;
}
function App() {
return (
<div className="App">
{useUserName()}
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
useUserName returns user name as a string. It seems instinctively strange and wrong that react hook useUserName is used inside JSX that is inside the "render" function. Is this valid usage? I couldn't find any reference which forbids such usage.

It seems instinctively strange and wrong that react hook useUserName is used inside JSX that is inside the "render" function.
The entire component is nothing but a render() function.
Is this valid usage?
What's the difference between that and
function App() {
const userName = useUserName();
return (
<div className="App">
{userName}
</div>
);
}

Related

How to correct React Portal error: Target container is not a DOM element

im having an issue getting react portals working. I dont understand why I am receiving error message portal id is not DOM element that is clearly a valid DOM element.
I have a sandbox here
Code in its entirety presented here. The console.log reports correctly that the element is a DOM element but React is throwing an error.
import "./styles.css";
import { createPortal } from "react-dom";
import { useEffect, useState } from "react";
export default function App() {
const [portalDiv, setPortalDiv] = useState(undefined);
useEffect(() => {
let pd = document.getElementById("portalDiv");
console.log(pd);
setPortalDiv(pd);
}, []);
return (
<>
<div id="portalDiv">portal container</div>
<div className="app">
{/* {console.log("render portaldiv", portalDiv)} */}
{
(portalDiv &&
createPortal(
<>
<h1>Inside portal</h1>
</>
),
portalDiv)
}
<h1>Outside portal</h1>
</div>
</>
);
}
Any advice appreciated. Thanks.
This usecase is not recommended as stated in my comment, but here is a reproducible example.
If you intend to inject a React Node into VDOM, you should use React API for that, so you won't get a race condition while querying the DOM via DOM API.
import "./styles.css";
import { createPortal } from "react-dom";
import { useRef } from "react";
export default function App() {
const containerRef = useRef();
return (
<>
<div ref={containerRef}>portal container</div>
<div id="app">
{containerRef.current &&
createPortal(<h1>Example Element</h1>, containerRef.current)}
<h1>Outside portal</h1>
</div>
</>
);
}

document.getElementById() equivalent in React 2020

I have a component called Button.js that has a button that when clicked i simply would like to know if i am accessing the a div in another component called Timer.js. In vanilla javascript i would simply use document.getElementById() to capture the DOM node. How is this done in React?
I came across callback-refs in the docs but it isn't working. If using a ref isn't the React way of accessing DOM elements please refer me to the best way to do this. thanks in advance.
Button.js
function Button() {
const getHtml = () => {
const node = test.current;
console.log(node);
}
return (
<button onClick={getHtml}>GetHtml</button>
)
}
Timer.js
function Timer() {
const test = useRef(null);
return (
<div ref={test}>... </div>
<Button />
}
I would not use a reference to check if a component is rendered inside of another one.
You could get what you're looking for with createContext and useContext.
(It could work like you tried it. If you'd pass the ref to the button as a prop.)
With the context: You create a TimerContext.Provider in your Timer component and in your button you can check with useContext(TimerContext) if the expected key is in the object. If it's not there then the button is not inside of your Timer.
Please have a look at the snippet below or in the following Codesandbox.
//import React, { useContext, createContext } from "react";
//import "./styles.css";
const { useContext, createContext } = React;
const ContainerContext = createContext({
isInContainer: null
});
const Container = () => {
return (
<ContainerContext.Provider value={{ isInContainer: true }}>
<p>
In container:
<Button />
</p>
</ContainerContext.Provider>
);
};
const Button = () => {
const { isInContainer } = useContext(ContainerContext);
console.log(isInContainer);
const isInside = () => {
alert(isInContainer ? "clicked inside" : "not in container");
};
return <button onClick={isInside}>Click me</button>;
};
function App() {
return (
<div className="App">
<Container />
<Button />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
rootElement
);
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root"></div>
Update 15.04.2020
The question was not clear to me at first but now I understand the use-case. The idea is to have an Editor component where you're writing markup that can be used to generate a copied snippet view and/or a html markup output.
For this the best is to use a reference to the Editor component and pass it as prop to the preview/output component - it would be also possible with a context but passing it is easier.
Like in the following Sandbox.

Invalid hook call. Hooks can only be called inside of the body of a function component when using Hooks

I am not able to use React hooks. I have 4 components:
ComponentA
componentC
componentE
componentF
I need to pass value to componentF directly from componentA without having to pass from componentC and componentE. All components are in a single tree.
// componentA
import React from 'react';
import './App.css';
import ComponentC from "./components/ComponentC";
export const UserContext = React.useContext();
function App() {
return (
<div className="App">
<UserContext.Provider>
<ComponentC value={'My message'}/>
</UserContext.Provider>
</div>
);
}
export default componentA;
// componentF
import React from 'react';
import UserContext from '../App';
function ComponentF() {
return (
<div>
<UserContext.Consumer>
{
user => {
return (
<div>you are {user}</div>
)
}
}
</UserContext.Consumer>
</div>
)
}
export default ComponentF;
It is giving an error when I am trying to use context:
Invalid hook call. Hooks can only be called inside of the body of a function component.
The mistake is that you are using React.useContext()
You should be doing:
export const UserContext = React.createContext();
To pass to the child components of Component A, you can do it like this:
<UserContext.Provider value={100}>
<ComponentC .../>
</UserContext.Provider>
And instead of using UserContext.Consumer, you can get the value using React.useContext inside the component body of ComponentF.
// componentF
import React from 'react';
import {UserContext} from '../App';
function ComponentF() {
const value = React.useContext(UserContext);
return (
<div>
....
{value} // which will be equal to 100
</div>
)
}
export default ComponentF;

Why setting state to a constant triggers rerender two times?

Demonstration:
https://codesandbox.io/s/2zkxyk31oy
import React, { useState } from "react";
import ReactDOM from "react-dom";
function App() {
console.log("render");
let [val, setVal] = useState(0);
return <button onClick={() => setVal(1)}>go</button>;
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
What I expect to see is two render calls: the initial one, and the one caused by the state change.
The third setState call should have no effect, since react bails out of rendering if the state hasn't changed. https://reactjs.org/docs/hooks-reference.html#bailing-out-of-a-state-update
What exactly is happening?
The doc link you provided states:
Note that React may still need to render that specific component again before bailing out. That shouldn’t be a concern because React won’t unnecessarily go “deeper”
It appears the behaviour is consistent with what the doc says. In order to test that, you can check if child components are being rendered. Something like this:
import React, { useState } from "react";
import ReactDOM from "react-dom";
function App() {
console.log("render");
let [val, setVal] = useState(0);
return <button onClick={() => setVal(1)}><Child/></button>;
}
function Child() {
console.log('render the child')
return <span>go</span>
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
The result in the console now is:
render
render the child
render
render the child
render

How to share stateSetter functions using the useContext hook?

I have the following code high up in the component hierarchy:
import VisibilityContext from '../visibilityContext';
...
const [showEditModal, setEditModalVisibility] = useState(false);
...
<VisibilityContext.Provider value={{setEditModalVisibility}}>
<EditModal/>
</VisibilityContext.Provider>
And within the EditModal I have this piece of code:
import VisibilityContext from '../visibilityContext';
...
const {setEditModalVisibility} = useContext(VisibilityContext);
But the function setEditModalVisibility is empty when I console it out in the EditModal component. If I pass showEditModal instead of setEditModalVisibility, useContext gives me its correct value. I even tried putting setEditModalVisibility in the state using another useState, and passed it down, in case useContext required references to be stored in a state.
I just want components down the tree to be able to call the setEditModalVisibility function. And I want to be able to share this function without having to pass it down the tree as a prop.
Here is a sample code how you can effectively manage your state using Context.
import React, { createContext, useState, useContext } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
const VisibilityContext = createContext();
const Provider = props => {
const [visible, setVisible] = useState(false);
const value = { state: { visible }, actions: { setVisible } };
return (
<VisibilityContext.Provider value={value}>
{props.children}
</VisibilityContext.Provider>
);
};
function App() {
const { state, actions } = useContext(VisibilityContext);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<button onClick={() => actions.setVisible(!state.visible)}>
{state.visible ? "ON" : "OFF"}
</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(
<Provider>
<App />
</Provider>,
rootElement
);

Resources