Simplify react component and make it better - javascript

I tried to add some optional costs for products, but I can't find a simpler way than this... I also don't know how to exactly formulate the question, so if anyone has an idea, I would gladly rename it... this is the code I want to simplify (i would like to automate the process for every item, for eg. do this for every item in array:
const extrasList = [
{name: extra1, price: 100, active: false},
{name: extra1, price: 100, active: false},
{name: extra1, price: 100, active: false}
]
):
1.Extras.jsx
import React, { useState } from "react";
import styles from "../styles/extras.module.css";
function Extras() {
const [extra1, setExtra1] = useState(false);
const extraItem1 = extra1 === true ? 20 : 0;
const [extra2, setExtra2] = useState(false);
const extraItem2 = extra2 === true ? 40 : 0;
const extrasPrice = extraItem1 + extraItem2;
return (
<div className={styles.extrasWrapper}>
<p className={extra1 === true ? styles.activeExtras : styles.extras} onClick={() => setExtra1(!extra1)}>Extra1 {extraItem1}</p>
<p className={extra2 === true ? styles.activeExtras : styles.extras} onClick={() => setExtra2(!extra2)}>Extra2 {extraItem2}</p>
</div>
);
}
export default Extras;
Extras.module.css
.activeExtras {
color: red;
}
.extras {
color: white;
}
.extrasWrapper {
display: flex;
gap: 0.2em;
}

You need to create a separate component and 1 state to maintain all extras and update them accordingly based on click. Assuming the name is unique. Check the components you can use.
function Extras(props) {
const [extrasList, setExtrasList] = useState(props.extrasList);
const toggleExtra = (extra) => {
const updatedExtrasList = extrasList.map((ex) => {
if (extra.name === ex.name) {
ex.active = !ex.active;
}
return ex;
});
setExtrasList(updatedExtrasList);
};
return (
<div className={styles.extrasWrapper}>
{extrasList.map((extra) => {
return (
<p
key={extra.name}
className={extra.active ? "activeExtras" : "extras"}
onClick={() => toggleExtra(extra)}
>
{extra.name} {extra.active ? extra.price : 0}
</p>
);
})}
</div>
);
}
From your parent component pass extrasList props.
const extrasList = [
{ name: "Extra1", price: 100, active: false },
{ name: "Extra2", price: 200, active: false },
{ name: "Extra3", price: 300, active: false }
];
return (
<div className="App">
<Extras extrasList={extrasList} />
</div>
);
Working sample https://codesandbox.io/s/lucid-http-3iy5d8

Related

Displaying number of correct answers for quiz app

