Reputation: 187
I'm trying to do a application which create a component with a 7 segment led display, but the led display should show the inputted number. I've been trying to think in a logic to this, but it's not working.
switch
, and depending on the inputted number, it'll add the className
which contains the disabled style to each div
.And when I try to input just one number, it works fine! But when I try it with two numbers, it just bug. As you can see in the images:
I've just two components: <App/>
and <Numbers/>
.
App:
function App() {
const [input, setInput] = useState('');
const [number, setNumber] = useState([]);
const changeNumbers = () => {
// Splitting inputted numbers to add each number to array.
const inputSplit = input.toString().split('');
const inputArray = inputSplit.map(Number);
setNumber(inputArray);
}
return (
<div>
<input onChange={(e) => {setInput(e.target.value)}}/>
<button onClick={() => {changeNumbers()}}>Confirm</button>
{
number.length ?
number.map((singleNumber, idx) => (
<Numbers singleNumber={singleNumber} key={idx} />
))
:
<Numbers singleNumber={0} />
}
</div>
);
}
Numbers:
const Numbers = ({ singleNumber }) => {
const switchDisabled = (element) => {
const segmentB = document.querySelector('.segment-b');
const segmentC = document.querySelector('.segment-c');
const segmentF = document.querySelector('.segment-f');
switch(element) {
case 2:
segmentC.classList.add('segment-disabled');
segmentF.classList.add('segment-disabled');
break;
case 6:
segmentB.classList.add('segment-disabled');
break;
default:
break;
}
}
useEffect(() => {
switchDisabled(singleNumber);
}, [singleNumber]);
return (
<div className="container">
<div className="segment segment-a"></div>
<div className="segment segment-b"></div>
<div className="segment segment-c"></div>
<div className="segment segment-d"></div>
<div className="segment segment-e"></div>
<div className="segment segment-f"></div>
<div className="segment segment-g"></div>
</div>
)
}
Styles:
.container {
width: 110px;
height: 160px;
position: relative; }
.segment {
position: absolute;
background-color: red; }
.segment-a {
top: 0;
left: 10px;
width: 90px;
height: 10px; }
.segment-b {
top: 13px;
right: 0;
height: 60px;
width: 10px; }
.segment-c {
bottom: 13px;
right: 0;
height: 60px;
width: 10px; }
.segment-d {
bottom: 0;
left: 10px;
width: 90px;
height: 10px; }
.segment-e {
bottom: 13px;
left: 0;
height: 60px;
width: 10px; }
.segment-f {
top: 13px;
left: 0;
height: 60px;
width: 10px; }
.segment-g {
top: 75px;
left: 16px;
width: 80px;
height: 10px; }
.segment-disabled {opacity: 0.1;}
Obs: I didn't posted the imports
and exports
to save space.
Upvotes: 2
Views: 525
Reputation: 9055
The react way is to not manipulate the dom by calling on a dom node but building the dom nodes. So instead of adding the classes by calling on the dom node just build the dom node with the classes already in place.
In your app component you can just set the input as a string and then split it and loop through it placing a number component for each one. In your number component you can just set the segements that you want in an array and map through them placing the classNames that you want when the component renders.
Here is a codesandbox example led display codesandbox
App.js
import { useState } from "react";
import Number from "./Number";
function App() {
const [input, setInput] = useState("0");
return (
<>
<input
onChange={(e) => {
setInput(e.target.value);
}}
/>
{input.split("").map((number, i) => (
<Number key={i} number={number} />
))}
</>
);
}
export default App;
Number.js
const Number = ({ number }) => {
let segments = [];
switch (number) {
case "1":
segments = ["b", "c"];
break;
case "2":
segments = ["a", "b", "d", "e", "g"];
break;
case "3":
segments = ["a", "b", "c", "d", "g"];
break;
case "4":
segments = ["b", "c", "f", "g"];
break;
case "5":
segments = ["a", "c", "d", "f", "g"];
break;
case "6":
segments = ["a", "c", "d", "f", "e", "g"];
break;
case "7":
segments = ["a", "b", "c"];
break;
case "8":
segments = ["a", "b", "c", "d", "e", "f", "g"];
break;
case "9":
segments = ["a", "b", "c", "d", "f", "g"];
break;
case "0":
segments = ["a", "b", "c", "d", "e", "f"];
break;
default:
segments = ["a", "b", "c", "d", "e", "f"];
}
return (
<div className="container">
{segments.map((segment) => (
<div key={segment} className={`segment segment-${segment}`}></div>
))}
</div>
);
};
export default Number;
Upvotes: 2
Reputation: 36
The problem is that document.querySelector
is being called in the Numbers
component to get a segment, e.g.
const segmentB = document.querySelector('.segment-b');
This call will find the first DOM element on the whole page that has a class containing segment-b
, which then means that the wrong number is having it's segments altered when the 2nd number is being rendered. You can verify this by inputting a double digit number where both digits are the same e.g. 66 - the first number should be rendered 6
and the 2nd number will not be correct.
Instead of using document.querySelector
with classList.add
and classList.remove
you could instead use a dynamic variable to calculate the disabled segment classes required in each Number component that are then rendered on that specific Number component:
const Numbers = ({ singleNumber }) => {
const switchDisabled = (element) => {
const allSegments = ['a', 'b', 'c', 'd', 'e', 'f', 'g'];
let disabledSegments = [];
switch (element) {
case 2:
disabledSegments.push("c");
disabledSegments.push("f");
break;
case 6:
disabledSegments.push("b");
break;
default:
break;
}
};
return (
<div className="container">
{allSegments.forEach(segmentLetter => {
const disabledClass = disabledSegments.includes(segmentLetter) ? 'segment-disabled' : '';
return (
<div className={`segment segment-${segmentLetter} ${disabledClass}`} />
)
})}
</div>
);
};
P.S. in general you want to 99.99% of the time calling document.querySelector
in react due to these kinds of problems
Upvotes: 2