How can I read the current React.useRef in the child component? - javascript

I have a React.useRef in the parent component const portfolioRef = React.useRef(null) And want to use the current version of that in the child component const parent = portfolioRef.current. But when I use it in this way, it says cannot read property of current. Has anyone an idea how to fix this?
export function Portfolio() {
const portfolioRef = React.useRef(null)
return (
<div className={cx(styles.component, styles.scrollWrapper)}>
<div className={styles.topIcon} dangerouslySetInnerHTML={{ __html: arrow }} />
<div ref={portfolioRef} className={styles.scroll}>
<PortfolioItem
title='Article about Kaliber Academie'
text='I wrote an article about my experience at Kaliber'
link='https://medium.com/kaliberinteractive/hoe-technologie-het-hart-van-een-luie-scholier-veranderde-3cd3795c6e33'
linkTekst='See Article' />
</div>
</div>
)
}
function PortfolioItem({ text, title, link, linkTekst, portfolioRef }) {
const portfolioItemRef = React.useRef(null)
React.useEffect(() => {
const element = portfolioItemRef.current
const parent = portfolioRef.current
calculateDistance(parent, element)
}, [portfolioRef])
return (
<div ref={portfolioItemRef} className={styles.componentItem}>
<div className={styles.title}>{title}</div>
<div className={styles.content}>
<div className={styles.text}>{text}</div>
<div className={styles.links}>
<a className={styles.linkTekst} href={link}>{linkTekst} </a>
<div className={styles.linkIcon} dangerouslySetInnerHTML={{
__html:arrow }} />
</div>
</div>
</div>
)
}
function calculateDistance(parent, element) {
const parentRect = parent.getBoundingClientRect()
const parentCenter = parentRect.top + parentRect.height / 2
const elementRect = element.getBoundingClientRect()
const elementCenter = elementRect.top + elementRect.height / 2
const distance = Math.abs(elementCenter - parentCenter)
console.log(distance)
}

It seems that you don't pass the ref to the child at all. Try to pass it in props
<PortfolioItem
portfolioRef={portfolioRef} {/* here you are passing the ref in props */}
title="Article about Kaliber Academie"
text="I wrote an article about my experience at Kaliber"
link="https://medium.com/kaliberinteractive/hoe-technologie-het-hart-van-een-luie-scholier-veranderde-3cd3795c6e33"
linkTekst="See Article"
/>;

function PortfolioItem({ text, title, link, linkTekst, portfolioRef }) {
const portfolioItemRef = React.useRef(null) //line 1
const element = portfolioItemRef.current //line 2
const parent = portfolioRef.current //line 3
calculateDistance(parent, element)
return (
<div ref={portfolioItemRef} className={styles.componentItem}>
</div>
)
}
when the code execute in order
Line #1: create a reference to hold an element or value by using useRef nad initiate it to null,
Line #2: trying to access current value of the ref which is still null //Error, because ref is not holding any value at this moment
to access the value you have to put the logic inside useRef
React.useEffect(() => {
const element = portfolioItemRef.current
const parent = portfolioRef.current
calculateDistance(parent, element)
}, [])
not tested.

Related

Jquery code is not working in next js . Showing unexpected results but working on react

