Only 1 true state intro array React js - reactjs

What's up ?
I'm trying to reproduce the sliding button effect from frontity home page with ReactJS (NextJS).
Sliding buttons from Frontity
I managed to create the sliding button effect BUT I'm struggling with state management.
I have all my objects mapped with a "isActive : true/false" element and I would to create a function that put "isActive : true" on the clicked button BUT put "isActive: false" on all the other buttons.
I don't know the syntax / method for that kind of stuff.
Please, take a look at my codesandbox for more clarity (using react hooks):
https://codesandbox.io/s/busy-shirley-lgx96
Thank you very much people :)

UPDATE: As pointed out above by Drew Reese, even more cleaner/easier is to have just one activeIndex state:
const TabButtons = () => {
const [activeIndex, setActiveIndex] = useState(0);
const handleButtonClick = (index) => {
setActiveIndex(index);
};
return (
<>
<ButtonsWrapper>
{TabButtonsItems.map((item, index) => (
<div key={item.id}>
<TabButtonItem
label={item.label}
ItemOrderlist={item.id}
isActive={index === activeIndex}
onClick={() => handleButtonClick(index)}
/>
</div>
))}
<SlidingButton transformxbutton={activeIndex}></SlidingButton>
</ButtonsWrapper>
</>
);
};
I have made a slight modification of your TabButtons:
const TabButtons = () => {
const [buttonProps, setButtonProps] = useState(TabButtonsItems);
// //////////// STATE OF SLIDING BUTTON (TRANSLATE X ) ////////////
const [slidingbtn, setSlidingButton] = useState(0);
// //////////// HANDLE CLIK BUTTON ////////////
const HandleButtonState = (item, index) => {
setButtonProps((current) =>
current.map((i) => ({
...i,
isActive: item.id === i.id
}))
);
setSlidingButton(index);
};
return (
<>
<ButtonsWrapper>
{buttonProps.map((item, index) => (
<div key={item.id}>
<TabButtonItem
label={item.label}
ItemOrderlist={item.id}
isActive={item.isActive}
onClick={() => HandleButtonState(item, index)}
/>
</div>
))}
<SlidingButton transformxbutton={slidingbtn}></SlidingButton>
</ButtonsWrapper>
</>
);
};
When we click on a button, we set its isActive state to true and all the rest buttons to isActive: false. We also should use state, since we also declared it. Changing state will force component to re-render, also we are not mutating anything, but recreating state for buttons.

Related

show / hide component based on if else statement in react

I have some code that looks like this
const Movies = () => {
const [show, setShow] = useState(false);
const [show1, setShow1] = useState(false);
const onClick = () => setShow(!show);
const onClick1 = () => setShow1(!show1);
return (
<div className="movie">
<Header />
<h2>Discover a new movie!</h2>
<div className="showBtns">
<button onClick={onClick} className="right">Search <FaSearch /></button>
<button onClick={onClick1}>Discover <FaSearch /></button>
</div>
{show1 ? <DropDown /> : null }
{show ? <MovieSearch /> : null }
<Nav />
</div>
);
};
as of right now if I click on the button for either one it will show the corresponding component but if both are clicked they both show up.
I'd like to write an if else statement to check if one is showing then the other should not be shown.
I've tried a few different things and can't seem to get it to work.
any feedback on a better way to do this or how to get it to work would be appreciated.
if(show === true){
setShow1(false)
} else if(show1 === true) {
setShow(false)
}
this gives an error of Too many re-renders. React limits the number of renders to prevent an infinite loop.
You can handle the hiding/showing logic for these button in the click events because that is where the state changes.
Example:
https://codesandbox.io/s/wild-water-f5nzor?file=/src/App.js
You can modify your onClick functions like this:
const onClick = () => setShow((prevState) => !show1 && !prevState); const onClick1 = () => setShow1((prevState) => !show && !prevState);

Show/Hide div onClick

