Component's JavaScript doesn't execute on routing with React router - javascript

I'm working on my first React project. I need to style some parts with JS because I'm dependent on the content. I'm calling the JS function from the components componentDidMount() upon JQuery's $(document).ready() because else it won't find the nodes to style.
When I enter the page or refresh the styling works as planned, but when I use the router's <Link> or <NavLink> the JS won't load.
Is there a way make it work?
class About extends React.Component {
constructor() {
super();
}
componentDidMount() {
$(document).ready( function () {
indentation($('.page__content__career').children(), 85);
indentation($('.page__about .page__content__text').children(), 100);
});
}
render() {
return (
<div className="page page__about">
<div className="page__content page__about__content">
<h1>about</h1>
<hr />
<div className="page__content__photo">
<img src={'../images/profile-picture-square--dark.jpg'} />
</div>
<div className="page__content__text">
<p>Hello, World</p>
</div>
<ul className="page__content__career">
{
cvList.map( (job) => {
return (
<CVElement
startTime={job.startTime}
endTime={job.endTime}
description={job.description}
place={job.place}
link={job.link}
/>
)
})
}
</ul>
</div>
</div>
);
}
}
export default About;
I also tried calling the respected functions when entering, which did not work
<Route path="/en/about" component={About} onEnter={() => console.log('Entered About')}/>

Why use jQuery to style? Why use jQuery at all? Just pass your styles in to the appropriate element using the style={{}} attribute, like this:
<ul className="page__content__career" style={{ marginLeft: '100px' }}>
...
</ul>

Ok, I found it out. It's actually pretty simple. As #Alex Dovzhanyn suggested I got rid of jQuery but then used DOM style objects.
componentDidMount() {
const pageAboutChildren = document.querySelector('.page__about .page__content__text').childNodes;
const pageAboutCareer = document.querySelector('.page__content__career').childNodes;
indentation(pageAboutChildren, 100);
indentation(pageAboutCareer, 85);
changeColor();
}
with indentation() being:
const indentation = (element, indent) => {
for(var i = 0; i < element.length; i++) {
element[i].style.marginLeft = indent * (i+1) + 'px';
}
}
I had some referencing troubles which further confused me
it's not so hard after all

Related

react-dnd DragSource that has child DragSource

I have a tree structure with a root "Tree" component that has a list of root "TreeNodes", then TreeNodes can have an arbitrary number of children.
So inside of the TreeNode render method I have
childrenHTML = this.state.children.map((child) => {
return (<TreeNode nodeClick ={this.props.nodeClick} parentNode={this}
key={child.childId} node={child} level={this.state.level+1} />);
});
and
const { isDragging, connectDragSource, connectDragPreview} = this.props;
Then the final return for the render method looks like
return connectDragSource(
<div>
<div style={nodeStyle}>
{connectDragPreview(
<div className = {"nodeContainer" + ' ' + this.state.nodeHover} onMouseLeave={this.nodeUnHover} onMouseOver={this.nodeHover} onClick={()=>this.props.nodeClick(this)}>
<img alt = {this.state.titleIcon} className = "titleIcon" src = {Connections.getImageURLByName(this.state.titleIcon)} />
<p className="nodeLabel"> {this.state.nodeName}</p>
{nodeLabelsHTML}
<DescriptiveIcons descriptiveIcons={this.state.icons} />
</div>
)}
</div>
{childrenHTML}
</div>
);
I am exporting:
export default DragSource(DragTypes.STRUCTURE, treeNodeSource, collect)(TreeNode);
Then in the parent Tree file I am exporting
export default DragDropContext(HTML5Backend)(Tree)
and rendering the rootnodes like
rootNodesHTML = rootNodes.map((node) => {
return <TreeNode nodeClick={this.props.nodeClick} key={node.childId} node={node} level={0}/>
});
...
return (
<div className="treeContainer">
<div className="wrapContainer">
{rootNodesHTML}
</div>
</div>
);
This works great but only for the rootnodes, when I try to render the children (the childrenHTML variable is only populated after the parent is clicked on) I get the following error:TypeError: connectDragPreview is not a function
Leading me to believe that those react-dnd props that come from the "collect" function is not being passed to the rootnodes but not the children. It seems like it should to me because the same code should be executed for the parents as for the children as its the same class... really stuck here.
I am relatively new to react, and new to ideas like HOCs so all tips or suggestions are appreciated. Thank you!
I was able to get this working. Check out the example posted at the end of the thread in
https://github.com/react-dnd/react-dnd/issues/332.
Ultimately the solution was to wrap the TreeNode in a "DragContainer" with a very simple render method
render(){
const {...props} = this.props;
return <TreeNode {...props}/>
}
Then in the TreeNode render method, when rendering the child nodes render a DragContainer instead, passing in all the usual props.
childrenHTML = this.state.children.map((child) => {
return <DragNodeContainer modalFunctions = {this.props.modalFunctions} nodeClick ={this.props.nodeClick} parentNode={this} key={child.childId} node={child} level={this.state.level+1} />;
});
I am still unsure as to the technical reason for this, however, the fix seems to work for other people and it works for me!