i am trying to implement an ui requirement. I want to add a active class name to the children div one at a time. 1st it will add the class in first child, and then the class will be removed and to be added in the 2nd child div. And it will infinitly itereate.
Here is my code in next js
$(".softwares_container").each(function () {
(function ($set) {
setInterval(function () {
var $cur = $set
.find(`.${st.active}`)
.removeClass(`${st.active}`);
//store inner html of current item
var $next = $cur.next().length
? $cur.next()
: $set.children().eq(0);
$next.addClass(`${st.active}`);
//store inner element of next item
//set inner html of current item to inner html of next item
var $next_inner = $next.children().eq(0);
setValue({
name: $next_inner.attr('alt'),
description: $next_inner.attr('data-info')
})
// setImage($next_inner.attr('src'))
}, 1000);
})($(this));
});
<div className={`softwares_container ${st.left_container}`}>
<div className={` ${st.img}`} alt="1">
<img src={ae.src} data-info="this is aftereffects" alt="After effects" />
</div>
<div className={st.img} alt="2">
<img src={pr.src} alt="Adobe Premiere pro" />
</div>
<div className={st.img}>
<img src={ps.src} alt="Adobe Photoshop" />
</div>
<div className={st.img}>
<img src={xd.src} alt="Adobe Xd" />
</div>
</div>
But it is not working.it is showing unexpected behaviour. It works fine in react .
Can anyone please give me an alternative solution or tell me how to fix the issue?
Here's the link where you can see the unexpected behaviour.
https://diptnc.ml/about
You can write an effect that sets the classname for elements in an array in a round-robin manner.
// Keep the interval id around so that
// it can be cleared when unsubscribing the effect.
let activeFxId;
/*
Applies active class to an array of HTMLElement in a round-robin manner.
*/
function activeFx(elements) {
activeFxId = setInterval(() => {
const elementsArr = [...elements];
let idx = elementsArr.findIndex((element) => {
return element.classList.contains('active');
});
if (idx === -1) {
idx = 0;
}
elementsArr[idx].classList.remove('active');
elementsArr[(idx + 1) % elementsArr.length].classList.add('active');
}, 2000);
return () => {
clearInterval(activeFxId);
};
}
How you provide this array of elements is left to you. An approach is to store a ref to the parent element containing them and pass that on to the function.
For example,
/* Example component */
import React, {useEffect, useRef} from 'react';
export default () => {
const ref = useRef();
useEffect(() => {
if (ref.current && ref.current.children) {
return activeFx(ref.current.children);
}
});
return (
<div ref={ref}>
<div>One</div>
<div>Two</div>
<div>Three</div>
</div>
);
};

List Item is Still visible even after deleting from array as well as local storage

I have a button named yes in the child component where I delete a list item from array and local storage using the props.id passed from the parent component.
The problem is the item is deleted from array and local storage but is still visible on the screen until I press delete button on another item in the parent component.
when I press delete button in the parent component it opens an overlay. When I press yes button on overlay I want list item to be removed from the screen immediately.
here is the code in the child component.
import React, { useCallback, useState } from "react";
import styles from "./Delete.module.css";
function Delete(props) {
// console.log();
const store = props.store;
const [no, setNo] = useState(false);
let [deleted, setDelted] = useState(store);
console.log(deleted);
console.log("Length :" + store.length);
const noBtnHandler = () => {
console.log("clicked");
setNo(!no);
props.setDel(false);
};
const yesBtnHandler = () => {
console.log("Dlete.js :" + props.id);
const filteredStore = deleted.filter((task) => task.id !== props.id);
setDelted([...filteredStore]);
localStorage.removeItem(props.id);
// console.log(deleted);
setNo(!no);
};
return (
<div className={`${no === false ? styles.del : styles.delOn}`}>
<section>
<div>
<h3>Are you Sure ?</h3>
</div>
<div>
<button type="button" onClick={yesBtnHandler}>
{" "}
Yes{" "}
</button>
<button type="button" onClick={noBtnHandler}>
{" "}
No{" "}
</button>
</div>
</section>
</div>
);
}
export default Delete;
You are passing store from the parent component to the Delete Component and setting a new state here 'deleted'. so you are only calling the setDeleted on the Delete component which wont affect the parent component.
The correct implementation is to have the store state in the parent component if you don't already have it. It is will still be same like deleted state but possibly with a better name. Say const [store, setStore] = useState([])
Define a function to filter out a particular record just like you have in the yesBtnHandler handler. but this function will be defined in the parent component. Say as an example
const removeRecord = (id) => {
const filteredStore = store.filter((task) => task.id !== id);
localStorage.removeItem(id);
setStore(filteredStore);
}
You now need to pass the a function to the Delete Component from the parent rather than passing the whole store. Like
<Delete removeFunc= {() => { removeRecord(id) }} />
After passing this function, you need to call it in your yesBtnHandler function. Like
function Delete({removeFunc}) {
...
const yesBtnHandler = () => {
removeFunc();
setNo(!no);
};
}
Try remove the trailing ...
const yesBtnHandler = () => {
console.log("Dlete.js :" + props.id);
const filteredStore = deleted.filter((task) => task.id !== props.id);
setDelted([filteredStore]);
//or setDelted(filteredStore);
localStorage.removeItem(props.id);
// console.log(deleted);
setNo(!no);
};
my understanding of this is that you're trying to change the state of a parent component from a child component. If that's what you're intending to do then you can do the following:
Define the function Delete(id) {...} inside the parent component rather than the child component.
Next, you'll have to pass both the function and the array to your child component, something like this: <ChildComponent array={array} onDelete={Delete}, where array is the array in your parent component and Delete is the function to delete an item from the array.
Finally, in your child component, with the props passed in correctly, i.e, function ChildComponent({array, Delete}) {...}you can now have access to the array in your parent component, and actually modify it like you'd like. To fire the event listener on the yes button in the child component, do this: <button type="button" onClick={() => Delete(array.id)}> {" "} Yes{" "} </button>
I hope this will be helpful

