Compare this.state to a function to apply style - javascript

I am using a function to filter values.
If I map over the function filteredMovies, it removes the genre buttons and movies which do not apply.
However, instead of removing them I want to a custom className. I want to apply a different style to <Filter1> & <Movies>. What is the correct way to do this?
I've put this inside a codesandbox (https://codesandbox.io/s/76ZjOyBvA)
Example 1:
Compare this.state.movies
{this.uniqueGenres1(this.state.movies).map(e => (
<Filter1
key={e.id}
/>
))}
To filteredMovies
{this.uniqueGenres1(filteredMovies).map(e => (
<Filter1
key={e.id}
/>
))}
For the values that do NOT appear in filteredMovies, apply a different className

Use the className attribute of React:
class Filter1 extends React.Component {
handleClick = () => {
this.props.onChange(this.props.genre.name);
};
render() {
const { genre, isActive } = this.props;
return (
<button onClick={this.handleClick} className={isActive? 'active' : 'inactive'}>
{genre.name}
{' '}
<strong>{isActive ? 'Active' : 'Inactive'}</strong>
</button>
);
}
}
Also, don't forget to change 'filteredMovies' to 'movies' in the second map:
<h3> Using filteredMovies function</h3>
{this.uniqueGenres1(movies).map(e => (
<Filter1
key={e.id}
genre={e}
isActive={!!selectedFilters[e.name]}
value={e.type}
onChange={this.handleChange}
/>
))}
Update:
To apply a css style to the <li>s rather than hiding them, change the relevant sections in your code to these:
class Result extends React.Component {
render() {
const { result, isActive } = this.props;
return (
<div>
<li className={isActive? 'active' : 'inactive'}>
{result.name} {' '}
({result.genres.map(x => x.name).join(', ')}){' '}
</li>
</div>
);
}
}
{movies.map(movie => {
var isActive = !!filteredMovies.find(filteredrMovie => filteredrMovie.name === movie.name);
return (
<Result key={movie.id} result={movie} isActive={isActive} />
)
})}
It basically uses the JS find function to know if a movie from the filtered lists exists in the entire one. Then it passes the result as a prop to the Result component.
Update2:
In order to style your buttons based on rather or not they exist in the results rather than if they active:
<h3>Using filteredResults function</h3>
{this.uniqueGenres(filteredResults).map(genre => {
const isExisting = !!this.uniqueGenres(allFilters).find(
genre1 => genre1 === genre,
);
return (
<Filter1
key={genre.id}
genre={genre}
isActive={!!selectedFilters[genre.name]}
isExisting={isExisting}
onChange={genre.handleChange}
/>
)})}
Replace 'allFilters' with the correct name of the variable that contains the complete list.
Update3:
It's working for me now after changing the code of Filter1 component, so it will resolve the className based on 'isExisting':
class Filter1 extends React.Component {
handleClick = () => {
this.props.onChange(this.props.genre);
};
render() {
const { genre, isActive, isExisting } = this.props;
return (
<button onClick={this.handleClick} className={isExisting ? 'existing' : 'notExisting'}>
{genre.name}
{' '}
<strong>{isActive ? 'Active' : 'Inactive'}</strong>
</button>
);
}
}

Related

Switching classes between items from the list in React

im doing this simple ecommerce site, and on the product page you have different attributes you can choose, like sizes, colors - represented by clickable divs, with data fetched from GraphQl and then generated to the DOM through map function.
return (
<div className="attribute-container">
<div className="attribute">{attribute.id.toUpperCase()}:</div>
<div className="attribute-buttons">
{attribute.items.map((item) => {
if (type === "Color") {
return (
<AttributeButton
key={item.id}
className="color-button"
style={{ backgroundColor: item.value }}
onClick={() => addAtribute({type: type, value: item.value })}/>
);
}
return (
<AttributeButton
key={item.id}
className="size-button"
size={item.value}
onClick={() => addAtribute({ type: type, value: item.value })}
/>
);
})}
</div>
</div>
);
Im importing external component, then I check if an attribute's type is Color (type color has different styling), then render depending on that type.
What I want to implement is that when i click on one attribute button, its style changes, BUT of course when i click on another one, choose different size or color for the item i want to buy, new button's style changes AND previously selected button goes back to it's default style. Doing the first step where buttons style changes onClick is simple, but i cant wrap my head around switching between them when choosing different attributes, so only one button at the time can appear clicked.
Here is code for AttributeButton:
class Button extends PureComponent {
constructor(props){
super(props);
this.state = {
selected: false,
}
}
render() {
return (
<div
className={ !this.state.selected ? this.props.className : "size-selected "+this.props.className}
style={this.props.style}
onClick={() => {this.props.onClick(); this.setState({selected: !this.state.selected}) }}
>
{this.props.size}
</div>
);
}
}
export default Button;
PS - i have to use class components for this one, it was not my choice.
You need to have the state selected outside of your <Button> component and use it as a prop instead. Something like:
handleSelect = (button) => {
const isSelected = this.state.selected === button;
this.setState({ selected: isSelected ? null : button });
};
render() {
return (
<>
<Button
isSelected={this.state.selected === "ColorButton"}
onClick={() => this.handleSelect("ColorButton")}
/>
<Button
isSelected={this.state.selected === "SizeButton"}
onClick={() => this.handleSelect("SizeButton")}
/>
</>
);
}