I currently have my toggle action in place but the only help I need is that, I would like to close the div as well, like an toggle action. The one that I've currently done is that once I click on another div element the previous one that has been clicked closes, but I'd rather prefer that I have an toggle action on closing and opening on the div element being clicked, without needing to click on another just to close the previous div, I've only grabbed the parts that are needed in the code, just to prevent on copying and pasting the whole file, just to save time on reading.
Code Snippet
const [posts, setPosts] = useState([]);
const [commentState, commentChange] = useState({
activeObject: null
});
const toggleComment = (index) => {
commentChange({...commentState, activeObject: posts[index]})
}
const toggleActiveStyles = (index) => {
if(posts[index] === commentState.activeObject) {
return "dashboard__commentContent toggle";
} else {
return "dashboard__commentContent";
}
}
return error ? (
<span>{error}</span>
) : (
{posts.map((post, i) => (
<button onClick={() => toggleComment(i)} >toggle</button>
<div className={toggleActiveStyles(i)}>
<h1>{post.title}</h1>
</div>
)}
Here is a working codesandbox that you can manipulate to fit to your needs.
Explanation
You would want to keep track of toggled divs and make sure to adjust your class based on that. You can filter out or add to the toggled divs state variable, and do whatever you want while rendering.
Code
import { useState } from "react";
import "./styles.css";
const DATA = ["1", "2", "3", "4"];
export default function App() {
const [closedDivs, setClosedDivs] = useState([]);
const toggleDiv = (i) => {
setClosedDivs((divs) =>
divs.includes(i) ? divs.filter((d) => d !== i) : [...divs, i]
);
};
return (
<div className="App">
{DATA.map((d, i) => (
<div
className={`${closedDivs.includes(i) ? "close" : ""} box`}
onClick={() => toggleDiv(i)}
>
<p> {d} </p>
</div>
))}
</div>
);
}

Removing object from one array while simultaneously adding it to a different array - React hooks

I'm working on a Tinder-like app and trying to remove the current card from the array and move on to the next when clicking either the like or dislike button. Simultaneously, I am trying to add the card to a new array (list of liked or disliked). Adding the object to new array seems to work (although there's a delay and the button needs clicked twice - which also needs to be sorted), but as soon as I try to remove it from the current array it all crashes.
I tried looking at this solution: Removing object from array using hooks (useState) but I only ever get "TypeError: Cannot read property 'target' of undefined" no matter what I try. What am I missing?
This is the code:
import React, { useState, useEffect } from 'react';
import { Card, Button, Container } from 'react-bootstrap';
const url = 'https://swiperish-app.com/cards';
const SwiperCard = () => {
const [cardData, setCardData] = useState([]);
const [likedItem, setLikedItem] = useState([]);
useEffect(() => {
fetch(url)
.then(res => res.json())
.then(cardData => setCardData(cardData))
});
const handleRemoveItem = (event) => {
const name = event.target.getAttribute("name")
setCardData(cardData.filter(item => item.id !==name));
};
const likedCards = (itemId, itemImg, ItemTitle) => {
let likedArr = [...likedItem];
setLikedItem(likedItem => likedItem.concat({itemId, itemImg, ItemTitle}))
handleRemoveItem();
console.log(likedArr);
};
return (
<div id="contentView">
{cardData.map((item, index) => {
return(
<Card key={index} className="cardContainer" name={item.id}>
<Container className="btnContainer">
<div className="btnWrapper">
<Button className="btn" onClick={() => console.log(item.id)}>DISLIKE</Button>
</div>
</Container>
<Container className="cardContentContainer">
<Card.Img style={{width: "18rem"}}
variant="top"
src={item.image}
fluid="true"
/>
<Card.Body style={{width: "18rem"}}>
<Card.Title className="cardTitle">{item.title.toUpperCase()}</Card.Title>
<Card.Subtitle className="cardText">{item.body}</Card.Subtitle>
</Card.Body>
</Container>
<Container className="btnContainer">
<div className="btnWrapper">
<Button className="btn" onClick={() => likedCards(item.id, item.image,item.title) }>LIKE</Button>
</div>
</Container>
</Card>
)
})}
</div>
);
};
export default SwiperCard;
You can move cards between two arrays with
const likedCards = (item) => {
setLikedItem([...likedItem, item]);
let filtered = cardData.filter((card) => card.itemId !== item.itemId);
setCardData(filtered);
};
I suggest you to add empty array as second parameter of useEffect,since you are using as componentDidMount.
As second suggestion you can setLoading true before fetch and setLoading false after to reduce errors in render.
You're calling handleRemoveItem with no arguments, but that function is doing something with an event parameter, so you're going to get a TypeError.
It seems like handleRemoveItem really only needs to know about the item ID to remove, so you can simplify to:
const removeCard = id => {
setCardData(cardData.filter(item => item.id !== id));
};
const handleLike = (itemId, itemImg, ItemTitle) => {
setLikedItem([...likedItem, {itemId, itemImg, ItemTitle}]);
removeCard(itemId);
};
I also noticed that you're sometimes logging a state variable immediately after calling the setting. That won't work. It's not until the next call to useState on the next render when you'll receive the value, so if you want to log changes to state, I'd log in your render function, not in an event handler.

How can I set focus on a dynamically added field with React

I have a basic task list app that gives users the ability to add items to the task list. When the "Add Item" button is clicked I will insert a new row to the bottom of the list. The row contains an empty text field, where the user can enter their task name. I want to set the focus on this field as soon as it's push()ed into the array. I know how to set the focus using a ref if the field already exists, but I can't seem to figure it out for a dynamically added field. How can I do this?
Here is my code:
const tasks = [array_of_task_objects];
const [state, setState] = React.useState({tasks: tasks});
const newTask = {title: ''};
const addTask = () => {
let newTasks = [...state.tasks];
newTasks.push(newTask);
setState({...state, tasks: newTasks});
// Now, set focus in the input field...(how?)
};
Elsewhere:
<button onClick={addTask}>Add Task</button>
<ul>
{
state.tasks.map(task => {
return(
<li><input value={task.title}></li>
);
})
}
</ul>
One way to do this is to have a ref that's always referring to the last textbox and then running an effect that sets focus on that last element when tasks are updated. This is a shell of an example that should basically get you there:
export default function App() {
const [tasks, setTasks] = useState([newTask]);
const lastRef = useRef(null);
useEffect(() => {
if (lastRef.current)
lastRef.current.focus();
}, [tasks]);
return (
<div className="App">
{tasks.map((task, i) => (
<>
<input key={i} ref={i === tasks.length - 1 ? lastRef : undefined} />
<br />
</>
))}
<button
onClick={() => {
setTasks(tasks => [...tasks, newTask]);
}}
>
Add
</button>
</div>
);
}
You can make the task input focus itself when it is rendered the first time.
const Task = ({value}) => {
const ref = useRef(null);
useEffect(() => if (ref.current) {ref.current.focus()}, [ref.current])
return <li><input ref={ref} value={value} /></li>
}
This will work if you are only mounting one at a time. If you have multiple inputs rendered in an initial state for example you could introduce a shouldTakeFocus prop. Then you limit the effect to only run when shouldTakeFocus is true.

Change css class of a component by onClick on Icon in React using useState hook

I am new to React and I was hoping someone could help me with this issue. I am trying to render some images called 'cards' from an array based on the same data I've received from Axios. I basically need to render an array of card props which have an <i> tag with some font-awesome class attached to them. When I click on the "fa-search-plus" font-awesome icon, I want the parent of this icon <div> to trigger the onClick such that the css property of the sibling <img> of this <div> can be changed. For some reason with the following code, this does not seem to happen. Any fix is appreciated. Thanks!
const GameCards = (cards) => {
const [cardimgclass, setCardimgclass] = useState(true);
const onClick = (e) => {
e.preventDefault();
setCardimgclass(!cardimgclass);
};
const loadCardsByCategory = (cards) => {
var allCards = [];
if (cards)
cards.forEach((item, i) => {
allCards.push(
<div key={item._id} className="card-container">
<img
className={cardimgclass ? "card-reg" : "card-big"}
src={item.src}
alt="No file"
/>
<div onClick={(e) => onClick(e)}>
{" "}
<i className="fas fa-search-plus"></i>
</div>
</div>
);
});
return allCards;
};
const loadCards = (cards) => {
return (
<Fragment>
<div className="cardgallery">{loadCardsByCategory(cards)}</div>
</Fragment>
)
};
const loadCardsUsingMemo = useMemo(() => loadCards(cards), [cards]);
return <Fragment>{loadCardsUsingMemo}</Fragment>;
};
It looks like your primary problem is that you are not destructuring your props object:
const GameCards = cards => {
You need to change that to:
const GameCards = ({ cards }) => {
Also, remove the useMemo stuff. It is not helping you here. Here is a slimmed down version of your code. I'm changing the background-color property with the class, but the concept is the same. Hope that helps!
EDIT: Also note, as in the example your logic is currently changing all of the elements. If you want to only modify the class for one element you could use the method of passing the index (or better yet, the ID!). Here is an example:
const [cardimgclass, setCardimgclass] = useState();
...
const onClick = (e, item) => setCardimgclass(item._id)
...
<div onClick={e => onClick(e, item)} />

Resources