ReactJS - valid function not working when called in componentDidMount() and componentDidUpdate() - javascript

******** EDITED TO SIMPLIFY EXAMPLE AND CLARIFY REQUIREMENT AND PROBLEM **********
I'm stumped with this one, I hope someone can help.
I have a nav bar that I need to run a function on to add .active classes to li elements if they have descendants of a.active.
The menu system is a React component: -
import React, {Component} from "react";
import { Link, NavLink } from 'react-router-dom'
import {activateMenu} from './ActivateMenu'
class SidebarMenu extends React.Component {
componentDidMount() {
activateMenu()
}
componentDidUpdate() {
activateMenu()
}
render() {
const renderNavLink = (to, text, icon, renderArrow = false) => {
return(
<NavLink to={to}>
<i className="bullet">{icon}</i>
<span>{text}</span>
{renderArrow ? <span className="pull-right-container">
<i className="angle-left"><FaAngleLeft /></i>
</span> : null}
</NavLink>
)
}
return (
<ul className="sidebar-menu" data-widget="tree">
<li className="">
{renderNavLink('/','Home',<FaHome />)}
</li>
<li className="treeview">
{renderNavLink("#",'Users',<FaGroup />, true)}
<ul className="treeview-menu">
<li>
{renderNavLink(userSearchSlug,'Search',<FaSearch />)}
</li>
</ul>
</li>
<button onClick={activateMenu}>Press Me</button>
</ul>
)
}
}
export default SidebarMenu
This will give me an HTML structure like this: -
<ul class="sidebar-menu tree" data-widget="tree">
<li class="treeview">
<a href="#">
<i class="fa fa-dashboard"></i> <span>Links</span>
<span class="pull-right-container">
<i class="fa fa-angle-left pull-right"></i>
</span>
</a>
<ul class="treeview-menu">
<li>
<i class="fa fa-circle-o"></i> Link1
</li>
<li>
<i class="fa fa-circle-o"></i> Link2
</li>
</ul>
</li>
</ul>
After React has rendered the HTML, I need to trigger a click event on the the .treeview > a node if any a.active nodes are found under .treeview-menu. So: -
<li class="treeview">
<a href="#" *****TRIGGER CLICK EVENT*****>
<i class="fa fa-dashboard"></i> <span>Links</span>
<span class="pull-right-container">
<i class="fa fa-angle-left pull-right"></i>
</span>
</a>
<ul class="treeview-menu">
<li>
<i class="fa fa-circle-o *****.ACTIVE CLASS HERE****"></i> Link1
</li>
<li>
<i class="fa fa-circle-o"></i> Link2
</li>
</ul>
</li>
activeMenu() looks like this: -
$('ul.sidebar-menu li.treeview:not(.menu-open)').has('a.active').find('a').trigger( "click" );
This function works when called from onClick() from a button on the page but it is not working in componentDidMount() and componentDidUpdate(). The function will run (tested with console.log() but not affect the HTML as it should. However, if I run it from a Button, it works perfectly. It also works perfectly when HMR runs.
I've no idea why this is happening. Does anyone have any ideas?

This is probably happening because you're selecting the element directly rather than using refs, although it's hard to say because we have no idea what $('ul.sidebar-menu .treeview a').parent().has('a.active').parent().find('.treeview a') is selecting, which is why this kind of code is an antipattern.
React may be in some state where it's not prepared to handle click events at those points. Try using something like the following:
import React, {Component} from "react";
import { Link, NavLink } from 'react-router-dom'
class SidebarMenu extends React.Component {
constructor(props) {
super(props);
this.menuRefs = [];
}
componentDidUpdate() {
if (this.menuRefs.length) {
this.menuRefs[0].click();
}
}
render() {
const renderNavLink = (to, text, icon, renderArrow = false) => {
return(
<NavLink to={to} innerRef={ref => this.menuRefs.push(ref)}>
<i className="bullet">{icon}</i>
<span>{text}</span>
{renderArrow ? <span className="pull-right-container">
<i className="angle-left"><FaAngleLeft /></i>
</span> : null}
</NavLink>
)
}
return (
<ul className="sidebar-menu" data-widget="tree">
<li className="">
{renderNavLink('/','Home',<FaHome />)}
</li>
<li className="treeview">
{renderNavLink("#",'Users',<FaGroup />, true)}
<ul className="treeview-menu">
<li>
{renderNavLink(userSearchSlug,'Search',<FaSearch />)}
</li>
</ul>
</li>
<button onClick={() => this.menuRefs[0] && this.menuRefs[0].click()}>Press Me</button>
</ul>
)
}
}
export default SidebarMenu
Notice
Now there's an array of "menuRefs" and you just use them like normal DOM elements.
We push to the menuRefs in the NavLink innerRef prop (found here)
Note however that you may want to keep a map to ensure that no duplicates get pushed into menuRefs.
To learn more about refs, visit the docs: https://reactjs.org/docs/refs-and-the-dom.html

Related

Why does adding useState result in a white screen in my react app?

The code below works completely fine and results in the image below.
import React from "react";
import "./App.css";
import { useState } from "react";
function App(){
return(
<body>
<div className="nav_bar">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"></link>
<ul className="top">
<div className="circle circle1">
<li className="material-icons noselect">
drag_handle
</li>
</div>
<div className="circle circle2">
<li className="material-icons noselect">
home
</li>
</div>
<div className="circle circle3">
<li className="material-icons noselect">
person_outline
</li>
</div>
</ul>
<nav>
<ul className="bottom">
<li className="material-icons noselect" id="feed-bottom">
drag_handle
</li>
<li className="material-icons noselect" id="home-bottom">
home
</li>
<li className="material-icons noselect" id="profile-bottom">
person_outline
</li>
</ul>
</nav>
</div>
</body>
);
}
export default App;
Result
Adding useState to get and set the current state causes the navbar to disappear and show a completely white screen. Specifically I am using useState to change the icon shown in the nav bar to text and to set the currernt state to the icon that is clicked. Code Below
import React from "react";
import "./App.css";
import { useState } from "react";
function App(){
const[selected, setselected] = useState('home');
if(selected === 'feed'){
const feed = document.getElementById('feed-bottom');
feed.innerHTML = 'FEED';
} else if (selected === 'profile') {
const profile = document.getElementById('profile-bottom');
profile.innerHTML = 'PROFILE';
}else{
const home = document.getElementById('home-bottom');
home.innerHTML = 'HOME';
}
return(
<body>
<div className="nav_bar">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"></link>
<ul className="top">
<div className="circle circle1">
<li className="material-icons noselect">
drag_handle
</li>
</div>
<div className="circle circle2">
<li className="material-icons noselect">
home
</li>
</div>
<div className="circle circle3">
<li className="material-icons noselect">
person_outline
</li>
</div>
</ul>
<nav>
<ul className="bottom">
<li className="material-icons noselect" id="feed-bottom" onClick={setselected('profile')}>
drag_handle
</li>
<li className="material-icons noselect" id="home-bottom" onClick={setselected('home')}>
home
</li>
<li className="material-icons noselect" id="profile-bottom" onClick={setselected('profile')}>
person_outline
</li>
</ul>
</nav>
</div>
</body>
);
}
export default App;
I've looked up many post that refernece similar issues but couldn't find one that pretained to mine. I would grealty appreciate some assitance.
This line
home.innerHTML = 'HOME';
will cause an error on mount, because on mount, the React elements returned from App haven't been returned yet - the container is still empty; the #feed-bottom element doesn't exist yet.
While you could fix it by only assigning if the element actually exists, it would be far better to do it the React way and put the values conditionally into the JSX. Don't use vanilla DOM methods in React unless you absolutely have to.
Another problem is that your listeners - eg onClick={setselected('home')} - run when computed (immediately), because you're invoking the setselected function instead of passing a function as a prop to onClick.
You also probably meant to pass feed in the feed-bottom element (instead of profile).
To implement your logic in the React way, you need something like:
<li className="material-icons noselect" id="feed-bottom" onClick={() => setselected('feed')}>
{ selected === 'feed' ? 'FEED' : 'drag_handle' }
</li>
<li className="material-icons noselect" id="home-bottom" onClick={() => setselected('home')}>
{ selected === 'home' ? 'HOME' : 'home' }
home
</li>
<li className="material-icons noselect" id="profile-bottom" onClick={() => setselected('profile')}>
{ selected === 'profile' ? 'PROFILE' : 'person_outline' }
person_outline
</li>
and remove all of the if(selected === 'feed'){ and the like code from the top.

How do I add a class to a react element when it is clicked

I have this react component and I am trying to create a hamburger menu but, I don't know how to add a class when a button is clicked. I am new to React so please go easy on me. When I run the below code it doesn't work and throws an error in the Chrome dev tools.
function Nav() {
const hamburger = document.getElementById('hamburger');
const navUL = document.getElementById('navul');
hamburger.addEventListener('click', () =>{
navUL.classList.toggle('show');
});
return (
<header>
<h2>
<a href="/">
Travis Helms
</a>
</h2>
<nav>
<ul className="flex-row" id='navul'>
<li className="mx-2">
<a href="#about">
About me
</a>
</li>
<li>
<span>Portfolio</span>
</li>
<li>
<span>Contact</span>
</li>
<li>
<span>Resume</span>
</li>
</ul>
</nav>
<button className="hamburger" id="hamburger">
<FontAwesomeIcon icon={faBars}></FontAwesomeIcon>
</button>
</header>
);
}
First of all, welcome to React! It's an amazing framework!
It would have been more helpful if you provided the error you are getting but from the code you shared I can see there are a few things you need to do differently.
Generally, you can use event listeners in React for certain things, and there is a certain way to add them properly, but for the most part they are not used very frequently. Instead, for example, to assign a click event to an element in React, all you need to do is pass it the onClick attribute with the function you want it to execute when it is clicked, for example <button onClick={doSomething}>click me</button>.
An easy way to control the state of your component is by using the useState hook and toggle the class of an element using state. In your case, it could look like this:
import React, { useState } from 'react';
const Nav = () => {
const [isOpen, setIsOpen] = useState(false);
return (
<header>
<h2>
Travis Helms
</h2>
<nav>
<ul className={"flex-row " + (isOpen ? "show" : "")}>
<li className="mx-2">
About me
</li>
<li>
<span>Portfolio</span>
</li>
<li>
<span>Contact</span>
</li>
<li>
<span>Resume</span>
</li>
</ul>
</nav>
<button
className="hamburger"
onClick={() => setIsOpen(!isOpen)}
>
<FontAwesomeIcon icon={faBars}></FontAwesomeIcon>
</button>
</header>
);
};
It looks like you are in a very early stage of learning React and for you to be successful coding with React you need to cover a few basics like state management, props, children and a few more things. I would recommend going on YouTube and find a good introduction video to React and code along to it! There are a lot of great resources in there for people who are just starting with React, just make sure it's something fairly recent (I'd say 2020 onwards).
Ok. So I went to a tutor and worked with them to come up with this solution to toggle my hamburger menu. Just thought I would add this answer for the benefit of other beginning React programmers.
import React,{ useState } from 'react';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome';
import { faBars } from '#fortawesome/free-solid-svg-icons';
function Nav(props) {
const [navClass, setnavClass]= useState("flex-row");
const [toggledNav, settoggledNav]= useState(true);
const addClass=() => {
if (toggledNav){
setnavClass("flex-row show");
settoggledNav(false);
}else {
setnavClass("flex-row");
settoggledNav(true);
}
}
return (
<header>
<h2>
<a href="/">
Travis Helms
</a>
</h2>
<nav>
<ul className={navClass} id='navul'>
<li className="mx-2">
<a href="#about">
About me
</a>
</li>
<li>
<span>Portfolio</span>
</li>
<li>
<span onClick={() => props.setmenuSelect(1)}>Contact</span>
</li>
<li>
<span>Resume</span>
</li>
</ul>
</nav>
<button onClick={addClass} className="hamburger" id="hamburger">
<FontAwesomeIcon icon={faBars}></FontAwesomeIcon>
</button>
</header>
);
}
export default Nav;

how to rendering element in state React JS?

i have a trouble at the moment will rendering html tag inside state. When i add 'div dangerouslySetInnerHTML={{__html: this.state.actions}} it seems can't because i will render in li tag for menu
this.state = {
sideBar : '<li>[object Object]</li>'
}
// when i render
render(){
return(
{ this.state.sideBar}
) ==> // '<li>[object Object]</li>'
// what should happen is to create a new list
[object Object]
You can get it working without using dangerouslySetInnerHTML. Here's an example of using <ul> and an array of <li> tags that you can render directly in React.
class App extends Component {
constructor(props) {
super(props);
this.state = {
list: ["list 1", "list 2", "list 3"]
};
}
render() {
return (
<div>
<ul>
{this.state.list.map((obj, index) => <li key={index}>{obj}</li> )}
</ul>
</div>
);
}
}
like this code
import React, { Component } from 'react'
import { Route, NavLink, Link } from 'react-router-dom'
import ReactTimeout from 'react-timeout'
class Menu extends Component {
constructor() {
super();
this.state = {
menuBar: [],
sideBar: []
}
}
async componentDidMount() {
const res = await fetch('http://localhost:3001/api/menu/' + 18)
const something = await res.json()
this.setState({ menuBar: something })
console.log(this.state.menuBar)
let menuBar = this.state.menuBar
let html = "";
let link_menu = []
for (var i = 0; i < menuBar.length; i++) {
if (menuBar.menu_url == 'dashboard' || menuBar.menu_flag_link === 1) {
var span_selected = ''
} else {
var span_selected = 'arrow'
}
if (menuBar.menu_flag_link == 0) {
var title_menu = menuBar[i].menu_title
link_menu =
<NavLink
to='javascript:;'
exact>title_menu</NavLink>
} else {
var title_menu = menuBar[i].menu_title
link_menu = <NavLink
to="/"
exact>title_menu</NavLink>
}
html += '<li>' + link_menu
if (menuBar[i].child.length > 0) {
html += "<ul class='sub-menu'>"
for (var j = 0; j < menuBar[i].child.length; j++) {
if (menuBar[j].child.menu_flag_link == 0) {
var link_menu2 = <NavLink
to='javascript:;'
exact>menuBar.child.menu_title</NavLink>
} else {
var link_menu2 =
<NavLink
to="/"
exact>menuBar.child.menu_title</NavLink>
}
html += "<li>, ${link_menu2}"
if (menuBar[i].child[j].length > 0) {
html += "<ul class='sub-menu'>"
for (var kjh = 0; kjh < menuBar[i].child[j].length; kjh++) {
var link_menu3 =
<NavLink
to="/"
exact>menuBar.child.menu_title</NavLink>
html += '<li> ${link_menu3} </li>'
}
html += '</ul>'
}
html += '</li>'
}
html += '</ul>'
}
html += '</li>'
}
this.setState({ sideBar: html })
}
render() {
let menuBar = this.state.menuBar
let sideBar = this.state.sideBar
// console.log(sideBar)
return (
<div class="page-sidebar-wrapper">
<div class="page-sidebar navbar-collapse collapse">
<ul class="page-sidebar-menu page-sidebar-menu-light" data-keep-expanded="true" data-auto-scroll="true" data-slide-speed="200">
<li class="sidebar-toggler-wrapper">
<div class="sidebar-toggler">
</div>
</li>
<li class="sidebar-search-wrapper">
<form class="sidebar-search " action="extra_search.html" method="POST">
<a href="javascript:;" class="remove">
<i class="icon-close"></i>
</a>
<div class="input-group">
<input type="text" class="form-control" placeholder="Search..." />
<span class="input-group-btn">
<i class="icon-magnifier"></i>
</span>
</div>
</form>
</li>
<li class="start active open">
<a href="javascript:;">
<i class="icon-home"></i>
<span class="title">Dashboard</span>
<span class="selected"></span>
<span class="arrow open"></span>
</a>
<ul class="sub-menu">
<li>
<NavLink
to="/"
exact>Home</NavLink>
</li>
<li class="active">
<NavLink to={{
pathname: '/blog',
hash: '#submit',
search: '?quick-submit=true'
}}
>Blog</NavLink>
</li>
<li>
<NavLink to={{
pathname: '/Table'
}}>Table</NavLink>
</li>
<li>
<NavLink to={{
pathname: '/BCC'
}}>BCC</NavLink>
</li>
</ul>
</li>
{this.state.sideBar}
<li>
<a href="javascript:;">
<i class="icon-folder"></i>
<span class="title">Multi Level Menu</span>
<span class="arrow "></span>
</a>
<ul class="sub-menu">
<li>
<a href="javascript:;">
<i class="icon-settings"></i> Item 1 <span class="arrow"></span>
</a>
<ul class="sub-menu">
<li>
<a href="javascript:;">
<i class="icon-user"></i>
Sample Link 1 <span class="arrow"></span>
</a>
<ul class="sub-menu">
<li>
<i class="icon-power"></i> Sample Link 1
</li>
<li>
<i class="icon-paper-plane"></i> Sample Link 1
</li>
<li>
<i class="icon-star"></i> Sample Link 1
</li>
</ul>
</li>
<li>
<i class="icon-camera"></i> Sample Link 1
</li>
<li>
<i class="icon-link"></i> Sample Link 2
</li>
<li>
<i class="icon-pointer"></i> Sample Link 3
</li>
</ul>
</li>
<li>
<a href="javascript:;">
<i class="icon-globe"></i> Item 2 <span class="arrow"></span>
</a>
<ul class="sub-menu">
<li>
<i class="icon-tag"></i> Sample Link 1
</li>
<li>
<i class="icon-pencil"></i> Sample Link 1
</li>
<li>
<i class="icon-graph"></i> Sample Link 1
</li>
</ul>
</li>
<li>
<a href="#">
<i class="icon-bar-chart"></i>
Item 3 </a>
</li>
</ul>
</li>
</ul>
</div>
</div>
)
}
}
export default ReactTimeout(Menu)

Clear DOM element after updating state in React

Hello I am trying to implement an auth logic to my page. The issue I am having, after updating the state of my page, it doesn't remove the authenticated stuffs. How, do I clear the elements?
I am trying to use a ternary operator, it does work. However it doesn't update when the user login or logout. It only renders one time, and it just sits there afterwards.
header.js
import React, { Component } from 'react';
import LoginHeader from './auth_logic/login_header';
import PublicHeader from './auth_logic/public_header';
export default class Header extends Component {
constructor(props) {
super(props);
this.state = {isAuth: false};
}
render() {
if (Meteor.userId) {
this.setState({isAuth: !this.state.isAuth});
}
else {
this.setState({isAuth: !this.state.isAuth});
}
return (
<div>
{this.state.isAuth ? <PublicHeader /> : <LoginHeader />}
</div>
);
}
}
Here is my LoginHeader, when the user is authenticated it will show additional information, and navigation authenticated links.
auth_logic/login_header.js
import React, { Component } from 'react';
import Accounts from '../accounts';
export default class LoginHeader extends Component {
render() {
return (
<div className="nav navbar-default">
<a className="navbar-brand">Nick Reviews</a>
<ul className="nav navbar-nav">
<li>
<a href="#">
To Watch
<span className="badge margin-left-3px">0</span>
</a>
</li>
<li>
<a href="#">
Reviews
<span className="badge margin-left-3px">0</span>
</a>
</li>
<li>
<Accounts />
</li>
<li> Create new item </li>
</ul>
</div>
);
}
}
It is exactly the same as the login_header.js file, but with less information.
auth_logic/public_header.js
import React, { Component } from 'react';
import Accounts from '../accounts';
export default class PublicHeader extends Component {
render() {
return (
<div className="nav navbar-default">
<a className="navbar-brand">Nick Reviews</a>
<ul className="nav navbar-nav">
<li>
<a href="#">
To Watch
<span className="badge margin-left-3px">0</span>
</a>
</li>
<li>
<a href="#">
Reviews
<span className="badge margin-left-3px">0</span>
</a>
</li>
<li>
<Accounts />
</li>
</ul>
</div>
);
}
}
Assuming that your userId is a prop from above, use componentWillReceiveProps() react life cycle method to update your state depends on your props. for more info refer this link - https://reactjs.org/docs/react-component.html
Header component should not keep isAuth state internally, it should come from the container component like Home/About(Router Page)... Change render condition according to prop.

React creating span where it shouldn't

I have a component to show a sort of Table of Contents. I'm struggling with react for it is creating a span tag where, in my understanding, it shouldn't.
The rendering functions is:
render() {
return(
<ul>
{
this.state.contents.map(function(li, i) {
let subItems = li.subItems.map(function(subLi, j) {
return (
<li>
<span>{subLi.title}</span>
</li>
)
}, this);
return (
<li tabIndex={i}>
<span><i class="fa fa-caret-right" />{li.title}</span>
<ul class="subitens">
{subItems}
</ul>
</li>
);
}, this)
}
</ul>
);
The line <span><i class="fa fa-caret-right" />{li.title}</span> is creating the following structure:
<span>
<i></i>
<span>Parent 1</span>
</span>
And I would like:
<span><i></i> Parent 1</span>
That span around my text is messing with my CSS.
I wrapped it up on a fiddle: https://jsfiddle.net/fpcvzsLo/
Any ideas on what am I getting wrong?
Thanks!
As #degr told, this is a known issue with old react versions. It emits tags along with every text.
All I did was to update my React to 0.15 and it works.
Thanks!
Try the following:
return (
<li tabIndex={i}>
<span className="fa fa-caret-right">
{li.title}
</span>
<ul className="subitens">
{subItems}
</ul>
</li>
);
https://jsfiddle.net/fpcvzsLo/4/ (included Font Awesome to show)
Output:
<span class="fa fa-caret-right" data-reactid=".0.1.0">Parent 2</span>

Categories