REACT nav bar requires two clicks - javascript

Summary
When a user clicks the hamburger icon className='.landing-page-hamburger' it takes two clicks to toggle the navigation className='landing-page-nav-links' and I can't figure out why.
The display value for .landing-page-nav-links is set to none by default in the CSS.
Problem
Navigation bar is requiring two clicks to toggle the display value
Expected Result
Would expect to only need one click
LandingPage.js
import React from 'react';
const LandingPage = () => {
const toggleNav = () => {
const navLinks = document.querySelector('.landing-page-nav-links');
if (navLinks.style.display === 'none') {
navLinks.style.display = 'initial';
} else {
navLinks.style.display = 'none';
}
}
return (
<nav className='landing-page-nav'>
<h1 className='take-notes-logo'>Take Notes</h1>
<span className='landing-page-hamburger' onClick={() => toggleNav()}>☰</span>
<ul className='landing-page-nav-links'>
<li>Sign Up</li>
<li>Log In</li>
</ul>
</nav>
);
};
export default LandingPage;

This is happening because your external CSS is not setting a style property on your element. The first click sets it to none because it isn't there at all. Your second click will now work as expected.
To fix this, either set the style of .landing-page-hamburger inline, or just toggle classes and let your external CSS handle it.
EDIT: The OP asks an excellent question. The .style property refers to the element's CSSStyleDeclaration object. This is a HTMLElement interface that you are directly manipulating. Notice, when you inspect an element in your console, the CSSStyleDeclaration does not necessarily match what you see in the CSS you get from the style sheet. Also, note that the properties are JS-style camelCase; this illustrates that this is not a direct mapping, yet they both effect the element.
So my initial description of the property being not there at all isn't accurate. It's there, and it's set to the empty string, which is the default CSSStyleDeclaration. So you set it to "none" on the second click and all goes well from there. Your initial CSS declaration is working correctly, but not figuring into your if statement because it's not referring to the same thing. Same effect, but you're interfacing with your element in a different way.

Related

When i use ref to nuxt-link, i can't change styles on it (Nuxt.js)

In my function I am using ref, which is binded to link, and when I try to change color (ref.style.color = 'red'), I see a error. Because ref which is binded to nuxt-link is Object, and it hasn't style property. I know that I can use the tag <a></a>, but does someone has ideas how can i make it with nuxt-link?
More info:
I have a link
<nuxt-link
ref="card"
#mousemove.native="move"
#mouseleave.native="leave"
#mouseover.native="over">
Click
</nuxt-link>
In my function i want to change link position, useng transform.
move () {
const card = this.$refs.card
card.style.transform = `perspective(500px)`
}
End i get this message in console
TypeError: Cannot set properties of undefined (setting 'transform')
By selecting nuxt-link using $refs will only return Vue Component instead of node element due to nuxt-link is a component in Nuxt.js.
The correct way to selecting node element is using $el.
Answer referred from here.
Example:
const card = this.$refs.card.$el
card.style.transform = `perspective(500px)`
To be mentioned, I'm not sure what you trying to achieve but assuming you want to modify the style of an element in Vue way, you are supposed to use :style="theElementStyles" then only you update the element style with this.theElementStyles = { transform: 'perspective(500px)' }.
More details about inline style binding can check on Vue documentation.

Change id of multiple item of array React