How can I conditionally show title tooltip if CSS ellipsis is active within a React component

Problem:
I'm looking for a clean way to show a title tooltip on items that have a CSS ellipsis applied. (Within a React component)
What I've tried:
I setup a ref, but it doesn't exist until componentDidUpdate, so within componentDidUpdate I forceUpdate. (This needs some more rework to handle prop changes and such and I would probably use setState instead.) This kind of works but there are a lot of caveats that I feel are unacceptable.
setState/forceUpdate - Maybe this is a necessary evil
What if the browser size changes? Do I need to re-render with every resize? I suppose I'd need a debounce on that as well. Yuck.
Question:
Is there a more graceful way to accomplish this goal?
Semi-functional MCVE:
https://codepen.io/anon/pen/mjYzMM
class App extends React.Component {
render() {
return (
<div>
<Test message="Overflow Ellipsis" />
<Test message="Fits" />
</div>
);
}
}
class Test extends React.Component {
constructor(props) {
super(props);
this.element = React.createRef();
}
componentDidMount() {
this.forceUpdate();
}
doesTextFit = () => {
if (!this.element) return false;
if (!this.element.current) return false;
console.log(
"***",
"offsetWidth: ",
this.element.current.offsetWidth,
"scrollWidth:",
this.element.current.scrollWidth,
"doesTextFit?",
this.element.current.scrollWidth <= this.element.current.offsetWidth
);
return this.element.current.scrollWidth <= this.element.current.offsetWidth;
};
render() {
return (
<p
className="collapse"
ref={this.element}
title={this.doesTextFit() ? "it fits!" : "overflow"}
>
{this.props.message}
</p>
);
}
}
ReactDOM.render(<App />, document.getElementById("container"));
.collapse {
width:60px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
<script crossorigin src="https://unpkg.com/react#16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.production.min.js"></script>
<div id="container"></div>
Since a lot of people are still viewing this question. I did finally figure out how to do it. I'll try to rewrite this into a working example at some point but here's the gist.
// Setup a ref
const labelRef = useRef(null);
// State for tracking if ellipsis is active
const [isEllipsisActive, setIsEllipsisActive] = useState(false);
// Setup a use effect
useEffect(() => {
if(labelRef?.current?.offsetWidth < labelRef?.current?.scrollWidth) {
setIsEllipsisActive(true);
}
}, [labelRef?.current, value, isLoading]); // I was also tracking if the data was loading
// Div you want to check if ellipsis is active
<div ref={labelRef}>{value}</div>
I use this framework agnostic snippet to this. Just include it on your page and see the magic happen ;)
(function() {
let lastMouseOverElement = null;
document.addEventListener("mouseover", function(event) {
let element = event.target;
if (element instanceof Element && element != lastMouseOverElement) {
lastMouseOverElement = element;
const style = window.getComputedStyle(element);
const whiteSpace = style.getPropertyValue("white-space");
const textOverflow = style.getPropertyValue("text-overflow");
if (whiteSpace == "nowrap" && textOverflow == "ellipsis" && element.offsetWidth < element.scrollWidth) {
element.setAttribute("title", element.textContent);
} else {
element.removeAttribute("title");
}
}
});
})();
From:
https://gist.github.com/JoackimPennerup/06592b655402d1d6181af32def40189d

React.js won't render in my Codepen