How to wrap text typed with 'typed.js' ReactJs?

I'm building a web app with React that generates random movie quotes...
The problem arises when the quote is too long and it overflows outside the parent div...
I've tried altering the css with display flex and flex-wrap set to wrap. It does't work.
Here is my code.
import React from 'react';
import Typed from 'typed.js';
import './App.css';
import quotes from './quotes.json';
const random_quote = () => {
const rand = Math.floor(Math.random() * quotes.length);
let selected_quote = quotes[rand].quote + ' - ' + quotes[rand].movie;
return selected_quote;
}
const TypedQuote = () => {
// Create reference to store the DOM element containing the animation
const el = React.useRef(null);
// Create reference to store the Typed instance itself
const typed = React.useRef(null);
React.useEffect(() => {
const options = {
strings: [
random_quote(),
],
typeSpeed: 30,
backSpeed: 50,
};
// elRef refers to the <span> rendered below
typed.current = new Typed(el.current, options);
return () => {
// Make sure to destroy Typed instance during cleanup
// to prevent memory leaks
typed.current.destroy();
}
}, [])
return (
<div className="type-wrap">
<span style={{ whiteSpace: 'pre' }} ref={el} />
</div>
);
}
const App = () => {
return (
<>
<div className='background' id='background'>
<div className='quote-box'>
<TypedQuote />
</div>
<button onClick={random_quote}>New Quote</button>
</div>
</>
);
}
export default App;
I have this idea where I could implement a function that adds '\n' after like 10 words or like maybe after a '.' or ',' (I could implement some logic here). But this seems like a longshot. Is there a fancier way to do this?? Any help would be appreciated.
Try the property below on the parent container.
word-wrap: break-word;
or the below if you want to break words as well
word-break: break-all;

Add events to svg string in component and render it