how to toggle between two list of arrays with one button react js

looking for advice on how to have one button that when clicked will switch the list of items to the alphabetized list of items and back when clicked again and so on. as of right now when i click the button it will show the alphabetized list but its just rendering on top of the original list already showing. not really sure on where to go from here
class MenuItems extends Component {
state = {
sortedItems: []
}
handleclick = (item) => {
this.props.deleteMenuItem(item.id);
}
menuSort = () => {
const ogList = [...this.props.menuItems]
let sortedList = ogList.sort((a, b) => a.name.localeCompare(b.name));
this.setState({sortedItems: sortedList})
};
render(){
return (
<div>
<button onClick={this.menuSort}>filter a to z</button>
{this.state.sortedItems.map((item) =>(
<li class="list" key={item.id}>
{item.name}
<br></br>
{item.body}
<br></br>
<img src={item.image}></img>
<br></br>
<button id={item.id} onClick={() => this.handleclick(item)}>delete </button>
</li>
))}
{this.props.menuItems.map((item) =>(
<li class="list" key={item.id}>
{item.name}
<br></br>
{item.body}
<br></br>
<img src={item.image}></img>
<br></br>
<button id={item.id} onClick={() => this.handleclick(item)}>delete </button>
</li>
))}
</div>
)
}
}
export default connect(null, {deleteMenuItem})(MenuItems)```
You correctly thought to keep the sorted version in the state. But you have to somehow instruct the component to render the original list or the sorted one.
So you could add a flag in the state to specify which one you want.
You could also set the sorted list in the componentDidMount lifecycle event, and updated it whenever the menuItems change through the componentDidUpdate lifecycle event.
So something like
state = {
sortedItems: [],
showSorted: false
};
toggleSort = () => {
this.setState(({ showSorted }) => ({
showSorted: !showSorted
}));
};
updateSortedItems() {
const sorted = [...this.props.menuItems].sort((a, b) =>
a.name.localeCompare(b.name)
);
this.setState({
sortedItems: sorted
});
}
componentDidMount() {
this.updateSortedItems();
}
componentDidUpdate(prevProps) {
if (this.props.menuItems !== prevProps.menuItems) {
this.updateSortedItems();
}
}
and in your render method
let itemsToShow = this.props.menuItems;
if (this.state.showSorted) {
itemsToShow = this.state.sortedItems;
}
and use the itemsToShow when you want to display them
{itemsToShow.map((item) => (
Full working example at https://codesandbox.io/s/elated-heyrovsky-m3jvv

Toggle font-awesome icon on click in react

I want to toggle font awesome icons on click. When the page is loaded i query the backend and find out whether a user is enrolled into a course or not, incase they are enrolled I show a tick icon, otherwise I show a coffee icon.
The end goal is have each individual icon change into the opposite when clicked. Currently when i click the icons, for example if i click a cup icon it not only changes into a tick but changes the rest of the cup icons into ticks too. How can I resolve this issue so that when clicked, only the clicked icon is affected?
Here is my code
Functional component
export const CourseCard = ({
video,
normaluser,
adminuser,
userauthenticated,
adminauthenticated,
handleUnroll,
handleEnroll,
enrolled,
unrolled
}) => (
<Grid item xs={6} md={4} lg={3}>
{(video.enrolled_students.includes(normaluser) &&
userauthenticated) ||
(video.enrolled_students.includes(adminuser) &&
adminauthenticated) ? (
<div className="enrol__button">
<div>
<a href="#" onClick={() => handleUnroll(video.slug)}>
<FontAwesomeIcon
icon={enrolled ? faCheckSquare : faCoffee}
/>
</a>
</div>
</div>
) : (!video.enrolled_students.includes(normaluser) &&
userauthenticated) ||
(!video.enrolled_students.includes(adminuser) &&
adminauthenticated) ? (
<div>
<a href="#" onClick={() => handleEnroll(video.slug)}>
<FontAwesomeIcon
icon={unrolled ? faCoffee : faCheckSquare}
/>
</a>
</div>
) : (
""
)}
</Grid>
Container
export class AllCourses extends React.Component {
constructor(props) {
super(props);
this.user = details(AUTHENTICATED);
this.admin = AdminDetails(AUTHENTICATED);
const token = localStorage.getItem("token");
let normaldetail = details(token);
this.normaluser = normaldetail.user_id;
let admindetail = AdminDetails(token);
this.adminuser = admindetail.user_id;
this.state = {
enrolled: true,
unrolled: true
};
}
handleEnroll = slug => {
this.props.dispatch(Enroll(slug));
this.setState({unrolled: !this.state.unrolled});
}
handleUnroll = slug => {
this.props.dispatch(Enroll(slug));
this.setState({enrolled: !this.state.enrolled});
}
render() {
const userauthenticated = this.user;
const adminauthenticated = this.admin;
const adminuser = this.adminuser;
const normaluser = this.normaluser;
const { allCourses } = this.props;
const {search, enrolled, unrolled} = this.state;
return (
<div className="container">
<Grid
container
spacing={3}
className="courses__row courses__row__medium"
>
{allCourses.map(video => (
<CourseCard
key={video.slug}
video={video}
enrolled={enrolled}
unrolled={unrolled}
handleEnroll={this.handleEnroll}
handleUnroll={this.handleUnroll}
normaluser={normaluser}
adminuser={adminuser}
userauthenticated={userauthenticated}
adminauthenticated={adminauthenticated}
/>
))}
;
</Grid>
</div>
);
}
You can instead a conditional on a boolean try to compare current card key to checked card key
<FontAwesomeIcon icon={this.props.checkedCard == this.props.key ? faCoffee : faCheckSquare} />
and in your container :
handleEnroll = slug => {
this.props.dispatch(Enroll(slug));
this.setState({checked: this.props.key});
}
and :
<CourseCard
key={video.slug}
video={video}
checkedCard={this.state.checkedCard}
It's because handleEnroll and handleUnroll set state that is shared across all the courses. From what you described it sounds like you want the state to actually be per course.
So you should alter AllCourses slightly
handleEnroll = slug => {
// This should cause some change to the relevant course in allCourses
this.props.dispatch(Enroll(slug));
}
handleUnroll = slug => {
// This should cause some change to the relevant course in allCourses
this.props.dispatch(Enroll(slug));
}
// Delete this
this.state = {
enrolled: true,
unrolled: true
};
And then change the CourseCard mapping to use the properties from video rather than the ,now eliminated, AllCourses state.
{allCourses.map(video => (
<CourseCard
key={video.slug}
video={video}
enrolled={video.enrolled}
unrolled={video.unrolled}
handleEnroll={this.handleEnroll}
handleUnroll={this.handleUnroll}
normaluser={normaluser}
adminuser={adminuser}
userauthenticated={userauthenticated}
adminauthenticated={adminauthenticated}
/>
))}
Since you're checking in the Grid component whether the current user is enrolled or not (by seeing if that video.enrolled_students array includes the current user), then those enrolled and unrolled flags don't seem necessary anymore.
So in Grid you should be able to change the first <FontAwesomeIcon /> call to just:
<FontAwesomeIcon icon='faCheckSquare' />
and the second one to
<FontAwesomeIcon icon='faCoffee' />
Also, you have a typo in AllCourses where you're calling this.props.dispatch(Enroll(slug)); in both handleEnroll and handleUnroll where it should most probably be Unroll(slug) in the second one.

How to show tooltips for react-select?

I need to show tooltips for react-select container (not for a separate options) using react-tooltip library.
I made my own SelectContainer component based on the original one and added there data-tip and data-for HTML attributes. Tooltip shows up but when I change selected value it disappears and is not displayed any more.
Here is my code:
const getSelectContainer = options => props => {
return (
<components.SelectContainer
{...props}
innerProps={{
...props.innerProps, ...{
'data-tip': options.tooltipText,
'data-for': options.tooltipId,
}
}}
/>
)
}
const CustomSelect = (props) => {
const tooltipId='tooltip_id'
const tooltipText='tooltip'
const selectedOption = colourOptions.filter(option => option.value === props.value)
return (
<div>
<ReactTooltip effect="solid" html={true} place="bottom" id={tooltipId} />
<Select
defaultValue={colourOptions[4]}
value={selectedOption}
options={colourOptions}
classNamePrefix="react-select"
onChange={item => props.onChange(item.value)}
className="my-select"
components={{
SelectContainer: getSelectContainer({
tooltipText:tooltipText,
tooltipId:tooltipId
})
}}
/>
</div>
)
}
class Page extends Component {
constructor (props) {
super(props)
this.state = {
selectValue: 'red'
}
}
render () {
const onChange = (value)=> {
this.setState({selectValue: value})
//alert(value)
}
return (
<CustomSelect
value={this.state.selectValue}
onChange={onChange}>
</CustomSelect>
)
}
}
See full example here:
If I wrap Select with another <div> and assign tooltip HTML attributes to it everything works correctly but I don't want to add one more DOM element just for that.
What can I do to show tooltips after changing selection?
Try rebuilding the react-tooltip when the state changes.
useEffect(() => {
ReactTooltip.rebuild();
}, [state]);

render displaySIngleElement component onClick react

I am pretty new to react and I have been stuck in a problem for quite a good time.
I have a component DisplayList that iterates through an array of objects and displays them in a list form. Each object becomes a button. I also have another component to render the single view of each item on the list once the item is clicked. My problem is that I get to render the single view of all my items at once INSIDE my displayList component. All I want is to be able to click on the list item and render another component with ONLY info about the item I clicked on and passing my "project" as the props to it. what should I do? What is my error?
My DisplayList component (the part that matters for this problem):
export default class DisplayList extends Component {
constructor() {
super();
this.state = {
displaySingle: false
};
}
handleClick = () => {
this.setState({
displaySingle: true
})
}
render() {
if (this.props.projects && this.props.projects.length > 0) {
return (
<List component="nav">
{this.props.projects.map(project => (
<div className="all-content-wrapper" key={project.id}>
<ListItem button value={project} onClick={this.handleClick}>
{this.state.displaySingle ?
<DisplaySingleItem project={project} /> :
null
}
<ListItemICon>
<img
className="single-item-img-in-list-view"
src={project.img}
/>
</ListItemICon>
You are just a hint away from doing it the right way:
Change the condition in your onClick() as:
onClick={()=>this.handleClick(project.id)}
{ this.state.displayProject_id === project.id ?
<DisplaySingleItem project={project} /> :
null
}
Now define handleClick() as:
handleClick = (project_id) => {
this.setState({
displayProject_id: project_id
})
}
Don't forget to define the initial state in the constructor:
this.state = {
displayProject_id:null
};
<div className="all-content-wrapper" key={project.id}>
<ListItem button value={project} onClick={()=>this.handleClick(project)}>
{this.state.displayProject && this.state.displayProject.id==project.id ?
<DisplaySingleItem project={project} /> :
null
}
<ListItemICon>
<img
className="single-item-img-in-list-view"
src={project.img}
/>
</ListItemICon>
</ListItem>
</div>
change your JSX like the above so you pass the current project to handleClick and change handleClick like the following.
handleClick = (project) => {
this.setState({
displayProject : project
})
}
It should now display the <DisplaySingleItem/> for the clicked project.
For you to be able to show only the project that was selected it is important that you have a reference to it. Right now your handleClick() function does not accept and parameters or data that you can identify the project that was selected.
My solution for you is to pass the project as a parameter to handleClick(project). So your code should look like.
export default class DisplayList extends Component {
constructor() {
super();
this.state = {
displaySingle: false
};
}
handleClick = (project) => {
this.setState({
selectedProject: project, // <- use this state to show your popup or
// whatever view you're using
displaySingle: true
})
}
render() {
if (this.props.projects && this.props.projects.length > 0) {
return (
<List component="nav">
{this.props.projects.map(project => (
<div className="all-content-wrapper" key={project.id}>
<ListItem button value={project} onClick={() => this.handleClick(project)}>
{this.state.displaySingle ?
<DisplaySingleItem project={project} /> :
null
}
<ListItemICon>
<img
className="single-item-img-in-list-view"
src={project.img}
/>
</ListItemICon>
)
}

Categories

Resources