Only one key press is registered, then the input loses focus however I can click back into the component and add ... one character. State is updated.
Since the child components are state-less I assumed that just passing the handlers down as props as described in the docs would be sufficient, but everything from changing the binding for the method to adding a constructor have yielded the same results. I can usually find an answer on the site but no luck this time.
const list = [
{
id : 0,
title : "Went well",
showCard : false,
cards : [],
color : "pink"
},
{
id : 1,
title : "To Improve",
showCard : false,
cards : [],
color : "yellow"
},
{
id : 2,
title : "Action Items",
showCard : false,
cards : [],
color : "blue"
}
]
class App extends Component {
state = {list : list, usrInpt : ""}
handleChange = e => {this.setState({usrInpt:e.target.value})}
add = e => {
e.preventDefault()
let updatedList = this.state.list.map(obj => {
if(obj.id == e.target.id)
this.state.list[obj.id].cards.push(this.state.usrInpt)
return obj
})
console.log("uL",updatedList)
this.setState({list:updatedList})
//this.setState(this.state.list[e.target.id].cards = [...this.state.list[e.target.id].cards,"pp"])
}
render() {
return (
<div className="App">
<h2>Metro Board</h2>
<ul className="container">
{this.state.list.map((item) =>
<List key={(Math.random() * 1)} text={item.title}
id={item.id} cards={item.cards} color={item.color}
usrInpt={this.state.usrInpt} add={this.add} handleChange={this.handleChange}/>
)}
</ul>
</div>
)
}
}
const List = (props) =>{
return(
<li>
<h3>{props.text}</h3>
<ul className="stack">
<li><button id={props.id} type="button" className="block" onClick={e =>props.add(e)}>+</button></li>
{props.cards.map((card,i)=> {
console.log("card",card)
return <ListItem key={(Math.random() * 1)}
idx={i}
color={props.color}
handleChange={props.handleChange}
usrInpt={props.usrInpt}/>
})}
</ul>
</li>
)
}
const ListItem = (props) =>{
console.log("card props and value",props)
return <li>
<div className="card" style={{backgroundColor: props.color}}>
<textarea type="text"
className="card"
placeholder="Enter text here"
value={props.usrInpt}
onChange={e => props.handleChange(e)}>
</textarea>
<div><a className="ltCtl" href="./logo" onClick={e => console.log(e)}><</a>
<a className="clCtl" href="./logo" onClick={e => console.log(e)}>x</a>
<a className="rtCtl" href="./logo" onClick={e => console.log(e)}>></a>
</div>
</div>
</li>
}
Both List && ListItem are separate files... Any help would be great. Thanks.
UPDATE:
I was able to reach out to a full time developer and it seems I screwed up by trying to make unique keys :
The key needs to be consistent, but in this case it is a different value every time
React uses the key when it IDs which element is focusing on, but in this case, it is different than the last render. So React does not know what to focus on. You can have unique keys if you use a string with the index of the loop in it, or if you use an ID that you store outside in the loop, like in state
It does need to be unique, but it also needs to be consistent.
So the code:
return (
<Card
key={Math.random() * 1} // <--- Don't!!
idx={i}
color={props.color}
handleChange={props.handleChange}
usrInpt={props.usrInpt}
/>
);
was causing React to lose track of what to render since the keys where changing with each render. The preferred method is using a string interpolation with an identifier and an index if a loop is used:
return(
<li>
<h3>{props.text}</h3>
<ul className="stack">
<li><button id={props.id} type="button" className="block" onClick={e =>props.add(e)}>+</button></li>
{props.cards.map((card,i)=> {
console.log("card",card)
return <Card key={`card-column-${props.id}-${i}`}
idx={i}
color={props.color}
handleChange={props.handleChange}
usrInpt={props.usrInpt}/>
})}
</ul>
</li>
)
Which was also a comment made by #miyu ... which I did not test. Listen to your peers and mentors... you will not lose 12 hours chasing bugs. Thanks.
state is non-hierarchical. Meaning, when you update a child object of your state but the parent object is not updated, then react will not trigger componentDidChange.
Try adding a counter which gets updated when the list is updated.
add = e => {
e.preventDefault()
let updatedList = this.state.list.map(obj => {
if(obj.id == e.target.id)
this.state.list[obj.id].cards.push(this.state.usrInpt)
return obj
})
console.log("uL",updatedList)
let counter = this.state.counter || 0;
this.setState({list:updatedList, counter: counter++})
//this.setState(this.state.list[e.target.id].cards = [...this.state.list[e.target.id].cards,"pp"])
}
Related
I have a SideBar navigation component, that holds an open/close state.
This state is drilled down to childs (SideBarButton) to perform conditional CSS styling.
Strange thing is, that the SideBarButton, despite receiving the prop once, it is shown twice in the rendered tree, and always taking the false value.
Any one any idea why?
NavigationLinksView receives isOpenSideNav:
<div className={styles.root}>
<NavigationLinksView
isOpenSideNav={isOpenSideNav}
navigationLinksList={navigationLinksTop}
/>
<NavigationLinksView
isOpenSideNav={isOpenSideNav}
navigationLinksList={navigationLinksBottom}
/>
</div>
NavigationLinksView.js:
const NavigationLinksView = ({isOpenSideNav, navigationLinksList}) => {
return (
<ul
className={styles.root}
>
{_.map(navigationLinksList, (navigationItem, idx) => {
const {sideMenuContent, name} = navigationItem;
if (sideMenuContent) {
return (
<SideBarButtonWithSubMenu
key={`${name}_${idx}`}
isOpensideNav={isOpenSideNav}
navigationItem={navigationItem}
/>
);
}
return (
<SideBarButton
key={`${name}_${idx}`}
isOpensideNav={isOpenSideNav}
navigationItem={navigationItem}
/>
);
})}
</ul>
);
};
Yet, each SidebarButton or SideBarButtonWithSubMenu has problems reading that prop:
const SideBarButton = ({navigationItem, isOpenSideNav}) => {
const {id, className, withSeparator, disabled, to, name, analytics, icon} = navigationItem;
console.log({isOpenSideNav, comp: 'SideBarButton'});
const {icon: iconName, color, hoverColor} = icon;
const currentPathLink = to || `/${id}`;
const location = useLocation();
const activePath = _.split(location.pathname, '/')[1] === id;
const {translate} = useLocalisationService();
return (
<li key={name} className={classNames(styles.root, {[styles.disabled]: disabled})}>
{withSeparator && <div className={styles.separator}/>}
<Link
className={classNames(styles.link, {[styles.link.wideView]: isOpenSideNav, [styles.activePath]: activePath}, className)}
to={currentPathLink}
onClick={() => analyticsService.event(analytics)}
>
<Hint offset={Hint.offsets.SM} side={Hint.sides.RIGHT} show={!isOpenSideNav} content={translate(name)}>
<div className={classNames(styles.iconContainer)}>
<Icon icon={iconName} color={color} hoverColor={hoverColor || Icon.colors.PRIMARY_BRAND_REGULAR}/>
</div>
</Hint>
<div
className={classNames(className, styles.textContent, {
[styles.wideView]: isOpenSideNav,
})}
>
{translate(name)}
</div>
</Link>
</li>
);
};
Any help will be welcomed.
Thanks.
I have tried inspecting the prop drilling.
NavigationLInksView gets the right prop value every time it changes.
When drilling to a child, the Childs get it duplicated as shown in the screenshot.
The direct Childs sideBarButton and SideBarButtonWithSideMenu for some reason get it twice.
The value that they always take from the duplicated prop is "false", regardless of some scenarios being true.
Apologies if this is poorly written (first time posting here so feedback on how to better write posts welcome!)
I am using react map to iterate through lists of data.
{level1Folders.level2Folders.map(
(level2Folders, index) => {
return (
<li
id={level2Folders.folderLevel2Name}
key={level2Folders.folderLevel2Name + index}
>
<div
className="menu-item-folder-level-2"
onClick={() =>
hideMenuItem(
level2Folders.folderLevel2Name
)
}
>
<FaIcons.FaCaretRight />
{level2Folders.folderLevel2Name}
</div>
<ul
className="manuals d-none"
id={level2Folders.folderLevel2Name}
>
{level2Folders.manuals.map(
(manual, index) => {
return (
<li key={manual + index} id={manual}>
<div
onClick={() =>
handleExplorerItemClick(manual)
}
className="menu-item-manual"
>
{manual}
</div>
</li>
);
}
)}
I have a method hideMenuItem(menuItemId) which will hide items based on their id's, so the idea is to set the id = to the name of the item, so when the parent item is clicked the child elements will be hidden.
function hideMenuItem(menuItemId) {
console.log(menuItemId);
let x = document.getElementById(menuItemId);
if (x.classList.contains('d-block')) {
x.classList.add('d-none');
x.classList.remove('d-block');
} else {
x.classList.add('d-block');
x.classList.remove('d-none');
}
}
I have 5 uses of this - level2Folders.folderLevel2Name, the only one that won't work is when trying to enter this as a parameter in hideMenuItem(menuItemId), the value here is returned as the index of the item.
The point here is you want to toggle the item's child by using the element's classlist. It might be better if you change your approach and use react ways to achieve your goals. One of them is conditional styling where you can read here for the details.
For your case, let me show you one of many approach which using state in show and hide elements. Try absorp the concept and implement it at yours.
First, the data should have name which is the name of folder, showSubFolders which is property that will keep the boolean value, and subFolders that contains the sub folders details.
const folderData = [
{
name: "My Documents",
showSubFolders: false,
subFolders: [
{
name: "My Music",
icon: faMusic
},
{
name: "My Images",
icon: faImage
}
]
},
...
}
Then, set a folders state that will keep the folder's data in our component:
export default function OurComponent() {
const [folders, setFolders] = useState(folderData);
...
}
Since we use <FontAwesomeIcon icon={...} />, so we can render the main folder icon by using conditional icon selection which depends on folder's showSubFolders property value:
return (
...
<ul>
{folders.map((folder, index) => {
return (
<li>
<FontAwesomeIcon
icon={folder.showSubFolders ? faFolderOpen : faFolder}
/>
</li>
...
)
})}
</ul>
...
)
The next is toggle subFolders section by creating a toggleFolder method that use useCallback hooks and depends on folders state. We negate the value of showSubFolders property if the folder name equal to the argument name supplied.
const toggleFolder = useCallback(
(name) => {
const toggledFolders = folders.map((folder) => {
if (folder.name === name) {
folder.showSubFolders = !folder.showSubFolders;
}
return folder;
});
setFolders(toggledFolders);
},
[folders]
);
And we can call the toggleFolder method from our list item as follow:
return (
...
{folders.map((folder, index) => {
return (
<li key={index} style={{ cursor: "pointer" }}>
<span onClick={() => toggleFolder(folder.name)}>
<FontAwesomeIcon
icon={folder.showSubFolders ? faFolderOpen : faFolder}
/>
{folder.name}
</span>
...
</li>
...
)
})}
...
)
And when the folders state change, the component will re-render and we can use conditional render technique {folder.showSubFolders && ( ... ) here:
<li>
...
{folder.showSubFolders && (
<ul>
{folder.subFolders.map((subFolder, index) => {
return (
<li key={index}>
<FontAwesomeIcon icon={subFolder.icon} />
{subFolder.name}
</li>
);
})}
</ul>
)}
</li>
Of course, this is not the only way to achieve your goal, but it is more React Way in doing so.
And lastly, this is the final code:
I want to make a button check which add a new calssName to my list. I use a function to update a state and take the string. If you want to help me be more specific because I am a beginner. Thanks !
const [check, setCheck] = useState({
id: '',
status: false
});
This is the function. With 'on' I take the string to add to id.
let complet = (on) =>{
if(check.status == false){
setCheck({
id: on,
status: true
})
}
else {
setCheck({
id: on,
status: false
})
}
}
And how I Display the list and how I check correspond to the string.
return(
<div className='display'>
{ list.map( (list,index) => (
<div className={ check.status && check.id == list ? 'lista complet' : 'lista'} key= {index} id='lista' >
{list}
<button className='btnCheck' onClick={complet.bind(this, list)}> <FcCheckmark/> </button>
<button className='btnRemove' onClick={remove.bind(null, list)}> <BsTrash/> </button>
</div>
))}
</div>
)
If you want to store the checked ids and the unchecked ids, you must change your state variable because currently it can only stores a single element. However, it seems you are rendering a list of elements that can be checked individually
Here is a possible solution :
function App({list}) {
const [checkIds, setCheckIds] = useState(() => {
const item = localStorage.getItem('checkIds');
return item ? JSON.parse(item) : {};
});
// reset the checkIds when the list is updated
useEffect(() => setCheckIds({}), [list]);
// save the checkIds into the local storage
useEffect(() => {
localStorage.setItem('checkIds', JSON.stringify(checkIds));
}, [checkIds]);
function checkId(id) {
setCheckIds({...checkIds, [id]: true);
}
function uncheckId(id) {
setCheckIds({...checkIds, [id]: false);
}
return (
<div className='display'>
{list.map(id => (
<div key={id} id={id} className={check[id] ? 'lista complet' : 'lista'}>
{id}
<button className='btnCheck' onClick={() => checkId(id)}>
<FcCheckmark/>
</button>
<button className='btnRemove' onClick={() => uncheckId(id)}>
<BsTrash/>
</button>
</div>
))}
</div>
)
}
My problem is this: I have a component that must be rendered 3 times in the app, and the component has a button which should update the component with a new component. I'm getting the placeholder component in each instance instead of just the component that triggered the event. My code:
class App extends Component {
constructor(){
super()
this.state = {showCard : false,cards : []}
this.buttonClick = this.buttonClick.bind(this)
}
buttonClick(ev){
console.log(ev.target)
const nextId = this.state.cards.length + 1
this.setState({cards: this.state.cards.concat([nextId])})
this.setState({showCard: true,})
}
render() {
console.log(this)
return (
<div className="App">
<h2>React Demo</h2>
<ul className="container">
<Contact key="0" text={"Contact 1"} buttonClick={this.buttonClick} showCard={this.state.showCard} cards={this.state.cards}/>
<Contact key="1" text={"Contact 2"} buttonClick={this.buttonClick} showCard={this.state.showCard} cards={this.state.cards}/>
<Contact key="2" text={"Contact 3"} buttonClick={this.buttonClick} showCard={this.state.showCard} cards={this.state.cards}/>
</ul>
</div>
);
}
}
function Contact(props){
return <li>
<h3>{props.text}</h3>
<ul className="stack">
<li><button id={props.text} type="button" className="block" onClick={e =>props.buttonClick(e)}>+</button></li>
{props.cards.map(cardId => <Card key={props.text+cardId}/>)}
</ul>
</li>
}
function Card(){
return <li><div className="card">Place Holder</div></li>
}
export default App;
I have tried conditional rendering with showCard and mapping as seen here, in both cases all three of instances of the component are updated instead of the correct one. I know it's something stupid I'm doing, I just can't see it. Thanks in advance.
J
Updated code:
const list = [
{
id : 0,
title : "Contact 1",
showCard : false,
addCard : ()=> <Card key={"x"+count++}/>,
cards : []
},
{
id : 1,
title : "Contact 2",
showCard : false,
addCard : ()=> <Card key={"y"+count++}/>,
cards : []
},
{
id : 2,
title : "Contact 3",
showCard : false,
addCard : ()=> <Card key={"z"+count++}/>,
cards : []
}
]
let count = 0
class App extends Component {
constructor(){
super()
this.state = {list : list}
this.buttonClick = this.buttonClick.bind(this)
}
buttonClick(ev,id){
let a0 = null
for(let obj of this.state.list){
if(obj.id === id){
a0 = obj.addCard()
obj.cards.push(a0)
}
}
this.setState({list:this.state.list})
}
render() {
return (
<div className="App">
<h2>React Demo</h2>
<ul className="container">
{this.state.list.map((item) =>
<Contact key={item.title+item.id} text={item.title} buttonClick={this.buttonClick} showCard={item.showCard} id={item.id} cards={item.cards}/>
)}
</ul>
</div>
);
}
}
function Contact(props){
return <li>
<h3>{props.text}</h3>
<ul className="stack">
<li><button id={props.text} type="button" className="block" onClick={e =>props.buttonClick(e,props.id)}>+</button></li>
{props.cards.map((card)=> {
return card || null
})}
</ul>
</li>
}
function Card(){
return <li><div className="card">Place Holder</div></li>
}
export default App;
As Jonas H suggested I was sharing state with all three instances of the Contact component. I've only been doing React for a couple of weeks hence the time it took to solve this. My solution may not be optimal, but it works, although it did break my UI... but that's another mission. Thanks to all.
J
Thanks to #Jonas H
shouldComponentUpdate() method will solve your problem
Use shouldComponentUpdate() to let React know if a component’s output is not affected by the current change in state or props. The default behavior is to re-render on every state change, and in the vast majority of cases you should rely on the default behavior. more
shouldComponentUpdate(nextProps, nextState) {
// should return either true or fase
// component should render on state changes or initial render
if((JSON.stringify(nextState) !== JSON.stringify(this.state)) || (JSON.stringify(this.state) === (JSON.stringify({showCard : false,cards : []})) ) {
return true;
}
return false;
}
put above method to your code
I have 4 different divs each containing their own button. When clicking on a button the div calls a function and currently sets the state to show a modal. Problem I am running into is passing in the index of the button clicked.
In the code below I need to be able to say "image0" or "image1" depending on the index of the button I am clicking
JS:
handleSort(value) {
console.log(value);
this.setState(prevState => ({ childVisible: !prevState.childVisible }));
}
const Features = Array(4).fill("").map((a, p) => {
return (
<button key={ p } onClick={ () => this.handleSort(p) }></button>
)
});
{ posts.map(({ node: post }) => (
this.state.childVisible ? <Modal key={ post.id } data={ post.frontmatter.main.image1.image } /> : null
))
}
I would suggest:
saving the button index into state and then
using a dynamic key (e.g. object['dynamic' + 'key']) to pick the correct key out of post.frontmatter.main.image1.image
-
class TheButtons extends React.Component {
handleSort(value) {
this.setState({selectedIndex: value, /* add your other state here too! */});
}
render() {
return (
<div className="root">
<div className="buttons">
Array(4).fill("").map((_, i) => <button key={i} onClick={() => handleSort(i)} />)
</div>
<div>
posts.map(({ node: post }) => (this.state.childVisible
? <Modal
key={ post.id }
data={ post.frontmatter.main.[`image${this.state.selectedIndex}`].image }
/>
: null
))
</div>
</div>
);
}
}
This is a good answer which explains "Dynamically access object property using variable": https://stackoverflow.com/a/4244912/5776910