What's the proper React way of toggling a class on a tag outside of the specific component?
So currently I have the following working example:
import React, { useState, useEffect } from "react";
export default function Header() {
const [toggled, setToggled] = useState(0);
useEffect(() => {
document.body.classList.toggle("toggled");
});
return (
<nav>
<ul>
<li>
<a onClick={() => setToggled(!toggled)} href="#">
Open menu
</a>
</li>
</ul>
</nav>
);
}
This feels wrong for some reason.
First of all I'm using document.body.classList.toggle instead of the useState variable. I know when toggling a class inside this Header component I would just use useState instead of useEffect. But when I need to toggle some class outside the component, like in my case, is that possible with useState alone?
Second of all, should I even be using document.body.classList.toggle in the useEffect, or is there some more specific way of doing it? What if I wanted to target some other element without any id or class?
Lastly, if anyone is wondering, I'm toggling the class on the body instead of directly inside the header component because I want to modify many different elements down the line, not just the menu.
Looks like an X/Y problem - if you want an app-wide UI change, you should change the status on the main component (that holds the parent HTMLElement of the whole app ideally), like:
function App() {
const [showNav, setShowNav] = useState(false);
function toggleNav() {
setShowNav(!showNav);
}
return (
<main className={showNav ? 'show-nav' : ''}>
<Header onButtonClick={toggleNav}/>
...
</main>
);
}
function Header({ onButtonClick }) {
return (
<header>
<button onClick={onButtonClick}>Menu</button>
...
</header>
);
}
If you need to pass the function various levels of hierarchy down the component tree, I recommend to use a state container like redux.
Related
I'm new to React and having some difficulty trying to add a new child component to a component that has already been rendered.
I have an App component which initially contains a Main component (main menu).
I also have Popover components which I want to appear on top of Main when they are children of <App> (and hence siblings of <Main>).
These Popover components vary in number. Each <Popover> can contain buttons which launch another <Popover> over the top again. So the structure would be like
<App>
<Main></Main>
<Popover></Popover>
<Popover></Popover>
...
</App>
However, when the page first loads there are no Popover components open, and the<App> is rendered without any. Here is a stripped-down version of my code:
class App extends React.Component{
constructor(props){ super(props) }
render(){
return (
<div>{this.props.children}</div>
)
}
}
class Main extends React.Component{
constructor(props){ super(props) }
render(){
return (
//main menu stuff here
)
}
}
ReactDOM.render(<App><Main /></App>, root);
How can I add new <Popover>s to my <App> when the user clicks something? Before React I would simply do App.appendChild(Popover) kind of thing, but I'm quite lost here.
I should add that the elements the user will click to trigger an initial <Popover> are not contained within <Main>; they are outside of the <App>, as I am trying to slowly transition my existing page to using React. I think this could be part of my problem.
So basically in React, you have multiple ways of doing this, but to be more reliable you need to have data that represents the dynamic components you will render in your DOM. And to do this you need to create a state and a function that can add new information to your state. Then simply by sharing this function with your other components, you can trigger it from wherever you want, and this will update your state which will increase the amount of dynamic components you will render.
Take a look at this example
import { useState } from "react";
export default function App() {
const [popups, setPopups] = useState([]);
const addNewPopup = () => {
setPopups([...popups, { title: "I am a popup" }]);
};
return (
<div className="App">
<ChildComponent onClick={addNewPopup} />
{popups.map((p) => {
return <Popup title={p.title} />;
})}
</div>
);
}
function ChildComponent({ onClick }) {
return (
<div>
<p>I am a child component</p>
<button onClick={onClick}>Add new element</button>
</div>
);
}
function Popup({ title }) {
return <div>I am a popup with title = {title}</div>;
}
I am using React with Node.js.
I have a component ItemList, which fetches some API in the componentDidMount() method, because it allows me to easily render a "loading state".
I need to pass a state to this component, which would change the API's url using a toggle button. This toggle button is an individual component (ToggleButton). These two components are siblings and I am using parent as a way for these components to communicate.
I thought Context is perfect for this kind of job. The issue is, that using React's Context is just re-rendering (calling the function render() of a ItemList and not remounting the component, thus not calling the componentDidMount() method or even constructing the component again).
export const ToggleContext = React.createContext({
switched: false
});
export default class Items extends React.Component
{
constructor(props)
{
super(props)
this.state = {
switched: false
}
this.toggleSwitch = () => {
this.setState(state => ({
switched: !state.switched
}))
}
}
render()
{
console.log(this.state.switched)
return (
<>
<div class="page-header">
<div class="container-fluid">
<h2 class="h5 mb-0">Items</h2>
</div>
</div>
<section class="py-0">
<div class="container-fluid">
<ToggleContext.Provider value={this.state.switched}>
<ToggleButton callFunc={this.toggleSwitch}/>
<ItemList loadWeb={this.state.switched}/>
</ToggleContext.Provider>
</div>
</section>
</>
)
}
}
ItemList component is heavily inspirated by React docs
I am succesfully getting the changed state through ToggleButton in parent component, sending it to ItemList and picking it up as props.loadWeb, I am just not sure if my implementation is wrong or even if what I demand is possible with Context.
Is it possible to reconstruct the whole component using context, should I use refs, sould I fetch the API in the render() method, etc.?
So it appears that you need your componentDidMount called on every toggle of ItemList. But as you noted, it does not remount every time. As such, using componentDidUpdate inside your ItemList is more appropriate to toggle that API link.
Docs: https://reactjs.org/docs/react-component.html#componentdidupdate
I also encourage you to check out this article on using Hooks vs Classes: https://blog.bitsrc.io/6-reasons-to-use-react-hooks-instead-of-classes-7e3ee745fe04
What you are doing seems right, I would say, you could probably simplify (in my opinion) by using function components along with hooks.
import React, {useState} from 'react';
const Items = () => {
const [toggled, toggle] = useState(false);
return (
<div class="container-fluid">
<div class="page-header">
<h2 class="h5 mb-0">Items</h2>
</div>
<section class="py-0">
<ToggleButton callFunc={toggle(!toggled)}/>
<ItemList loadWeb={toggled}/>
</section>
</div>
);
};
Where's the right place to put code that interacts with the DOM in a gatsby site? I want to toggle the visibility of some components by adding/removing a class when another element is clicked.
The gatsby-browser.js file seems like it should contain this code but the API doesn't seem to call any of the functions after the DOM has loaded.
Similarly, using Helmet calls it too soon. Using window.onload never seems to trigger at all regardless of where it's included.
window.onload = function () {
// add event listener to the toggle control
}
Is there an event I can use to run my code when the DOM is ready?
Do you really need to wait for the DOM to be ready? When working in react you need to change the way you think about these things. For example you could add an on click that changes state and then reflect the state change in your classname prop.
Code Example:
import React, { useState } from "react"
const MyApp = () => {
const [visible, setVisible] = useState(true) // true is the initial state
return (
<div>
<div className={visible ? "visible-class" : "hidden-class"}>
My content
</div>
<button onClick={() => setVisible(!visible)}>Click me!</button>
</div>
)
}
export default MyApp
Or you could take it a step further and not even render that content to the DOM until you want to.
Example:
import React, { useState } from "react"
const MyApp = () => {
const [visible, setVisible] = useState(true) // true is the inital state
return (
<div>
<button onClick={() => setVisible(!visible)}>Click me!</button>
{visible && <div>My content here</div>}
</div>
)
}
export default MyApp
You can use the React cyclelife with componentDidMount().
This need to update your component like that :
import React from 'react'
class YourComponent extends React.Component {
componentDidMount() {
// Your Javascript function here
}
render() {
return(
<div className="YourComponentHere"></div>
)
}
}
export default YourComponent
Hope that help you!
If your component is a functional component, try using React Hook useEffect, which will guarantee the execution after the component is rendered.
import React, { useEffect } from 'react'
const MyComponent = () => {
useEffect(() => {
console.log("Document loaded");
});
return (
<main>
<text>Pretty much the component's body code around here</text>
</main>
)
}
export default MyComponent
my home component has:
const Home = () => (
<div>
<Head title="Home" />
<Nav />
<div className="container o_block u_blue">
<div className="notification">
This container is <strong>centered</strong> on desktop.
</div>
</div>
</div>
)
export default Home
and I'm trying to add some DOM manipulation into that component:
componentDidMount() {
let burger = document.querySelector('.burger');
let nav = document.querySelector('#'+burger.dataset.target);
burger.addEventListener('click', function(){
burger.classList.toggle('is-active');
nav.classList.toggle('is-active');
});
}
const Home = () => (
<div>
<Head title="Home" />
<Nav />
<div className="container o_block u_blue">
<div className="notification">
This container is <strong>centered</strong> on desktop.
</div>
</div>
</div>
)
export default Home
but unfortunately I am getting a:
SyntaxError: Unexpected token, expected ";" (8:20)
what am I doing wrong and where should I put the method?
Home is a presentational component in your code. Which means a presentational component is like a pure function in Java Script. A Presentational component doesn’t call any React life cycle method and doesn’t modify React state. It only takes props and returns jsx elements. This is also called as stateless component in react.
If you want to play with React life cycle methods then you should go with statefull component.
componentDidMount is one of React life cycle method so it’s not accessible in presentational or functional components in React.
Edit:
If you want to do DOM manipulation before component initial render then do DOM manipulation in componentWillMount() method but please see this method is deprecated in latest React versions.
If you want to do DOM manipulation after first render then do that in componentDidMount() method. This is the one wr you also make axios calls and do setState accordingly. I would recommend you to go with componentDidMount().
import React, { Component} from "react";
export default class Home extends Component {
componentDidMount() {
let burger = document.querySelector('.burger');
let nav = document.querySelector('#'+burger.dataset.target);
burger.addEventListener('click', function(){
burger.classList.toggle('is-active');
nav.classList.toggle('is-active');
});
}
render(){
return(
<div>
<Head title="Home" />
<Nav />
<div className="container o_block u_blue">
<div className="notification">
This container is <strong>centered</strong> on desktop.
</div>
</div>
</div>
)
}
}
Please excuse me if there are any typo error because I am answering in my mobile
You need to transform your component into a class-based component, like this :
export default class Home extends React.Component {
render() {
// Return the JSX code
}
componentDidMount() {
// Your init code
}
}
But I really suggest you to take a look at the official React doc as it's a fairly simple mistake.
I'm trying to build my first React project, and am currently putting together a burger nav button, and a menu which appears when clicking the nav.
I've broken this into two components; Hamburger and MenuOverlay. The code for both is below.
Currently I have an onClick on Hamburger toggling a class on it, but how would I also toggle the menu from that click? It's hidden with display: none; by default. Probably a very basic question so apologies - still trying to get my head around React.
MenuOverlay
import React from 'react';
import { Link } from 'react-router';
const MenuOverlay = () => {
return (
<div className="menuOverlay">
<div className="innerMenu">
<p><Link to="/">Home</Link></p>
<p><Link to="/">About</Link></p>
<p><Link to="/">Contact</Link></p>
</div>
</div>
);
};
export default MenuOverlay;
Hamburger
import React, { Component } from 'react';
class Hamburger extends Component {
constructor(props) {
super(props);
this.state = { active: '' };
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
var toggle = this.state.active === 'is-active' ? '' : 'is-active';
this.setState({active: toggle});
}
render() {
return (
<button className={`hamburger hamburger--emphatic fadein one ${this.state.active}`} onClick={this.handleClick} type="button">
<span className="homeMenuTextButton">Menu</span>
<span className="hamburger-box">
<span className="hamburger-inner"></span>
</span>
</button>
);
}
}
export default Hamburger;
In the most simplistic form you would have a container component that wraps around both of them and manages the state of the components.
<MenuContainer>
<Hamburger />
<MenuOverlay />
</MenuContainer>
And in <MenuContainer> you would have a state of active some quick pseudocode.
class MenuContainer extends React.Component {
constructor() {
super();
this.state = { active: false}
}
toggleMenu = () => {
// function that will toggle active/false
this.setState((prevState) => {
active: !prevState.active
});
}
render() {
return (
<div>
<Hamburger active={this.state.active} onClick={this.toggleMenu} />
<MenuOverlay active={this.state.active} />
</div>
)
}
}
so in hamburger you would just use the this.props.onClick to change the state of active and then in those corresponding components use the prop of this.props.active to determine what classes should be applied, etc.
Given that one element is not the parent of another element, you will have to pull up the variable keeping the toggle information up the chain of elements until it resides in one common place.
That is, keep the "active" state variable in an ancestor of the two elements and provide to the Hamburger a callback in the props that, when called, modifies the state of that ancestor component. At the same time, also pass the active state variable down to the MenuOverlay as a prop, and everything should work together.
See here for more information:
https://facebook.github.io/react/tutorial/tutorial.html#lifting-state-up
Specifically,
When you want to aggregate data from multiple children or to have two child components communicate with each other, move the state upwards so that it lives in the parent component. The parent can then pass the state back down to the children via props, so that the child components are always in sync with each other and with the parent.