React.js - Change image based on door puzzle answer - javascript

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;
}
}

Related

React tab-like system using CSS classes

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!

How to click on a div in a chessboard using componenents in react?

I am trying to code a chessboard in order to prepare for an interview from this - challenge
I want it to look like this -
When you click on any tile of the chessboard all the diagonals change color to red.
Till I have achieved to display the chessboard using 2d for loop and tile component.
This is how I have done it -
app.js
import Chessboard from './Chessboard'
function App() {
return (
<div className="app">
<Chessboard/>
</div>
);
}
export default App;
chessboard.js
import React from "react";
import '../Chessboard.css';
import Pixel from "./Pixel";
const horizontalAxis = [ "a", "b","c","d","e","f","g","h"]
const verticalAxis = [ "1", "2","3", "4","5","6","7","8"]
function Chessboard() {
let board=[]
for (let j = verticalAxis.length - 1; j >= 0; j--) {
for (let i = 0; i < horizontalAxis.length; i++) {
if((i+j)%2===0)
{
board.push(<Pixel key={i.toString()+j.toString()} selectedColor="black"/>)
}
else
{
board.push(<Pixel key={i.toString()+j.toString()} selectedColor="white"/>) // board.push(<div className="tile white-tile"></div>)
}
}
};
return (
<div id="chessboard">
{board}
</div>
);
}
export default Chessboard;
Pixel.js
export default function Pixel(props){
const {selectedColor} = props
return (<div className="tile" style={{ backgroundColor: selectedColor }}></div>)
}
pixel css
.tile{
width: 100px;
height: 100px;
}
This looks like this
Now this where I am stuck, how do I make the tiles clickable?
I tried onClick function and using events like this
function test(_e){
console.log("works")
}
for (let j = verticalAxis.length - 1; j >= 0; j--) {
for (let i = 0; i < horizontalAxis.length; i++) {
if((i+j)%2===0)
{
board.push(<Pixel onClick={e => test(e)} key={i.toString()+j.toString()} selectedColor="black"/>)
}
else
{
board.push(<Pixel key={i.toString()+j.toString()} selectedColor="white"/>) // board.push(<div className="tile white-tile"></div>)
}
}
};
this should console log message when you click on black tiles but this does not work.
I am stuck as to I have no idea on how to display the diagonal red colour on clicking of a tile.
What can I do to solve this?
Edit: Made changes to pixel.js now clicking of div works and colour changes
export default function Pixel(props){
const {selectedColor} = props
const [pixelColor, setPixelColor] = useState(selectedColor);
const [originalColor, changeOriginalColor] = useState(true);
function applyColor() {
if(originalColor)
{
setPixelColor("#FF0000");
changeOriginalColor(false);
}
else{
setPixelColor(selectedColor);
changeOriginalColor(true);
}
}
return (<div
className="tile"
onClick={applyColor}
style={{ backgroundColor: pixelColor }}
></div>)
}
Now I cant figure out how to do the diagonal thing where if you click on any one tile all the diagonals will turn red as shown in the picture above.
How should I trigger it from chessboard.js?
Any solution for that is appreciated, I am stuck over here.
This is already explained in this question: onClick does not work for custom component
That said, in your code, the onClick prop is unused in your Pixel component right now, so passing it down to the DOM element would be enough to unblock you.
export default function Pixel({ selectedColor, onClick }) {
return (
<div
onClick={onClick} // <--- Here
className="tile"
style={{ backgroundColor: selectedColor }}
/>
);
}

Is there a way to set the initial height of an element in Reactjs?

I have a react component that allows for a resizable text area based on the text inside.
return (<div className={`resizable-textbox ${className}`}>
<textarea
value={value}
onChange={onChangeMade}
onBlur={onBlur}
readOnly={readOnly} />
</div>);
The onChangeMade method looks like this:
const onChangeMade = (e) => {
const scrollHeightPadded = e.target.scrollHeight;
if ((scrollHeightPadded + "px") !== e.target.style.height) {
e.target.style.height = 0;
const height = Math.max(scrollHeightPadded, 31) + 3;
e.target.style.height = `${height}px`;
}
}
This is a slightly ugly method I know, needs cleaning up. However I want to call this method once on the first load of the component but e is an event triggered by the textarea tag.
Is there a way to hook into this or directly into the component with the method? (I am using React Hooks and stateless components).
Thanks.
You can use createRef to create a ref and give it to the ref prop of your textarea and use that instead of the event.
Example
class MyComponent extends React.Component {
ref = React.createRef();
componentDidMount() {
this.onChangeMade();
}
onChangeMade = () => {
const { current } = this.ref;
const scrollHeightPadded = current.scrollHeight;
if (scrollHeightPadded + "px" !== current.style.height) {
current.style.height = 0;
const height = Math.max(scrollHeightPadded, 31) + 3;
current.style.height = `${height}px`;
}
};
render() {
return (
<div className={`resizable-textbox ${className}`}>
<textarea
ref={this.ref}
value={value}
onChange={onChangeMade}
onBlur={onBlur}
readOnly={readOnly}
/>
</div>
);
}
}

Check width before rendering in React

I'm trying to make a div that gives a count of overflowed items something like this:
[item1] [item2] [+4]
where the +4 is the amount of items that couldn't fit into the div.
I've able to do this based on count but doing it based on width is tough.
Are there any ways to at render time see if a component is going to overflow it's parent div?
MY code so far is below.
import React, { Component } from "react";
import { Button } from "antd";
import ReactDOM from "react-dom";
class ReciepesList extends Component {
state = { elWidth: "" };
componentDidMount() {
//determineWidth();
}
determineWidth() {
var inwidth = ReactDOM.findDOMNode(this.refs.inner).getBoundingClientRect()
.width;
var inheight = ReactDOM.findDOMNode(this.refs.inner).getBoundingClientRect()
.height;
var outwidth = ReactDOM.findDOMNode(this.refs.outer).getBoundingClientRect()
.width;
var outheight = ReactDOM.findDOMNode(
this.refs.outer
).getBoundingClientRect().height;
console(
"inW",
inwidth,
"outW",
outwidth,
"inH",
inheight,
"outH",
outheight
);
}
render() {
const { reciepes, all, style } = this.props;
let count = 0;
return (
<div ref="outer">
<div ref="inner" style={{ overflowY: "hidden" }}>
{reciepes.map(reciepe => {
//NEED SOLUTION HERE TO CHECK WIDTH
if (count < 6 || all) {
count++;
return (
<Button type="dashed" key={reciepe}>
{reciepe}
</Button>
);
} else {
return null;
}
})}
{reciepes.length - count > 0 && (
<Button type="dashed">+{reciepes.length - count}</Button>
)}
</div>
</div>
);
}
}
export default ReciepesList;

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

Categories

Resources