set Style using React and Hooks - css

I have been using React JS for a short time. Looking at the following example, I would like to change the background color only on the clicked lines and not all lines. Actually, clicking on a row turns it red and clicking again makes the row background white. Before changing color, I would set all lines to default color states, but something is wrong.
import { useState } from "react";
export default function App() {
const [color, setColor] = useState("white");
const handleClick = (e) => {
const value_color = e.target.style.backgroundColor;
if (value_color != "" && value_color != undefined) {
if (value_color === "white")
var new_color = "red";
else
var new_color = "white";
}
setColor("white");
e.target.style.backgroundColor = new_color;
};
return (
<div>
<div
id={"line1_id"}
style={{
backgroundColor: color
}}
onClick={handleClick}
>
First Line
</div>
<div
id={"line2_id"}
style={{
backgroundColor: color
}}
onClick={handleClick}
>
Second Line
</div>
</div>
);
}
EDIT:
Maybe I have explained the problem wrong. Actually my code works, but clicking on both I get two red lines. In general, when I click on a new line, the other line should go white again. Thank you

Create JSON for the data and use map for rendering the data and updating the backgroundColor
import { useState } from 'react';
export default function App() {
const [data, setData] = useState([
{ id: 1, text: 'First Line', color: 'white' },
{ id: 2, text: 'Second Line', color: 'white' }
]);
const handleClick = item => {
setData(data =>
data.map(n => {
return {
...n,
color: n.id === item.id && n.color === 'white' ? 'red' : 'white'
};
})
);
};
return (
<div>
{data.map(item => (
<div
id={item.id}
key={item.id}
style={{
backgroundColor: item.color
}}
onClick={() => handleClick(item)}
>
{item.text}
</div>
))}
</div>
);
}
here is the Codesandbox example

The new_color value is not defined because you are setting it in the if-else statements. So, only code in the if-else blocks can access them. You can declare a variable and assign a default value outside them and then reassign it:
// some other code
let new_color = "white"
if (value_color != "" && value_color != undefined) {
if (value_color === "white")
new_color = "red";
else
new_color = "white";
}
// some code
e.target.style.backgroundColor = new_color;

Related

Possible to style button according to if the state value is true or false?