I'm trying to create a counter and I can't get what's in the render to show on the page.
Here is my code in JS from Codepen (with React and ReactDOM added in external JS)
class Counter extends React.Component {
constructor() {
super();
this.state = {
};
this.flash = "";
this.count = 0;
}
componentWillReceiveProps(nextProps) {
var newValue = nextProps.count;
if (this.count !== newValue) {
this.flash = this.flash === "flash1" ? "flash2" : "flash1";
}
}
render () {
return (
<div id="counter">
<button>+</button>
<button>-</button>
<div id="count" className={this.flash}>
{this.count}
</div>
</div>
);
};
}
ReactDOM.render(<Counter />, document.getElementById('countContainer'));
I can see in my JS code that normally the should be the color brown in my Codepen, so I'm obviously missing something (it's currently yellow).
In my HTML I have the following
<div id="countContainer">
</div>
Note: I'm not done with the code in regards to what it should be able to do in the end. I figured I should try and get it to render on the page before I continue.
My codepen is: URL
I tried this and got an error at the line where the first JSX tag is inside render(). I fixed it by adding the Babel preprocessor in addition to React and ReactDOM.
You can try this code to get the counter working with both increment and decrement count .
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
count :0
};
this.increment=this.increment.bind(this);
this.decrement=this.decrement.bind(this);
}
increment(){
this.setState({count:this.state.count+1});
}
decrement(){
this.setState({count:this.state.count-1});
}
render () {
return (
<div id="counter">
<button onClick={this.increment}>+</button>
<button onClick={this.decrement}>-</button>
<div>
{this.state.count}
</div>
</div>
);
}
}
ReactDOM.render(<Counter />, document.getElementById('countContainer'));
just define the functions and trigger them using onClick().
Here is the working counter in codepen with your code, modified it a bit to make it work. https://codepen.io/coderakki/pen/QOQqMP?editors=0010
Update : also, you need to define count within the state of your component .

React.js - Change image based on door puzzle answer