I'm currently stuck on trying to display the number of correct answers once the quiz is finished.
Basically, I have created a state that keeps track of the number of correct answers shown within the QuizItem component. If the user selected answer matches the correct answer, then the user selected answer turns to green and it will increase the state of correctCount (as seen in the code) to 1. This new value is then passed to the parent component of QuizItem which is QuizList.
/* eslint-disable react/prop-types */
import React from "react";
import AnswerButton from "../UI/AnswerButton";
import classes from "./QuizItem.module.css";
export default function QuizItem(props) {
const [correctCount, setCorrectCount] = React.useState(0)
function addToCorrectCount() {
setCorrectCount(correctCount + 1)
}
props.onSaveCorrectCountData(correctCount)
console.log(correctCount);
return (
<div>
<div key={props.id} className={classes.quizlist__quizitem}>
<h3 className={classes.quizitem__h3}>{props.question}</h3>
{props.choices.map((choice) => {
const styles = {
backgroundColor: choice.isSelected ? "#D6DBF5" : "white",
};
// React.useEffect(() => {
// if (choice.isSelected && choice.choice === choice.correct) {
// addToCorrectCount();
// }
// }, [choice.isSelected, choice.correct]);
function checkAnswerStyle() {
/* this is to indicate that the selected answer is right, makes button go green*/
if (choice.isSelected && choice.choice === choice.correct) {
addToCorrectCount()
return {
backgroundColor: "#94D7A2",
color: "#4D5B9E",
border: "none",
};
/* this is to indicate that the selected answer is wrong, makes button go red*/
} else if (choice.isSelected && choice.choice !== choice.correct) {
return {
backgroundColor: "#F8BCBC",
color: "#4D5B9E",
border: "none",
};
/* this is to highlight the right answer if a selected answer is wrong*/
} else if (choice.choice === choice.correct) {
return {
backgroundColor: "#94D7A2",
color: "#4D5B9E",
border: "none",
};
/* this is to grey out the incorrect answers*/
} else {
return {
color: "#bfc0c0",
border: "1px solid #bfc0c0",
backgroundColor: "white",
};
}
}
return (
<AnswerButton
key={choice.id}
onClick={() => {
props.holdAnswer(choice.id);
}}
style={props.endQuiz ? checkAnswerStyle() : styles}
>
{choice.choice}
</AnswerButton>
);
})}
</div>
</div>
);
}
// create a counter, and for every correct answer (green button), increase the counter by 1.
In the QuizList component, I have set another state to receive the incoming value from the QuizItem component and use this new value to display the number of correct answers once the check answers button has been clicked.
import React from "react";
import { nanoid } from "nanoid";
import QuizItem from "./QuizItem";
import Button from "../UI/Button";
import Card from "../UI/Card";
import classes from "./QuizList.module.css";
export default function QuizList(props) {
const [quiz, setQuiz] = React.useState([]);
const [endQuiz, setEndQuiz] = React.useState(false);
// const [newGame, setNewGame] = React.useState(false);
const [noOfCorrectAnswers, setNoOfCorrectAnswers] = React.useState()
function addCorrectCountHandler(correctCount) {
setNoOfCorrectAnswers(correctCount)
}
React.useEffect(() => {
/* This function turns HTML element entities into normal words */
function decodeHtml(html) {
const txt = document.createElement("textarea");
txt.innerHTML = html;
return txt.value;
}
fetch(
"https://opentdb.com/api.php?amount=5&category=9&difficulty=medium&type=multiple"
)
.then((res) => res.json())
.then((data) => {
const dataArray = data.results;
const newDataArray = dataArray.map((item) => {
return {
question: decodeHtml(item.question),
choices: [
{
choice: decodeHtml(item.correct_answer),
isSelected: false,
correct: decodeHtml(item.correct_answer),
id: nanoid(),
},
{
choice: decodeHtml(item.incorrect_answers[0]),
isSelected: false,
correct: decodeHtml(item.correct_answer),
id: nanoid(),
},
{
choice: decodeHtml(item.incorrect_answers[1]),
isSelected: false,
correct: decodeHtml(item.correct_answer),
id: nanoid(),
},
{
choice: decodeHtml(item.incorrect_answers[2]),
isSelected: false,
correct: decodeHtml(item.correct_answer),
id: nanoid(),
},
].sort(() => 0.5 - Math.random()),
id: nanoid(),
};
});
return setQuiz(newDataArray);
});
}, []);
// console.log(quiz);
function finishQuiz() {
setEndQuiz((prevEndQuiz) => !prevEndQuiz);
}
// function startNewGame() {
// setNewGame(true);
// }
function holdAnswer(quizId, choiceId) {
setQuiz((oldQuiz) =>
oldQuiz.map((quiz) => {
if (quiz.id !== quizId) return quiz;
return {
...quiz,
choices: quiz.choices.map((choice) =>
choice.id === choiceId
? // If the choice selected is the current choice, toggle its selected state
{ ...choice, isSelected: !choice.isSelected }
: // Otherwise, deselect the choice
{ ...choice, isSelected: false }
),
};
})
);
}
const quizItemComponents = quiz.map((item) => {
return (
<QuizItem
key={item.id}
question={item.question}
choices={item.choices}
holdAnswer={(id) => holdAnswer(item.id, id)}
endQuiz={endQuiz}
correct={quiz.correct}
onSaveCorrectCountData={addCorrectCountHandler}
/>
);
});
return (
<Card className={classes.quizlist}>
{quizItemComponents}
{!endQuiz && <Button onClick={finishQuiz}>Check Answers</Button>}
{endQuiz && (
<div className={classes.result}>
<p>You scored {noOfCorrectAnswers}/5 answers</p>
<Button onClick={startNewGame}>Play Again</Button>
</div>
)}
</Card>
);
}
The error that I was getting is that there were too many re-renders, so I tried using useEffect on the setCorrectCount state within my QuizItem component (this can be seen in my code and greyed out) but it would not tally up the count.
Is there a good workaround to this problem? Any help or advice would be appreciated.
Link to the code via Stackblitz:
https://stackblitz.com/edit/quizzical