I have two buttons that show two different components when toggling them. For UX reasons (to know which component is showing) I would like to style the buttons according to if the value of the state is true or false (give them an underline and a darker color if the state is true). Is this possible in any way?
This is my GitHub repo: https://github.com/uohman/Portfolio2022
And this is the component where I handle the buttons:
`
import React, { useState } from 'react'
import ReactDOM from 'react-dom';
import { Subheading } from 'GlobalStyles';
import { FrontendProjects } from './FrontendProjects'
import { GraphicDesignProjects } from './GraphicDesignProjects';
import 'index.css'
export const FeaturedProjects = () => {
const [buttons, setButtons] = useState([
{ label: 'Development', value: true },
{ label: 'Graphic design', value: false }
]);
const handleButtonsChange = () => (label) => {
const newButtonsState = buttons.map((button) => {
if (button.label === label) {
return (button = { label: button.label, value: true });
}
return {
label: button.label,
value: false
};
});
setButtons(newButtonsState);
};
return (
<>
<Subheading><span>Featured projects</span></Subheading>
<SpecialButton {...{ buttons, setButtons, handleButtonsChange }} />
{buttons[0].value && <FrontendProjects />}
{buttons[1].value && <GraphicDesignProjects />}
</>
);
};
const SpecialButton = ({ buttons, setButtons, handleButtonsChange }) => {
return (
<div className="button-container">
{buttons.map((button, index) => (
<button
key={`${button.label}-${index}`}
onClick={() => handleButtonsChange({ buttons, setButtons })(button.label)}>
{button.label.toUpperCase()}
</button>
))}
</div>
);
};
const rootElement = document.getElementById('root');
ReactDOM.render(<FeaturedProjects />, rootElement);
`
I've given the buttons the pseudo element :focus and that nearly solves my problem, but still as a default the buttons are the same color although it is one of the components that is showing. Thankful for suggestions on how to solve this!
You can provide a style props to any html component.
You should pass an object where attributes are camelcased.
<button
style={{ // double bracket to pass an object
backgroundColor: yourVariable ? 'red' : undefined // notice css background-color became backgroundColor
}}
>
{button.label.toUpperCase()}
</button>
You can do the same with classes
<button
className={yourVariable && "yourClass"}
>
{button.label.toUpperCase()}
</button>
You can set styles for button based on a condition.
In this use case, you already have the state button.value which can be used as a condition to set inline styles (or classes) for the mapped button.
Example:
const SpecialButton = ({ buttons, setButtons, handleButtonsChange }) => {
return (
<div className="button-container">
{buttons.map((button, index) => (
<button
key={`${button.label}-${index}`}
// 👇 This property is added
style={{
backgroundColor: button.value ? "#aaa" : "#eee",
textDecoration: button.value ? "underline" : "none",
}}
onClick={() =>
handleButtonsChange({ buttons, setButtons })(button.label)
}
>
{button.label.toUpperCase()}
</button>
))}
</div>
);
};
The buttons are set to become darker when selected in the above example, but you can further customize the styles for the desired result.
More about inline styles
On a side note, it is not necessary to pass state values to the the event by onClick={() => handleButtonsChange({ buttons, setButtons })(button.label)}.
The parent component always have these values, so you do not need to pass it down to SpecialButton and pass it back.
Hope this will help!
Full example:
import React, { useState } from "react";
import ReactDOM from "react-dom";
import { Subheading } from "GlobalStyles";
import { FrontendProjects } from "./FrontendProjects";
import { GraphicDesignProjects } from "./GraphicDesignProjects";
import "index.css";
export const FeaturedProjects = () => {
const [buttons, setButtons] = useState([
{ label: "Development", value: true },
{ label: "Graphic design", value: false },
]);
const handleButtonsChange = (label) => {
const newButtonsState = buttons.map((button) => {
if (button.label === label) {
return (button = { label: button.label, value: true });
}
return {
label: button.label,
value: false,
};
});
setButtons(newButtonsState);
};
return (
<>
<Subheading>
<span>Featured projects</span>
</Subheading>
<SpecialButton {...{ buttons, handleButtonsChange }} />
{buttons[0].value && <FrontendProjects />}
{buttons[1].value && <GraphicDesignProjects />}
</>
);
};
const SpecialButton = ({ buttons, handleButtonsChange }) => {
return (
<div className="button-container">
{buttons.map((button, index) => (
<button
key={`${button.label}-${index}`}
// 👇 This property is added
style={{
backgroundColor: button.value ? "#aaa" : "#eee",
textDecoration: button.value ? "underline" : "none",
}}
onClick={() =>
handleButtonsChange(button.label)
}
>
{button.label.toUpperCase()}
</button>
))}
</div>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<FeaturedProjects />, rootElement);

React-beautiful-dnd: how to fix list items sliding down on isDraggingOver

I have a react-beautiful-dnd app where the user can drag cards from one column to another. When I drag a card to a column with existing cards, the existing cards jump down. This makes sense if I'm trying to drag the card to the top of the column. However, the first card slides down even if I drag the new card to the middle or the list. And if I drag the new card to the bottom of the list everything above it slides down. Once I release the mouse button and the dragging is complete, the cards snap back to where they should be. Therefore, it's something to do with isDraggingOver. I checked my styled components, but the only styles that change on isDraggingOver are background colors and border colors. Has anyone else encountered this? If so, how did yo fix it. Thanks in advance!
Update 9/6:
After unsuccessfully trying #Jordan solution I looked at the GitHub issues for the project and found this. Sure enough, if I do not render provided.placeholder the problem goes away. Of course, now I get a warning in the console about not rendering that placeholder. I then tried rendering it again but wrapping it in a call to React.cloneElement() and passing {style: {height: '0'}} as props. This does not work. So for now, I am simply not rendering the placeholder. Does anyone know another way around this? Here's a screenshot of the latest diff:
Update 8/25
Trying the solution of #jordan but I"m still seeing the issue. Here is my updated component file for the draggable component called Task
Draggable file:
import React, { memo, useState } from 'react'
import { Draggable } from 'react-beautiful-dnd'
import TaskForm from '../TaskForm/TaskForm'
import Task from '../Task/Task'
import { useStyletron } from 'baseui'
import useDraggableInPortal from '../../Hooks/useDraggableInPortal'
const TaskCard = ({ description, id, index, column }) => {
const renderDraggable = useDraggableInPortal()
const [isEditing, setIsEditing] = useState(false)
const [css, theme] = useStyletron()
const handleEdit = id => {
setIsEditing(true)
}
return (
<Draggable draggableId={id} index={index}>
{renderDraggable((provided, snapshot) => (
<div
className={css({
boxSizing: 'border-box',
':focus': {
outline: `${theme.colors.hotPink} 3px solid`,
borderRadius: '6px',
margin: '3px',
}
})}
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}>
{!isEditing &&
<Task
column={column}
description={description}
handleEdit={handleEdit}
id={id}
snapshot={snapshot}
/>
}
{isEditing &&
<TaskForm
column={column}
handleOnCancel={() => setIsEditing(false)}
initialValues={{ description, id }} isEditing={true}
/>
}
</div>
))}
</Draggable>
)
}
export default memo(TaskCard)
Hook from #jordan :
import { createPortal } from 'react-dom'
import { useEffect, useRef } from 'react'
const useDraggableInPortal = () => {
const self = useRef({}).current
useEffect(() => {
const div = document.createElement('div')
div.style.position = 'absolute'
div.style.pointerEvents = 'none'
div.style.top = '0'
div.style.width = '100%'
div.style.height = '100%'
self.elt = div
document.body.appendChild(div)
return () => {
document.body.removeChild(div)
}
}, [self])
return (render) => (provided, ...args) => {
const element = render(provided, ...args)
if (provided.draggableProps.style.position === 'fixed') {
return createPortal(element, self.elt)
}
return element
}
}
export default useDraggableInPortal
I had this issue, and I found a solution to it here.
Basically when the library is using position: fixed, there are are some unintended consequences - and in those cases you need to use portal to get around them:
I had the same issue. Following the great solution of #kasperpihl, I created a simple hook do do the trick:
const useDraggableInPortal = () => {
const self = useRef({}).current;
useEffect(() => {
const div = document.createElement('div');
div.style.position = 'absolute';
div.style.pointerEvents = 'none';
div.style.top = '0';
div.style.width = '100%';
div.style.height = '100%';
self.elt = div;
document.body.appendChild(div);
return () => {
document.body.removeChild(div);
};
}, [self]);
return (render) => (provided, ...args) => {
const element = render(provided, ...args);
if (provided.draggableProps.style.position === 'fixed') {
return createPortal(element, self.elt);
}
return element;
};
};
Usage: Considering the following component:
const MyComponent = (props) => {
return (
<DragDropContext onDragEnd={/* ... */}>
<Droppable droppableId="droppable">
{({ innerRef, droppableProps, placeholder }) => (
<div ref={innerRef} {...droppableProps}>
{props.items.map((item, index) => (
<Draggable key={item.id} draggableId={item.id} index={index}>
{(provided) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
{item.title}
</div>
)}
</Draggable>
)}
{placeholder}
</div>
)}
</Droppable>
</DragDropContext>
);
};
Just call the hook and use the returned function to wrap the children callback of component:
const MyComponent = (props) => {
const renderDraggable = useDraggableInPortal();
return (
<DragDropContext onDragEnd={/* ... */}>
<Droppable droppableId="droppable">
{({ innerRef, droppableProps, placeholder }) => (
<div ref={innerRef} {...droppableProps}>
{props.items.map((item, index) => (
<Draggable key={item.id} draggableId={item.id} index={index}>
{renderDraggable((provided) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
{item.title}
</div>
))}
</Draggable>
))}
{placeholder}
</div>
)}
</Droppable>
</DragDropContext>
);
};
Another potential fix is to add a css rule to override whatever behaviour you've unintentionally set for your draggable:
HTML:
<div className="draggableClassName"
{...provided.draggableProps}
{...provided.dragHandleProps}
ref={provided.innerRef}
>
<div>
<input
//...etc.
index.css
.draggableClassName {
left: auto !important;
top: auto !important;
}
Okay, I finally figured this out. I feel rather silly. I had originally rendered the Droppable part of my React component tree like so:
<Droppable droppableId={column}>
{(provided, snapShot) => (
<TaskList
ref={provided.innerRef}
{...provided.droppableProps}
isDraggingOver={snapShot.isDraggingOver}
tasks={tasks}>
{provided.placeholder} // incorrect
</TaskList>
)}
</Droppable>
This makes no sense as my TaskList component does not take children as a prop. I think I was trying to imitate some sample code that I had seen and I got confused.
The correct way to mark it up for my specific context is:
<Droppable droppableId={column}>
{(provided, snapShot) => (
<>
<TaskList
ref={provided.innerRef}
{...provided.droppableProps}
isDraggingOver={snapShot.isDraggingOver}
tasks={tasks}
/>
{provided.placeholder} // correct
</>
)}
</Droppable>
The Eureka moment was when I read the warning in the console which said that the provided.placeholder must be a child of the Droppable.
Note
I have to put the provided.placeholder after my TaskList. If it is the first child of the fragment I still have the original problem (the entire list slides down when you drag a new item onto it)

