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
Related
The input loses its focus when I start typing a character. I saw many StackOverflow answers but none of them is working. I have added unique keys also. What is the reason the code is not working? Without the state, it is working fine. But after adding the state, the input loses the focus.
import React, { useState } from "react";
const Footer = ({ formData }) => {
const [colorsArray, setColors] = useState(["Red", "Green", "Blue", "Yellow"]);
const [sizeArray, setSizes] = useState(["S", "M", "L", "XL"]);
const [sizeInput, setsizeInput] = useState("");
const colorElementRemoveHandler = (indexToRemove) => {
const filteredValue = colorsArray.filter((data, index) => {
return indexToRemove !== index;
});
setColors(filteredValue);
};
const sizeElementRemoveHandler = (indexToRemove) => {
const filteredValue = sizeArray.filter((data, index) => {
return indexToRemove !== index;
});
setSizes(filteredValue);
};
const addColorHandler = (e) => {
let input = e.target.value.toLowerCase();
if (input.length > 2) {
let temp = colorsArray;
temp.push(input);
setColors(temp);
}
};
const addSizeHandler = (e) => {
let input = e.target.value.toUpperCase();
if (input.length > 0) {
let temp = sizeArray;
temp.push(input);
setSizes(temp);
console.log(sizeArray);
}
};
const Test = () => {
return (
<input
type="text"
onChange={(e) => {
setsizeInput(e.target.value);
}}
value={sizeInput}
/>
);
};
const VariantUI = () => {
return (
<div>
<label>Size</label>
<input
id="optionName"
type="text"
placeholder="e.g S, M, L, XL"
onChange={(e) => {
setsizeInput(e.target.value);
}}
value={sizeInput}
/>
</div>
<ul>
{sizeArray.map((data, index) => {
return (
<li key={index}>
{data}
<i onClick={() => {sizeElementRemoveHandler(index);}}></i>
</li>
);
})}
</ul
);
};
return (
<VariantUI formData={formData} />
);
};
export default Footer;
Thanks in advance.
const Footer = ({ formData }) => {
// ..
const VariantUI = () => {
// ...
return (<VariantUI formData={formData} />)
}
You are creating a brand new type of component (VariantUI), in the middle of rendering Footer. This will happen on ever render. Each VariantUi function might have the same text as the previous one, but it's a different function, and thus to react it's a different type of component. Since it's a different type of component, the old one unmounts, and the new one mounts. A newly-mounted <input> does not have focus.
Component types must be defined only once, not on ever render. So VariantUI needs to be moved outside of footer. Since you're currently relying on closure variables, you will need to changes those to props:
const VariantUI = ({
sizeArray, setSizes, sizeInput, setSizeInput, // I might have missed a couple props
}) => {
// ...
}
const Footer = ({ formData }) => {
// ...
return (
<VariantUI
sizeArray={sizeArray}
setSizes={setSizes}
sizeInput={sizeInput}
setSizeInput={setSizeInput}
/>
);
}
i am trying to create a search form using React typescript props event.I have acheived half of it but now stuck on an checkbox multiSelector where i have no idea how we can implement it.i have googled a lot but got nothing in return.
here is my code.
I am using common typescript props event onChange for setting all the values inside my search Api Object.
can anyone help me out with code or docs how we can acheive multiSelector checkbox for React Typescript props event.
1.here is my search for structure=>
enter code here
let columns = useMemo(
() => [
{
Header: "Name", accessor: "username",
Cell: (props: any) => {
if (authoritiesList.includes("USR-U")) {
let path = "/users/" + props.value;
return createClickableLink(path, props.value);
} else {
return <>
<span>{props.row.original.username}</span>
</>
}
},
FilterOptions: {
FilterInput:
<input type="text" placeholder="Username..." />,
overrideFilterLabel: "Username",
overrideFilterAccessor: "username"
}
},
{
Header: "Role(s)", accessor: "roles", disableSortBy: true,
Cell: (props: any) => {
return <>
{props.row.original.roles.map((role: any) => {
return (
<div>
<span>{role}</span><br/>
</div>)
})}
</>
},
FilterOptions: {
FilterSelect:
roleData.items.map((curRole:any)=>{
return (
<input type="checkbox value=
{curRole.name} />
)
})} ,
overrideFilterLabel: "Roles",
overrideFilterAccessor: "roles"
}
},
},
], [customerData,roleData]
)
enter code here
const selector = (state: any) => state.users;
return (
<div className="m-0 p-0 ">
<section id="content-wrapper">
<div className="row">
<div className="col-lg-12 ml-auto">
<Breadcrumb crumbs={crumbs}/>
<div className="table_data mt-2">
{createSearchButton()}
{authoritiesList.includes("USR-C") && createAddButton("/users/create", "Add User")}
<DataTable columns={columns}
fetchAction={userActions.getAllData as Dispatch<Action>}
exportAction={userActions.exportData as Dispatch<Action>}
selector={selector}/>
</div>
</div>
</div>
</section>
</div>
);
}
I want to handle multi selected checkbox event for this form in
Typescript. all forms input tags are working currently but multiselected checkbox is not working for brining output to the query object.
here is my typescript code.
for (let column of tableColumns) {
if (!column.FilterOptions) {
column.FilterOptions = {};
}
if (column.FilterOptions?.FilterSelect) {
column.FilterOptions.FilterSelect.props.onKeyPress = (event: KeyboardEvent) => {
event.key === 'Enter' && setApplyFilter(true);
}
column.FilterOptions.FilterSelect.props.onChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
updateFilterQuerySelect(column, filterQuery, setFilterQuery, event);
}
}
if (column.FilterOptions?.FilterInput) {
column.FilterOptions.FilterInput.props.onKeyPress = (event: KeyboardEvent) => {
event.key === 'Enter' && setApplyFilter(true);
}
column.FilterOptions.FilterInput.props.onChange = (event: React.ChangeEvent<HTMLInputElement>) => {
updateFilterQuery(column, filterQuery, setFilterQuery, event);
}
}
}
here is function updateFilterQuery
const updateFilterQuery = (column: DataTableColumn, filterQuery: any, setFilterQuery: Function, event: React.ChangeEvent) => {
let tempQuery: any = {...filterQuery};
let key: string = column.FilterOptions?.overrideFilterAccessor || column.accessor;
let value: any = event.target.value;
if (event.target.value == "on" && event.target.checked != undefined) {
value = event.target.checked;
}
if (event.target.value == undefined) {
delete tempQuery[key];
} else {
key === 'phone' ? tempQuery[key] = getUnformattedPhoneNumber(value)
:
tempQuery[key] = value;
}
setFilterQuery(tempQuery);
}
It is a search form and similary it is working same as for other forms as well th eonly part missing in this form is now multiselector which is not working.
You have to separate selection state into a custom hook. A state is an array of selected items.
CodeSandbox
hooks.ts
import React, { useState } from "react";
export const useMultiselect = (initialValue: string[]) => {
const [selected, setSelected] = useState<string[]>(initialValue);
const onChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const value = event.target.value;
const index = selected.indexOf(value);
if (index > -1) {
setSelected([...selected.slice(0, index), ...selected.slice(index + 1)]);
} else {
setSelected([...selected, ...[value]]);
}
};
const isSelected = (value: string) => {
return selected.includes(value);
};
return { selected, isSelected, onChange };
};
App.tsx
import { useMultiselect } from "./hooks";
const data = ["Apple", "Orange", "Banana", "Pear", "Peach"];
export default function App() {
const { selected, isSelected, onChange } = useMultiselect([]);
return (
<div>
<div>Select your favorite fruites!</div>
<ul style={{ listStyleType: "none" }}>
{data.map((value) => (
<li key={value}>
<input
id={value}
type="checkbox"
value={value}
checked={isSelected(value)}
onChange={onChange}
/>
<label htmlFor={value}>{value}</label>
</li>
))}
</ul>
<div>Selected: {selected.join()}</div>
</div>
);
}
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;
import React, { useState } from 'react';
const Accordion = ({ items }) => {
const [activeIndex, setActiveIndex] = useState(null);
const onTitleClick = (index) => {
setActiveIndex(index);
};
const renderedItems = items.map((item, index) => {
const active = index === activeIndex ? 'active' : '';
return (
<React.Fragment key={item.title}>
<div className={`title ${active}`} onClick={() => onTitleClick(index)}>
<i className='dropdown icon'></i>
{item.title}
</div>
<div className={`content ${active}`}>
<p>{item.content}</p>
</div>
</React.Fragment>
);
});
return <div className='ui styled accordion'>{renderedItems}</div>;
};
export default Accordion;
I created an accordion using Semantic UI library.
I was able to set class of dropdown "active" so anything that I click will expand.
I am trying to implement "Close on second click" functionality to the below code,
so I try to implement by adding following code under onTitleClick function:
const onTitleClick = (index) => {
if (index === activeIndex) {
setActiveIndex(null); // in case 'clicked index' is the same as what's active, then configure it to "null" and rerender
}
setActiveIndex(index);
};
My understanding is whenever the state updates, react will run its script again, so in this particular situation, that variable "active" should return empty string if clicked for the same Index.
Rather than my expectation, nothing happened when I clicked it for the second time.
I tried to console.log in the if statement and it will show that I have clicked the item for the second time.
Please advise.
The issue is here:
const onTitleClick = (index) => {
if (index === activeIndex) {
setActiveIndex(null); // in case 'clicked index' is the same as what's active, then configure it to "null" and rerender
}
setActiveIndex(index); <-- here
}
what happening here is if if condition matches then it sets to setActiveIndex null but code will run so again it sets to setActiveIndex(index). That's why click is not working . You need to do this:
const onTitleClick = (index) => {
if (activeIndex === index) {
setActiveIndex(null);
} else {
setActiveIndex(index);
}
};
Here is the demo: https://codesandbox.io/s/musing-poitras-bkiwh?file=/src/App.js:270-418
Try the below approach,
const onTitleClick = (index) => {
setActiveIndex(activeIndex !== index ? index : null);
};
I have tried with react class component so worked it.
import React, { Component } from 'react';
class Accordion extends Component {
state = {
activeIndex: null
}
onTitleClick = (index) => event => {
const { activeIndex } = this.state;
this.setState({ activeIndex: activeIndex == index ? null : index })
};
render() {
const { activeIndex } = this.state;
const{items}=this.props;
return <div className='ui styled accordion'>
{
items.map((item, index) => {
const active = index === activeIndex ? 'active' : '';
return <React.Fragment key={item.title}>
<div className={`title ${active}`} onClick={this.onTitleClick(index)}>
<i className='dropdown icon'></i>
{item.title}
</div>
<div className={`content ${active}`}>
<p>{item.content}</p>
</div>
</React.Fragment>
})
}
</div>
}
}
export default Accordion;
I am working on react js app where I used "react-dragula" to drag and drop the list items. I am showing the preview of the child component html inside the parent wrapper component. After dropping an element my html is not render properly. I have no idea whether it is because of Dragula or there is some other issue.
After dropping the list item I am updating list values according to the element index and updating the state and the re rendering the child component. But it shows me old html it's not re rendering the html of child component using updated props return by parent component.
Here is my code::
class App extends React.Component {
drake = null;
socialContainers = [];
/** lifecycle method */
componentDidMount() {
this.drake = Dragula(this.socialContainers, {
isContainer: function (el) {
return (el.id === 'social-container');
},
moves: function (el, source, handle, sibling) {
return (handle.id === "socialSortBtn");
},
accepts: function (el, target, source, sibling) {
return (target.id === 'social-container' && source.id === 'social-container');
},
});
this.drake.on('drop', (el, target, source, sibling) => {
el.style.cursor = "grab";
let oldIndex = el.dataset.index
let type = el.dataset.type
let iconIndx = Array.prototype.indexOf.call(target.children, el);
let targetIndex = iconIndx > 0 ? iconIndx : 0;
if (type) {
let content = {}
content.reOrder = true;
content.oldIndex = oldIndex;
this.props.callback(content, 'content', targetIndex)
}
});
this.drake.on('drag', (el, source) => {
el.style.cursor="grabbing";
})
}
updateLinkText(val, index) {
const { data, callback } = this.props;
let textParsedHtml = new DOMParser().parseFromString(data.content[index].text, 'text/html');
if (textParsedHtml.getElementsByTagName("a")[0]) {
textParsedHtml.getElementsByTagName("a")[0].innerHTML = val;
}
let content = {}
content.changeLinkTxt = true
content.linkText = val
content.linkTextHtml = textParsedHtml.body.innerHTML
//update content
callback(content, 'content', index)
}
onSelectShareOpt(selectedVal, selectedIndx, event) {
event.stopPropagation();
let socialObj = socialShareArr.find((obj) => obj.value === selectedVal)
if (socialObj) {
let htmlObj = this.getHTMLObj(socialObj);
let content = {}
content.active_social_icons = socialObj
if(htmlObj) { content.content = htmlObj }
// update content
this.props.callback(content, 'content', selectedIndx)
}
}
isIconDisabled = (data, val) => {
let found = false;
found = data.some(function (obj) {
if (obj.value === val) {
return true;
}
return false;
});
return found;
}
renderSocialIcons(dragulaDecoratorRef) {
const { data } = this.props;
let socialIcons = data.activeIcons;
if (!socialIcons) {
return null
}
return (
<Fragment>
{socialIcons && socialIcons.length > 0 && socialIcons.map((activeIcon, index) => (
<li key={index} data-index={index} data-type={activeIcon.value} className="mb-30">
<div className="mr-2">
<button data-toggle="tooltip" title="Drag to reorder" className="btn btn-primary btn-sm btn-icon" id="dragBtn"><span id="socialSortBtn" className="material-icons m-0">drag_indicator</span>
</button>
</div>
<div className="mb-30">
<img className="mr-2" src={activeIcon.icon} alt="social-icon" width="36" height="36" />
<FormControl
value={activeIcon.value}
as="select"
onChange={e => this.onSelectShareOpt(e.target.value, index, e)}
custom
size=''
>
{socialShareArr && socialShareArr.map((option, index) => (
<option
key={index}
disabled={this.isIconDisabled(socialIcons, option.value)}
value={option.value}
>
{option.label}
</option>
))}
</FormControl>
</div>
<Form.Group>
<Form.Label>Link Text</Form.Label>
<TextInput
value={activeIcon.linkText}
onChange={(e) => this.updateLinkText(e.target.value, index)}
wrapperClass="mx-10 mb-0"
/>
</Form.Group>
</li>
))}
</Fragment>
);
}
// main function
render() {
const { data } = this.props;
const dragulaDecoratorRef = (componentBackingInstance) => {
if (componentBackingInstance) {
this.socialContainers.push(componentBackingInstance)
}
};
return (
<Fragment>
{data &&
<AppCard>
<Card.Body>
<div className="social-opt-container">
<ul id='social-container' ref={dragulaDecoratorRef}>
{this.renderSocialIcons(dragulaDecoratorRef)}
</ul>
</div>
</Card.Body>
</AppCard>
}
</Fragment>
)
}
}
export { App }
I have also tried to remove innerHTML of ""and then return new structure but in this case it returns nothing in html. Please check this once why this issue occurring.