I'm using React JSX with Hooks, and I have a problem. I'd like to change a p id of all the vector items when I click on one of them.
In particular:
I have a vector and for every item of this vector I call a component:
cards.map((item, index) => (<Card key={index} card={item} index={index} hover={hover} /> ))}
Inside the Card componet I have the div tag with onClick event: <div onClick={TitleTransition}>
The TitleTransition function changes a state of click: function TitleTransition() { setClick(true);}
This is the p tag inside the Card component: <p id={click ? "titleTransition" : ""}>{props.card.p1}</p>
Hence, when I click in one of these item (I have called them card) I'd like to change the p id for all the items (cards).
Anyone could help me? Thank you!
this is more a guess answer than anything else, but i'm unable to comment before 50 reputation.
If i had to guess, I'd say you are trying to change the styling onClick on a Card component and maybe additionally reset the styling of the other cards.
In that case you should try to use instead of the id prop of the p tag either the style or the className prop. I imagine you have a css file with something similar to
#titleTransition {
/* Your styling if element is clicked */
}
If you use the className prop, which is the React equivalent of the html class attribute, you have to change your css line to something like
.titleTransition {
/* Your styling if element is clicked */
}
or
:global(.titleTransition) {
/* Your styling if element is clicked */
}
or whatever way of styling you prefer in react, there are so many.
If you want to change all Card elements, onClick you will probably have to save the dataset for the cards in a state in their parent component (where the .map(...) is used to add the elements) and add an attribute to that data, such as 'isClicked'. Then define a function that takes the index of the and add the function to the props of the Card, like () and call that function in the Card by adding it in the . Then use it in the :
<div onClick={() => props.onChildClickChangeDataState(props.index)} />
This will trigger the function in the parent, where you can iterate over the dataset and change the isClicked attribute to the value you like.
To change the styling you then can use
<p className={props.item.isClicked ? 'titleTransition' : '' />
This an approach that helps you changing all at once as well as avoiding the invalid HTML #CertainPerformance mentioned.
I hope i guessed right and it helps you on the right track. It is hard to help you more at the moment. Maybe you could share the Code of you Card Parent Component, Card, your CSS file, and why you would like to change all id's at once. Then somebody might be able to help you a bit better.

Why is document.getElementById() returning null when there is an element with that Id?

I'm trying to build a navbar in React that has a child component Menu that renders one of two different versions of the menu depending on the width on the div that the Menu is rendered inside. There is also a logo and company name that take up a relatively constant amount of width in the navbar and the div that holds the menu grows and shrinks to adjust for screen width and aspect ratio changes. Anyway, that div that grows and shrinks I gave an id="menu-section" so that I could use document.getElementById("menu-section").offsetWidth; to grab the available width and use that in the function that decided which version of the menu to render. The trouble is I get a TypeError: Cannot read property 'offsetWidth' of null. Why would the document.getElementById("menu-section") be failing to find the div and returning null?
function Menu(props) {
let availableWidthPx = document.getElementById("menu-section").offsetWidth;
if (availableWidthPx > 600) {
return expanded version;
}
return collapsed version
}
function Navbar(props) {
render(){
return (
<nav className="navbar">
...
<div id="menu-section">
<Menu />
</div>
</nav>
)
}
}
export default Navbar;
Because the DOM is not loaded yet. Here's how to fix it:
function Menu(props) {
React.useEffect(() => {
let availableWidthPx = document.getElementById("menu-section");
if (availableWidthPx) {
console.log(availableWidthPx.offsetWidth);
}
}, [])
return <div>Hello</div>;
}
Also, your Navbar functional component needs just return, not render
Because there isn't an element with that ID until all the rendering has finished and the result added to the DOM … but you are trying to call it from the render method.
You should also consider calling the Menu function after rendering, or define it in an afterRender() block.
Function Navbar doesn't add anything to the DOM.

React replace event.target with input

UPDATE
Here's are some demos
contentEditable demo - requires double click for H1 to become editable
replace with input demo - adopts event.target styles but makes the UI 'twitch' when rendered
So I have some functional components, let's say:
component1.js
import React from 'react';
const component1 = props => (
<div>
<h1>Title</h1>
</div>
);
export { component1 };
They are variable. event.target could be anything with text, so paragraph, heading, anything. I'm trying to let users edit content inline by clicking on it, so I'll pass a function editMode to these functional components, that'll update parent state with editing info, let's say like this:
<h1 onClick={event => {editMode(event, props.name, props.title, 'title')}}>title</h1>
This changes parent local state to have all the necessary information to grab the value from redux, define a target etc. For this example, props.name is the name of the component, props.title is the value, and 'title' is object key in redux.
So I'll add something to my component1.js and make it look a bit like this:
import React from 'react';
const component1 = props => (
<div>
{props.editState === 'true' &&
<EditLayout
name={props.name}
target={props.target}
value={props.value}
onChange={event => someFunc(event)}
/>
}
<h1>Title</h1>
</div>
);
export { component1 };
Now this works fine, except it doesn't scale. EditLayout, in this case, will just return an input with correct value. What I need it to do is to adapt to whatever is being clicked, get font size, background, padding, margin, position. Am I doing this right? Every way I try, I run into huge issues:
Idea 1 - move EditLayout component outside of the functional component
Issue: positioning
So I'll move EditLayout to parent component that contains both component1.js and EditLayout. This will allow me to manipulate it from inside the functional component, without having to include it everywhere. I'll then grab coordinates and other important information from event.target like so:
const coords = event.target.getBoundingClientRect();
const offsetX = coords.left;
const offsetY = coords.top;
const childHeight = coords.height;
const childWidth = coords.width;
const childClass = event.target.className;
I'll then wrap the EditLayout to return a container which contains an input, and apply size/coordinates to the absolutely positioned container. This'll present an issue of input being offset by a random amount of pixels, depending on how big/where is the event.target.
Idea 2 - pass relevant computed styles to EditLayout
Issue: twitching on render, and I have to add EditLayout for every possible event.target there is, as well as condition its' render
So I'll grab all important computed styles like this:
const computedTarget = window.getComputedStyle(event.target);
const childMargins = computedTarget.marginBottom;
const childPaddings = computedTarget.padding;
const childFontSize = computedTarget.fontSize;
const childTextAlign = computedTarget.textAlign;
And pass it to component1.js, and then pass it to EditLayout component inside the component1.js. I'll then condition theevent.target to hide if it's being edited like this:
<h1 className={ props.target === 'title' ? 'd-none' : ''}>Title</h1>
And condition the EditLayout to show only if it's needed:
{props.target === 'title' && <EditLayout />}
In this example, clicking h1 will show the input, but the layout itself with twitch on render. Input will have the exact same margin and font size as the h1, or event.target, but it'll appear bigger and extend the layout. Demo:
Idea 3 - Use conditional contentEditable
Issue: Requires double click to enable, doesn't work in safari, doesn't let me preselect the value
This is the weirdest of them all. I figured it'd be pretty simple, do something like this inside the functional component render:
<h1 contentEditable={props.target === 'title'} onClick={event => props.setTarget(event)}>Title</h1>
However, I have to double click to enable it. I have no idea why, if I attach a console log every time onClick is fired, I'll get correct outputs, I'll get the correct target value as well. I've tried numerous ways, but it simply requires double click. Even attempted to handle this inside the functional component, as most of the stuff is handled by a parent component, doesn't make a difference.
I have oversimplified the examples, so it's safe to assume/understand the following:
I am passing props in a correct fashion, they aren't undefined
I am using bootstrap
I am using styled components, and EditLayout is a styled component
which accepts props and turns them into CSS like: font-size: ${props
=> props.fontSize};
The values should be correct, I am not manipulating anything I get back from getComputedStyle() or getBoundingClientRect()
I am keen on keeping my functional components functional, and easy to
add. Functional components, in this case, are simple HTML structures,
and I'd like to keep them as simple as possible
So there's a neat solution to contentEditable requiring two clicks instead of one, instead of binding onClick and passing it to enable contentEditable, simply keep contentEditable true and handle the change however you like. Here's a working h1 that doesn't require two clicks to enable contentEditable, unlike the one in the demo
<h1
className="display-4 text-center"
contentEditable
suppressContentEditableWarning
onBlur={event => updateValues(event)}
>
Title
</h1>
The available methods for trigger update could be onBlur or onInput.

Highlight current page with CSS and Javascript

I've managed to get it so when I click on say "about" then it adds the class that I've called "active" however it doesn't remove the previous "active" on the home page link
Here is my HTML
<nav id="main-nav" role="navigation">
<ul>
<li>
Home
</li>
<li>
About
</li>
<ul>
</nav>
and here is my javascript
function setActive() {
aObj = document.getElementById('main-nav').getElementsByTagName('a');
for(i=0;i<aObj.length;i++) {
if(document.location.href.indexOf(aObj[i].href)>=0) {
aObj[i].className='active';
}
}
}
window.onload = setActive;
Basically what I want to be able to do is the following:
When on homepage it adds the class "active" so that I can highlight the link with CSS.
Then when I click on About it removes the "active" class from home (thus removing the highlighted css) and adds the "active" class to about (thus adding the highlighted css).
Just add aObj[i].className = ""; before the if condition.
function setActive() {
aObj = document.getElementById('main-nav').getElementsByTagName('a');
for(i=0;i<aObj.length;i++) {
aObj[i].className = "";
if(document.location.href.indexOf(aObj[i].href)>=0) {
aObj[i].className='active';
}
}
}
window.onload = setActive;
What this is basically doing is removing every class from all the links and only adding the 'active' class if it's the correct page. But since it seems the page is refreshing, I don't know how the class would persist from one page to the other, so it would be helpful to have a link to the code.
I checked the source of your page and I think the problem lies in the way the links are created (the JavaScript code you have posted is okay).
PROBLEM : The URL of the "Home" tab is
https://dhctranslations.com/
And the other URLs (hrefs) on the navigation are
About Tab - https://dhctranslations.com/about
Certified Translation Tab - https://dhctranslations.com/certified-translation-services
Pricing Tab - https://dhctranslations.com/pricing
...
...
The check you are doing in your function is 'indexOf' check. That is why the condition always returns true for the 'Home' tab (as this part of the URL is common to all other links).
SUGGESTION : To remedy this you can either change the 'indexOf' check and replace it with equality check in your code
OR change the URL of the 'Home' tab to something like
https://dhctranslations.com/home
Please note that these are just my suggestions and I am pretty sure that you can work out a solution that suits better to your application's design/architecture.
UPDATE : Here is the modified code that works (Please note this is just to give you an idea and is not a clean solution at all)
aObj = document.getElementById('main-nav').getElementsByTagName('a');
for(i=0;i<aObj.length;i++) {
aObj[i].className = "";
// Changed the condition
// Concatenated the "/" for comparison as the array does not have trailing "/"s stored
if(document.location.href === aObj[i].href+"/") {
aObj[i].className='active';
}
}

Categories