How can I set the z-index for this select?

Hello I would like to set the z-index of the following component :
import React from 'react';
import chroma from 'chroma-js';
import { colourOptions } from './docs/data';
import Select from 'react-select';
const colourStyles = {
control: styles => ({ ...styles, backgroundColor: 'white' }),
option: (styles, { data, isDisabled, isFocused, isSelected }) => {
const color = chroma(data.color);
return {
...styles,
backgroundColor: isDisabled
? null
: isSelected
? data.color
: isFocused
? color.alpha(0.1).css()
: null,
color: isDisabled
? '#ccc'
: isSelected
? chroma.contrast(color, 'white') > 2
? 'white'
: 'black'
: data.color,
cursor: isDisabled ? 'not-allowed' : 'default',
':active': {
...styles[':active'],
backgroundColor: !isDisabled && (isSelected ? data.color : color.alpha(0.3).css()),
},
};
},
multiValue: (styles, { data }) => {
const color = chroma(data.color);
return {
...styles,
backgroundColor: color.alpha(0.1).css(),
};
},
multiValueLabel: (styles, { data }) => ({
...styles,
color: data.color,
}),
multiValueRemove: (styles, { data }) => ({
...styles,
color: data.color,
':hover': {
backgroundColor: data.color,
color: 'white',
},
}),
};
export default () => (
<Select
closeMenuOnSelect={false}
defaultValue={[colourOptions[0], colourOptions[1]]}
isMulti
options={colourOptions}
styles={colourStyles}
/>
);
I found this solution :
styles={{menu: provided => ({ ...provided, zIndex: 9999, colourStyles })}}
instead of
styles={colourStyles}
But I lose all the colors...
Could you help me please ?
Here is the code :
https://codesandbox.io/s/condescending-noether-1ee0v?file=/example.js:0-1531
Thank you very much !
Note that if you used styles={{colourStyles}} instead of styles={colourStyles}, then the app would also lose the colors. This is because it is not expanded as it should. However, styles={{...colourStyles}} would work. Read more in this post.
So this bit of code should fix your problem:
example.js
export default () => (
<Select
[your other props],
styles={{
...colourStyles,
...{control: styles => ({ ...styles, zIndex: 9999})},
}}
/>
);
where the two objects colourStyles and {zIndex: 9999} were merged (in ES6 compatible syntax, see this post for different ways to do this). Alternatively you can just append zIndex: 9999 right behind backgroundColor: 'white' within the colourStyles constant.
Upon inspection you can see it works:

