React Passing Same Prop Twice - javascript

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.

Related

React setting the state of all rendered items with .map in parent component

I have a parent component with a handler function:
const folderRef = useRef();
const handleCollapseAllFolders = () => {
folderRef.current.handleCloseAllFolders();
};
In the parent, I'm rendering multiple items (folders):
{folders &&
folders.map(folder => (
<CollapsableFolderListItem
key={folder.id}
name={folder.name}
content={folder.content}
id={folder.id}
ref={folderRef}
/>
))}
In the child component I'm using the useImperativeHandle hook to be able to access the child function in the parent:
const [isFolderOpen, setIsFolderOpen] = useState(false);
// Collapse all
useImperativeHandle(ref, () => ({
handleCloseAllFolders: () => setIsFolderOpen(false),
}));
The problem is, when clicking the button in the parent, it only collapses the last opened folder and not all of them.
Clicking this:
<IconButton
onClick={handleCollapseAllFolders}
>
<UnfoldLessIcon />
</IconButton>
Only collapses the last opened folder.
When clicking the button, I want to set the state of ALL opened folders to false not just the last opened one.
Any way to solve this problem?
You could create a "multi-ref" - ref object that stores an array of every rendered Folder component. Then, just iterate over every element and call the closing function.
export default function App() {
const ref = useRef([]);
const content = data.map(({ id }, idx) => (
<Folder key={id} ref={(el) => (ref.current[idx] = el)} />
));
return (
<div className="App">
<button
onClick={() => {
ref.current.forEach((el) => el.handleClose());
}}
>
Close all
</button>
{content}
</div>
);
}
Codesandbox: https://codesandbox.io/s/magical-cray-9ylred?file=/src/App.js
For each map you generate new object, they do not seem to share state. Try using context
You are only updating the state in one child component. You need to lift up the state.
Additionally, using the useImperativeHandle hook is a bit unnecessary here. Instead, you can simply pass a handler function to the child component.
In the parent:
const [isAllOpen, setAllOpen] = useState(false);
return (
// ...
{folders &&
folders.map(folder => (
<CollapsableFolderListItem
key={folder.id}
isOpen={isAllOpen}
toggleAll={setAllOpen(!isAllOpen)}
// ...
/>
))}
)
In the child component:
const Child = ({ isOpen, toggleAll }) => {
const [isFolderOpen, setIsFolderOpen] = useState(false);
useEffect(() => {
setIsFolderOpen(isOpen);
}, [isOpen]);
return (
// ...
<IconButton
onClick={toggleAll}
>
<UnfoldLessIcon />
</IconButton>
)
}

react component return problem when props are null

export default function RenderPages({storage, setStorage, state, setState}){
let elRefs= useRef()
if(!storage) return
if(!state.currentFileId || !state.currentFolderId) return
const content = storage[state.currentFolderId][state.currentFileId].content
return (
<div className="writing">
<input ref={elRefs}/>
{content.map((page, index)=>
<div className='textarea'>
<textarea placeholder='write here' value={page} id={"page"+index} onChange={(e)=>onChange(e, index)} rows={rows} cols={cols}></textarea>
</div>)}
</div>
)
}
There are some props(state, storage) and they are sometimes null value. What I am doing now is checking the values of state and storage, returning blank early if those values are null. If I don't return in advance, the variable "content" get error because it needs state and storage value. Now this is the problem. I want to use "useRef", and if the component return early "elRefs" is assigned null value, so I can't get DOM element. What should I do?
I put your booleans into single function and seperated the renderable part of component. If bool is false, that component is simply not rendered. Of course you can put in there other component which shows error data or loading gif or something like that. Hope this works!
export default function RenderPages({ storage, setStorage, state, setState }) {
const elRefs = useRef()
const content = storage[state.currentFolderId][state.currentFileId].content
// content to render in seperate component
const Pages = () => (
<div className="writing">
<input ref={elRefs} />
{
content.map((page, index) =>
<div className='textarea'>
<textarea
placeholder='write here'
value={page} id={"page" + index}
onChange={(e) => onChange(e, index)}
rows={rows}
cols={cols}
/>
</div>
)
}
</div>
)
// decide to or not to render component
const renderable = storage &&
state.currentFileId &&
state.currentFolderId
return (
<>
{
renderable
? <Pages />
: <></> // returns empty component
}
</>
)
}

Data rerenders only once onClick using function with filter() in reactjs?