I've made this little game in React.js:
Demo: https://door-game.netlify.com/
App.js file: https://github.com/Blazej6/Door-game/blob/master/src/App.js
I want to render a picture in the center button that matches the choosen framework. 3 Vue renders vue, 3 react - react etc.
How do I make the logic to do that?
Did some experimental approches, like placing a color class anchor inside app and circle components but it seems to not reading current state at all, at least not from current angle, also tried to actualy use react router and encolse circle component in a link, but that really screws up the css for whatever reason
Is there really no one up to the task?
For a simple app like this, there is no need to integrate redux/mobx yet. What I recommend is something that is very common in React, and that is to lift your state up.
We can accomplish this through three steps:
Dumb down the Circleone, Circletwo, Circlethree components. They only need to know what the current angle is in order to render
ClawCircle should be told what image to render (or otherwise blank)
App needs to hold the state for all this information (and thus we've "lifted" the state up from CircleX to its parent, App).
Step 1
Instead of holding the currentAngle in the state, let's assume that information is given to us through the prop currentAngle. When a circle gets clicked, we'll just tell whoever created the circle that we were clicked on, because they will pass us a prop called onClick.
Since we now don't need to keep track of our state, we can make the component stateless and just turn it into a functional component.
For example, CircleOne might turn out to look more like this:
const CircleOne = ({ currentAngle, onClick }) => (
<div
className="App-logo small-logo"
alt="logo"
style={{ transform: `rotateZ(${currentAngle}deg)` }}
onClick={onClick}
>
<div className="little-circle one react">
{/* ... rest of your divs */}
</div>
);
Step 2
Next, let's change ClawCircle, we'll give it an optional imageClass prop that might be claw-react, claw-vue etc, or it might just be an empty string (update css accordingly to render the image too!). So the render method might change into this:
render() {
const circleStyle = { transform: `rotateZ(${this.props.currentAngle}deg)` };
return (
<div
className={`App-logo claw-circle ${this.props.imageClass}`}
alt="logo"
style={circleStyle}
onClick={this.rotateCircle.bind(this)}
/>
);
}
By the way, the bind call can be done in the constructor instead of the render method, this way we don't have to re-bind every time the component re-renders.
constructor(props) {
super(props);
// constructor code
this.rotateCircle = this.rotateCircle.bind(this);
}
// later: onClick={this.rotateCircle}
Step 3
This is the more complicated step, as we now have to delegate the heavy work to App instead of the individual Circles.
So App needs to know the angles of each individual circle, and handle what happens when each circle is clicked. Furthermore, when angles change, we want to check if all three of them are equal. If they are equal, we need to tell ClawCircle what image to render.
All in all, it would probably look something like this:
EDIT: I should have probably tried running this code before writing it on the fly here. Here's the full version (tested!) Just make sure you have claw-react claw-vue and claw-angular rules in your CSS
import React, { Component } from 'react';
import './App.css';
import { CSSTransitionGroup } from 'react-transition-group';
class HalfCircle extends Component {
render() {
return (
<div className="App-logo half-circle" alt="logo">
</div>
);
}
}
const Circleone = ({ currentAngle, onClick }) => (
<div
className="App-logo small-logo"
alt="logo"
style={{ transform: `rotateZ(${currentAngle}deg` }}
onClick={onClick}
>
<div className="little-circle one react"></div>
<div className="little-circle two angular"></div>
<div className="little-circle three vue"></div>
</div>
);
const Circletwo = ({ currentAngle, onClick }) => (
<div
className="App-logo big-logo"
alt="logo"
style={{ transform: `rotateZ(${currentAngle}deg` }}
onClick={onClick}
>
<div className="little-circle un react"></div>
<div className="little-circle dos angular"></div>
<div className="little-circle tres vue"></div>
</div>
);
const Circlethree = ({ currentAngle, onClick }) => (
<div
className="App-logo biggest-logo"
alt="logo"
style={{ transform: `rotateZ(${currentAngle}deg` }}
onClick={onClick}
>
<div className="little-circle ein react"></div>
<div className="little-circle zwei angular"></div>
<div className="little-circle drei vue"></div>
</div>
);
class ClawCircle extends Component {
constructor(props){
super(props)
this.state = {
currentAngle: 45,
anglePerClick: 360,
}
}
rotateCircle() {
const { currentAngle, anglePerClick } = this.state;
this.setState({
currentAngle: currentAngle + anglePerClick
})
}
render() {
const circleStyle = {
transform: `rotateZ(${this.state.currentAngle}deg)`
}
return (
<div
className={`App-logo claw-circle ${this.props.imageName}`}
alt="logo"
style={circleStyle}
onClick={this.rotateCircle.bind(this)}
/>
);
}
}
const getNameForAngle = (one, two, three) => {
if (one === two && one === three) {
switch(one) {
case 120:
return 'claw-react';
case 240:
return 'claw-vue';
case 360:
return 'claw-angular';
default:
return '';
}
}
return '';
};
class App extends Component {
constructor(props) {
super(props);
this.state = {
oneAngle: 120,
twoAngle: 120,
threeAngle: 120,
};
this.handleOneClick = this.handleOneClick.bind(this);
this.handleTwoClick = this.handleTwoClick.bind(this);
this.handleThreeClick = this.handleThreeClick.bind(this);
}
handleClick(circle) {
const nextAngle = this.state[circle] + 120;
this.setState ({
[circle]: nextAngle
});
}
handleOneClick() {
this.handleClick('oneAngle');
}
handleTwoClick() {
this.handleClick('twoAngle');
}
handleThreeClick() {
this.handleClick('threeAngle');
}
render() {
const { oneAngle, twoAngle, threeAngle } = this.state;
const imageName = getNameForAngle(oneAngle, twoAngle, threeAngle);
return (
<div className="App">
<header className="App-header">
<Circleone
currentAngle={oneAngle}
onClick={this.handleOneClick}
/>
<Circletwo
currentAngle={twoAngle}
onClick={this.handleTwoClick}
/>
<Circlethree
currentAngle={threeAngle}
onClick={this.handleThreeClick}
/>
<ClawCircle imageName={imageName} />
<HalfCircle/>
</header>
</div>
);
}
}
export default App;
Ok it seems that components encapsulation really disfavors this king of fun in here, anyway I got the app working with pure js, all hail global variables!
Here is the codepen if anyone needs it: https://codepen.io/Raitar/pen/OOWRzb
And of course the JS code:
var angle=0;
var angle2=0;
var angle3=0;
count = 0;
count2 = 0;
count3 = 0;
document.getElementById("small-logo").addEventListener("click", rotateCircle)
document.getElementById("big-logo").addEventListener("click", rotateCircle2)
document.getElementById("biggest-logo").addEventListener("click", rotateCircle3)
function rotateCircle(){
angle+=120;
this.style.webkitTransform="rotate("+angle+"deg)";
count += 1;
if (count > 2) {
count = 0;
}
}
function rotateCircle2(){
angle2+=120;
this.style.webkitTransform="rotate("+angle2+"deg)";
count2 += 1;
if (count2 > 2) {
count2 = 0;
}
}
function rotateCircle3(){
angle3+=120;
this.style.webkitTransform="rotate("+angle3+"deg)";
count3 += 1;
if (count3 > 2) {
count3 = 0;
}
}
angular = "background-image:
url(https://raw.githubusercontent.com/Blazej6/Door-
game/master/src/img/angular.png);"
react = "background-image:
url(https://raw.githubusercontent.com/Blazej6/Door-
game/master/src/img/react.png);"
vue = "background-image: url(https://raw.githubusercontent.com/Blazej6/Door-
game/master/src/img/vue.png);"
document.getElementById("claw-circle").addEventListener("click",
changeCenter)
var x = document.getElementById("claw-circle")
function changeCenter() {
if (count == 0 && count2 == 0 && count3 == 0) {
x.style.cssText = angular;
} else if(count == 1 && count2 == 1 && count3 == 1) {
x.style.cssText = react;
} else if(count == 2 && count2 == 2 && count3 == 2) {
x.style.cssText = vue;
}
}

