Displaying one List at a time on Clicking button - javascript

I have a web page where i am displaying list of records using React JS table and each row has a button that shows user a list of action item.If the user clicks on a button in a row and clicks on another button in another row the previous list should hide but both the list are displaying.How to solve this?
const dataset = [
{"id" : 1, "buttons" : (<ActionItemList />)},
{"id" : 2, "buttons" : (<ActionItemList />)}
]
<ReactTable data={dataset} />
const ActionItemList = () => {
return (
<div>
<button>...</button>
</div>
<div>
<ul>
<li>action-item1</li>
<li>action-item2</li>
<li>action-item3</li>
</ul>
/>
</div>
</div>
)
}

If you can use hooks you can have your menu set the openId with a method passed by a wrapper. Here is an example of how you can do it:
import React, { useState, memo, useMemo } from 'react';
import ReactTable from 'react-table';
//make ActionItemList a pure component using React.memo
const ActionItemList = memo(
({ isOpen, setOpenId, id }) =>
console.log('in render', id) || (
<button
onClick={() =>
isOpen ? setOpenId() : setOpenId(id)
}
>
{isOpen ? 'Close' : 'Open'} Menu
</button>
)
);
const dataset = [
{ id: 1 },
{ id: 2 },
{ id: 3 },
{ id: 4 },
];
const columns = [
{ id: 1, accessor: 'id' },
{
id: 2,
//if accessor is a function table will render what function returns
// passing in the data where we added isOpen and setOpenId
accessor: ({ Menu, ...props }) => (
//passing in the needed props to pure component Menu
<Menu {...props} />
),
},
];
//wrap this on your react table, adding isOpen and setOpenId
export default () => {
//set state for open id
const [openId, setOpenId] = useState();
//memoize creating the data prop based on openId changing
const dataWithProps = useMemo(
() =>
dataset.map(item => ({
...item,
isOpen: item.id === openId,
setOpenId,
Menu: ActionItemList,
})),
[openId]
);
//return the react table with the extra props in data
return (
<ReactTable data={dataWithProps} columns={columns} />
);
};

Related

Changing State on Multiple React Elements in a Grid

