I am trying to access the horizontal scroll bar with buttons in react but turns out it is not working because of error that cannot read the null values of .scrollLeft.
<>
<div className="containerOuterSider">
<FaAngleLeft className= "FaAngleLeft" onClick={slide('left')}/>
<div id="container3" className="container3">
{products &&
products
.map((product) => (
<AAsOfLowNav key={product._id} product={product} />
))
.reverse()}
</div>
<FaAngleRight className="FaAngleRight" onClick={slide('right')}/>
</div>
</>
and the main function is
function slide(direction) {
var container = document.getElementById('container3');
let scrollCompleted = 0;
var slideVar = setInterval(function() {
if (direction === 'left') {
container.scrollLeft -= 10;
} else {
container.scrollLeft += 10;
}
scrollCompleted += 10;
if (scrollCompleted >= 100) {
window.clearInterval(slideVar);
}
}, 50);
}
The error I am facing is:
Uncaught TypeError: Cannot read properties of null (reading
'scrollLeft')
at LowerCatNav.js:31:1 Uncaught TypeError: Cannot read properties of null (reading 'scrollLeft')
at LowerCatNav.js:33:1
How can I make it right? Should I use the hooks or anything else?
Issue
You are immediately invoking the slide function while rendering:
<FaAngleLeft
className="FaAngleLeft"
onClick={slide('left')} // <-- immediately invoked when rendered
/>
...
<FaAngleRight
className="FaAngleRight"
onClick={slide('right')} // <-- immediately invoked when rendered
/>
The React component hasn't been fully rendered and pushed to the DOM, so queries to the DOM, i.e. document.getElementById('container3'), return null.
Solution
Fix the click handler so slide is not being immediately invoked.
<FaAngleLeft
className="FaAngleLeft"
onClick={() => slide('left')} // <-- asynchronously invoked when clicked
/>
...
<FaAngleRight
className="FaAngleRight"
onClick={() => slide('right')} // <-- asynchronously invoked when clicked
/>
It is considered a React anti-pattern to directly query the DOM for DOMNodes, use a React ref for this.
Example:
...
const containerRef = React.useRef(); // <-- (1) create Ref
const sliderTimerRef = React.useRef();
useEffect(() => {
return () => {
// clear any running intervals on component unmount
clearInterval(sliderTimerRef.current);
};
}, []);
...
function slide(direction) {
// clear any previously set intervals and reset scrollCompleted
clearInterval(sliderTimerRef.current);
let scrollCompleted = 0;
sliderTimerRef.current = setInterval(function() {
const container = containerRef.current; // <-- (3) access current ref value
if (direction === 'left') {
container?.scrollLeft -= 10; // <-- (4) Optional Chaining null check
} else {
container?.scrollLeft += 10; // <-- (4) Optional Chaining null check
}
scrollCompleted += 10;
if (scrollCompleted >= 100) {
clearInterval(sliderTimerRef.current);
}
}, 50);
}
...
return (
<>
<div className="containerOuterSider">
<FaAngleLeft className="FaAngleLeft" onClick={() => slide('left')}/>
<div
ref={containerRef} // <-- (2) attach ref to element
id="container3"
className="container3"
>
{products
.map((product) => (
<AAsOfLowNav key={product._id} product={product} />
))
.reverse()
}
</div>
<FaAngleRight className="FaAngleRight" onClick={() => slide('right')}/>
</div>
</>
);
slide is called when #container3 is not yet created.
Change onClick={slide(...)} to onClick={() => slide(...)}
Access to global elements (e.g. by id) should be avoided.
Prefer ref/createRef/useRef to access elements belonging to your component.
Related
I wanted to adapt this code show that, for example if you hovered over a specific , then the relating would also show. useState seems to be the only way to make this work in React as I tried a different example with eventlistner which crashed the page.
const Showstuff = () => {
const [isHovering, setIsHovering] = useState(false);
const handleMouseOver = () => {
setIsHovering(true);
};
const handleMouseOut = () => {
setIsHovering(false);
};
return(
<div>
<div onMouseOver={handleMouseOver} onMouseOut={handleMouseOut}>
Hover over div #1 here
</div><br /><br />
<div>
Hover over div #2 here
</div>
{isHovering && (
<div>
<h2>Text here visible when hovering div 1</h2>
</div>
)}
</div>
)
};
export default Showstuff;
I made multiple useStates for each items as a work around, but this means there's 3x const lines for each item I want to add, and I have 6 elements to hover. Can this be combined into a shorter code? I also tried:
const el = document.getElementById('container');
const hiddenDiv = document.getElementById('hidden-div');
el.addEventListener('mouseover', function handleMouseOver() {
hiddenDiv.style.visibility = 'visible';
});
el.addEventListener('mouseout', function handleMouseOut() {
hiddenDiv.style.visibility = 'hidden';
});
from a guide on bobbyhadz website but this would require the same idea of making multiple lines of the same code with different names. This works immediately after saving the page in vscode but then shortly afterwards crashes the page, and does not work - I assume it is not React compatible.
I would do something like this :
function App() {
const [isHovered, setIsHovered] = useState(null)
const handleMouseOver = (e) => {
switch (e.target.id) {
case "1":
setIsHovered(1)
break
case "2":
setIsHovered(2)
break
}
}
return (
<div className="App">
<div id="1" onMouseOver={handleMouseOver} onMouseOut={() => setIsHovered(null)}>
DIV 1
</div>
<div id="2" onMouseOver={handleMouseOver} onMouseOut={() => setIsHovered(null)}>
DIV 2
</div>
{isHovered && <h2>{isHovered === 1 ? "Div 1 is hovered" : "Div 2 is hovered"}</h2>}
</div>
)
}
That way you only use one useState hook and set the value of isHovered depending on the targetted div's id.
Instead of having isHovering be a boolean, make it something else. If your design means you can only hover one thing at a time, the simplest solution is to make isHovering just hold some ID. But if you have overlapping elements where it's possible to hover multiple at once, you can use an array of IDs, or an object where each key is an ID and each value is a boolean.
You need to modify your onMouseOver (and, possibly, onMouseOut) function(s) to pass an ID as an argument.
Here is a simple example:
const Showstuff = () => {
const [isHovering, setIsHovering] = useState();
const handleMouseOver = (id) => setIsHovering(id);
const handleMouseOut = () => setIsHovering();
return (
<div>
{[1, 2, 3, 4, 5, 6].map((n) => (
<>
<div
onMouseOver={() => handleMouseOver(n)}
onMouseOut={handleMouseOut}
>
{`Hover over div #${n} here`}
</div>
</>
))}
{isHovering && (
<div>
<h2>{`Text here visible when hovering div ${isHovering}`}</h2>
</div>
)}
</div>
);
};
You don't have to use a map function if that won't work for you. That's just what I'm doing in this example. Just make sure your IDs are unique.
If you need to be able to hover multiple items at once, you'll have to modify the handleMouseOver and handleMouseOut functions. For example, if you wanted to store the values in an array, you can do something like this:
const handleMouseOver = (id) =>
setIsHovering((oldIsHovering) => [...oldIsHovering, id]);
const handleMouseOut = (id) =>
setIsHovering((oldIsHovering) => oldIsHovering.filter((n) => n !== id));
You can use an array as a state variable and map over it:
export default function App() {
const [isHovering, setIsHovering] = useState(new Array(4).fill(false));
function handleMouseEnter(i) {
setIsHovering((prev) => {
const next = [...prev];
next[i] = true;
return next;
});
}
function handleMouseLeave(i) {
setIsHovering((prev) => {
const next = [...prev];
next[i] = false;
return next;
});
}
return (
<>
{isHovering.map((_, i) => (
<span
onMouseEnter={() => handleMouseEnter(i)}
onMouseLeave={() => handleMouseLeave(i)}
></span>
))}
{isHovering.map((v, i) => (
<p>
Hovering on {i}: {v.toString()}
</p>
))}
</>
);
}
https://stackblitz.com/edit/react-ts-owprrr?file=App.tsx
This is good if the HTML elements are the same. If all your elements are unique, you are better off using multiple states and naming them uniquely. You'll just end up making the design more confusing by trying to save a few lines of code.
I have 5 div's and 5 buttons. On each button clicked one div become visible. the other four gets hidden. I just want to ask is there any other better way to do it. Give suggestion as much as possible. Thank you!
let id1 = React.createRef()
let id2 = React.createRef()
let id3 = React.createRef()
let id4 = React.createRef()
let id5 = React.createRef()
function iid1() {
id1.current.classList.remove('hidden')
id1.current.classList.add('contents')
id2.current.classList.add('hidden')
id3.current.classList.add('hidden')
id4.current.classList.add('hidden')
id5.current.classList.add('hidden')
}
function iid2() {
id1.current.classList.add('hidden')
id2.current.classList.remove('hidden')
id2.current.classList.add('contents')
id3.current.classList.add('hidden')
id4.current.classList.add('hidden')
id5.current.classList.add('hidden')
}
function iid3() {
id1.current.classList.add('hidden')
id2.current.classList.add('hidden')
id3.current.classList.remove('hidden')
id3.current.classList.add('contents')
id4.current.classList.add('hidden')
id5.current.classList.add('hidden')
}
function iid4() {
id1.current.classList.add('hidden')
id2.current.classList.add('hidden')
id3.current.classList.add('hidden')
id4.current.classList.remove('hidden')
id4.current.classList.add('contents')
id5.current.classList.add('hidden')
}
function iid5() {
id1.current.classList.add('hidden')
id2.current.classList.add('hidden')
id3.current.classList.add('hidden')
id4.current.classList.add('hidden')
id5.current.classList.remove('hidden')
id5.current.classList.add('contents')
}
I just want the above code to be more efficient & readable. I'm looking for best practices for javascript. You can also tell me you would you solve this problem. I'm not looking for answer's. I'm here to seek best practices,
Thank you.
Use state to identify which div is the selected one. Buttons will change the state and your app will re-render adjusting the classNames for the divs.
const App = () => {
const [selected,setSelected] = React.useState(0);
const DIV_IDS = [0,1,2,3,4,5];
const selectItems = DIV_IDS.map((item) => {
return(
<button onClick={() => setSelected(item)}>{item}</button>
);
});
const divItems = DIV_IDS.map((item) => {
return (
<div key={item} className={selected === item ? 'visible' : 'hidden'}>
I am div {item}
</div>
);
});
return(
<div>
<div>{selectItems}</div>
<div>{divItems}</div>
</div>
);
};
ReactDOM.render(<App/>, document.getElementById('root'));
.hidden {
visibility: hidden;
}
.visible {
visibility: visible;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
May be best to just have the class in your JSX element classes. Something like:
<element className={(condition_for_shown) ? 'contents' : 'hidden'}>
...
</element>
and then for each button would be:
<button type="button" onClick={() => setStateConditonToSomething}>
...
</button>
Note that you'll need to store the condition in react state with useState or however you wanna store it.
The way i'd do it is -
const DivHidingComponent = ({ elementCount = 5 }) => { // element count defaults to 5
const [visibilityIndex, setVisibilityIndex] = useState(0);
const onClickCallback = useCallback((index) => () => {
setVisibilityIndex(index);
})
const buttonGroup = useMemo(() => {
const buttonGroup = [];
for (let i = 0; i < elementCount; i++) {
buttonGroup.push(
<button key={`${i}-button`} onClick={onClickCallback(i)} />
)
}
return buttonGroup;
}, [elementCount])
// only re-runs on a button click
const divGroup = useMemo(() => {
const divGroup = [];
for (let i = 0; i < elementCount; i++) {
divGroup.push(
<div key={`${i}-div`} style={{ visibility: visibilityIndex === i ? 'visible' : 'hidden' }} />
);
}
return divGroup;
}, [visibilityIndex]);
return (
<div>
<div>
{buttonGroup}
</div>
<div>
{divGroup}
</div>
</div>
);
}
I set the style directly in the div group loop, but you could assign a class name or go about setting the style however you want.
Div's visibility is set by the visibility index that is driven by the buttons being clicked on.
I passed the elementCount variable in the props so you could scale this to however many elements you want. 5 or a 1000. I assigned elementCount a value of 5 that will act as a default for when no value is passed when the component is initialized.
Also, you could drop the useMemo and useCallback hooks and it would still execute fine. But it would help improve performance if you say, set the element count to 10,000. With those hooks in place it'd only re-build the div group on re-render. That'd be the difference between running the loops 20k times (10k for buttons, 10k for divs).
I added the last paragraph incase you were not aware of React Hooks!
I hope this helps!
I need help with my code. The thing I want create is to change className according to page url
So when I scroll or go to page /kontakt I want to change class from "hamburger" to "hamburger active".
I also tried regex. Any ideas?
Here is code:
const HamMenu = ()=> {
const [sidebar, setSidebar] = useState(false)
const [burger, setBurger] = useState(false)
const url = window.location.href;
const showSidebar = () => setSidebar(!sidebar)
const changeColor = () => {
if((window.scrollY >= 60) || (url.indexOf("kontakt") > -1)){
setBurger(true);
} else {
setBurger(false);
}
}
window.addEventListener('scroll', changeColor);
return (
<StyledMenu>
<div>
<Link to="#" className={sidebar ? 'menu-bars open' : 'menu-bars'} >
<FontAwesomeIcon
icon={faBars}
size="2x"
className={burger ? 'hamburger active' : 'hamburger'}
onClick={showSidebar}
/>
</Link>
</div>
Dealing with window in Gatsby could be a little bit tricky because two fundamental reasons:
window object is only defined in the browser, so it will work perfectly under gatsby develop but you will need to add a "hack" to avoid a code-breaking in the gatsby build (because there's no window in the Node server).
Treating the window outside React ecosystem, may break the rehydration of the components. This means that React won't potentially know what components need to re-render on-demand, causing unmounted components, especially when navigating forward and backward using the browser's history.
There are a few workarounds to achieve what you're trying to do.
Gatsby, by default, provides a location prop in all top-level components (pages). So you can pass it to any child components at any time to change the class name based on its value:
const IndexPage = ({ location }) =>{
return <Layout>
<HamMenu location={location} />
<h1> some content</h1>
</Layout>
}
Then, in your <HamMenu> component:
const HamMenu = ({ location })=> {
const [sidebar, setSidebar] = useState(false)
const [burger, setBurger] = useState(false)
const url = window.location.href;
const showSidebar = () => setSidebar(!sidebar)
const changeColor = () => {
if((window.scrollY >= 60) || (url.indexOf("kontakt") > -1)){
setBurger(true);
} else {
setBurger(false);
}
}
useEffect(() => {
if(typeof window !== "undefined"){
const url = window.location.href
const changeColor = () => {
setBurger(window.scrollY >= 60 || url.contains("kontakt"))
}
window.addEventListener('scroll', changeColor)
return () => {
window.removeEventListener('scroll', changeColor)
}
}
}, [])
return (
<StyledMenu>
<div>
<Link to="#" className={sidebar ? 'menu-bars open' : 'menu-bars' location.pathname.includes("your-page")? ''some-class' : 'some-other-class' } >
<FontAwesomeIcon
icon={faBars}
size="2x"
className={burger ? 'hamburger active' : 'hamburger'}
onClick={showSidebar}
/>
</Link>
</div>
I would suggest another approach to get the scroll position rather than using directly the window, using React-based approach to avoid what I was pointing before (How to add a scroll event to a header in Gatsby).
However, I've fixed your initial approach, wrapping it inside a useEffect with empty deps ([]). This function will be triggered once the DOM tree is loaded, to avoid the code-breaking window use that I was talking about. Alternatively to url.indexOf("kontakt") > -1 you may want to use url.includes("kontakt") which is way more readable.
Regarding the rest, it's quite self-explanatory. Destructuring the location props you get access to a bunch of data, the pathname property holds the page name so based on that, you can add a ternary condition wherever you want, such as location.pathname.includes("your-page") ? ''some-class' : 'some-other-class' (includes is more semantic in my opinion).
As you see, I've fixed your approach but I've also added a React/Gatsby-based one, choose what makes you feel comfortable.
React components rendering server-side (such as during gatsby build) do not have access to window, and in order to avoid breaking hydration, the first render needs to match what is rendered server-side. For these reasons, you'll want to use useEffect to make client-side changes that rely on window after the component mounts.
Note that this solution is going to perform rather poorly since changeColor is calling setBurger on each scroll event, which prompts the component to be re-rendered (even if the value is the same). You'll want to add a debounce or throttle routine to mitigate this.
const HamMenu = ()=> {
const [burger, setBurger] = useState(false)
useEffect(() => {
const url = window.location.href
const changeColor = () => {
setBurger(window.scrollY >= 60 || url.contains("kontakt"))
}
window.addEventListener('scroll', changeColor)
return () => {
window.removeEventListener('scroll', changeColor)
}
}, [])
return (
<StyledMenu>
<div>
<Link to="#" className={sidebar ? 'menu-bars open' : 'menu-bars'} >
<FontAwesomeIcon
icon={faBars}
size="2x"
className={burger ? 'hamburger active' : 'hamburger'}
/>
</Link>
</div>
</StyledMenu>
)
}
i have a function calculateDistance which calculate when the child component is in the center of the parent component. I want to fire the function on the onScroll event. But the variables I need for it a set in a useEffect and cannot use outside that scope. Has anyone an idea how to fix this?
export function Portfolio() {
const portfolioRef = React.useRef(null)
React.useEffect(() => {
portfolioRef.current.scrollTop = 100
}, []
)
return (
<div className={cx(styles.component, styles.scrollWrapper)}>
<div className={styles.topIcon} dangerouslySetInnerHTML={{ __html: arrow }} />
<div ref={portfolioRef} onScroll={calculateDistance} className={styles.scroll}>
<PortfolioItem
{...{ portfolioRef }}
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' />
<PortfolioItem
{...{ portfolioRef }}
title='Article about Kaliber Academie'
text='hola'
link='#'
linkTekst='#' />
<PortfolioItem
{...{ portfolioRef }}
title='Article about Kaliber Academie'
text='hola'
link='#'
linkTekst='#' />
<PortfolioItem
{...{ portfolioRef }}
title='Article about Kaliber Academie'
text='hola'
link='#'
linkTekst='#' />
</div>
<div className={styles.bottomIcon} dangerouslySetInnerHTML={{ __html: arrow }} />
</div>
)
}
export function PortfolioItem({ text, title, link, linkTekst, portfolioRef }) {
const portfolioItemRef = React.useRef(null)
React.useEffect(() => {
const element = portfolioItemRef.current
const parent = portfolioRef.current
}, [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) {
if (!parent || !element) return 0
const parentRect = parent.getBoundingClientRect()
const parentCenter = (parentRect.top + parentRect.bottom) / 2
const elementRect = element.getBoundingClientRect()
const elementCenter = (elementRect.top + elementRect.bottom) / 2
const distance = Math.abs(parentCenter - elementCenter)
return clamp(distance / (parentRect.height / 2), 0, 1)
}
It doesnt look as if your calculated distance is being stored anywhere. You probably want the calulateDistance function to update a state variable which you refer to in Portfolio's useEffect.
You can create new variables from your component props and use useState(). you can then update them and reference them in your jsx and changing them will trigger your useEffect(), as long as you have it bounded correctly. Using props directly will only trigger your unbounded useEffect on the initial load. Additionally the reference you made 'portfolioItemRef' is only set when rendered and using it as the bound for the useEffect will not update to the calculated distance as is. move the calculated distance function into the portfolio component.
For Example:
const [stateText, setStateText] = useState(text)
useEffect(()=>{console.log('do something')},[stateText]);
here is a helpful explanation: https://medium.com/better-programming/tips-for-using-reacts-useeffect-effectively-dfe6ae951421
Best thing you can do is listen for the scroll event inside the useEffect
remove the onScroll attached to element
....
<div ref={portfolioRef} className={styles.scroll}>
....
inside useEffect
React.useEffect(() => {
const calculateDistance = (parent, element) => {
if (!parent || !element) return 0
const parentRect = parent.getBoundingClientRect()
const parentCenter = (parentRect.top + parentRect.bottom) / 2
const elementRect = element.getBoundingClientRect()
const elementCenter = (elementRect.top + elementRect.bottom) / 2
const distance = Math.abs(parentCenter - elementCenter)
return clamp(distance / (parentRect.height / 2), 0, 1)
}
//attach event listener
portfolioRef.current.addEventListener("scroll", calculateDistance);
return () => {
// remove the event listener when component unmounts
portfolioRef.current.removeEventListener("scroll", calculateDistance);
}
}, [])
Demo
you have to decide how to get the parent and element inside the calculateDistance
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;
}
}