I'm trying to mark divs that is clicked on my website. When I click, the array is updated but the mark won't show. It seems like the statement gameChoices.includes('Fortnite') is false, even though the array contains the exact value Fortnite.
Does anyone know why this happens? Eventually a new solution for the problem?
Code:
<Container onClick={() => {
if (gameChoices.includes('Fortnite')) {
const findIndex = gameChoices.findIndex(a => a === 'Fortnite')
findIndex !== -1 && gameChoices.splice(findIndex , 1)
} else if (gameChoices.includes('Fortnite') === false) {
gameChoices.push('Fortnite')
}
}} fluid className="d-flex fortnite gameoption position-relative">
{gameChoices.includes('Fortnite') ?
<>
<BsCheckSquare color="lightgreen" size="2rem" style={{ top: '50%', right: '50%' }} />
</>
: null
}
<h1 className="fw-bolder text-light text-center m-auto">FORTNITE</h1>
</Container>
const [gameChoices, setGameChoices] = useState([])
As I have commented:
Do not use inline click handler. It makes your markup difficult to read.
findIndex !== -1 is not required as you are already checking if it is included in array
Also gameChoices.includes('Fortnite') === false is redundant. Just a simple else is enough
But in addition to this, you need to set value to state.
Apart from that, you should instead look into .some and check for same cased text. You can in addition do trim if game name is coming from user input
const choiceExists = (game) => {
return gameChoices.some(
(name) => name.toLowerCase() === game.toLowerCase()
)
}
const clickHandler = () => {
const name = 'fortnite'
if (choiceExists(name)) {
const newGames = gameChoices.filter((game) => game.toLowerCase() !== name)
setGameChoices(newGames)
} else {
setGameChoices((choices) => choices.concat(name))
}
}
<Container onClick={clickHandler} fluid className="d-flex fortnite gameoption position-relative">
{
gameChoices.includes('Fortnite')
? <BsCheckSquare
color="lightgreen"
size="2rem"
style={{ top: '50%', right: '50%' }} />
: null
}
<h1 className="fw-bolder text-light text-center m-auto">FORTNITE</h1>
</Container>
When you update a reactive state value you should use the state setter method, so setGameChoices((choices)=>[...choices, 'Fortnite'])
Related
I'm trying to send an object as 'props' from a parent Class into a Child class with the intention of showing information:
Parent class
const TaskDetail = () => {
//Get parameter from URL
const { id } = useParams()
const [taskDetail, setTaskDetail] = useState([])
useEffect(() => {
TaskService.getTaskById(id)
.then(response => {
setTaskDetail(response.data);
})
}, [id]) //This [id] avoid infinite loop of requests
if (!taskDetail) return <div>No data</div>
return (
<div>
<Detail taskDetail={taskDetail}/>
</div>
)
}
This class makes a request to the server and gather a data object. This object is then passed onto the Child Detail where it will be deserialized and visualized accordingly.
Child class
const Detail = ({ taskDetail }) => {
return (
<Box
align='center'
justifyContent='center'
sx={{ width: '100%', marginTop: 4}}
bgcolor=''
//border='solid'
>
<Stack
//border='solid'
sx = {{width: '50%'}}
justifyContent='center'
//border='solid'
>
<Typography
sx = {{ marginTop: 5}}
variant='h4'
fontWeight='bold'
bgcolor='#b2dafa'
>NOMBRE DEL EJERCICIO<br/>{taskDetail.taskName}</Typography>
<Box
sx = {{ marginTop: 3}}
bgcolor='#b2dafa'
>
<Typography
variant='h5'
align='center'
sx = {{ margin: 2}}
fontWeight='bold'
>DETALLES DEL EJERCICIO</Typography>
<Typography
sx = {{ margin: 2}}
variant='text'
border='#555'
>{taskDetail.details}
</Typography>
</Box>
<Box
sx = {{ marginTop: 5}}>
<Typography
variant='h6'
>Marca para completar</Typography><Toogle
label=''
toogled={false}
onClick={null}/>
<br></br>
</Box>
{taskDetail.id}
<Box
sx = {{ marginTop: 2}}>
<AddComment taskId={taskDetail.id}/>
</Box>
<Box
sx = {{ marginTop: 2}}>
<ListComments taskId={taskDetail.id}/>
</Box>
</Stack>
</Box>
)
}
As you can observe, this object is also passed to other child components. The context is that TaskDetail shows information and then offers two features, ListComments and AddComments. At the current moment, I am having an issue in AddComment where the prop taskId={taskDetail.id} is undefined.
Function in Child where I am having this issue
function ListComments(props) {
const [comments, setComments] = useState([])
useEffect(() => {
console.log('DEBUG listcomments: ' + props.taskId)
TaskService.getTaskCommentsById(props.taskId)
.then(response => {
setComments(response.data)
})
}, [])
return (
<div>
<h2>Comentarios</h2>
{comments.map((comment, _) => {
return <Comment key={comment.id} comment={comment}/>
})}
</div>
)
}
I have noticed, that if I change something in the code and save it (re-renders the page). All of a sudden I get the atribute that I need instead of the undefined value.
How could I avoid this situation?
I trust that I have made a huge mistake that I am unable to see, but its part of the learning. For this reason, I am open to suggestions.
Since the data you get back from the service is an object I would suggest to initialize the state with an object {}.
const [taskDetail, setTaskDetail] = useState({});
In the ListComments component you can do the same as you did in the TaskDetail component. Run useEffect when the props.taskId changes. And add a early return if the taskId have no value yet.
useEffect(() => {
console.log("DEBUG listcomments: " + props.taskId);
if (!props.taskId) return;
TaskService.getTaskCommentsById(props.taskId).then((response) => {
setComments(response.data);
});
}, [props.taskId]);
Very cool that you're reaching out for help!
First, just a minor correction: They're not classes, they're functions / components.
I think the problem is the check condition at the top:
if (!taskDetail) return <div>No data</div>
Since taskDetail is initialised as an array, the condition will always be true since arrays are objects in javascript.
Because of this too, when you're passing it down, at least on the first render, none of these props in the lower components exist. So maybe try initalising it either as null, or changing the condition to the following:
if (!taskDetail || taskDetail.length === 0) return <div>No data</div>
One more thing, to make sure that the data is fetched, you need to add props.taskId to the dependency list in the List component.
I passed a function to the child to check a checkbox and then to set setDispatch(true), the problem is that when I check the checkbox everything freezes and the website stops until I close and open again.
the function:
const [selectedChkBx, setSelectedChkBx] = useState({ arrayOfOrders: [] });
const onCheckboxBtnClick = (selected) => {
const index = selectedChkBx.arrayOfOrders.indexOf(selected);
if (index < 0) {
selectedChkBx.arrayOfOrders.push(selected);
} else {
selectedChkBx.arrayOfOrders.splice(index, 1);
}
setSelectedChkBx(selectedChkBx)
toggleDispatchButton()
};
const toggleDispatchButton = () => {
if (selectedChkBx.arrayOfOrders.length == 0) {
setDispatchButtonDisplay(false)
}
else {
setDispatchButtonDisplay(true)
}
}
Child Component:
<form style={{ display: 'block' }} >
<Row sm={1} md={2} lg={3}>
{ordersDisplay.map((value, key) => {
return (
<motion.div key={value.id} layout>
<DeliveryQueueComp
selectedChkBx={selectedChkBx}
toggleDispatchButton={toggleDispatchButton}
setDispatchButtonDisplay={setDispatchButtonDisplay}
value={value}
onCheckboxBtnClick={onCheckboxBtnClick}
/>
</motion.div>
)
})
}
</Row> </form>
DeliveryQueueComp Code:
<div
className={styles1.checkBox}
style={{ background: selectedChkBx.arrayOfOrders.includes(value.id) ?
'#f84e5f' : 'transparent' }}
onClick={() => { onCheckboxBtnClick(value.id) }}
>
<FontAwesomeIcon icon={faCheck} style={{ fontSize: '10px', opacity:
selectedChkBx.arrayOfOrders.includes(value.id) ? '1' : '0' }} />
</div>
If I remove toggleDispatchButtonDisplay, it works but then after a while the page freezes again.
Any thoughts about this?
As you didn't provide setDispatch code I don't know what it does, but for the rest I think I know why it's not working.
You're assigning the array and then set it to the state. If you want to do this that way you should only do a forceUpdate instead of a setState (as it has already been mutated by push and splice).
To properly update your state array you can do it like this
const onCheckboxBtnClick = (selected) => {
const index = selectedChkBx.arrayOfOrders.indexOf(selected);
if (index < 0) {
//the spread in array creates a new array thus not editing the state
setSelectedChkBx({
arrayOfOrders: [...selectedChkBx.arrayOfOrders, selected]
});
} else {
// same thing for the filter here
setSelectedChkBx({
arrayOfOrders: selectedChkBx.arrayOfOrders.filter(
(value) => value !== selected
)
});
}
toggleDispatchButton();
};
Here is the sandbox of your code https://codesandbox.io/s/eager-kalam-ntc7n7
I have below code where I am checking or unchecking the checkbox based on some conditions, and I came across an issue where I am trying to check the checkbox, and it is failing the first time and from the second time onwards works perfectly.
export const AntdDefaultOverrideInputNumberAdapter = ({
input: { onChange, value },
disabled,
defaultValue,
formatter,
...rest
}) => {
const [isDefaultValueOverriden, setIsDefaultValueOverriden] = useState(
!!value && value !== defaultValue
);
const handleCheckboxChange = () => {
const hasOverride = !isDefaultValueOverriden;
setIsDefaultValueOverriden(hasOverride);
onChange(hasOverride && !!value ? value : defaultValue);
};
const handleOverrideChange = (v) => {
onChange(v);
};
return (
<Badge
offset={[0, -6]}
count={
<div>
<Checkbox
disabled={disabled}
checked={isDefaultValueOverriden}
onChange={handleCheckboxChange}
style={{ zIndex: 10 }}
/>
</div>
}
>
<InputNumber
size="small"
onChange={handleOverrideChange}
disabled={disabled || !isDefaultValueOverriden}
style={{ width: 65 }}
value={isDefaultValueOverriden ? value : defaultValue}
formatter={formatter}
{...rest}
/>
</Badge>
);
};
I am not sure where I am wrong with the above code, The problem only appears on trying to check the checkbox the first time, and it disappears from the second time onwards..
Could anyone suggest any idea on this? Many thanks in advance!!
I am using the "ANTD" library for the checkbox, and the "value" is an empty string, and the defaultValue is "5"
Sorry for the confusing title
I'm adding editing feature to a forum, and I need to get the topic's title elements height and width, so that I click the EDIT button, component re-renders and instead of the title we have a text input that has title's width and height. The problem is that I can properly do this just once. If I edit for the second time with no refresh and I save, the next text input has the first edited titles width/height
Here my code:
export default TopicComponent(someProps...){
const titleRef = useRef('');
const [isAuthor, setIsAuthor] = useState(author === user.id);
const [editContentState, setEditContent] = useState(false);
const [titleSize, setTitleSize] = useState({ height: null, width: null });
useEffect(() => {
setTitleSize({height: titleRef.current ? titleRef.current.offsetHeight : null, width: titleRef.current ? titleRef.current.offsetWidth : null})
},[]);
function edit() {
setIsAuthor(!isAuthor);
setEditContent(!editContentState);
}
async function discard() {
setIsAuthor(!isAuthor);
setEditContent(!editContentState);
}
async function handleUpdateTopic() {
setIsAuthor(!isAuthor);
setEditContent(!editContentState);
//do more stuff
}
return (
<div className={styles.container}>
<span>
<img alt="User Profile Pic" src={img_url} />
<div className={styles.info}>
{editContentState ? (
<input
className={styles.comment_box}
style={{ height: titleSize.height, width: titleSize.width }}
defaultValue={topicContent.title}
onChange={(e) => (topicContent.title = e.target.value)}
/>
) : (
<h1 ref={titleRef}>{topicContent.title}</h1>
)}
<p>
{name} {surname}
</p>
{editContentState && (
<>
<button
style={{ width: 200 }}
type="button"
onClick={handleUpdateTopic}
>
Save Changes
</button>
<button type="button" onClick={discard}>
Discard changes
</button>
</>
)}
{isAuthor && (
<button type="button" onClick={edit}>
Edit
</button>
)}
</div>
</span>
So I know that this way with useEffect I'm setting titleSize state just once, I tried to just mutate titleSize properies straight as:
useEffec(()=> {
titleSize.width = titleRef.current ? titleRef.current.offsetWidth : null })
Without forcing a re-rendering, but I'm getting the same result, I can just get first load's title element height & width.
Anyone knows a better approach to to this? I reached the net but unafortunately I didnt get expected answers (probably cause I didnt use correct keywords to reach the correct answer)
Any help truly appreciated!
I think you should pass topicContent to useEffect. so when the topicContent change, the useEffect runs again.
useEffect(() => {
setTitleSize({height: titleRef.current ? titleRef.current.offsetHeight : null, width: titleRef.current ? titleRef.current.offsetWidth : null})
},[topicContent]);
I am trying to conditionally render part of an object (user comment) onClick of button.
The objects are being pulled from a Firebase Database.
I have multiple objects and want to only render comments for the Result component I click on.
The user comment is stored in the same object as all the other information such as name, date and ratings.
My original approach was to set a boolean value of false to each Result component and try to change this value to false but cannot seem to get it working.
Code and images attached below, any help would be greatly appreciated.
{
accumRating: 3.7
adheranceRating: 4
cleanRating: 2
date: "2020-10-10"
place: "PYGMALIAN"
staffRating: 5
timestamp: t {seconds: 1603315308, nanoseconds: 772000000}
userComment: "Bad"
viewComment: false
}
const results = props.data.map((item, index) => {
return (
<div className='Results' key={index}>
<span>{item.place}</span>
<span>{item.date}</span>
<Rating
name={'read-only'}
value={item.accumRating}
style={{
width: 'auto',
alignItems: 'center',
}}
/>
<button>i</button>
{/* <span>{item.userComment}</span> */}
</div >
)
})
You have to track individual state of each button toggle in that case.
The solution I think of is not the best but you could create a click handler for the button and adding a classname for the span then check if that class exists. If it exists then, just hide the comment.
Just make sure that the next sibling of the button is the target you want to hide/show
const toggleComment = (e) => {
const sibling = e.target.nextElementSibling;
sibling.classList.toggle('is-visible');
if (sibling.classList.contains('is-visible')) {
sibling.style.display = 'none'; // or set visibility to hidden
} else {
sibling.style.display = 'inline-block'; // or set visibility to visible
}
}
<button onClick={toggleComment}>i</button>
<span>{item.userComment}</span>
You can try like this:
const [backendData, setBackendData] = useState([]);
...
const showCommentsHandler = (viewComment, index) => {
let clonedBackendData = [...this.state.backendData];
clonedBackendData[index].viewComment = !viewComment;
setBackendData(clonedBackendData);
}
....
return(
<div>
....
<button onClick={() => showCommentsHandler(item.viewComment, index)}>i</button>
{item.viewComment && item.userComment}
<div>
You can store an array with that places which are clicked, for example:
const [ selectedItems, setSelectedItems] = React.useState([]);
const onClick = (el) => {
if (selectedItems.includes(el.place)) {
setSelectedItems(selectedItems.filter(e => e.place !== el.place));
} else {
setSelectedItems(selectedItems.concat(el));
}
}
and in your render function
const results = props.data.map((item, index) => {
return (
<div className='Results' key={index}>
<span>{item.place}</span>
<span>{item.date}</span>
<Rating
name={'read-only'}
value={item.accumRating}
style={{
width: 'auto',
alignItems: 'center',
}}
/>
<button onClick={() => onClick(item)}>i</button>
{ /* HERE */ }
{ selectedItems.includes(item.place) && <span>{item.userComment}</span> }
</div >
)
})
You need to use useState or your component won't update even if you change the property from false to true.
In order to do so you need an id since you might have more than one post.
(Actually you have a timestamp already, you can use that instead of an id.)
const [posts, setPosts] = useState([
{
id: 1,
accumRating: 3.7,
adheranceRating: 4,
cleanRating: 2,
date: "2020-10-10",
place: "PYGMALIAN",
staffRating: 5,
timestamp: { seconds: 1603315308, nanoseconds: 772000000 },
userComment: "Bad",
viewComment: false
}
]);
Create a function that updates the single property and then updates the state.
const handleClick = (id) => {
const singlePost = posts.findIndex((post) => post.id === id);
const newPosts = [...posts];
newPosts[singlePost] = {
...newPosts[singlePost],
viewComment: !newPosts[singlePost].viewComment
};
setPosts(newPosts);
};
Then you can conditionally render the comment.
return (
<div className="Results" key={index}>
<span>{item.place}</span>
<span>{item.date}</span>
<Rating
name={"read-only"}
value={item.accumRating}
style={{
width: "auto",
alignItems: "center"
}}
/>
<button onClick={() => handleClick(item.id)}>i</button>
{item.viewComment && <span>{item.userComment}</span>}
</div>
);
Check this codesandbox to see how it works.