Basically what I need is to add event listeners to particular elements in my svg, that I receive as in param in my component
export default function RoomPlan({ svg, startDate, endDate}) {
const [selectedDesk, setSelectedDesk] = useState(null);
const [previouslySelectedDesk, setPreviouslySelectedDesk] = useState(null);
var doc = new DOMParser().parseFromString(svg, "text/xml");
var elements = Array.from(doc.querySelectorAll('#desk rect'));
if (elements) {
elements.forEach(function (el) {
el.addEventListener("click", function(){alert('hi')});
})
}
return (
<div>
<h2 className={css.labels}>Select desk</h2>
<div id={css.roomPlan} dangerouslySetInnerHTML={{ __html: new XMLSerializer().serializeToString(doc)}}></div>
<div className={css.bookingButtons}>
<Button id={css.cancelButton}>CANCEL</Button>
<Button onclick={bookDesk(selectedDesk)}>BOOK DESK</Button>
</div>
</div>
);
I get it as a plain string then parse it to DOM then add my eventListeners and serialize it back. But with this approach the ivents are not presented and do not trigger. So question is: how I can make it work as intendet with string -> DOM -> addedEvents -> render the svg
You can only attach event handlers after react rendered the markup into the DOM. For this to work you might look up react ref with callback.
const ref = useCallback(node => {
if (node !== null) {
/* do your magic here: node.querySelectorAll('#desk rect') ... */
}
}, []);
<div id={css.roomPlan} dangerouslySetInnerHTML={{ __html: svg}} ref={ref}></div>

Keeping inline style changes when dispatch is fired?

I'm having a problem with inline style changes that are reset when my dispatch is finished, because the state is being re-rendered, despite the other functionality of my component is working (you can still see that the counter is not stopping).
Here's a demonstration of what I mean.
You can see that the orange bar of the left box vanishes when the orange bar of the right bar finishes (the animation ends). Essentially what I'm doing here is changing the width property in inline styles.
import React, { useEffect, useRef } from "react";
import { useDispatch, connect } from "react-redux";
import { addProfessionExperience } from "../../actions/index";
import "./Professions.sass";
const timers = [];
const progressWidths = [];
const mapStateToProps = (state, ownProps) => {
const filterID = +ownProps.match.params.filter;
const { professions, professionExperience } = state;
return {
professions: professions.find(item => item.id === filterID),
professionExperience: professionExperience
};
};
const produceResource = (dispatch, profession, sub, subRef) => {
if(timers[sub.id]) return;
/*
* Begin the progress bar animation/width-change.
*/
Object.assign(subRef.current[sub.id].style, {
width: "100%",
transitionDuration: `${sub.duration}s`
});
/*
* Updates the progress text with the remaining time left until done.
*/
let timeLeft = sub.duration;
const timeLeftCountdown = _ => {
timeLeft--;
timeLeft > 0 ? setTimeout(timeLeftCountdown, 1000) : timeLeft = sub.duration;
subRef.current[sub.id].parentElement.setAttribute("data-duration", timeLeft + "s");
}
setTimeout(timeLeftCountdown, 1000);
/*
* Dispatch the added experience from profession ID and sub-profession level.
* We do not allow duplicate timers, only one can be run at a time.
*/
const timer = setTimeout(() => {
Object.assign(subRef.current[sub.id].style, {
width: "0%",
transitionDuration: "0.2s"
});
dispatch(addProfessionExperience({ id: profession.id, level: sub.level }));
delete timers[sub.id];
}, sub.duration * 1000);
timers[sub.id] = timer;
};
const isSubUnlocked = (professionMaxExperience, subLevel, professionExperience) => {
if(professionExperience <= 0 && subLevel > 1) return false;
return professionExperience >= getExperienceThreshold(professionMaxExperience, subLevel);
};
const getExperienceThreshold = (professionMaxExperience, subLevel) => (((subLevel - 1) * 1) * (professionMaxExperience / 10) * subLevel);
const ConnectedList = ({ professions, professionExperience }) => {
const currentExperience = professionExperience.find(item => item.profession === professions.id);
const subRef = useRef([]);
const dispatch = useDispatch();
useEffect(() => {
subRef.current = subRef.current.slice(0, professions.subProfessions.length);
}, [professions.subProfessions]);
return (
<div>
<div className="list">
<ul>
{professions.subProfessions.map(el => {
const unlocked = isSubUnlocked(
professions.maxExperience,
el.level,
(currentExperience ? currentExperience.amount : 0)
);
const remainingExperience = getExperienceThreshold(professions.maxExperience, el.level) - (currentExperience ? currentExperience.amount : 0);
return (
<li
key={Math.random()}
style={{ "opacity": unlocked ? "1" : "0.5" }}
>
<div className="sprite">
<img alt="" src={`/images/professions/${el.image}.png`} />
</div>
<div className="caption">{el.name}</div>
<div
className="progress-bar"
data-duration={unlocked ? `${el.duration}s` : `${remainingExperience} XP to Unlock`}
data-identifier={el.id}
>
<span ref={r => subRef.current[el.id] = r} ></span>
</div>
<div className="footer">
<button
className="btn"
onClick={() => unlocked ? produceResource(dispatch, professions, el, subRef) : false}
>
{unlocked ?
`Click` :
<i className="fa fa-lock"></i>
}
</button>
</div>
</li>
);
})}
</ul>
</div>
</div>
);
};
const List = connect(mapStateToProps)(ConnectedList);
export default List;
How can I make it so the orange bars persist on their own and not disappears when another one finishes?
One problem is that you're using Math.random() to generate your keys. Keys are what the virtual DOM uses to determine whether an element is the "same" as the one on a previous render. By using a random key, you're telling the virtual DOM that you want to spit out a brand new DOM element instead of reusing the prior one, which means the new one won't retain any of the side effects you placed on the original element. Read up on React's reconciliation for more info on this.
Try to use keys that logically represent the thing you're rendering. In the case of your code, el.id looks like it may be a unique identifier for the subprofession you're rendering. Use that for the key instead of Math.random().
Additionally, refs are going to make reasoning about your code really difficult. Rather than using refs to manipulate your DOM, use state manipulation and prop passing, and let React re-render your elements with the new attributes.

Categories