How do I write 'input[type="radio"]:checked + label' inside state in React?

I have the following selectors in my CSS file:
input[type="radio"]:checked + label.true {
background: #94D7A2;
border: none
}
input[type="radio"]:checked + label.false {
background: #F8BCBC;
border: none;
opacity: 0.5;
}
But I need to change the background on the click of another button (not the inputs), so I wanted to put this inside my component using state, like so:
.... other code here ....
const [styling, setStyling] = useReact({})
function handleClick() {
setStyling({
input[type="radio"]:checked + label.true {
background: #94D7A2;
border: none
},
input[type="radio"]:checked + label.false {
background: #F8BCBC;
border: none;
opacity: 0.5;
}
})
}
.... other code here ....
<input type="radio" id="radio1" style={${correct_answer === answer ? "true" : "false"}}>
<label htmlFor="radio1">Choice 1</label> // this is what I want to update
<button onClick={handleClick}>Check</button> // this is the button I want to activate the change of the state
Does this make sense? Basically what I need is to:
conditionally update the element with a .true or .false class
but I only want that styling to be visible once I click the "Check" button, so I need to change the state on click
This all works great when the styling is in my separate CSS file, but since I need to update it on click, I need it to be inside my component.
Maybe this can give you a start, to see how to use module css look here: https://create-react-app.dev/docs/adding-a-css-modules-stylesheet/
import { useRef, useState } from "react"
const questionList = [
{
id: 1,
title: "What is the correct answer?",
options: [
{ text: "Wrong", correct: false },
{ text: "Right", correct: true },
{ text: "Wrong", correct: false }
]
},
{
id: 2,
title: "And now which is the correct one?",
options: [
{ text: "Right", correct: true },
{ text: "Wrong", correct: false },
{ text: "Wrong", correct: false }
]
},
{
id: 3,
title: "Can you choose the right option?",
options: [
{ text: "Wrong", correct: true },
{ text: "Wrong", correct: false },
{ text: "Right", correct: true }
]
}
]
export const Option = ({ option, showCorrectAnswers, questionId, answers, index }) => {
const correctAnswerStyle = { background: "#94D7A2" }
const wrongAnswerStyle = {
background: "#F8BCBC",
opacity: 0.5
}
const inputRef = useRef();
const id = `question_${questionId}_option${index}`;
const getStyle = () => {
if (!showCorrectAnswers) return {};
if (inputRef.current.checked && option.correct) return correctAnswerStyle;
return wrongAnswerStyle;
}
const handleClick = () => {
answers[questionId] = 1;
}
return <div style={getStyle()}>
<input id={id} onClick={handleClick} ref={inputRef} type="radio" name={`question_${questionId}`} />
<label htmlFor={id}>{option.text}</label>
</div>;
}
export const Question = ({ question, showCorrectAnswers, answers }) => {
return <div>
<p>{question.title}</p>
{question.options.map((option, index) =>
<Option key={`question_${question.id}_${index}`}
questionId={question.id}
option={option}
showCorrectAnswers={showCorrectAnswers}
answers={answers}
index={index} />)
}
</div>
}
export const Quiz = ({ questionList }) => {
const [showCorrectAnswers, setShowCorrectAnswers] = useState(false);
const [answers] = useState({});
const errorMsgRef = useRef();
const handleCheck = () => {
if (Object.keys(answers).length != questionList.length) {
errorMsgRef.current.innerHTML = "Please, answer all questions";
return;
}
errorMsgRef.current.innerHTML = "";
setShowCorrectAnswers(true);
}
return <div>
{questionList.map(question =>
<Question key={`question_${question.id}`}
question={question}
showCorrectAnswers={showCorrectAnswers}
answers={answers} />)
}
<button onClick={handleCheck} >Check</button>
<p ref={errorMsgRef}></p>
</div>
}
export default function App() {
return <Quiz questionList={questionList} />;
}

