I am creating a React app and have the following list:
const list = [
{
id: '1',
task: 'task 1',
activities: [{
'Google': [
{url: 'https://www.google.com'},
{visited: false}
],
'Yahoo': [
{url: 'https://www.yahoo.com'},
{visited: false}
],
'Bing': [
{url: 'https://www.bing.com'},
{visited: false}
]
}],
visitedAll: false
},
{
id: '2',
task: 'task 2',
activities: [{
'Facebook': [
{url: 'https://www.facebook.com'},
{visited: false}
],
'Instagram': [
{url: 'https://www.instagram.com'},
{visited: false}
],
'Twitter': [
{url: 'https://www.twitter.com'},
{visited: false}
]
}],
visitedAll: false
}
];
I am iterating through the list like so:
<ul>
{list.map(item => (
<li key={item.id}>
{item.task}
<ul>
{Object.keys(item.activities[0]).map((act, i) => <li key={i}>{act}</li>)}
</ul>
</li>
))}
</ul>
Which will produce the following output:
task 1
Google
Yahoo
Bing
task 2
Facebook
Instagram
Twitter
How can I wrap the submenu list items with their corresponding anchors?
How do I add an onClick event which will set the visited key to true for each item?
You could first modify activities property to be one object using reduce method and then you can create separate component for sub-items where each holds state with visited property.
const {useState} = React;
const list = [{"id":"1","task":"task 1","activities":[{"Google":[{"url":"https://www.google.com"},{"visited":false}],"Yahoo":[{"url":"https://www.yahoo.com"},{"visited":false}],"Bing":[{"url":"https://www.bing.com"},{"visited":false}]}],"visitedAll":false},{"id":"2","task":"task 2","activities":[{"Facebook":[{"url":"https://www.facebook.com"},{"visited":false}],"Instagram":[{"url":"https://www.instagram.com"},{"visited":false}],"Twitter":[{"url":"https://www.twitter.com"},{"visited":false}]}],"visitedAll":false}]
.map(({activities, ...rest}) => ({
...rest, activities: activities.reduce((r, e) => Object.assign(r, e), {})
}))
const SubListItem = props => {
const {data, name} = props;
const [visited, setVisited] = useState(data[1].visited);
const toggle = event => {
event.preventDefault();
setVisited(!visited);
}
return <li>
<a style={{color: visited ? "green" : ''}}
onClick={toggle}
href={data[0].url}>{name}</a>
</li>
}
const List = ({data}) => <ul>
{data.map(item => <li key={item.id}>
{item.task}
<ul>{Object.keys(item.activities).map(act => (
<SubListItem key={act} name={act} data={item.activities[act]} />
))}</ul>
</li>)}
</ul>
ReactDOM.render(<List data={list} />, document.querySelector("#app"))
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
li {
margin: 8px 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="app"></div>
I would implement an Anchor component which will save the state of visited.
Also, the parent component App would have a state of "all visited anchors".
Example:
const list = [
{
id: '1',
task: 'task 1',
activities: {
Google: {
url: 'https://www.google.com'
},
Yahoo: {
url: 'https://www.yahoo.com'
},
Bing: {
url: 'https://www.bing.com'
}
}
},
{
id: '2',
task: 'task 2',
activities: {
Facebook: {
url: 'https://www.facebook.com'
},
Instagram: {
url: 'https://www.instagram.com'
},
Twitter: {
url: 'https://www.twitter.com'
}
}
}
];
const Anchor = ({ name, href, onClick }) => {
const [isVisited, setIsVisited] = useState(false);
const onAnchorClick = () => {
setIsVisited(visited => !visited);
onClick();
};
return (
// v add href={href}
<a onClick={onAnchorClick}>{`${name} is ${
isVisited ? '' : 'not'
} visited`}</a>
);
};
// You can get such object from reducing the list
const visitedInitial = {
Google: false,
Yahoo: false,
Bing: false,
Facebook: false,
Instagram: false,
Twitter: false
};
const App = () => {
const [visited, setVisited] = useState(visitedInitial);
return (
<>
<h1>
{Object.values(visited).every(value => value === true)
? 'All Visited'
: 'Please visit all'}
</h1>
<ul>
{list.map(({ id, task, activities }) => (
<li key={id}>
{task}
<ul>
{Object.entries(activities).map(([name, { url }], i) => (
<li key={i}>
<Anchor
name={name}
href={url}
target="_blank"
onClick={() =>
setVisited(prev => ({ ...prev, [name]: true }))
}
/>
</li>
))}
</ul>
</li>
))}
</ul>
</>
);
};
Here is the answer based on the items you've provided. I've extracted drawing sub-items into a separate function that accepts an item argument. Every sub-item has an onClick handler which sets visited to true.
import React, { useState, useCallback } from "react";
import ReactDOM from "react-dom";
const initialItems = [
{
id: "1",
task: "task 1",
activities: [
{
Google: [{ url: "https://www.google.com" }, { visited: false }],
Yahoo: [{ url: "https://www.yahoo.com" }, { visited: false }],
Bing: [{ url: "https://www.bing.com" }, { visited: false }]
}
]
},
{
id: "2",
task: "task 2",
activities: [
{
Facebook: [{ url: "https://www.facebook.com" }, { visited: false }],
Instagram: [{ url: "https://www.instagram.com" }, { visited: false }],
Twitter: [{ url: "https://www.twitter.com" }, { visited: false }]
}
]
}
];
function App() {
const [items, setItems] = useState(initialItems);
const setVisited = React.useCallback(
(id, activityName) => {
const item = items.find(item => item.id === id);
if (item) {
item.activities[0][activityName][1].visited = true;
setItems([...items]);
}
},
[items, setItems]
);
const getSubItems = useCallback(
item => {
const activities = item.activities[0];
return Object.keys(activities).map(activityName => {
const url = activities[activityName][0].url;
const onClick = () => {
setVisited(item.id, activityName);
};
return (
<li key={activityName}>
<a
href={url}
onClick={onClick}
target="_blank"
rel="noopener noreferrer"
>
{activityName}
</a>
</li>
);
});
},
[setVisited]
);
return (
<div>
<ul>
{items.map(item => (
<li key={item.id}>
{item.task}
<ul>{getSubItems(item)}</ul>
</li>
))}
</ul>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
EXAMPLE
Related
I have a working prototype where I cant seem to figure out when I click on a particular button in my nav, then pass that index to another component. It doesn't update the index when I query using "document.querySelector". My goal is to just move the content to the front child position/firstChild depending on what button you clicked. So if you clicked the first button then the last, my last content would be first and so and so on...I've tried a timeout and still no luck.
JS:
.....
const Context = createContext(false);
const dataArr = [
{
id: "113062",
name: "Blue",
region: "US",
sort: "1"
},
{
id: "115102",
name: "Red",
region: "DE",
sort: "2"
},
{
id: "70884",
name: "Green",
region: "US",
sort: "3"
},
{
id: "114683",
name: "Yellow",
region: "US",
sort: "4"
},
{
id: "112878",
name: "Pale",
region: "US",
sort: "5"
},
{
id: "114682",
name: "Orange",
region: "US",
sort: "6"
},
{
id: "120093",
name: "Black",
region: "CH",
sort: "8"
},
{
id: "120594",
name: "White",
region: "CH",
sort: "9"
}
];
const useStyles = makeStyles((theme) => ({
root: {
display: "flex",
"& > *": {
margin: theme.spacing(1)
}
},
grey: {
border: "4px solid white"
},
orange: {
color: theme.palette.getContrastText(deepOrange[500]),
backgroundColor: deepOrange[500],
border: "4px solid black"
},
info: {
margin: "10px"
},
wrapper: {
display: "flex"
},
contentWrapper: {
display: "flex",
flexDirection: "column"
},
elWrapper: {
opacity: 0,
"&.active": {
opacity: 1
}
}
}));
const ToggleItem = ({ id, description }) => {
const { handleChange, selected, classes } = useContext(Context);
const isSelected = selected.includes(description);
const handleClick = (idx) => {
console.log("idx====", idx);
handleChange(description, idx);
};
return (
<>
<Avatar
className={isSelected ? classes.orange : classes.grey}
onClick={() => handleClick(id)}
>
<span style={{ fontSize: ".75rem" }}>{description}</span>
</Avatar>
</>
);
};
const ToggleContainer = ({ selected, list }) => {
const isSelected = list.filter((element) => selected.includes(element));
return (
<>
{dataArr.map((item, idx) => (
<div
key={idx}
className="carouselWrapper"
style={{ display: isSelected.includes(item.name) ? "block" : "none" }}
>
{item.name}
</div>
))}
</>
);
};
const ToggleWrapper = () => {
const data = [];
dataArr.map((el) => data.push(el.name));
const classes = useStyles();
const [selected, setSelected] = useState([]);
const [viewAll, setViewAll] = useState(true);
const handleChange = (val, idx) => {
setViewAll(false);
setSelected((selected) => {
if (selected.length === 1 && selected.includes(val)) {
return handleViewAll();
}
if (selected.includes(val)) {
return selected.filter((v) => v !== val);
} else {
return [val, ...selected];
}
});
setTimeout(() => {
const someParentObject = document.getElementById("contentWrapper");
const someChildObject = document.querySelectorAll(".carouselWrapper")[
idx
];
// eslint-disable-next-line no-unused-expressions
selected.length > 0 &&
someParentObject.insertBefore(
someChildObject,
someParentObject.firstChild
);
console.log(
"someParentObject.children[0]==",
someParentObject.firstChild
);
console.log("someChildObject", someChildObject);
}, 2000);
};
const handleViewAll = () => {
setViewAll(true);
setSelected([]);
};
return (
<Context.Provider
value={{
viewAll,
handleChange,
handleViewAll,
selected,
classes
}}
>
<div className={classes.wrapper}>
<Avatar
className={viewAll ? classes.orange : null}
onClick={handleViewAll}
>
<span style={{ fontSize: "0.75rem", textAlign: "center" }}>
View All
</span>
</Avatar>
{dataArr.map((d, id) => {
return (
<div key={id}>
<ToggleItem id={id} description={d.name} />
</div>
);
})}
</div>
<div id="contentWrapper" className={classes.contentWrapper}>
<ToggleContainer selected={viewAll ? data : selected} list={data} />
</div>
</Context.Provider>
);
};
export default function App() {
return <ToggleWrapper />;
}
Here is a very quick example of one option. It stores the data in a Map which allows for efficient retrieval (this could be moved to context to avoid passing it to multiple child components) and tracks selected by id in a separate state array. It's then just a matter of mapping the selected array to get your ordered list. If selected is empty render the full values() of the Map.
const { useState, useEffect } = React;
function App({ data }) {
const [selected, setSelected] = useState([]);
const [itemMap, setItemMap] = useState(new Map());
useEffect(() => {
setItemMap(new Map(data.map(({ id, ...rest }) => [id, { id, ...rest }])));
}, [data]);
const selectHandler = (id) => {
setSelected(selected => {
if (id === undefined) return [];
return selected.includes(id)
? selected.filter(_id => _id !== id)
: [id, ...selected];
});
}
return (
<div>
<Header itemMap={itemMap} selected={selected} selectHandler={selectHandler} />
<Selected itemMap={itemMap} selected={selected} />
</div>
);
}
function Header({ itemMap, selected, selectHandler }) {
return (
<div className={"header-container"}>
<div className={!selected.length && 'selected'} onClick={() => selectHandler()}>All</div>
{[...itemMap.values()].map(({ id, name }) => (
<div key={id} className={selected.includes(id) && 'selected'} onClick={() => selectHandler(id)}>
{name}
</div>
))}
</div>
);
}
function Selected({ itemMap, selected }) {
return (
<div className={"selected-container"}>
{selected.length
? selected.map((id) => (
<div key={id} >
{itemMap.get(id).name}
</div>
))
: [...itemMap.values()].map(({ id, name }) => (
<div key={id} >
{name}
</div>
))
}
</div>
);
}
const dataArr = [{ id: "113062", name: "Blue", region: "US", sort: "1" }, { id: "115102", name: "Red", region: "DE", sort: "2" }, { id: "70884", name: "Green", region: "US", sort: "3" }, { id: "114683", name: "Yellow", region: "US", sort: "4" }, { id: "112878", name: "Pale", region: "US", sort: "5" }, { id: "114682", name: "Orange", region: "US", sort: "6" }, { id: "120093", name: "Black", region: "CH", sort: "8" }, { id: "120594", name: "White", region: "CH", sort: "9" }];
const container = document.getElementById('root');
const root = ReactDOM.createRoot(container);
root.render(<App data={dataArr} />);
.header-container { width: 100vw; display: flex; justify-content: flex-start;}
.header-container div { width: 40px; height: 40px; display: flex; overflow: hidden; position: relative; margin: 4px; font-size:.75rem; align-items: center; user-select: none; border-radius: 50%; justify-content: center; color: #fafafa; background-color: #bdbdbd;}
.header-container div.selected { background-color: tomato;}
<script crossorigin src="https://unpkg.com/react#18/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.production.min.js"></script>
<div id='root'></div>
i'm new to react and i'm trying to convert this class based component to a functionnal component but i get an error of state, how can i convert it please ?
This is my components :)
sandbox link
Thank you
In this example, it is quite straight forward as there are no component life cycle methods. You can just define all the methods in the function, and return the component.
function Demo() {
const [state, setState] = React.useState({
expandedKeys: [],
autoExpandParent: true,
checkedKeys: [],
allCheckedKeys: [],
selectedKeys: [],
newTreeView: false,
newTreeData: []
});
const onExpand = (expandedKeys) => {
console.log("onExpand", expandedKeys);
// if not set autoExpandParent to false, if children expanded, parent can not collapse.
// or, you can remove all expanded children keys.
setState({
...state,
expandedKeys,
autoExpandParent: false
});
};
const onCheck = (checkedKeys, e) => {
const allCheckedKeys = [...checkedKeys, ...e.halfCheckedKeys];
console.log("onCheck", allCheckedKeys);
console.log(createNewTreeData(treeData, allCheckedKeys));
setState((prevState) => ({
...prevState,
allCheckedKeys,
checkedKeys
}));
};
const onSelect = (selectedKeys, info) => {
console.log("onSelect", info);
setState({ ...state, selectedKeys });
};
const renderTreeNodes = (data) =>
data.map((item) => {
if (item.children) {
return (
<TreeNode title={item.title} key={item.key} dataRef={item}>
{renderTreeNodes(item.children)}
</TreeNode>
);
}
return <TreeNode {...item} />;
});
const createTree = () => {
setState((prevState) => ({
...prevState,
newTreeView: true,
newTreeData: createNewTreeData(treeData, prevState.allCheckedKeys)
}));
};
return (
<>
<Tree
checkable
onExpand={onExpand}
expandedKeys={state.expandedKeys}
autoExpandParent={state.autoExpandParent}
onCheck={onCheck}
checkedKeys={state.checkedKeys}
onSelect={onSelect}
selectedKeys={state.selectedKeys}
>
{renderTreeNodes(treeData)}
</Tree>
<button onClick={createTree}>Validate</button>
{state.newTreeView && <Tree>{renderTreeNodes(state.newTreeData)}</Tree>}
</>
);
}
import React, { useState } from 'react';
import ReactDOM from 'react-dom';
import 'antd/dist/antd.css';
import './index.css';
import { Tree } from 'antd';
const treeData = [
{
title: '0-0',
key: '0-0',
children: [
{
title: '0-0-0',
key: '0-0-0',
children: [
{
title: '0-0-0-0',
key: '0-0-0-0',
},
{
title: '0-0-0-1',
key: '0-0-0-1',
},
{
title: '0-0-0-2',
key: '0-0-0-2',
},
],
},
{
title: '0-0-1',
key: '0-0-1',
children: [
{
title: '0-0-1-0',
key: '0-0-1-0',
},
{
title: '0-0-1-1',
key: '0-0-1-1',
},
{
title: '0-0-1-2',
key: '0-0-1-2',
},
],
},
{
title: '0-0-2',
key: '0-0-2',
},
],
},
{
title: '0-1',
key: '0-1',
children: [
{
title: '0-1-0-0',
key: '0-1-0-0',
},
{
title: '0-1-0-1',
key: '0-1-0-1',
},
{
title: '0-1-0-2',
key: '0-1-0-2',
},
],
},
{
title: '0-2',
key: '0-2',
},
];
const Demo = () => {
const [expandedKeys, setExpandedKeys] = useState(['0-0-0', '0-0-1']);
const [checkedKeys, setCheckedKeys] = useState(['0-0-0']);
const [selectedKeys, setSelectedKeys] = useState([]);
const [autoExpandParent, setAutoExpandParent] = useState(true);
const onExpand = (expandedKeysValue) => {
console.log('onExpand', expandedKeysValue); // if not set autoExpandParent to false, if children expanded, parent can not collapse.
// or, you can remove all expanded children keys.
setExpandedKeys(expandedKeysValue);
setAutoExpandParent(false);
};
const onCheck = (checkedKeysValue) => {
console.log('onCheck', checkedKeysValue);
setCheckedKeys(checkedKeysValue);
};
const onSelect = (selectedKeysValue, info) => {
console.log('onSelect', info);
setSelectedKeys(selectedKeysValue);
};
return (
<Tree
checkable
onExpand={onExpand}
expandedKeys={expandedKeys}
autoExpandParent={autoExpandParent}
onCheck={onCheck}
checkedKeys={checkedKeys}
onSelect={onSelect}
selectedKeys={selectedKeys}
treeData={treeData}
/>
);
};
ReactDOM.render(<Demo />, document.getElementById('container'));
I have updated the code using ES6 arrow functions resulting in shorter and simpler code than traditional functional components.
import React,{useState} from "react";
import ReactDOM from "react-dom";
import "antd/dist/antd.css";
import "./index.css";
import { Tree } from "antd";
const { TreeNode } = Tree;
const treeData = [
{
title: "0-0",
key: "0-0",
children: [
{
title: "0-0-0",
key: "0-0-0",
children: [
{ title: "0-0-0-0", key: "0-0-0-0" },
{ title: "0-0-0-1", key: "0-0-0-1" },
{ title: "0-0-0-2", key: "0-0-0-2" }
]
},
{
title: "0-0-1",
key: "0-0-1",
children: [
{ title: "0-0-1-0", key: "0-0-1-0" },
{ title: "0-0-1-1", key: "0-0-1-1" },
{ title: "0-0-1-2", key: "0-0-1-2" }
]
},
{
title: "0-0-2",
key: "0-0-2"
}
]
},
{
title: "0-1",
key: "0-1",
children: [
{ title: "0-1-0-0", key: "0-1-0-0" },
{ title: "0-1-0-1", key: "0-1-0-1" },
{ title: "0-1-0-2", key: "0-1-0-2" }
]
},
{
title: "0-2",
key: "0-2"
}
];
const createNewTreeData = (treeData, checkedKeys) => {
return treeData.reduce((acc, treeDataItem) => {
if (checkedKeys.includes(treeDataItem.key)) {
if (treeDataItem.children) {
acc.push({
...treeDataItem,
children: createNewTreeData(treeDataItem.children, checkedKeys)
});
} else {
acc.push(treeDataItem);
}
}
return acc;
}, []);
};
const Demo =()=> {
const [state,setState] = useState({
expandedKeys: [],
autoExpandParent: true,
checkedKeys: [],
allCheckedKeys: [],
selectedKeys: [],
newTreeView: false,
newTreeData: []
});
const onExpand = (expandedKeys) => {
console.log("onExpand", expandedKeys);
// if not set autoExpandParent to false, if children expanded, parent can not collapse.
// or, you can remove all expanded children keys.
setState({
expandedKeys,
autoExpandParent: false
});
};
const onCheck = (checkedKeys, e) => {
const allCheckedKeys = [...checkedKeys, ...e.halfCheckedKeys];
console.log("onCheck", allCheckedKeys);
console.log(createNewTreeData(treeData, allCheckedKeys));
setState((prevState) => ({
...prevState,
allCheckedKeys,
checkedKeys
}));
};
const onSelect = (selectedKeys, info) => {
console.log("onSelect", info);
setState({ selectedKeys });
};
const renderTreeNodes = (data) =>
data.map((item) => {
if (item.children) {
return (
<TreeNode title={item.title} key={item.key} dataRef={item}>
{renderTreeNodes(item.children)}
</TreeNode>
);
}
return <TreeNode {...item} />;
});
const createTree = () => {
setState((prevState) => ({
...prevState,
newTreeView: true,
newTreeData: createNewTreeData(treeData, prevState.allCheckedKeys)
}));
};
return (
<>
<Tree
checkable
onExpand={onExpand}
expandedKeys={state.expandedKeys}
autoExpandParent={state.autoExpandParent}
onCheck={onCheck}
checkedKeys={state.checkedKeys}
onSelect={onSelect}
selectedKeys={state.selectedKeys}
>
{renderTreeNodes(treeData)}
</Tree>
<button onClick={createTree}>Validate</button>
{state.newTreeView && (
<Tree>{renderTreeNodes(state.newTreeData)}</Tree>
)}
</>
);
}
ReactDOM.render(<Demo />, document.getElementById("container"));
I have a list of li elements that is being passed down to one of my components. These are being rendered in the component. I have a search bar in the same component. I want to be able to only render the items that match what is written down in the search bar. This is what my component looks like.
import React, {Component} from 'react'
import {NavLink} from 'react-router-dom'
import LikeBtn from './LikeBtn'
class SearchForm extends Component {
constructor(props) {
super(props)
this.state = {
search: '',
newList: []
}
}
handleChange = (e) => {
this.setState({
[e.target.name]: e.target.value,
})
}
render() {
let list = this.props.listProp.map(item => <li className="listItem" key={item.id}><NavLink style={{ color: 'white' }} to={`activities/${item.id}`}>{item.name}</NavLink><LikeBtn /></li>)
let newList = list.filter(item => item.innerText === this.state.search)
console.log(newList)
return (
<>
<input type="text" name='search' onChange={this.handleChange}/>
<ul>
{list}
</ul>
</>
)
}
}
export default SearchForm
I don't know how to get that filtered out so that I can render the items. I tried doing innerText but since I have a LikeBtn component in the li element my filter doesn't work. How else would I be able to implement this? Are there more efficient ways of doing this?
You need to filter your data and not grab something you've already rendered on screen.
render() {
let filteredList = this.state.search
? this.props.listProp.filter((item) =>
item.name.includes(this.state.search),
)
: this.props.listProp;
return (
<>
<input type="text" name="search" onChange={this.handleChange} />
<ul>
{filteredList.map((item) => (
<li className="listItem" key={item.id}>
<NavLink style={{ color: 'white' }} to={`activities/${item.id}`}>
{item.name}
</NavLink>
<LikeBtn />
</li>
))}
</ul>
</>
);
}
There is also a snippet below that you can run:
class SearchForm extends React.Component {
constructor(props) {
super(props);
this.state = {
search: '',
};
}
handleChange = (e) => {
this.setState({
[e.target.name]: e.target.value,
});
};
render() {
let filteredList = this.state.search
? this.props.listProp.filter((item) =>
item.name.toLowerCase().includes(this.state.search.toLowerCase())
)
: this.props.listProp;
return (
<React.Fragment>
<input type="search" name="search" autocomplete="off" onChange={this.handleChange} />
<ul>
{filteredList.map((item) => (
<li className="listItem" key={item.id}>
<a href={`activities/${item.id}`}>{item.name}</a>
<button>Like</button>
</li>
))}
</ul>
</React.Fragment>
);
}
}
const data = [
{
id: 1,
name: 'Congress, The',
},
{
id: 2,
name: 'Glen or Glenda',
},
{
id: 3,
name: "Don't Drink the Water",
},
{
id: 4,
name: 'Blind',
},
{
id: 5,
name: 'Sirocco',
},
{
id: 6,
name: 'Sunset Strip',
},
{
id: 7,
name: 'Better Living',
},
{
id: 8,
name: '4:44 Last Day on Earth',
},
{
id: 9,
name: 'The Heavy',
},
{
id: 10,
name: 'Dr. Who and the Daleks',
},
{
id: 11,
name: 'Legend of Hell House, The',
},
{
id: 12,
name: 'Exit Humanity',
},
{
id: 13,
name: 'Young in Heart, The',
},
{
id: 14,
name: 'Soul Kitchen',
},
{
id: 15,
name: 'Accattone',
},
];
ReactDOM.render(<SearchForm listProp={data} />, document.querySelector('#root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>
You can move this part
let list = this.props.listProp.map(item => <li className="listItem" key={item.id}><NavLink style={{ color: 'white' }} to={`activities/${item.id}`}>{item.name}</NavLink><LikeBtn /></li>)
to the return and filter the listProp on handleChange.
You can refer to this codeSandbox here for a working sample
I want to make a filter system using multiple checkbox. But when i checked one checkbox it filter the state but when i unchecked it how i can get back the all data in state . Also if i select multiple checkbox then it will filter from the filtered item.
Here is my code.
state = {
restaurant : [
{name: 'La mesa', cuisine: ['italian', 'indian']},
{name: 'Red Bull', cuisine: ['chiness', 'french']}
{name: 'Purnima', cuisine: ['thai', 'arabic']}
]
cuisine: [
{id: 1, name: 'italian'},
{id: 2, name: 'indian'},
{id: 3, name: 'chiness'}
{id: 4, name: 'french'},
{id: 4, name: 'arabic'},
]
}
handleCuisineFilter = (e) => {
if (e.target.checked) {
const filter =
this.state.restaurant.length &&
this.state.restaurant.filter((rest) => rest.cuisine.includes(e.target.value));
this.setState({ restaurant: filter });
} else {
Now when unchecked how i can get previous state???
}
};
render() {
return (
<div>
{this.state.cuisine.length && this.state.cuisine.map(
cuisine=> (<li>
<input
id={cuisine.id}
type='checkbox'
onChange={this.handleCuisineFilter}
name='check'
value={cuisine.name}
/>
{cuisine.name } {here will be count of number of restaurant}
</li>
))}
{this.state.restaurant.length && this.state.restaurant.map(rest=> <h5>rest.name</h5>)}
</div>
I tried to explain best via my code . Help me please. Thank you in advance
You have to keep track of checked state for each filter and then filter against all filters at once every time.
Here is the solution
EDIT
import React, { Component } from "react";
import "./App.css";
class App extends Component {
state = {
restaurant: [
{ name: "La mesa", cuisine: ["italian", "indian"] },
{ name: "Red Bull", cuisine: ["chiness", "french"] },
{ name: "Purnima", cuisine: ["thai", "arabic"] },
],
// maintain a checked state for each filter
cuisine: [
{ id: 1, name: "italian", checked: false },
{ id: 2, name: "indian", checked: false },
{ id: 3, name: "chiness", checked: false },
{ id: 4, name: "french", checked: false },
{ id: 5, name: "arabic", checked: false },
],
};
setFilter = (cuisine, flag) => {
this.setState((prevState) => ({
cuisine: prevState.cuisine.map((c) =>
// check state for the selected cuisine
c.id === cuisine.id ? { ...c, checked: flag } : c
),
}));
};
handleCuisineFilter = (e, cuisine) => {
if (e.target.checked) {
this.setFilter(cuisine, true);
} else {
this.setFilter(cuisine, false);
}
};
filterRestaurants = (restaurant) => {
const checkedFilters = this.state.cuisine.filter((c) => c.checked);
const noFiltersChecked = checkedFilters.length === 0;
if (noFiltersChecked) {
return true;
} else {
// EDITED:
const tmp = checkedFilters.reduce(
(hasRestaurantAllTheseCuisines, nextCuisine) =>
(hasRestaurantAllTheseCuisines =
hasRestaurantAllTheseCuisines &&
restaurant.cuisine.includes(nextCuisine.name)),
true
);
return tmp;
}
};
render() {
return (
<div>
{this.state.cuisine.length &&
this.state.cuisine.map((cuisine) => (
<li key={cuisine.id}>
<input
id={cuisine.id}
type="checkbox"
onChange={(e) => this.handleCuisineFilter(e, cuisine)}
name="check"
value={cuisine.name}
/>
{cuisine.name} {/* here will be count of number of restaurant */}
</li>
))}
{/* Use .filter() with cuisine state */}
{this.state.restaurant.length &&
this.state.restaurant
.filter(this.filterRestaurants)
.map((rest) => <h5 key={rest.name}>{rest.name}</h5>)}
</div>
);
}
}
export default App;
Edited the code. The only change was the filter check here
...
const tmp = checkedFilters.reduce(
(hasRestaurantAllTheseCuisines, nextCuisine) =>
(hasRestaurantAllTheseCuisines =
hasRestaurantAllTheseCuisines &&
restaurant.cuisine.includes(nextCuisine.name)),
true
);
...
I'm using the react-sortablejs library.
When trying to move cards within the list. I get the error:
Cannot read property 'map' of undefined
I have a dense structure and it gets lost here. How to handle onChange so that I can see in the console that the order of the notes within the list has changed.
Demo here
import Sortable from 'react-sortablejs';
// Functional Component
const SortableList = ({ items, onChange }) => {
return (
<div>
<Sortable
tag="ul"
onChange={(order, sortable, evt) => {
console.log(order)
onChange(order);
}}
>
{items.listItems.map(val => {
return <li key={uniqueId()} data-id={val}>List Item: {val.title}</li>})
}
</Sortable>
</div>
);
};
class App extends React.Component {
state = {
item: {
id: "abc123",
name: "AAA",
lists: [
{
id: "def456",
list_id: "654wer",
title: 'List1',
desc: "description",
listItems: [
{
id: "ghj678",
title: "ListItems1",
listItemsId: "88abf1"
},
{
id: "poi098",
title: "ListItems2",
listItemsId: "2a49f25"
},
{
id: "1oiwewedf098",
title: "ListItems3",
listItemsId: "1a49f25dsd8"
}
]
},
{
id: "1ef456",
list_id: "654wer",
title: 'List 2',
desc: "description",
listItems: [
{
id: "1hj678",
title: "ListItems4",
listItemsId: "18abf1"
},
{
id: "1oi098",
title: "ListItems5",
listItemsId: "1a49f25"
},
{
id: "1oiwewe098",
title: "ListItems6",
listItemsId: "1a49f25dsd"
}
]
},
{
id: "2ef456",
title: 'List 3',
list_id: "254wer",
desc: "description",
listItems: [
{
id: "2hj678",
title: "ListItems7",
listItemsId: "28abf1"
},
{
id: "2oi098",
title: "ListItems8",
listItemsId: "234a49f25"
},
{
id: "df098",
title: "ListItems9",
listItemsId: "1asd8"
}
]
}
]
}
};
render() {
const c = this.state.item['lists'].map(item => { return item.listItems});
return (
this.state.item['lists'].map(item => {
return (<div>
{item.title}
<SortableList
key={uniqueId()}
items={item}
onChange={(item) => {
console.log(item)
this.setState({item});
}}
>
</SortableList>
</div>)
})
)
}
};
Thanks in advance.
You have to update few changes in your code.
Update the SortableList function as below.
First pass data-id={val.id} in li and after that in onChange method you will receive the order with id. So based on that we are sorting the records.
const SortableList = ({ items, onChange }) => {
return (
<div>
<Sortable
tag="ul"
onChange={(order, sortable, evt) => {
items.listItems.sort(function(a, b){
return order.indexOf(a.id) - order.indexOf(b.id);
});
onChange(items);
}}
>
{items.listItems.map(val => {
return <li key={uniqueId()} data-id={val.id}>List Item: {val.title}</li>})
}
</Sortable>
</div>
);
};
Update the onChange event of App component.
onChange={(item) => {
let itemObj = {...this.state.item};
itemObj.lists.map(x=>{
if(x.id === item.id) x = item;
});
this.setState({itemObj});
}}
That's it!
Here is the working demo for you
https://stackblitz.com/edit/react-sortablejs-blzxwd
When remove the onChange event in the Sortable list, Its works.
const SortableList = ({ items, onChange }) => {
return (
<div>
<Sortable
tag="ul"
>
{items.listItems.map(val => {
return <li key={uniqueId()} data-id={val}>List Item: {val.title}</li>})
}
</Sortable>
</div>
);
};