Switching classes between items from the list in React - javascript

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

Related

Material-UI Disabled attribute not working

I'm trying to disable the edit button once i click on complete but it is not working. I have passed in the state in disabled attribute but it seems not doing anything, don't know maybe because of setState's asynchronous nature. I passed callback while calling setState method and it seems logging data randomly, Can someone suggest what should be done ?
class App extends Component {
state = {
buttons: {
id: "test"
}
};
handleCheckBox = id => {
let buttons = Object.assign({}, this.state.buttons);
buttons.id = !this.state.buttons[id]
this.setState({buttons}, ()=>console.log(this.state.buttons));
}
render() {
return (
<div>
{todos.map(todo => (
<List key={todo.id}>
<ListItem
role={undefined}
dense
button
>
<Checkbox
onClick={()=>this.handleCheckBox(todo.id)}
checked={todo.complete}
tabIndex={-1}
disableRipple
/>
<ListItemText primary={todo.text} />
<ListItemSecondaryAction>
<Button mini color="secondary" variant="fab" disabled={this.state.buttons[todo.id]}>
<Icon>edit_icon</Icon>
</Button>
ListItemSecondaryAction>
</ListItem>
</List>
))}
</div>
);
}
}
Instead of using id to change the state use index of Array to update the state
Create an array in Component state which tracks the disabled attribute of each buttons
state = {
buttons: Array(todos.length).fill(false),
};
In componentDidMount initialise the array according to todos
componentDidMount(){
const buttons=this.state.buttons.slice();
for(var i=0;i<buttons.length;i++)
buttons[i]=todos[i].complete;
this.setState({buttons:buttons})
}
Now use the value in buttons state for disabled attribute of button based on the index of the component being rendered.
<Button mini color="secondary" variant="fab"
disabled={buttons[todos.indexOf(todo)]}>
Whenever CheckBox is clicked pass the index to the handleChange function and update the value corresponding to the index value
<Checkbox
onClick={() =>this.handleCheckBox(todos.indexOf(todo))}
checked={buttons[todos.indexOf(todo)]}{...other}
/>
handleCheckBox = index => {
const buttons=this.state.buttons.slice();
buttons[index] = !buttons[index];
this.setState({
buttons:buttons
})
}

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

Dynamically change styled component based on state AND index

So I have a list that was returned from an API request (not important)
lets call it list = [1,2,3,4,5,6,7];
Now, inside render(), I have something like the following
render(){
<Wrapper>
{list.map((i) => {
return (<Button id = {i} onClick = {this.customFunc.bind(this, i)} />)
}
</Wrapper>
}
Now, I have another list, lets call it list_check = [false...] (for all 7 elements listed above)
Assume that customFunc changes the respective button id in list_check from false to true.
e.g. if I clicked button 1 (id = 1) then list_check becomes [false, true, false...]
** Now my problem is: I have 2 styled components, Button and Button_Selected,
how can i dynamically change between the 2 styled components so that if that unique button is clicked (change list_check[index] = true), the element becomes Button_Selected instead of Button (The entire list is initalized as Button since all elements are false)
Just to make it clear:
Both arrays are located in this.state and by 2 styled components I mean there exists
const Button = styled.div`
//styling here
`;
and
const Button_Selected = Button.extend`
//Small styling change to differentiate between selected and not selected
`;
edit: all answers are great! too bad I can only select one :(
You could just save the component to another variable.
this.state.list_check.map((item, i) => {
const NecessaryButton = item ? SelectedButton : Button;
return <NecessaryButton onClick={() => this.makeSelected(i)}>Normal Button</NecessaryButton>
})
You can see a live example here.
Although you can return 2 buttons based on conditional rendering .You can also pass props to your styled Button so that based on props you can change your styles.
render(){
<Wrapper>
{list.map((i) => {
return (<Button id = {i} isSelected={this.state.list_check[i]} onClick = {this.customFunc.bind(this, i)} />)
}
</Wrapper>
}
And in your styled Button:
const Button = styled.div`
styleName: ${props => props.isSelected ? 'styling_if_Selected' : 'styling_if_not_selected'};
`;
The easiest approach would be to have a single Button component and handle in the state if it was selected. Depending on this state you could switch classes.
Example:
class Button extends React.Component {
state = {
isSelected: false
};
handleClick() {
//Here we will set the state change and call the onClick function passed by props
this.setState({isSelected: !this.state.isSelected}, () => { this.props.onClick(); });
}
render() {
return (
<button
class={this.state.isButtonSelected ? "classBtnSelected" : "classBtnDefault"}
onClick={this.handleClick}
/>
);
}
}
Still, if you want to switch components you can use state to control if it was selected and do a conditional rendering. Example:
render(){
<Wrapper>
{list.map((i) => {
return (this.state.isSelected
? <Button id={i} onClick = {this.customFunc.bind(this, i)} />
: <Button_Selected id={i} onClick = {this.customFunc.bind(this, i)} />)
}
</Wrapper>
}

Toggle component in react on button click

I have 4 components. I only want to render one at a time. I have buttons in my nav, when i click one it should render that component and then hide the other 3 (i.e. set them to null)
This is easy with 2 components. I just have a toggle function like so:
toggle() {
this.setState(prevState => ({
showTable: !prevState.showTable
}));
}
I have tried to adapt this for now where I have this:
showComponent(component) {
this.setState(prevState => ({
[component]: !prevState.component
}));
}
This currently shows the component when i click the corresponding button. However, it wont hide the component once the same button is clicked again.
I have all my buttons calling this method like so:
<button onClick={() => this.showComponent('AddPlayer')}>Add</button>
<button onClick={() => this.showComponent('ShowPlayers')}>Players</button>
<button onClick={() => this.showComponent()}>Table</button>
<button onClick={() => this.showComponent()}>Matches</button>
any ideas?
EDIT:
{this.state.AddPlayer ?
<div className="add-container">
<AddPlayer />
</div>
:
null
}
{this.state.ShowPlayers ?
<div className="players-container">
<Players />
</div>
:
null
}
You can do this in multiple ways,
One way is, create a const with all state values and components like
const components = {
"AddPlayer": <AddPlayer />,
"ShowPlayers": <Players />,
"Something1": <Something1 />,
"Something2": <Something2 />
}
set value to state like
showComponent(componentName) {
this.setState({displayedTable: componentName});
}
and inside render simply
render(){
return(
<div>
{components[this.state.displayedTable]}
</div>
)
}
Using Switch case
renderComponent(){
switch(this.state.displayedTable) {
case "AddPlayer":
return <AddPlayer />
case "ShowPlayers":
return <Players />
}
}
render () {
return (
<div>
{ this.renderComponent() }
</div>
)
}

Compare this.state to a function to apply style

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

Categories

Resources