I have a grid that has multiple buttons in it, the number of buttons varying by row. For example, row 1 could have 4 buttons, row 2 could have 3, row 3 could have 3, etc. I'm trying to make it to where when I click on one button, it changes not only its background color, but every button in the grid that also has the same text. So if my buttons kind of look like this:
[ text ] [ moreText ] [ someMoreText ]
[ texting] [ textAgain ] [text]
[textAgain] [ someMoreText]
The idea is that when I click on the button [ text ] in row 1, it'll also change the button in row 2 column 3 that is also [ text ].
Right now, I can get one button to change, but I'm stuck on getting the rest of them to change. Below is my code.
GridComponent.js
import React from "react";
import ButtonComponent from "./ButtonComponent";
const GridComponent = ({ arrayOfArray }) => {
const renderColumns = (array) => {
const columns = array.map((buttonText, index) => {
return (
<div key={index}>
<div className="column">
<ButtonComponent buttonText={buttonText} />
</div>
</div>
);
});
return columns;
};
const renderRows = () => {
const rows = arrayOfArry.map((array, index) => {
return (
<div key={index} className="row">
{renderColumns(array)}
</div>
);
});
return rows;
};
return (
<div>
<div className="ui grid">{renderRows()}</div>
</div>
);
};
export default GridComponent;
ButtonComponent.js
import React, { useState } from "react";
const ButtonComponent = ({ buttonText }) => {
const [status, setStatus] = useState(false);
const color = status ? "green" : "blue";
return (
<div className={`ui ${color} button`} onClick={() => setStatus(!status)}>
{buttonText}
</div>
);
};
export default ButtonComponent;
You need to maintain the state at the GridComponent level, not on each ButtonComponent, which has no knowledge of other buttons.
You can do this by using a "map" object that maps the button text to its status.
const GridComponent = ({ arrayOfArray }) => {
const [statuses, setStatuses] = useState({});
...
And pass this map and the update function to the ButtonComponent:
<ButtonComponent
buttonText={buttonText}
status={statuses}
updateStatus={setStatuses}
/>
And in the ButtonComponent set the color bases on the status in the map for this button text:
const ButtonComponent = ({ buttonText, status, updateStatus }) => {
const color = status[buttonText] ? "green" : "blue";
return (
<div
className={`ui ${color} button`}
onClick={() =>
updateStatus({ ...status, [buttonText]: !status[buttonText] })
}
>
{buttonText}
</div>
);
};
You can see how it works on codesandbox

React state wrongly updated in nested components with CodeMirror 6 Editor

I have a few components nested inside a larger "controller" component.
The whole demo app is below. There's also a StackBlitz.
import React, { useState } from 'react';
import CodeEditor from './CodeEditor';
import './style.css';
const PostEditor = ({ value, onChange }) => {
return (
<>
{value && (
<>
<CodeEditor value={value} onChange={value => onChange(value)} />
</>
)}
</>
);
};
const Wrapper = ({ post, updatePostProperty }) => {
return (
<>
<h2>Selected post: {post && post.title}</h2>
{post && post.title && (
<PostEditor
value={post.title}
onChange={value => {
console.log('update title->', value);
updatePostProperty(post.id, 'title', value);
}}
/>
)}
{post && post.subTitle && (
<PostEditor
value={post.subTitle}
onChange={value => {
console.log('update subTitle->', value);
updatePostProperty(post.id, 'subTitle', value);
}}
/>
)}
</>
);
};
export default function App() {
const [posts, setPosts] = useState([
{ id: 1, title: 'post no 1', subTitle: 'subtitle no 1' },
{ id: 2, title: 'post no 2', subTitle: 'subtitle no 2' },
{ id: 3, title: 'post no 3', subTitle: 'subtitle no 3' }
]);
const [post, setPost] = useState();
const updatePostProperty = (id, property, value) => {
const newPosts = [...posts];
const index = newPosts.findIndex(post => (post.id === id));
newPosts[index] = {
...newPosts[index],
[property]: value
};
setPosts(newPosts);
};
return (
<div>
<ul>
{posts &&
posts.length > 0 &&
posts.map((post, index) => (
<li
style={{ cursor: 'pointer' }}
onClick={() => {
setPost(post);
}}
>
{post.title} - {post.subTitle}
</li>
))}
</ul>
<Wrapper post={post} updatePostProperty={updatePostProperty} />
</div>
);
}
The App component hosts the updatePostProperty that is passed on to the Wrapper component which uses it when the PostEditor component triggers onChange the CodeEditor which is a wrapper for CodeMirror.
The issue here is that after you click one of the posts and edit the title and then the subtitle, the title gets reverted to the initial value.
Scenario:
Click on the first post and try to edit the title. Add an ! to the title. You'll see the post on the list gets updated.
After you edit the subtitle by adding a character to it, you'll see the title gets reverted to the previous state (without the !) in the App component.
Why is react doing this "revert" update?
Update:
New StackBlitz.
I made a few adjustments to the script to use useEffect before changing the original posts array.
I added a regular input element to see if the problem persists. It seems that the issue is fixed with regular inputs.
However, I'd love someone's input on why does the issue still persists with the way CodeMirror is wired up.
Inside updatePostProperty you're updating the wrong object.
You're updating:
posts[index] = {
...newPosts[index],
[property]: value
};
But you want to update newPosts instead, so you have to do:
newPosts[index] = {
...newPosts[index],
[property]: value
};
it needs to be newPosts instead of posts
no need to destructuring
you are using 1 = here newPosts.findIndex(post => (post.id = id)); there suppose to be 2 == like newPosts.findIndex(post => (post.id == id));
checkout this code
const updatePostProperty = (id, property, value) => {
const newPosts = [...posts];
const index = newPosts.findIndex(post => (post.id == id));
newPosts[index][property] = value
setPosts(newPosts);
};

React how to manage same multiple child components state?

I have implemented the following example, where I am keeping all child state info in the parent component in the form of array.
const parent= (props) => {
const [data, setData] = useState([]);
useEffect(() => {
const data = new Array(num).fill().map((_,i) => { id: i, name: ''});
setData(data);
/*
if num=5; then data becomes,
data=[{id: 0, name: ''},{id: 1, name: ''},{id: 2, name: ''},{id: 3, name: ''},{id: 4, name: ''}];
*/
),[num]}
const changeVal = (val, id) => {
const newData = data.map(d => d.id === id ? {...d, name: val} : d)
setData(newData);
}
return (
<div>
{
data.map(val => {
return (
<Child val={val} changeVal={changeVal}/>
)
})
}
<button onClick={() => alert(data)}>Show All State</button>
</div>
)
}
// Child Component
const Child = (props) => {
const { val, changeVal } = props;
return(
<input type="text" value={val} onChange={(e) => changeVal(e.target.value, val.id)} />
);
}
Also, I want to show all child components state information when clicked on the button in the parent component.
num - denotes the number of child components, it can change dynamically.
so, my question is that, is this a better approach to handle the state of multiple child components. ?
is there any better and efficient solution than the above one without using Redux. ?
Are there any problems in using the above approach. ?

React memo child component render

I'm having trouble rendering my comment components.
So I have a listComment component and it has 2 child component CommentItem and CommentGroup.
My CommentGroup component is like a dropdown where you can open and close it.
I tried to use React.memo() but it still rendering children thats already rendered
My problem is that every time I add a new comment it renders again the child components that's already rendered. So the comments that's already open the CommentGroup closes. And i use redux for state-management.
PS sorry for the bad english.
Comment Data
[{
body: "comment 1",
comment_counter: 0,
createdAt: "2020-06-14T13:42:38.465Z",
heart_counter: 0,
ownerId: "5edce08cabc7ab1860c7bdf4",
postId: "5ee3770495bfce029842bc68",
_id: "5ee6294eb7295a1c04b62374"
}, {
body: "comment 2",
comment_counter: 0,
createdAt: "2020-06-14T13:42:38.465Z",
heart_counter: 0,
ownerId: "5edce08cabc7ab1860c7bdf4",
postId: "5ee3770495bfce029842bc68",
_id: "5ee6294eb7295a1c04b62374"
}]
ListComments.js
const comments = useSelector(state => state.comment.comments)
return comments.map(comment => {
return (
<div key={comment._id}>
<CommentItem comment={comment} type="post_comment" />
<div className={classes.mLeft}>
<CommentGroup counter={comment.comment_counter} />
</div>
</div >
)
})
CommentGroup.js
const CommentGroup = React.memo((props) => {
const [open, setOpen] = useState(false)
const onOpen = () => {
setOpen(true)
}
const onClose = () => {
setOpen(false)
}
return (
<div>
<Button
size="small"
color="primary"
startIcon={
!open ? <ArrowDropDownOutlinedIcon /> : <ArrowDropUpOutlinedIcon />
}
onClick={
!open ? () => onOpen() : () => onClose()
}
>
{!open ? 'View' : 'Hide'} {1} Replies
</Button>
CommentGroupOpen: {open ? 'true' : 'false'}
</div>
)
}, (prevProps, nextProps) => {
console.log(prevProps) // not getting called
if (prevProps.counter !== nextProps.counter) {
return false
}
return true
})
export default CommentGroup
CommentItem is just a display component
It's likely because that all the comments have the same comment._id which is used as the key. I made a similar example and it worked fine. https://codesandbox.io/s/mutable-framework-stk5g

React Hooks, Conditionally change state inside onClick function, updates on the next click

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);
}
}

Categories

Resources