I am new to react and I've been trying to achieve a function that I am not sure of, I have a component that renders JSON file and shows products named 'product list', another component named 'person' which is used to show product items, both are working fine, but the third component called menucat includes the scrolling menu from https://www.npmjs.com/package/react-horizontal-scrolling-menu, the onselect function of the menu component returns an id number on selection, I want to pass that number inside the mapping function within the productlist.
Product list
import React from "react";
import Person from "./Person";MenuCat";
import MenuCat, {a, onSelect, selected} from "../components/
class ProductList extends React.Component {
state = {
error: null,
isLoaded: false,
items: []
};
componentDidMount() {
fetch("items.json")
.then(res => res.json())
.then(
result => {
this.setState({
isLoaded: true,
items: result
});
},
error => {
this.setState({
isLoaded: true,
error
});
}
);
}
render() {
const { error, isLoaded, items } = this.state;
if (error) {
return (
<div>
Error:{" "}
{error.message }
{console.log("check 1:", items)}
</div>
);
} else if (!isLoaded) {
return (
<div>
<img
src="loading.gif"
alt="loading"
height="100"
/>
</div>
);
} else {
return (
<div>
<MenuCat />
<div className="row">
{items.children[0].children.map(item => (
<Person
className="person"
Key={item.name}
Title={item.name}
imgSrc={item.image_url}
>
{item.base_price}
</Person>
))}
</div>
</div>
);
}
}
}
and the menuCat component looks like this
import React, { Component } from "react";
import ScrollMenu from "react-horizontal-scrolling-menu";
import "../menu.css";
// list of items
const list = [
{ name: "category1" , id : 0},
{ name: "category2" , id : 1},
{ name: "category3" , id : 2},
{ name: "category4" , id : 3},
{ name: "category5" , id : 4},
{ name: "category6" , id : 5},
{ name: "category7" , id : 6},
];
// One item component
// selected prop will be passed
const MenuItem = ({ text, selected }) => {
return <div className="menu-item">{text}</div>;
};
// All items component
// Important! add unique key
export const Menu = list =>
list.map(el => {
const { name } = el;
const { id } = el;
return <MenuItem text={name} key={id} />;
});
const Arrow = ({ text, className }) => {
return <div className={className}>{text}</div>;
};
const ArrowLeft = Arrow({ text: "<", className: "arrow-prev" });
const ArrowRight = Arrow({ text: ">", className: "arrow-next" });
export class Menucat extends Component {
state = {
selected: "0"
};
onSelect = key => {
console.log(`onSelect: ${key}`);
this.setState({ selected: key});
};
render() {
const { selected } = this.state;
// Create menu from items
const menu = Menu(list, selected);
return (
<div className="App">
<ScrollMenu
data={menu}
arrowLeft={ArrowLeft}
arrowRight={ArrowRight}
selected={selected}
onSelect={this.onSelect}
/>
</div>
);
}
}
export default Menucat;
I want the id generated from onselect function to be added instead of 0 in
{items.children[0].children.map(item => (
so that whenever the user clicks on a category item, the id of that category goes to the mapping function which will do the rest. I am aware that the category list is hardcoded, for now, I just want this communication between the components to happen, I want to pass the id from the menucat component to a something like a variable in product list that can go instead of the zero like {items.children[selected].children.map(item => (
you'll want to add an 'onSelect' prop to MenuCat to pass through the ScrollMenu's onSelect results, then a state value in ProductList to store the selected key. something like this:
ProductList
import React from "react";
import Person from "./Person";MenuCat";
import MenuCat, {a, onSelect, selected} from "../components/
class ProductList extends React.Component {
state = {
error: null,
isLoaded: false,
items: [],
selected: 0,
};
componentDidMount() {
fetch("items.json")
.then(res => res.json())
.then(
result => {
this.setState({
isLoaded: true,
items: result
});
},
error => {
this.setState({
isLoaded: true,
error
});
}
);
}
constructor(props) {
super(props);
this.onSelect = this.onSelect.bind(this);
}
onSelect(key) {
this.setState({selected: key});
}
render() {
const { error, isLoaded, items } = this.state;
if (error) {
return (
<div>
Error:{" "}
{error.message }
{console.log("check 1:", items)}
</div>
);
} else if (!isLoaded) {
return (
<div>
<img
src="loading.gif"
alt="loading"
height="100"
/>
</div>
);
} else {
return (
<div>
<MenuCat onSelect={this.onSelect} />
<div className="row">
{items.children[this.state.selected].children.map(item => (
<Person
className="person"
Key={item.name}
Title={item.name}
imgSrc={item.image_url}
>
{item.base_price}
</Person>
))}
</div>
</div>
);
}
}
}
and MenuCat like
import React, { Component } from "react";
import ScrollMenu from "react-horizontal-scrolling-menu";
import "../menu.css";
// list of items
const list = [
{ name: "category1" , id : 0},
{ name: "category2" , id : 1},
{ name: "category3" , id : 2},
{ name: "category4" , id : 3},
{ name: "category5" , id : 4},
{ name: "category6" , id : 5},
{ name: "category7" , id : 6},
];
// One item component
// selected prop will be passed
const MenuItem = ({ text, selected }) => {
return <div className="menu-item">{text}</div>;
};
// All items component
// Important! add unique key
export const Menu = list =>
list.map(el => {
const { name } = el;
const { id } = el;
return <MenuItem text={name} key={id} />;
});
const Arrow = ({ text, className }) => {
return <div className={className}>{text}</div>;
};
const ArrowLeft = Arrow({ text: "<", className: "arrow-prev" });
const ArrowRight = Arrow({ text: ">", className: "arrow-next" });
export class Menucat extends Component {
state = {
selected: "0"
};
onSelect = key => {
console.log(`onSelect: ${key}`);
this.setState({ selected: key});
this.props.onSelect(key);
};
render() {
const { selected } = this.state;
// Create menu from items
const menu = Menu(list, selected);
return (
<div className="App">
<ScrollMenu
data={menu}
arrowLeft={ArrowLeft}
arrowRight={ArrowRight}
selected={selected}
onSelect={this.onSelect}
/>
</div>
);
}
}
export default Menucat;
you pass an 'onSelect' function to menucat, menucat calls it when the item is selected, and back in ProductList, its 'onSelect' function is then run, setting state which can then be used in your item selection.
make sense?
Related
I'm trying to implement react horizontal scroll using React horizontal scrolling menu library, but when I use this component in my react app, it shows nothing and I am also not getting any error message.
Here is the code of that component:
import React, { useState } from "react";
import { ScrollMenu } from "react-horizontal-scrolling-menu";
// list of items
const list = [
{ name: "item1" },
{ name: "item2" },
{ name: "item3" },
{ name: "item4" },
{ name: "item5" },
{ name: "item6" },
{ name: "item7" },
{ name: "item8" },
{ name: "item9" },
];
// One item component
// selected prop will be passed
const MenuItem = ({ text, selected }) => {
return <div className="menu-item text-black">{text}</div>;
};
// All items component
// Important! add unique key
export const Menu = (list) =>
list.map((el) => {
const { name } = el;
return <MenuItem text={name} key={name} />;
});
const Arrow = ({ text, className }) => {
return <div className={className}>{text}</div>;
};
const ArrowLeft = Arrow({ text: "<", className: "arrow-prev" });
const ArrowRight = Arrow({ text: ">", className: "arrow-next" });
const AvataaarsScroll = () => {
const [selected, setSelected] = useState(0);
const onSelect = (key) => {
setSelected(key);
};
// Create menu from items
const menu = Menu(list, selected);
return (
<div className="App">
<ScrollMenu
data={menu}
arrowLeft={ArrowLeft}
arrowRight={ArrowRight}
selected={selected}
onSelect={onSelect}
/>
</div>
);
};
export default AvataaarsScroll;
Anyone please help me with this.
You are sending the menu items to the ScrollMenu through the property named data, but you need to send them as children elements:
<div className="App">
<ScrollMenu
arrowLeft={ArrowLeft}
arrowRight={ArrowRight}
selected={selected}
onSelect={onSelect}
>
{menu}
</ScrollMenu>
</div>
...or...
<ScrollMenu
arrowLeft={ArrowLeft}
arrowRight={ArrowRight}
selected={selected}
onSelect={onSelect}
children={menu}
/>
I have a checkbox component, I want my user to be able to check multiple items, and then the items to be saved in the state as an array.
If I select a checkbox my handleChange function seems to set my array to undefined, I'm not sure if it's the way I am sending the data or If I've setup my checkbox wrong, I'm quite new to React.
My main component is
export default class MainForm extends Component {
state = {
eventFormats: []
}
handleChange = input => event => {
this.setState({[input]: event.target.value})
console.log(this.state)
}
render() {
const eventFormat = {eventFormats: this.state.eventFormats}
return <EventFormat
nextStep={this.nextStep}
handleChange={this.handleChange}
values={eventFormat}
}
}
}
My event form component
export default class EventFormat extends Component {
state = {
eventFormats: [
{id: 1, value: 1, label: "Virtual", isChecked: false},
{id: 2, value: 2, label: "Hybrid", isChecked: false},
{id: 3, value: 3, label: "Live", isChecked: false},
]
}
saveAndContinue = (e) => {
e.preventDefault()
}
render() {
return (
<Form>
<h1 className="ui centered">Form</h1>
<Form.Field>
{
this.state.eventFormats.map((format) => {
return (<CheckBox handleChange={this.props.handleChange} {...format} />)
})
}
</Form.Field>
<Button onClick={this.saveAndContinue}>Next</Button>
</Form>
)
}
}
And finally my checkbox component
const CheckBox = (props) => {
return (<Checkbox label={props.label} onChange={props.handleChange('eventFormats')}/>)
}
export default CheckBox
The error is in your handleChange function, which sets state to a dictionary while you said you want the checkbox's value to be added to the eventFormats array in the state.
export default class MainForm extends Component {
state = {
eventFormats: []
}
handleChange = input => event => {
if (event.target.checked) {
this.setState({eventFormats: this.state.eventFormats.concat([event.target.value])});
} else {
const index = this.state.indexOf(event.target.value);
if (index === -1) {
console.error("checkbox was unchecked but had not been registered as checked before");
} else {
this.setState({eventFormats: this.state.eventFormats.splice(index, 1);
}
}
console.log(this.state)
}
render() {
const eventFormat = {eventFormats: this.state.eventFormats}
return <EventFormat
nextStep={this.nextStep}
handleChange={this.handleChange}
values={eventFormat}
}
}
}
There are a few things to fix:
this.setState({[input]: event.target.value})
this will always overwrite the array(eventFormats) with event.target.value.
<CheckBox handleChange={this.props.handleChange} {...format} />
in the above line, you're passing all the properties in each format object
const CheckBox = (props) => {
return (<Checkbox label={props.label} onChange={props.handleChange('eventFormats')}/>)
}
but here you're only using label and handleChange.
Here's a React StackBlitz that implements what you're looking for. I used <input type="checkbox" />, you can replace this with the Checkbox component you want. See the console logs to know how the state looks after toggling any of the checkboxes.
Also, added some comments to help you understand the changes.
const Checkbox = ({ id, checked, label, handleChange }) => {
return (
<>
<input
type="checkbox"
id={id}
value={checked}
// passing the id from here to figure out the checkbox to update
onChange={e => handleChange(e, id)}
/>
<label htmlFor={id}>{label}</label>
</>
);
};
export default class App extends React.Component {
state = {
checkboxes: [
{ id: 1, checked: false, label: "a" },
{ id: 2, checked: false, label: "b" },
{ id: 3, checked: false, label: "c" }
]
};
handleChange = inputsType => (event, inputId) => {
const checked = event.target.checked;
// Functional update is recommended as the new state depends on the old state
this.setState(prevState => {
return {
[inputsType]: prevState[inputsType].map(iT => {
// if the ids match update the 'checked' prop
return inputId === iT.id ? { ...iT, checked } : iT;
})
};
});
};
render() {
console.log(this.state.checkboxes);
return (
<div>
{this.state.checkboxes.map(cb => (
<Checkbox
key={cb.id}
handleChange={this.handleChange("checkboxes")}
{...cb}
/>
))}
</div>
);
}
}
I am trying to iterate through an array of objects to display all items of a certain name. For some reason my this.props.list.items cannot be read. Any help would be greatly appreciated.
The code for item.js
import React from "react";
class Item extends React.Component{
render() {
return(
<div>{this.props.list.items}</div>
)
}
}
export default Item
The code for horizontalscroll.js
import React from 'react';
import ScrollMenu from 'react-horizontal-scrolling-menu';
import './horizontalscroll.css';
import Item from './Item';
// list of items
const list = [
{
name: "Brands",
items: ["1", "2", "3"]
},
{
name: "Films",
items: ["f1", "f2", "f3"]
},
{
name: "Holiday Destination",
items: ["f1", "f2", "f3"]
}
];
// One item component
// selected prop will be passed
const MenuItem = ({ text, selected }) => {
return (
<div
className="menu-item"
>
{text}
</div>
);
};
// All items component
// Important! add unique key
export const Menu = (list) => list.map(el => {
const { name } = el;
return (
<MenuItem
text={name}
key={name}
/>
);
});
const Arrow = ({ text, className }) => {
return (
<div
className={className}
>{text}</div>
);
};
const ArrowLeft = Arrow({ text: '<', className: 'arrow-prev' });
const ArrowRight = Arrow({ text: '>', className: 'arrow-next' });
class HorizantScroller extends React.Component {
state = {
selected: 0
};
onSelect = key => {
this.setState({ selected: key });
}
render() {
const { selected } = this.state;
// Create menu from items
const menu = Menu(list, selected);
return (
<div className="HorizantScroller">
<ScrollMenu
data={menu}
arrowLeft={ArrowLeft}
arrowRight={ArrowRight}
selected={selected}
onSelect={this.onSelect}
/>
<div>{this.props.items.map((item) => {
return <Item item={item}/>
})}
</div>
</div>
);
}
}
export default HorizantScroller;
Inside Item, you should access it like this
<div>{this.props.item}</div>
Because when rendering Item, you did <Item item={item}/>
I have a list like this:
<div className="doubleCol">
{this.state.symptoms.map(item => (
<ListItem key={item.ObjectID}>
<input type="checkbox" className="sympSelect" />
{item.name}
</ListItem>
))}
</div>
All the items rendered have checkbox and I want it to filter a different list elsewhere on the page based on which boxes are checked. To do that I need the checkboxes to change the state and pass the new state to a method which is supposed to filter and display only those items on the second list with id's associated to items on the first list.
From what I have read it shouldn't matter for this purpose if the checkboxes are controlled or uncontrolled.
class Home extends React.Component {
state = {
conditions: [],
symptoms: [],
selectedSymptom: []
}
componentDidMount() {
this.getConditionsMethod();
this.getSymptomsMethod();
}
getConditionsMethod = () => {
API.getConditions()
.then(data => {
console.log(data);
data.data.sort((a, b) => a.name.localeCompare(b.name))
this.setState({
conditions: data.data
})
})
.catch(err => console.log(err))
};
filterConditionsMethod = () => {
API.getConditions()
.then(data => {
console.log(data);
data.data.sort((a, b) => a.name.localeCompare(b.name));
this.setState({
selectedSymptom: data.data
})
})
.catch(err => console.log(err))
};
But I am kind of stuck on how to structure the onChange for when the box is checked and how to make that implement the filter.
Here is you solution you can add onChange event for checkbox and filter your records as selectedSymptoms and symptoms. Please check code is
import React, { Component } from "react";
class Home extends Component {
constructor(props) {
super(props);
this.state = {
conditions: [],
symptoms: [
{ ObjectID: 1, name: "xyz" },
{ ObjectID: 2, name: "pqr" }
],
selectedSymptom: [],
checked: ""
};
}
updateCheckBox = (event, item) => {
if (event.target.checked) {
let selectedList = this.state.selectedSymptom;
selectedList.push(item);
this.setState({
...this.state,
checked: this.state.checked == "checked" ? "" : "checked",
selectedSymptom: selectedList
});
} else {
const symptomss = this.state.selectedSymptom.filter(element => {
if (element.ObjectID != data.ObjectID) {
return item;
}
});
this.setState({
...this.state,
checked: "",
selectedSymptom: symptomss
});
}
};
render() {
return (
<div className="doubleCol">
{this.state.symptoms.map(item => (
<ListItem key={item.ObjectID}>
<input
type="checkbox"
className="sympSelect"
onChange={this.updateCheckBox(e, item)}
id="symptoms_id"
defaultChecked={this.state.checked}
/>
{item.name}
</ListItem>
))}
</div>
);
}
}
export default Home;
I have a list of buttons and I'm trying to toggle the classname when one is clicked. So that only when I click on a specific button is highlighted. I have a TagList component that looks like this:
const Tags = ({tags, onTagClick}) => {
return (
<div className="tags-container">
{ tags.map(tag => {
return (
<span
key={tag.name}
className="tag"
onClick={() => onTagClick(tag)}
>
{tag.name} | {tag.numberOfCourses}
</span>
)
})
}
</div>
)
}
And this is found in the parent component:
onTagClick = (tag) => {
this.filterCourses(tag)
}
render() {
const { tags, courses } = this.state
return (
<div>
<h1> Course Catalog Component</h1>
<Tags tags={tags} onTagClick={this.onTagClick} />
<Courses courses={courses} />
</div>
)
}
I know how I could toggle the class for a single button but I'm a little confused when it comes to a list of buttons. How can I toggle one specifically from a list of buttons? Am I going to need a seperate Tag component and add state to that one component?
EDIT:
This is what my state currently looks like:
constructor(props) {
super(props)
this.state = {
tags: this.sortedTags(),
courses: courses
}
}
And this is what filterCourses looks like:
filterCourses = (tag) => {
this.setState({
courses: courses.filter(course => course.tags.includes(tag.name))
})
}
To start, you would want to give each tag object you're working with a selected property. That will make it easier for you to toggle the class. During the rendering of that markup.
Here is the working sandbox: https://codesandbox.io/s/stupefied-cartwright-6zpxk
Tags.js
import React from "react";
const Tags = ({ tags, onTagClick }) => {
return (
<div className="tags-container">
{tags.map(tag => {
return (
<div
key={tag.name}
className={tag.selected ? "tag selected" : "tag"}
onClick={() => onTagClick(tag)}
>
{tag.name} | {tag.numberOfCourses}
</div>
);
})}
</div>
);
};
export default Tags;
Then in the Parent component, we simply toggle the selected prop (True/False) when the tag is clicked. That will update the tags-array and it gets passed back down to the Child-component which now has the new selected values.
Parent Component
import React from "react";
import ReactDOM from "react-dom";
import Tags from "./Tags";
import Courses from "./Courses";
import "./styles.css";
class App extends React.Component {
state = {
tags: [
{ id: 1, name: "math", numberOfCourses: 2, selected: false },
{ id: 2, name: "english", numberOfCourses: 2, selected: false },
{ id: 3, name: "engineering", numberOfCourses: 2, selected: false }
],
courses: [
{ name: "Math1a", tag: "math" },
{ name: "Math2a", tag: "math" },
{ name: "English100", tag: "english" },
{ name: "English200", tag: "english" },
{ name: "Engineering101", tag: "engineering" }
],
sortedCourses: []
};
onTagClick = tag => {
const tagsClone = JSON.parse(JSON.stringify(this.state.tags));
let foundIndex = tagsClone.findIndex(tagClone => tagClone.id == tag.id);
tagsClone[foundIndex].selected = !tagsClone[foundIndex].selected;
this.setState(
{
tags: tagsClone
},
() => this.filterCourses()
);
};
filterCourses = () => {
const { tags, courses } = this.state;
const selectedTags = tags.filter(tag => tag.selected).map(tag => tag.name);
const resortedCourses = courses.filter(course => {
return selectedTags.includes(course.tag);
});
this.setState({
sortedCourses: resortedCourses
});
};
render() {
const { tags, sortedCourses, courses } = this.state;
return (
<div>
<h1> Course Catalog Component</h1>
<Tags tags={tags} onTagClick={this.onTagClick} />
<Courses courses={!sortedCourses.length ? courses : sortedCourses} />
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);