React CSSTransitionGroup does not work even with key being set

I have been trying since yesterday to make an animation to my image carousel. As far as I understand, you wrap the content to be animated with the CSSTransitionGroup and make sure it stays in the dom and also specify a unique key to each child of the transition group. I believe I have followed all this yet I see no transition.
One thing worth to mention, While I was trying to get this working I suspected if something could be wrong with the key, so I tried setting the key with a random string. The key would change every-time the state changes, and for some unknown reason I could see the animation. Can someone explain this to me.
I am not sure where I am going wrong, whether the version of transition group or in setting the key to children, No clue !
Below is the code replicating my problem.
var CSSTransitionGroup = React.addons.CSSTransitionGroup
class Images extends React.Component {
constructor(props){
super(props);
this.state = {
showComponent: false,
}
}
componentWillReceiveProps(nextProps){
if (this.props.name === nextProps.showComponentName){
this.setState({
showComponent: true,
})
} else {
this.setState({
showComponent: false,
})
}
}
render() {
if (this.state.showComponent){
return (
<img src={this.props.url} />
)
} else {
return null;
}
}
}
class TransitionExample extends React.Component {
constructor (props) {
super(props);
this.onClick = this.onClick.bind(this);
this.state= {
showComponentName: null,
}
}
onClick(button) {
this.setState({
showComponentName: button.currentTarget.textContent,
})
}
render() {
var imageData = [
"http://lorempixel.com/output/technics-q-c-640-480-9.jpg",
"http://lorempixel.com/output/food-q-c-640-480-8.jpg",
"http://lorempixel.com/output/city-q-c-640-480-9.jpg",
"http://lorempixel.com/output/animals-q-c-640-480-3.jpg"
];
var images = [];
for (var i in imageData) {
i = parseInt(i, 10);
images.push(
<Images url={imageData[i]} showComponentName={this.state.showComponentName} name={imageData[i]} key={imageData[i]} />
);
}
return (
<div>
<div>
<button onClick={this.onClick}>{imageData[0]}</button>
<button onClick={this.onClick}>{imageData[1]}</button>
<button onClick={this.onClick}>{imageData[2]}</button>
<button onClick={this.onClick}>{imageData[3]}</button>
</div>
<div className="transitions">
<CSSTransitionGroup
transitionName="viewphoto"
transitionEnterTimeout={2000}
transitionLeaveTimeout={2000}
transitionAppearTimeout={2000}
transitionAppear={true}
transitionEnter={true}
transitionLeave={true}>
{images}
</CSSTransitionGroup>
</div>
</div>
);
}
}
ReactDOM.render(
<TransitionExample />,
document.getElementById('container')
);
I am also providing the link to the example on jsfiddle
The problem with your code is that images is always an array of elements that don't mount/unmount. The correct approach for this is to change the child. For example, if you substitute the return of the render method of your fiddle with this:
return (
<div>
<div>
<button onClick={this.onClick}>{imageData[0]}</button>
<button onClick={this.onClick}>{imageData[1]}</button>
<button onClick={this.onClick}>{imageData[2]}</button>
<button onClick={this.onClick}>{imageData[3]}</button>
</div>
<div className="transitions">
<CSSTransitionGroup
transitionName="viewphoto"
transitionEnterTimeout={2000}
transitionLeaveTimeout={2000}
transitionAppearTimeout={2000}
transitionAppear={true}
transitionEnter={true}
transitionLeave={true}>
<img src={this.state.showComponentName} key={this.state.showComponentName}/>
</CSSTransitionGroup>
</div>
</div>
);
The animation works! Using a simple img instead of your Images component and giving it the image url (this only works when you have clicked a button, showComponentName should be initialized to show the first image). You could also use a custom component of course, but the point here is that the children elements of CSSTransitionGroup must be changed if you want the animation to trigger because otherwise you are always rendering the same four Images components no matter whether they return the img or not. You might want to check out react-css-transition-replace since it usually works better when it comes to replacing.

Categories

Resources