I have a navigation bar with 3 buttons (not the main navbar) that should filter menu items onto Pizzas, Sides, Drinks when clicked. When I click either button it only shows one of the filtered menu (if I click Pizzas, it shows the list of different pizzas).
The issue is that it only works once, so if I click again or click Sides or Drinks, it doesn't show anything, just an empty page. I thought about using useEffect to re-render, but can't think of a way on how to use it.
How can I make each button show the different data/menu items when clicking each menu category (Pizzas, Sides, Drinks)?
const Menu = () => {
const [menuList, setMenuList] = useState(initialMenuList);
function eventMenuClick(e) {
const nameSelection = e.currentTarget.textContent;
setMenuList((menuList) => {
return menuList.filter((item) => {
if (item.category === nameSelection) {
return console.log(item);
}
return null;
});
});
}
return (
<div className="container">
<h1>Menu</h1>
<div className="nav-container">
<ul className="menu-list">
<li className="menutype" onClick={eventMenuClick}>Pizzas</li>
<li className="menutype" onClick={eventMenuClick}>Sides</li>
<li className="menutype" onClick={eventMenuClick}>Drinks</li>
</ul>
</div>
<MenuList menuList={menuList} />
<NavBar />
</div>
);
};
export default Menu;
const MenuList = ({ menuList }) => {
return (
<div className="menulist-container">
{menuList.map((item) => (
<MenuItem
key={item.id}
title={item.name}
image={item.image}
price1={item.priceM}
price2={item.priceL}
/>
))}
</div>
);
};
export default MenuList;
In your menu, you can have another state which contains only filtered values.
const [menuList, setMenuList] = useState(initialMenuList);
const [visibleMenu, setVisibleMenu] = useState(initialMenuList); // <- HERE
function eventMenuClick(e) {
const nameSelection = e.currentTarget.textContent;
const filtered = menuList.filter((item) => item.category === nameSelection);
setVisibleMenu(filtered);
}
And when you render your list, instead of original data, use filtered to be rendered.
<MenuList menuList={visibleMenu} />

Trying to filter an array using checkboxes and useState, checkbox doesn't stay checked

i have an array, called reportsData, then i need to filter it, generating some checkboxes with each of them having a label based on each name that comes from another array (emittersData), so basically i set it like this:
const [searchUser, setSearchUser] = useState<string[]>([])
const mappedAndFiltered = reportsData
.filter((value: any) =>
searchUser.length > 0 ? searchUser.includes(value.user.name) : true
)
Then i render my checkboxes like this:
function EmittersCheckboxes () {
const [checkedState, setCheckedState] = useState(
new Array(emittersData.length).fill(false)
)
const handleOnChange = (position: any, label: any) => {
const updatedCheckedState = checkedState.map((item, index) =>
index === position ? !item : item
)
setSearchUser((prev) =>
prev.some((item) => item === label)
? prev.filter((item) => item !== label)
: [...prev, label]
)
setCheckedState(updatedCheckedState)
};
return (
<div className="App">
{emittersData.map((value: any, index: any) => {
return (
<li key={index}>
<div className="toppings-list-item">
<div className="left-section">
<input
className="h-4 w-4 focus:bg-indigo border-2 border-gray-300 rounded"
type="checkbox"
id={`custom-checkbox-${index}`}
name={value.Attributes[2].Value}
value={value.Attributes[2].Value}
checked={checkedState[index]}
onChange={() => handleOnChange(index, value.Attributes[2].Value)}
/>
<label className="ml-3 font-medium text-sm text-gray-700 dark:text-primary" htmlFor={`custom-checkbox-${index}`}>{value.Attributes[2].Value}</label>
</div>
</div>
</li>
);
})}
</div>
)
}
And on the react component i am rendering each checkbox, that is a li, like:
<ul><EmittersCheckboxes /></ul>
And i render the mappedAndFiltered on the end.
Then it is fine, when i click each generated checkbox, it filters the array setting the state in setSearch user and the array is filtered.
You can check it here: streamable. com /v6bpk6
See that the filter is working, the total number of items in the array is changing based on the checkbox selected (one or more).
But the thing is that each checkbox does not become 'checked', it remains blank (untoggled).
What am i doing wrong, why doesnt it check itself?
You've defined your EmittersCheckboxes component inside another component. and every time that the parent component renders (by state change) your internal component is redefined, again and again causing it to lose it's internal state that React holds for you.
Here's a simplified example:
import React, { useState } from "react";
function CheckboxeComponent() {
const [checkedState, setCheckedState] = useState(false);
return (
<div>
<span>CheckboxeComponent</span>
<input
type="checkbox"
checked={checkedState}
onChange={() => setCheckedState((x) => !x)}
/>
</div>
);
}
export default function App() {
const [counter, setCounter] = useState(1);
function InternalCheckboxeComponent() {
const [checkedState, setCheckedState] = useState(false);
return (
<div>
<span>InternalCheckboxeComponent</span>
<input
type="checkbox"
checked={checkedState}
onChange={() => setCheckedState((x) => !x)}
/>
</div>
);
}
return (
<>
<InternalCheckboxeComponent />
<CheckboxeComponent />
<button onClick={() => setCounter((c) => c + 1)}>{counter}</button>
</>
);
}
There's the App (parent component) with its own state (counter), with a button to change this state, clicking this button will increase the counter, causing a re-render of App. This re-render redefines a new Component named InternalCheckboxeComponent every render.
The InternalCheckboxeComponent also has an internal state (checkedState).
And there's an externally defined functional component named CheckboxeComponent, with this component React is able to hold its own state, because it's not redefined (It's the same function)
If you set the state of each to be "checked" and click the button, this will cause a re-render of App, this will redefine the InternalCheckboxeComponent function, causing React to lose its state. and the CheckboxeComponent state remains in React as it's the same function.

Controlled Input onChange Event Fires Only Once - React

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"])
}

Categories