I'm trying to put a React-Icons into innerHTML of element whenever I clicked it.
like this: target.innerHTML = '<ImCross/>';
But it always failed to show the icons. BUT It will work perfectly if I'm using another element like <span></span> and just put some letters in it. So, is there a way to put an Icon into InnerHtml?
This is the example of my code:
import { ImCross } from "https://cdn.skypack.dev/react-icons/im";
const App = () => {
const box = {
backgroundColor: '#000',
width: 150,
height: 140
}
const symbol = {
color: '#fff',
textAlign: 'center',
position: 'relative',
top: 30,
fontSize: 70
}
const handleClickedBox = (e) => {
const target = e.currentTarget.querySelector('.symbol');
// THIS IS FAILED TO SHOW THE ICON
target.innerHTML = '<ImCross/>';
// THIS DOESN'T WORK TOO
target.innerHTML = '<span> <ImCross/> </span>';
// BUT THIS ONE IS WORK
target.innerHTML = '<span>X</span>';
}
return (
<div>
{/* This is the box that will be clicked */}
<div onClick={(e) => handleClickedBox(e)} style={box}>
{/* This is element that it's innerHTML will show the symbol */}
<div className='symbol' style={symbol}></div>
</div>
</div>
)
}
THIS THE NEW CODE WITH MULTIPLE BOXES:
What it's the best way if it's not with innerHTML to show the icon ONLY in clicked box, like in my code here
import { ImCross } from "https://cdn.skypack.dev/react-icons/im";
const App = () => {
const boxes = {
backgroundColor: '#000',
color: '#fff',
width: 120,
height: 120,
}
const symbol = {
color: '#fff',
textAlign: 'center',
fontSize: 50
}
const styles = {
display: 'grid',
gridTemplateColumns: 'repeat(3, 120px)',
gap: 10
}
const handleClickBoard = (e) => {
// Here, I'm gettin <div> of clicked box
const clickedBox = e.currentTarget;
// What should i do here to make the children
element of clicked element/box show the icon <ImCross />
// I only can achieve what I want by using this
clickedBox.querySelector('.symbol-on-board').innerHTML = 'X';
}
let component = [];
let key = 0;
for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
key++;
component.push(
{/* This the BOX that wrap the symbol */}
<div style={boxes} id={key} key={key} onClick={(e) => handleClickBoard(e)}>
{/* This is where i want the symbol to be shown */}
<div style={symbol} className='symbol-on-board'></div>
</div>
)
}
}
return (
<div style={styles}>
{component}
</div>
)
}
JSX is not HTML. It gets transformed into a bunch of JavaScript calls by Babel before it goes near the browser.
Don't try to generate React code dynamically. Work with React. Make use of the features it provides, like state.
For example:
const [clicked, setClicked] = useState(false);
const clickHandler = () => { setClicked(current => !current); }
return (
<div>
<div onClick={clickHandler} style={box}>
<div className='symbol' style={symbol}>
{clicked ? <ImCross/> : "Not clicked yet" }
</div>
</div>
</div>
)
Re comment: A more complex example with multiple boxes.
const [data, setData] = useState([
{ name: "Alice", clicked: false },
{ name: "Bob", clicked: false },
{ name: "Chuck", clicked: false }
]);
return <div>{
data.map(person =>
<Person key={person.name} setData={setData} person={person} />
)
}</div>;
and then
const Person = ({setData, person}) => {
const clickHandler = () => {
setData(data => {
return data.map(item => {
if (item.name === person.name) {
return {
...person,
clicked: !person.clicked
};
}
return person;
})
});
}
return <div onClick={clickHandler}>
{person.clicked && <ImCross/>}
{person.name}
</div>
}
Related
I have got this UI below. When I first open this component ModalColorList and click goNext(), it does not go next at first but the second time. After that, it goes next well, so it means the user needs to click twice the button in order to go next.
In the case of goPrev() it works fine, but it does not seem to be clean either.
I usually google before beginning to code, but this time, I would like to try by myself, so maybe this does not work as expected. Please let me know the better way to make goPrev and goNext smoother.
ModalColorList
const ModalColorList = ({ data }) => {
const [isLoading, setIsLoading] = useState(false);
const [large, setLarge] = useState({
idx: data?.index,
name: data?.name,
src: data?.image,
});
const onColorClick = (color, idx) => {
setLarge({
idx,
name: color.node.name,
src: color.node.imageAttribute.image.mediaItemUrl,
});
};
const goPrev = () => {
let count = 0;
if (large.idx === 0) {
count = data.data.length - 1;
} else {
count = large.idx - 1;
}
setLarge({
idx: count,
name: data?.data[count]?.node.name,
src: data?.data[count]?.node.imageAttribute.image.mediaItemUrl,
});
};
const goNext = () => {
let count = data.index++;
if (data.index > data.data.length) {
data.index = 0;
count = 0;
}
setLarge({
idx: count,
name: data?.data[count]?.node.name,
src: data?.data[count]?.node.imageAttribute.image.mediaItemUrl,
});
};
useEffect(() => {
setIsLoading(true);
}, []);
return (
<>
{isLoading && (
<div >
<div>
<div onClick={goPrev}>
<RiArrowLeftSLine />
</div>
<div>
<Image src={large.src} objectFit="cover" />
</div>
<div className="icon-wrap -right-[50px]" onClick={goNext}>
<RiArrowRightSLine />
</div>
</div>
<ul>
{data.data.map((color, idx) => (
<li key={color.node.id} onClick={() => onColorClick(color, idx)} >
<div className={` ${large.idx === idx && 'border-[#f7941d] border-4'}`}>
<Image src={color.node.imageAttribute.image.mediaItemUrl} />
</div>
</li>
))}
</ul>
</div>
)}
</>
);
};
export default ModalColorList;
Kindly let me
I'm not sure why didn't things didn't work for you, as there was very little code that I could analysis. But till what you have shared, the component should have been working properly.
I have cleaned your work some what and create a codesandbox for the same, hope it gives you some idea.
If this doesn't help, please do share a codesandbox instance where the behavior is reproduceable I will check on it.
CODESANDBOX LINK
ModalColorList.js
import { useState, useEffect } from "react";
const ModalColorList = ({ data }) => {
const [isLoading, setIsLoading] = useState(true);
const [large, setLarge] = useState({
idx: data?.index,
name: data?.name,
src: data?.image
});
const onColorClick = (color, idx) => {
setLarge({
idx,
name: color.name,
src: color.src
});
};
const goPrev = () => {
let count = 0;
const collection = data.collection;
if (large.idx === 0) {
count = collection.length - 1;
} else {
count = large.idx - 1;
}
setLarge({
idx: count,
name: collection[count].name,
src: collection[count].src
});
};
const goNext = () => {
let count = 0;
const collection = data.collection;
if (large.idx + 1 >= collection.length) {
count = 0;
} else {
count = large.idx + 1;
}
console.log(collection, count);
setLarge({
idx: count,
name: collection[count].name,
src: collection[count].src
});
};
return (
<>
{isLoading && (
<div>
<div>
<div onClick={goPrev}>Left</div>
<div onClick={goNext}>Right</div>
<div>
<img src={large.src} objectFit="cover" />
</div>
</div>
<ul>
{data.collection.map((color, idx) => (
<li key={idx} onClick={() => onColorClick(color, idx)}>
<div
className={` ${
large.idx === idx && "border-[#f7941d] border-4"
}`}
>
<img src={color.src} />
</div>
</li>
))}
</ul>
</div>
)}
</>
);
};
export default ModalColorList;
colorSample.js
export default {
name: "glassy",
src:
"https://images.unsplash.com/photo-1604079628040-94301bb21b91?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=687&q=80",
index: 0,
collection: [
{
name: "glassy",
src:
"https://images.unsplash.com/photo-1604079628040-94301bb21b91?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=687&q=80"
},
{
name: "pop",
src:
"https://images.unsplash.com/photo-1498940757830-82f7813bf178?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1074&q=80"
},
{
name: "milky",
src:
"https://images.unsplash.com/photo-1556139943-4bdca53adf1e?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=687&q=80"
}
]
};
App.js
import "./styles.css";
import ModalColorList from "./ModalColorList";
import colorSample from "./colorSample";
export default function App() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<ModalColorList data={colorSample} />
</div>
);
}
if you understand you have list and want to navigate with next and pre, simple you can create temp array, use push and pop to add and remove items then always show the last item like that.
yourArray[yourArray.length - 1]
let me show you example.
let currentIndex = 0;
const mylistItem = [1,2,3,4,5];
let showItems = [mylistItem[currentIndex]] // default show first item always showing last item
const lastItem = showItems[showItems.length - 1]; // the item want to display
for Moving forward
showItems.push(myListItem[currentIndex+1]);
currentIndex += 1;
for Moving back
showItems.pop();
currentIndex -= 1;
Hello I have a single list component which render some <Card> components that has a prop isSelected . Because alot of things happens when a <Card> Component has isSelected === true I added the state on the <List> component, and I want when someone clicks a card to check:
1) If there are no previously selected items ( state===null to add that item's id to state )
2) If someone clicks the same item or another item while there is already an item selected in state, to just unselected the active item.
import {Card} from "./Card";
import cloneDeep from 'lodash/cloneDeep';
const List = () => {
const [selectedCard, setSelectedCard] = useState(null);
const onCardClick = id => {
console.debug(selectedCard, id)
const newSelectedCard = cloneDeep(selectedCard);
// if he clicks another item while an item is active
// or if he clicks the same item while active
// should just make it inactive
if (newSelectedCard !== null || newSelectedCard === id) {
setSelectedCard(null)
} else {
setSelectedCard(id)
}
console.debug(selectedCard, id)
}
return (
<ul className="card-list">
{cardData.map(card => (
<Card
onClick={() => onCardClick(card.id)}
key={card.id}
isSelected={selectedCard === card.id}
{...card}
/>
))}
</ul>
)
}
export const CardList = () => (
<List/>
);
The issue is that the 2 console.debugs print the same values which means that the state doesnt update imediately and Im experiencing some strange behaviours here and there. Am I missing something here?
Basically you need to follow 3 condition as below
if(newSelectedCard === null){
setSelectedCard(id)
}
else if(newSelectedCard === id){
setSelectedCard(null);
}
else{
setSelectedCard(id)
}
Here is the Complete example:
import cloneDeep from 'lodash/cloneDeep';
import React, {useState} from "react";
const List = () => {
const [cardData, setCardData] = useState([
{id: 1, title: 'First Card'},
{id: 2, title: 'Second Card'},
{id: 3, title: 'Third Card'},
{id: 4, title: 'Fourth Card'},
]);
const [selectedCard, setSelectedCard] = useState(null);
const onCardClick = id => {
console.log(selectedCard, id);
const newSelectedCard = cloneDeep(selectedCard);
// if he clicks another item while an item is active
// or if he clicks the same item while active
// should just make it inactive
if(newSelectedCard === null){
setSelectedCard(id)
}
else if(newSelectedCard === id){
setSelectedCard(null);
}
else{
setSelectedCard(id)
}
console.log(selectedCard, id)
};
return (
<ul className="card-list">
{cardData.map(card => (
<Card
onClick={() => onCardClick(card.id)}
key={card.id}
isSelected={selectedCard === card.id}
{...card}
/>
))}
</ul>
)
};
export const CardList = () => (
<List/>
);
const Card = (props) => {
const backColor = props.isSelected? '#F9740E' : '#3FB566';
return (
<div onClick={() => props.onClick()}>
<div style={{backgroundColor: backColor, border: '1px solid darkgreen', color: 'white', padding: 10, marginBottom: 10}}>
<h3>{props.id}</h3>
<h4>{props.title}</h4>
</div>
</div>
);
};
Update
Here is Code SandBox
Not sure why you need to use cloneDeep.
const onCardClick = id => {
if (selectedCard === id) {
setSelectedCard(null);
} else {
setSelectedCard(id);
}
}
today I offer you a new challenge
my problem and the next
in the following code
I map objects in my database
I also map a list of items
and so basically I would like to pass my database and not the Items table
basically I want to do exactly the same thing as in the following code except that instead of using the items array, I would like to be able to use the data array which contains my database
do you have any idea how to do this?
I hope I was precise thanks to you for the help Neff
ps: Sorry for the length of the code i try to do my best to clean a little
import React, { Component } from 'react';
import { CardText, Card,Row, Col, Button } from 'reactstrap';
import axios from 'axios'
import GridLayout from 'react-grid-layout';
import SmsForm from './Sms/SMSForm'
import FruitList from './FruitList'
import './AdminPage.scss'
const UP = -1;
const DOWN = 1;
const entrypoint = process.env.REACT_APP_API_ENTRYPOINT+'/api';
class AdminPage extends Component {
constructor(props) {
super(props);
this.state = {
items: [
{ id: 1, name: "orange", bgColor: "#f9cb9c" },
{ id: 2, name: "lemon", bgColor: "#fee599" },
{ id: 3, name: "strawberry", bgColor: "#e06666" }
],
data: [],
}
handleMove = (id, direction) => {
const { items } = this.state;
const position = items.findIndex(i => i.id === id);
if (position < 0) {
throw new Error("Given item not found.");
} else if (
(direction === UP && position === 0) ||
(direction === DOWN && position === items.length - 1)
) {
return; // canot move outside of array
}
const item = items[position]; // save item for later
const newItems = items.filter(i => i.id !== id); // remove item from array
newItems.splice(position + direction, 0, item);
this.setState({ items: newItems });
};
// rest of the component
onHandleChange(event) {
const name = event.target.getAttribute('name');
this.setState({
message: { ...this.state.message, [name]: event.target.value }
});
}
getRandom = async () => {
const res = await axios.get(
entrypoint + "/alluserpls"
)
this.setState({ data: res.data })
}
componentDidMount() {
this.getRandom()
}
render() {
let datas = this.state.data.map(datass => {
const status = JSON.parse(localStorage.getItem("validated-order") || "{}")[datass.id];
return (
<div>
< Col sm="12" key={datass.id} className="wtfFuHereIsForOnlyBackGroundColorForCol12Nice">
<FruitList fruitList={this.state.items} onMove={this.handleMove} />
<GridLayout className="GridlayoutTextOnlyForGridOuiAndHeigthbecauseHeigthWasBug" layout={layout} cols={12} rowHeight={30} width={1200}>
<div key="a">
<Card body className="yoloCardBodyForbackGroundAndRaduisBorderForAdminPageWtf">
<CardText className="cardTextForAdminPageForDataName"> Commande de {datass.name}</CardText>
</Card>
</div>
</ Col>
</div>
)
})
return (
<div> <div>
<Row className="wtfHereIsAgainOnlyForRowAndMarginForAdminPageJustWtf">
<div className="isJustForOnlyPaddingOnRowForAdminPage" >
<div className="navBarGridMenuAdminPage">
<div className="thatIsOnlyForSpaceArroundOnDivAdminPage">
<CardText className="maybeColForAdminPageOuiOui"> Nouvelles commandes </CardText>
</div>
</div>
<div>
{datas}
</div>
</div>
</Row>
</div>
<div className="box">
</div>
</div>
)
}
}
export default AdminPage
here my second components
import React from "react";
const LEFT = -1;
const RIGHT = 1;
class FruitList extends React.Component {
render() {
const { fruitList, onMove } = this.props;
return (
<div style={{ display: "flex" }}>
{fruitList.map(item => (
<div
key={item.id}
style={{
backgroundColor: item.bgColor,
display: "flex"
}}
>
<div className="fruitsArrows">
<a onClick={() => onMove(item.id, LEFT)}>←</a>
</div>
<div className="fruitsId">{item.id}</div>
<div className="fruitsName">{item.name}</div>
<div className="fruitsArrows">
<a onClick={() => onMove(item.id, RIGHT)}>→</a>
</div>
</div>
))}
</div>
);
}
}
export default FruitList;
To delete particular list do like this-
pass your item(object).
<a onClick={() => onMove(item)}>→</a>
handleMove function
handleMove = (row) => {
let filtered = this.state.items.filter(item=>item.id!==row.id);
this.setState({ items: filtered});
};
I am attempting to learn React and hooks by porting a jquery dropdown box to React. The problem I am having is that when I select an option, it's not being displayed in the div that should display the selected options. My code is:
export const ReactDropdown: React.FC<IDropdownProps> = (props) => {
const [options, setOptions] = React.useState([]);
const [selectedOptions, setSelectedOptions] = React.useState([]);
React.useEffect(() => {
if(options.length === 0) {
if(React.Children.count(props.children) > 0) {
var optionsList: ReactDropdownOption[] = [];
var index: number = 0;
React.Children.forEach(props.children, function(item) {
var entry = {
value: (item as React.ReactElement).props.value,
text: (item as React.ReactElement).props.children,
index: index
}
index++;
optionsList.push(entry);
});
setOptions(optionsList);
}
}
});
const itemSelect = (evt: React.MouseEvent): void => {
var item = evt.currentTarget as HTMLLIElement;
if(item.className == 'disabled-result') {
return;
}
var option = {
text: item.innerText,
value: item.dataset.value,
index: parseInt(item.dataset.index),
className: props.allowSingleDeselect || (props.isMultiple && selectedOptions.length > 0) ? 'search-choice' : ''
}
var currentOptions = props.isMultiple ? [...selectedOptions] : [];
currentOptions.push(option);
setSelectedOptions(currentOptions);
item.className = 'disabled-result';
if(props.onChange) {
props.onChange(currentOptions);
}
}
return (
<div>
<div className='dropdown-container' tabIndex={0}>
<div className='results-container'>
<div className='dropdown-container inner'>
{selectedOptions.map((item) => {
<span className='result' data-index={item.index} data-value={item.value} key={'selected-option-' + item.index}>{item.text}</span>
})}
<span className='result-search'><input type='text' onClick={openOptions} /></span>
</div>
</div>
</div>
<div clssName='options-container'>
<ul>
{options.map((item) => (
<li data-index={item.index} data-value={item.value} className='active-result' onClick={itemSelect} key={'option-' + item.index}>{item.text}</li>
))}
</ul>
</div>
</div>
);
}
The options.map works perfectly and all of the options are rendered the way they should be. When I select one of the options, itemSelect is firing. After itemSelect fires, return fires again. If I put a breakpoint in selectedOptions.map, all of the selectedOptions are being mapped, but the HTML is either never rendered or isn't showing and I don't know why. Can anyone tell me what I'm doing wrong here?
Here is a CodePen showing the error:
CodePen
Context
All of actions are keyboard only, no mouse click.
Example:
I have a 2 column table which has file and button.
When press enter key on button1, menu (contains download, view, delete) will popup. The focus is on button1
Press down arrow key now, the download background is blue highlighted.
My problem
I want download button to be focus instead of button1, when pressing down arrow on button1
Full code:
import React, {useState, useEffect, useRef, createRef, useContext} from 'react';
const AppContext = React.createContext({
name: 'AppContext'
});
const createMenuItemRefs = (items, rowInd) => {
// obj
let menuItemRefs = {};
// loop each
for (let i = 0; i < Object.keys(items).length; i++) {
// When assign createRef, no current
menuItemRefs[rowInd + i] = createRef();
}
return menuItemRefs;
};
function Menu({buttonName, parentRowIndex}) {
const [currRowInd, setCurrRowInd] = useState('');
const [open, setOpen] = useState(false);
// press down key, will get 1st item which at index 0
const [menuItemActiveIndex, setMenuItemActiveIndex] = useState(-1);
const menuItems = {download: 'download', view: 'view', delete: 'delete'};
const menuItemRefs = useRef(createMenuItemRefs(menuItems, parentRowIndex));
useEffect(() => {
if (open && menuItemActiveIndex >= 0 && parentRowIndex !== '') {
menuItemRefs.current[parentRowIndex + menuItemActiveIndex].focus();
}
}, [menuItemActiveIndex, open, parentRowIndex]);
// on the button level
const buttonIconKeyDown = (event, parentRowIndex) => {
if (event.keyCode === 13) {
// Enter pressed
console.log('enter is pressed');
setOpen(!open);
setCurrRowInd(parentRowIndex);
} else if (event.keyCode === 9) {
// tab away
console.log('tab away');
setOpen(!open);
setCurrRowInd('');
} else if (event.keyCode === 40) {
//test
console.log('down arrow');
// 38 is up arrow
// No scrolling
event.preventDefault();
// set to 1st item in 0 index
setMenuItemActiveIndex(0);
}
};
//test
console.log(
'menuItemRefs.current',
menuItemRefs.current,
'menuItemActiveIndex',
menuItemActiveIndex
);
return (
<div>
<button
onKeyDown={event => {
//test
console.log('parent buttonicon onkeydown: ');
buttonIconKeyDown(event, parentRowIndex);
}}
>
{buttonName}
</button>
{open && parentRowIndex === currRowInd && (
<ul style={{padding: '5px', margin: '10px', border: '1px solid #ccc'}}>
{Object.keys(menuItems).map((item, itemIndex) => {
if (itemIndex === menuItemActiveIndex)
return (
<li
key={itemIndex}
style={{
listStyle: 'none',
padding: '5px',
backgroundColor: 'blue'
}}
// put a ref
ref={element =>
(menuItemRefs.current[parentRowIndex + itemIndex] = element)
}
>
<button>{item}</button>
</li>
);
else
return (
<li
key={itemIndex}
style={{listStyle: 'none', padding: '5px'}}
ref={element =>
(menuItemRefs.current[parentRowIndex + itemIndex] = element)
}
>
<button>{item}</button>
</li>
);
})}
</ul>
)}
</div>
);
}
function TableElement() {
const items = [
{
file: 'file1',
button: 'button1'
},
{
file: 'file2',
button: 'button2'
},
{
file: 'file3',
button: 'button3'
}
];
return (
<table style={{borderCollapse: 'collapse', border: '1px solid black'}}>
<tbody>
{items.map((item, index) => {
return (
<tr key={index}>
<td style={{border: '1px solid black'}}>
{item.file}
</td>
<td style={{border: '1px solid black'}}>
<Menu buttonName={item.button} parentRowIndex={index} />
</td>
</tr>
);
})}
</tbody>
</table>
);
}
function App() {
const appContextObj = {};
return (
<>
<AppContext.Provider value={appContextObj}>
<TableElement />
</AppContext.Provider>
</>
);
}
export default App;
github
https://github.com/kenpeter/key-mouse-dropdown/tree/feature/focus
In your useEffect block
useEffect(() => {
if (open && menuItemActiveIndex >= 0 && parentRowIndex !== '') {
menuItemRefs.current[parentRowIndex + menuItemActiveIndex].children[0].focus();
}
}, [menuItemActiveIndex, open, parentRowIndex]);
You can call focus on your children in this case <button> instead of focusing on your <li> instead. Please note that if the code above will always focus on the first child of your ref current instance.
Bonus :
You can also use tabIndex={-1} on any elements to prevent it from being focused.