changing Material-ui table style in row.getRowProps()

In my react application I'm using Material-UI enhanced table which is based on react-table and I would like to change the style of their rows.
Reading from documentation (https://material-ui.com/api/table-row/) in the component TableRow should be used the prop "classes" to change the style, but in the MaterialUi code props are read this way:
<TableRow {...row.getRowProps()}>
My question is how can I use the prop classes if the TableRow props are added automatically? I thought I needed to have this:
<TableRow classes="rowStyle ">
where rowStyle is:
const styles = {
rowStyle : {
padding: 10,
border: "1px solid red"
}
};
But obviously I can't this way, how can I add "classes" to the getRowProps() and the new style in it?
I couldn't find an explanation or a good example in official documentation or stackOverflow
Many thanks for the help
EnhancedTable.js:
import React from "react";
import Checkbox from "#material-ui/core/Checkbox";
import MaUTable from "#material-ui/core/Table";
import PropTypes from "prop-types";
import TableBody from "#material-ui/core/TableBody";
import TableCell from "#material-ui/core/TableCell";
import TableContainer from "#material-ui/core/TableContainer";
import TableFooter from "#material-ui/core/TableFooter";
import TableHead from "#material-ui/core/TableHead";
import TablePagination from "#material-ui/core/TablePagination";
import TablePaginationActions from "./TablePaginationActions";
import TableRow from "#material-ui/core/TableRow";
import TableSortLabel from "#material-ui/core/TableSortLabel";
import TableToolbar from "./TableToolbar";
import {
useGlobalFilter,
usePagination,
useRowSelect,
useSortBy,
useTable,
} from "react-table";
const IndeterminateCheckbox = React.forwardRef(
({ indeterminate, ...rest }, ref) => {
const defaultRef = React.useRef();
const resolvedRef = ref || defaultRef;
React.useEffect(() => {
resolvedRef.current.indeterminate = indeterminate;
}, [resolvedRef, indeterminate]);
return (
<div>
<Checkbox ref={resolvedRef} {...rest} />
</div>
);
}
);
const inputStyle = {
padding: 0,
margin: 0,
border: 0,
background: "transparent",
};
// Create an editable cell renderer
const EditableCell = ({
value: initialValue,
row: { index },
column: { id },
updateMyData, // This is a custom function that we supplied to our table instance
}) => {
// We need to keep and update the state of the cell normally
const [value, setValue] = React.useState(initialValue);
const onChange = (e) => {
setValue(e.target.value);
};
// We'll only update the external data when the input is blurred
const onBlur = () => {
updateMyData(index, id, value);
};
// If the initialValue is changed externall, sync it up with our state
React.useEffect(() => {
setValue(initialValue);
}, [initialValue]);
return (
<input
style={inputStyle}
value={value}
onChange={onChange}
onBlur={onBlur}
/>
);
};
EditableCell.propTypes = {
cell: PropTypes.shape({
value: PropTypes.any.isRequired,
}),
row: PropTypes.shape({
index: PropTypes.number.isRequired,
}),
column: PropTypes.shape({
id: PropTypes.number.isRequired,
}),
updateMyData: PropTypes.func.isRequired,
};
// Set our editable cell renderer as the default Cell renderer
const defaultColumn = {
Cell: EditableCell,
};
const EnhancedTable = ({
columns,
data,
setData,
updateMyData,
skipPageReset,
}) => {
const {
getTableProps,
headerGroups,
prepareRow,
page,
gotoPage,
setPageSize,
preGlobalFilteredRows,
setGlobalFilter,
state: { pageIndex, pageSize, selectedRowIds, globalFilter },
} = useTable(
{
columns,
data,
defaultColumn,
autoResetPage: !skipPageReset,
// updateMyData isn't part of the API, but
// anything we put into these options will
// automatically be available on the instance.
// That way we can call this function from our
// cell renderer!
updateMyData,
},
useGlobalFilter,
useSortBy,
usePagination,
useRowSelect,
(hooks) => {
hooks.allColumns.push((columns) => [
// Let's make a column for selection
{
id: "selection",
// The header can use the table's getToggleAllRowsSelectedProps method
// to render a checkbox. Pagination is a problem since this will select all
// rows even though not all rows are on the current page. The solution should
// be server side pagination. For one, the clients should not download all
// rows in most cases. The client should only download data for the current page.
// In that case, getToggleAllRowsSelectedProps works fine.
Header: ({ getToggleAllRowsSelectedProps }) => (
<div>
<IndeterminateCheckbox {...getToggleAllRowsSelectedProps()} />
</div>
),
// The cell can use the individual row's getToggleRowSelectedProps method
// to the render a checkbox
Cell: ({ row }) => (
<div>
<IndeterminateCheckbox {...row.getToggleRowSelectedProps()} />
</div>
),
},
...columns,
]);
}
);
const handleChangePage = (event, newPage) => {
gotoPage(newPage);
};
const handleChangeRowsPerPage = (event) => {
setPageSize(Number(event.target.value));
};
const removeByIndexs = (array, indexs) =>
array.filter((_, i) => !indexs.includes(i));
const deleteUserHandler = (event) => {
const newData = removeByIndexs(
data,
Object.keys(selectedRowIds).map((x) => parseInt(x, 10))
);
setData(newData);
};
const addUserHandler = (user) => {
const newData = data.concat([user]);
setData(newData);
};
// Render the UI for your table
return (
<TableContainer>
<TableToolbar
numSelected={Object.keys(selectedRowIds).length}
deleteUserHandler={deleteUserHandler}
addUserHandler={addUserHandler}
preGlobalFilteredRows={preGlobalFilteredRows}
setGlobalFilter={setGlobalFilter}
globalFilter={globalFilter}
/>
<MaUTable {...getTableProps()}>
<TableHead>
{headerGroups.map((headerGroup) => (
<TableRow {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map((column) => (
<TableCell
{...(column.id === "selection"
? column.getHeaderProps()
: column.getHeaderProps(column.getSortByToggleProps()))}
>
{column.render("Header")}
{column.id !== "selection" ? (
<TableSortLabel
active={column.isSorted}
// react-table has a unsorted state which is not treated here
direction={column.isSortedDesc ? "desc" : "asc"}
/>
) : null}
</TableCell>
))}
</TableRow>
))}
</TableHead>
<TableBody>
{page.map((row, i) => {
prepareRow(row);
return (
<TableRow {...row.getRowProps()}>
{row.cells.map((cell) => {
return (
<TableCell {...cell.getCellProps()}>
{cell.render("Cell")}
</TableCell>
);
})}
</TableRow>
);
})}
</TableBody>
<TableFooter>
<TableRow>
<TablePagination
rowsPerPageOptions={[
5,
10,
25,
{ label: "All", value: data.length },
]}
colSpan={3}
count={data.length}
rowsPerPage={pageSize}
page={pageIndex}
SelectProps={{
inputProps: { "aria-label": "rows per page" },
native: true,
}}
onChangePage={handleChangePage}
onChangeRowsPerPage={handleChangeRowsPerPage}
ActionsComponent={TablePaginationActions}
/>
</TableRow>
</TableFooter>
</MaUTable>
</TableContainer>
);
};
EnhancedTable.propTypes = {
columns: PropTypes.array.isRequired,
data: PropTypes.array.isRequired,
updateMyData: PropTypes.func.isRequired,
setData: PropTypes.func.isRequired,
skipPageReset: PropTypes.bool.isRequired,
};
export default EnhancedTable;
Correct me if i'm wrong, but i think the first step to your solution is the correct definition of styles for the material-ui element. I can't say for certain if there is another way to generate these styles, but a simple object such as:
const inputStyle = { padding: 0, margin: 0, border: 0, background: "transparent", };
will probably not work. You probably have to use the material styles for that.
import { makeStyles } from "#material-ui/core/styles";
const useStyles = makeStyles({
root: {
border: "1px solid red",
padding: 10
},
});
and then you have to use the style hook in the component definition:
const classes = useStyles();
When overriding a material-ui definition this part is the important one to define which part is going to overriden (not allowed to include pictures yet, sorry):
Material-Ui CSS keys for Table Row
Then you can override the style <TableRow classes={{ root: classes.root }}> or in your case maybe more like <TableRow classes={{ root: classes.root }} {...row.getRowProps()}>
An additional problem you might face is that you have to override the style of the TableCells too, because the overlap the TableRow border.
I have here a code sandbox that is by no means perfect, but should help you on the right track: https://codesandbox.io/s/material-demo-535zq?file=/demo.js
import { makeStyles } from '#material-ui/core/styles';
const useStyles = makeStyles(() => ({
rowStyle : {
padding: 10,
border: "1px solid red"
}
}));
const EnhancedTable = ()=>{
const classes = useStyles();
return(
<TableRow className={classes.rowStyle}/>
)
}

Trying to change icon for a single list item

I'm trying to create an expandable list component. When I click on a list item,
I want the icon to the right of it to change. Right now, when a list item is tapped,
each icon is changed. I only want icon of that particular item to change. Been
thinking about it for 20min and figured I'd reach out for help . Thanks!
import { List, ListItem } from 'react-native-elements';
export default class ExpandingList extends Component {
constructor(props){
super(props)
this.state = {
visibleItems: false
}
}
toggleMenu(){
this.setState({
visibleItems: !this.state.visibleItems
})
};
render() {
const list = "list 1, list 2, list 3";
return (
<View>
<List>
{
list.map((item, i) => (
<ListItem
onPress={ () => this.toggleMenu() }
key={i}
rightIcon={this.state.visibleItems ? iconRight : iconDown}
title={item} />
))
}
</List
</View
)
}
}
You are doing a boolean value and all list items are looking at the same value. To achieve what you want you need to pass a unique value, in this situation i am using the index but ideally you will have a unique identifier besides the index.
Below should be able to acheive what you are looking for
import { List, ListItem } from 'react-native-elements';
export default class ExpandingList extends Component {
constructor(props){
super(props)
this.state = {
visibleItems: null
}
}
toggleMenu(itemIndex){
this.setState({
visibleItems: itemIndex
})
};
render() {
const list = "list 1, list 2, list 3";
return (
<View>
<List>
{
list.map((item, i) => (
<ListItem
onPress={ () => this.toggleMenu(i) }
key={i}
rightIcon={this.state.visibleItems === i ? iconRight : iconDown}
title={item} />
))
}
</List
</View
)
}
}
Note: I am assuming you have this rendering already but for other people. The list const is a comma separated string and string do not have a map function.
Also the code above does not take into account deselecting a list item that was already selected. That can be done by checking the value in the state and if it is the same reseting the value back to null.
Here's a way to do it also:
import React from "react";
import ReactDOM from "react-dom";
class App extends React.Component {
state = {
visibleItem: 0
};
select = i => {
this.setState({
visibleItem: i
});
};
render() {
const items = [1, 2, 3, 4];
return (
<React.Fragment>
{items.map((v, i) => {
return (
<div
style={{ color: this.state.visibleItem === i ? "red" : "black" }}
onClick={() => this.select(i)}
>
{v}
</div>
);
})}
</React.Fragment>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Working example here.

Resources