How to build a react button that stores the selection in an array

I am trying to create a list of buttons with values that are stored in a state and user is only allowed to use 1 item (I dont want to use radio input because I want to have more control over styling it).
import React from "react";
import { useEffect, useState } from "react";
import "./styles.css";
const items = [
{ id: 1, text: "Easy and Fast" },
{ id: 2, text: "Easy and Cheap" },
{ id: 3, text: "Cheap and Fast" }
];
const App = () => {
const [task, setTask] = useState([]);
const clickTask = (item) => {
setTask([...task, item.id]);
console.log(task);
// how can I make sure only 1 item is added to task
// and remove the other items
// only one option is selectable all the time
};
const chosenTask = (item) => {
if (task.find((v) => v.id === item.id)) {
return true;
}
return false;
};
return (
<div className="App">
{items.map((item) => (
<li key={item.id}>
<label>
<button
type="button"
className={chosenTask(item) ? "chosen" : ""}
onClick={() => clickTask(item)}
onChange={() => clickTask(item)}
/>
<span>{item.text}</span>
</label>
</li>
))}
</div>
);
};
export default App;
https://codesandbox.io/s/react-fiddle-forked-cvhivt?file=/src/App.js
I am trying to only allow 1 item to be added to the state at all the time, but I dont know how to do this?
Example output is to have Easy and Fast in task state and is selected. If user click on Easy and Cheap, select that one and store in task state and remove Easy and Fast. Only 1 item can be in the task state.
import React from "react";
import { useEffect, useState } from "react";
import "./styles.css";
const items = [
{ id: 1, text: "Easy and Fast" },
{ id: 2, text: "Easy and Cheap" },
{ id: 3, text: "Cheap and Fast" }
];
const App = () => {
const [task, setTask] = useState();
const clickTask = (item) => {
setTask(item);
console.log(task);
// how can I make sure only 1 item is added to task
// and remove the other items
// only one option is selectable all the time
};
return (
<div className="App">
{items.map((item) => (
<li key={item.id}>
<label>
<button
type="button"
className={item.id === task?.id ? "chosen" : ""}
onClick={() => clickTask(item)}
onChange={() => clickTask(item)}
/>
<span>{item.text}</span>
</label>
</li>
))}
</div>
);
};
export default App;
Is this what you wanted to do?
Think of your array as a configuration structure. If you add in active props initialised to false, and then pass that into the component you can initialise state with it.
For each task (button) you pass down the id, and active state, along with the text and the handler, and then let the handler in the parent extract the id from the clicked button, and update your state: as you map over the previous state set each task's active prop to true/false depending on whether its id matches the clicked button's id.
For each button you can style it based on whether the active prop is true or false.
If you then need to find the active task use find to locate it in the state tasks array.
const { useState } = React;
function Tasks({ config }) {
const [ tasks, setTasks ] = useState(config);
function handleClick(e) {
const { id } = e.target.dataset;
setTasks(prev => {
// task.id === +id will return either true or false
return prev.map(task => {
return { ...task, active: task.id === +id };
});
});
}
// Find the active task, and return its text
function findSelectedItem() {
const found = tasks.find(task => task.active)
if (found) return found.text;
return 'No active task';
}
return (
<section>
{tasks.map(task => {
return (
<Task
key={task.id}
taskid={task.id}
active={task.active}
text={task.text}
handleClick={handleClick}
/>
);
})};
<p>Selected task is: {findSelectedItem()}</p>
</section>
);
}
function Task(props) {
const {
text,
taskid,
active,
handleClick
} = props;
// Create a style string using a joined array
// to be used by the button
const buttonStyle = [
'taskButton',
active && 'active'
].join(' ');
return (
<button
data-id={taskid}
className={buttonStyle}
type="button"
onClick={handleClick}
>{text}
</button>
);
}
const taskConfig = [
{ id: 1, text: 'Easy and Fast', active: false },
{ id: 2, text: 'Easy and Cheap', active: false },
{ id: 3, text: 'Cheap and Fast', active: false }
];
ReactDOM.render(
<Tasks config={taskConfig} />,
document.getElementById('react')
);
.taskButton { background-color: palegreen; padding: 0.25em 0.4em; }
.taskButton:not(:first-child) { margin-left: 0.25em; }
.taskButton:hover { background-color: lightgreen; cursor: pointer; }
.taskButton.active { background-color: skyblue; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>

How to access property with a condition inside React.useState() and change it

I have a component that uses React.useState to keep a property named available
and what I want to do is to change its value to true with a conditional statement, so that my component gets rendered based on that condition, but I can't set up a conditional statement inside React.useState. I tried changing it in my other component with a conditional statement:
const [isUserLogged] = React.useState(true);
const arrowDir = props['data-expanded']
? 'k-i-arrow-chevron-down'
: 'k-i-arrow-chevron-right';
if (isUserLogged === true) {
props.available === true;
}
But that didn't work too. How can I achieve this with a conditional statement? Here is my entire code:
import * as React from 'react';
import { withRouter } from 'react-router-dom';
import {
Drawer,
DrawerContent,
DrawerItem,
} from '#progress/kendo-react-layout';
import { Button } from '#progress/kendo-react-buttons';
const CustomItem = (props) => {
const { visible, ...others } = props;
const [isUserLogged] = React.useState(true);
const arrowDir = props['data-expanded']
? 'k-i-arrow-chevron-down'
: 'k-i-arrow-chevron-right';
if (isUserLogged === true) {
props.available === true;
}
return (
<React.Fragment>
{props.available === false ? null : (
<DrawerItem {...others}>
<span className={'k-icon ' + props.icon} />
<span className={'k-item-text'}>{props.text}</span>
{props['data-expanded'] !== undefined && (
<span
className={'k-icon ' + arrowDir}
style={{
position: 'absolute',
right: 10,
}}
/>
)}
</DrawerItem>
)}
</React.Fragment>
);
};
const DrawerContainer = (props) => {
const [drawerExpanded, setDrawerExpanded] = React.useState(true);
const [isUserLogged] = React.useState(true);
const [items, setItems] = React.useState([
{
text: 'Education',
icon: 'k-i-pencil',
id: 1,
selected: true,
route: '/',
},
{
separator: true,
},
{
text: 'Food',
icon: 'k-i-heart',
id: 2,
['data-expanded']: true,
route: '/food',
},
{
text: 'Japanese Food',
icon: 'k-i-minus',
id: 4,
parentId: 2,
route: '/food/japanese',
},
{
text: 'Secret Food',
icon: 'k-i-minus',
id: 5,
parentId: 2,
route: '/food/italian',
available: false,
},
{
separator: true,
},
{
text: 'Travel',
icon: 'k-i-globe-outline',
['data-expanded']: true,
id: 3,
route: '/travel',
},
{
text: 'Europe',
icon: 'k-i-minus',
id: 6,
parentId: 3,
route: '/travel/europe',
},
{
text: 'North America',
icon: 'k-i-minus',
id: 7,
parentId: 3,
route: '/travel/america',
},
]);
const handleClick = () => {
setDrawerExpanded(!drawerExpanded);
};
const onSelect = (ev) => {
const currentItem = ev.itemTarget.props;
const isParent = currentItem['data-expanded'] !== undefined;
const nextExpanded = !currentItem['data-expanded'];
const newData = items.map((item) => {
const {
selected,
['data-expanded']: currentExpanded,
id,
...others
} = item;
const isCurrentItem = currentItem.id === id;
return {
selected: isCurrentItem,
['data-expanded']:
isCurrentItem && isParent ? nextExpanded : currentExpanded,
id,
...others,
};
});
props.history.push(ev.itemTarget.props.route);
setItems(newData);
};
const data = items.map((item) => {
const { parentId, ...others } = item;
if (parentId !== undefined) {
const parent = items.find((parent) => parent.id === parentId);
return { ...others, visible: parent['data-expanded'] };
}
return item;
});
return (
<div>
<div className="custom-toolbar">
<Button icon="menu" look="flat" onClick={handleClick} />
<span className="title">Categories</span>
</div>
<Drawer
expanded={drawerExpanded}
mode="push"
width={180}
items={data}
item={CustomItem}
onSelect={onSelect}
>
<DrawerContent>{props.children}</DrawerContent>
</Drawer>
</div>
);
};
export default withRouter(DrawerContainer);
if props.available is present when your component is rendering you can write a conditional expression while declaring the isLoggedIn inside useState.
In case it is available later we can always use useEffect hook to update the isLoggedIn
const Component = (props) => {
// if props.available is already present to you
const [isLoggedIn, setIsLoggedIn] = React.useState(props.isAvailable ? true : false);
// if props.isAvailable is not present when your component renders you can use
// useEffect
React.useEffect(() => {
setIsLoggedIn(props.isAvailable);
}, [props.isAvailable])
// use IsLoggedIN here
return (
<div>
{
isLoggedIn ?
<div> Logged in </div>
: <div>llogged out</div>
}
</div>
)
}

Function to add and delete from JSON object

I have two categories "A" and "B". On Click any button of category "A" removes the button and must move to category "B", On Click any button of category "B" adds the button to category "A" and must move from category "B".
export const LauncherButtons = [
{
linked: false,
type: 'ABC',
name: 'ReactJs'
},
{
linked: false,
type: 'ABC',
name: 'VueJS'
},
{
linked: true,
type: 'XYZ',
name: 'Angular'
},
{
linked: true,
type: 'XYZ',
name: 'Javascript'
}
];
This is what I am rendering for category "A".
{ LauncherButtons.map((button,index) => {
return(
button.linked === true &&
<LauncherActionButton
text={button.name}
onClick = {this.removeAction}/>
);
})}
Rendering category "B".
{ LauncherButtons.map((button,index) => {
return(
button.linked !== true &&
<LauncherActionButtonAdd
textAdd={button.name}
onClick = {this.addAction}/>
);
})}
So basically, when I click on a button of category "A" (True) it should move to category "B" and become false, similarly, when I click on a button of category "B" (False) it should become true and move to category "A".
Try something like this: https://codesandbox.io/s/holy-leftpad-hw1oe
I've laid out two sections, an active and inactive section. By clicking on a button, you move it to the opposite side. I don't know what your LauncherActionButton component looks like so consider this like a bare-bones template.
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
export const LauncherButtons = [
{
linked: false,
type: "ABC",
name: "ReactJs"
},
{
linked: false,
type: "ABC",
name: "VueJS"
},
{
linked: true,
type: "XYZ",
name: "Angular"
},
{
linked: true,
type: "XYZ",
name: "Javascript"
}
];
class App extends React.Component {
state = {
buttons: LauncherButtons
};
createActiveButtons = () => {
const { buttons } = this.state;
return buttons
.filter(button => {
return button.linked;
})
.map(activeButton => {
return (
<button onClick={this.handleOnClick} name={activeButton.name}>
{activeButton.name}
</button>
);
});
};
createInactiveButtons = () => {
const { buttons } = this.state;
return buttons
.filter(button => {
return !button.linked;
})
.map(inactiveButton => {
return (
<button onClick={this.handleOnClick} name={inactiveButton.name}>
{inactiveButton.name}
</button>
);
});
};
handleOnClick = event => {
const { buttons } = this.state;
const { name } = event.target;
let updatedButtons = buttons.map(button => {
if (button.name === name) {
return {
...button,
linked: !button.linked
};
} else {
return button;
}
});
this.setState({
buttons: updatedButtons
});
};
render() {
return (
<div style={{ display: "flex" }}>
<div style={{ width: "50%", background: "green", height: "300px" }}>
{this.createActiveButtons()}
</div>
<div style={{ width: "50%", background: "red", height: "300px" }}>
{this.createInactiveButtons()}
</div>
</div>
);
}
}
What about using the item as a parameter? for example:
removeAction(button) {
// change button however you like
}
// in the render method
{
LauncherButtons.map((button, index) => {
return (
button.linked &&
<LauncherActionButton
text={button.name}
onClick={() => removeAction(button)}/>
);
})
}

Categories

Resources