I am not able to remove the list item when i click dismiss? - javascript

There are no errors in the code, what is the problem?
I am not able to dismiss the list items and change the state for my component!
What should i do to make my dismiss work and is there a better way as above to do so ??
Here is the code :
import React, { Component } from 'react';
import './App.css';
const list = [
{
title: 'React',
url: 'https://facebook.github.io/react/',
author: 'Jordan Walke',
num_comments: 3,
points: 4,
objectID: 0,
},
{
title: 'facebook github',
url: 'https://facebook.github.io/',
author: 'Janardhan',
num_comments: 3,
points: 6,
objectID: 1,
},
]
class App extends Component {
constructor(props) {
super(props);
this.state = {
list
}
this.onDismiss = this.onDismiss.bind(this);
}
onDismiss(id) {
const isNotID = item => item.objectID !== id;
const updatedList = this.state.list.filter(isNotID)
this.setState({ list: updatedList })
console.log("dismissed??")
}
render() {
return (
<div className="App">
{list.map(item =>
<div key={item.objectID}>
<span>
<a href={item.url}>{item.title}</a>
</span>
<span>{item.author}</span>
<span>{item.points}</span>
<span>{item.num_comments}</span>
<button onClick={() => this.onDismiss(item.objectID)} >Dismiss</button>
</div>
)}
</div>
);
}
}
export default App;

the problem comes from here :
const isNotID = item => item.objectId !== id;
it should be objectID not objectId
Edit
And in the render method, it should be this.state.list.map

Related

react-sortable-tree - How to get the search API working

According to the API doc there needs to be a searchQuery prop which i've mentioned in my code but the search doesn't seem to be working
API doc doesn't explain how to implement it and the examples available online don't seem to be working on code sandbox.
The only article available which seems to explain search has incorrect code (duplicate props): https://frugalisminds.com/how-to-create-react-sortable-tree/
API Doc: https://www.npmjs.com/package/react-sortable-tree
Below is the code:
import React, { Component } from "react";
import SortableTree from "react-sortable-tree";
import "react-sortable-tree/style.css";
export default class Tree extends Component {
constructor(props) {
super(props);
this.state = {
treeData: [
{ title: "Chicken", children: [{ title: "Egg" }] },
{ title: "Fish", children: [{ title: "fingerline" }] },
],
searchString: ""
};
}
handleSearchOnChange = e => {
this.setState({
searchString: e.target.value,
});
};
render() {
return (
<div style={{ height: 400 }}>
<input
type="search"
onChange={this.handleSearchOnChange}
className="form-control"
/>
<SortableTree
searchQuery={this.state.searchString}
treeData={this.state.treeData}
onChange={treeData => this.setState([...treeData])}
isVirtualized={false}
/>
</div>
);
}
}
missing a searchFocusOffset to highlight the found item and a searchMethod which can be custom defined inside render method as follows:
import React, { Component } from "react";
import SortableTree from "react-sortable-tree";
import "react-sortable-tree/style.css"; // This only needs to be imported once in your app
export default class Tree extends Component {
constructor(props) {
super(props);
this.state = {
treeData: [
{ title: "Chicken", children: [{ title: "Egg" }] },
{ title: "Fish", children: [{ title: "fingerline" }] },
],
searchString: ""
};
}
render() {
// Case insensitive search of `node.title`
const customSearchMethod = ({ node, searchQuery }) =>
searchQuery &&
node.title.toLowerCase().indexOf(searchQuery.toLowerCase()) > -1;
return (
<div style={{ height: 400 }}>
<input
type="search"
onChange={event => this.setState({ searchString: event.target.value })}
className="form-control"
/>
<SortableTree
searchMethod={customSearchMethod}
searchQuery={this.state.searchString}
searchFocusOffset={0}
treeData={this.state.treeData}
onChange={treeData => this.setState([...treeData])}
isVirtualized={false}
/>
</div>
);
}
}

How can i solve the problem from the book road to react at page 78 ? I keep getting undefine for one of the methods in the table component

import React, { Component } from 'react';
import './App.css';
const list = [
{
title: 'React',
url: 'https://facebook.github.io/react/',
author: 'Jordan Walke',
num_comments: 3,
points: 4,
objectID: 0,
},
{
title: 'Redux',
url: 'https://github.com/reactjs/redux',
author: 'Dan Abramov, Andrew Clark',
num_comments: 2,
points: 5,
objectID: 1,
},
];
class App extends Component {
state = {
list,
text: 'abc',
searchTerm: ''
}
onDisMiss = (id) => {
const updateList = this.state.list.filter((item) => item.objectID != id)
return () => this.setState({ list: updateList })
}
onSearchChange = (event) => {
this.setState({ searchTerm: event.target.value })
}
isSearched = (searchTerm) => {
return (item) => item.title.toLowerCase().includes(searchTerm.toLowerCase())
}
render() {
const { searchTerm, list } = this.state
return (
<div>
<Search value={searchTerm}
onChange={this.onSearchChange}>Search</Search>
<Table list={list} pattern={searchTerm} onDissMiss={this.onDisMiss} />
</div>
);
}
}
class Search extends Component {
render() {
const { value, onChange, children } = this.props
return (
<div>
<form>
{children}<input type="text" onChange={onChange} value={value} />
</form>
</div>
);
}
}
class Table extends Component {
render() {
const { list, pattern, onDisMiss } = this.props
return (
<div>
{list.filter(isSearched(pattern)).map(item =>
<div key={item.objectID}>
<span><a href={item.url}>{item.title}</a></span>
<span>{item.author}</span>
<span>{item.num_comments}</span>
<span>{item.points}</span>
<span>
<button onClick={onDisMiss(item.objectID)} type="button">Dismiss</button>
</span>
</div>)
}
</div>
);
}
}
export default App;
Road to react Book The Table component related.I get undefined for the isSearched method. how can I fix it so it works correctly its from the book road to react it seems like the book has a few error which I have problems solving because am just learning react. can you help with the solution and why this problem is actually happening
You should put the isSearched method inside the Table class and not the App class

in React How to Update the State of an element in a mapped array of elements?

I have two data objects and 3 hierarchical components below, or in sandbox here. The data contain a list of questions, each rendered with input box right below it that allow multiple replies. However, I don't know how to proper update the state of the correct question after typing in a reply to a particular question.
index.js
import React from 'react';
import { render } from 'react-dom';
import QA from './qa';
//parent of qa.js
const questions = [
{id : 1,
question: "where is the origin of chihuahua?"},
{id : 2,
question: "when does the great migration happen in Africa?"}
]
const answers = [
{
id : 1,
id_question : 1,
answer: "Mexico"
},
{
id : 2,
id_question : 1,
answer: "Argentina"
},
{
id : 3,
id_question : 2,
answer: "Apr"
},
{
id : 4,
id_question : 2,
answer: "May"}
]
export default class App extends React.Component {
state = {
q : questions,
a : answers
}
handleSubmit = (val, index) => {
alert('index',index)
this.setState({
...this.state,
a: [...this.state.a, {id_question: index, answer: val}]
});
}
render() {
console.log(this.state)
return (
questions.map((q, index) =>
<QA
key={index}
question={q.question}
onSubmit={this.handleSubmit}
/>
)
)
}
}
render(<App />, document.getElementById('root'));
qa.js
import React from 'react';
import Answer from './answer';
import "./style.css"
//parent of answer.js
export default class QA extends React.Component {
constructor(props) {
super(props)
this.state = {
text: ""
}
}
render() {
const { question } = this.props
const { text } = this.state
return (
<div class='qa-block'>
<div>Question: {question}</div>
<Answer onSubmit={this.props.onSubmit}/>
</div>
)
}
}
and answer.js
import React from 'react';
const styles = {
backgroundColor: 'lightgray',
};
export default class Answer extends React.Component {
constructor(props) {
super(props)
this.state = {
text: ""
}
}
render() {
const { text } = this.state
return (
<div style={styles}>
<h4>Answers</h4>
<input type="text"
value={text}
onInput={(e) => this.setState({ text: e.target.value })} />
<button onClick={() => this.props.onSubmit(this.state.text)}>Send to the parent</button>
</div>
)
}
}
A few newbie questions:
where do I call index such that setState append to state.answer that right question id and increment answer id by 1?
should I have nested answers as a property of question instead?
Thanks for any help!
You can simply pass questionNo as props to qa & ans components, then retrive through the callback like following:
in index.js
render() {
console.log(this.state)
return (
questions.map((q, index) =>
<QA
questionNo={index}
question={q.question}
onSubmit={this.handleSubmit}
/>
)
)
}
in qa.js
render() {
const { question, questionNo } = this.props
const { text } = this.state
return (
<div class='qa-block'>
<div>Question: {question}</div>
<Answer questionNo={questionNo} onSubmit={this.props.onSubmit}/>
</div>
)
}
in answer.js
render() {
const { text } = this.state
return (
<div style={styles}>
<h4>Answers</h4>
<input type="text"
value={text}
onInput={(e) => this.setState({ text: e.target.value })} />
<button onClick={() => this.props.onSubmit(this.state.text, this.props.questionNo)}>Send to the parent</button>
</div>
)
}
after this you will get index of clicked item in index.js
So to identify the question you need to pass the id_question to the submit button, so if you have the parameter then on the callback you will be able to get it.
once you get you can do a find on the answers array of objects and update the userTyped answer.
handleSubmit = (val, text) => {
const typedAnswer = {...this.state.a.find(ans => ans.id_question === val), userTypedAnswer: text};
this.setState({
...this.state,
a: [...this.state.a, typedAnswer]
});
}
code
index.js
import React from 'react';
import { render } from 'react-dom';
import QA from './qa';
//parent of qa.js
const questions = [
{id: 1,
question: "where is the origin of chihuahua?"},
{id: 2,
question: "when does the great migration happen in africa?"}
]
const answers = [
{id_question: 1,
answer: "Mexico"},
{id_question: 1,
answer: "Argentina"},
{id_question: 2,
answer: "Apr"},
{id_question: 2,
answer: "May"}
]
export default class App extends React.Component {
state = {
q : questions,
a : answers
}
handleSubmit = (val, text) => {
const typedAnswer = {...this.state.a.find(ans => ans.id_question === val), userTypedAnswer: text};
this.setState({
...this.state,
a: [...this.state.a, typedAnswer]
});
}
render() {
return (
<>{
questions.map((q, index) =>
<QA
key={index}
question={q}
onSubmit={this.handleSubmit}
/>
)
}
<p>User Typed Answers and questions after submit</p>
{
this.state.a.map(ans => (
ans.userTypedAnswer && <div>
<span>{ans.id_question}</span>: <span>{ans.userTypedAnswer}</span>
</div>
))
}
</>
)
}
}
render(<App />, document.getElementById('root'));
// answer.js
import React from 'react';
const styles = {
backgroundColor: 'lightgray',
};
export default class Answer extends React.Component {
constructor(props) {
super(props)
this.state = {
text: ""
}
}
render() {
const { text } = this.state
const {onSubmit, qid} = this.props
return (
<div style={styles}>
<h4>Answers</h4>
<input type="text"
value={text}
onInput={(e) => this.setState({ text: e.target.value })} />
<button onClick={() => onSubmit(qid, this.state.text)}>Send to the parent</button>
</div>
)
}
}
qa.js
import React from 'react';
import Answer from './answer';
import "./style.css"
//parent of answer.js
export default class QA extends React.Component {
constructor(props) {
super(props)
this.state = {
text: ""
}
}
render() {
const { question: {question, id}, onSubmit } = this.props
const { text } = this.state
return (
<div class='qa-block'>
<div>Question: {question}</div>
<Answer onSubmit={onSubmit} qid={id}/>
</div>
)
}
}
Working example

Filtering todos assigned to the user (hidden and displayed)

I have an array of users andtodos. There are objects in the todos array and todos and user ids in them. I display users and their todos. When I click on a user, it hides his todos.
Problem: How to set up filtering so that clicking the user again shows his todos. A disabled user obtains a class with the opacity property.
Intended effect:
I click the user (id 1)
Hide todos assigned to this user (only todos for the user (id2) and user (id3) are visible
Click the user (id2)
Hide the todos assigned to this user (only todos for a user (id3) are visible)
Click user (id1) again
Displays hidden todos for this user (user todos are visible (id1 and id3)
Demo here: https://stackblitz.com/edit/react-s7aags
class App extends React.Component {
constructor() {
super();
this.state = {
users: [
{
id: 1,
name: 'Martin'
}, {
id: 3,
name: 'Gregor'
}, {
id: 2,
name: 'Paul'
}
],
todos: [
{
user_id: 3,
todos: ['swim', 'feed']
}, {
user_id: 1,
todos: ['sleep', 'read']
}, {
user_id: 2,
todos: ['drinking', 'dancing']
}
],
hideTodosUserId: ''
};
}
filterTodos = (userId) => {
console.log(userId);
const hideTodos = this.state.todos.filter(item => item.user_id !== userId);
console.log(hideTodos);
this.setState({todos: hideTodos, hideTodosUserId: userId})
}
render() {
return (<div>
<ul>
{
this.state.users.map(user => {
return <li key={user.id} onClick={() => this.filterTodos(user.id)} className={this.state.hideTodosUserId === user.id
? 'hideTodos'
: ''}>{user.name}</li>
})
}
</ul>
<ul>
{
this.state.todos.map(items => {
return <li key={items.id}>{items.todos.map(todo => <li key={todo.id}>{todo}</li>)}</li>
})
}
</ul>
</div>);
}
}
ReactDOM.render(<App/>, document.getElementById('root'));
.hideTodos {
opacity: 0.5;
}
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root"></div>
The problem is that you're replacing the original list of TODOs with the filtered one, making imposible to get back the TODOS from the selected user. Try to filter them in render and saving the selected user_id like this:
import React, { Component } from "react";
import { render } from "react-dom";
import Hello from "./Hello";
import "./style.css";
class App extends Component {
constructor() {
super();
this.state = {
users: [
{ id: 1, name: "Martin" },
{ id: 3, name: "Gregor" },
{ id: 2, name: "Paul" }
],
todos: [
{ user_id: 3, todos: ["swim", "feed"] },
{ user_id: 1, todos: ["sleep", "read"] },
{ user_id: 2, todos: ["drinking", "dancing"] }
],
selectedUserIds: []
};
}
userIdHandler = userId => {
this.setState(prevState => {
// Get the current index of the selected user id, if it doesn't exist it'll be -1
const userIdIndex = prevState.selectedUserIds.indexOf(userId);
const newSelectedUserIds = [...prevState.selectedUserIds];
if (userIdIndex === -1) {
// Add the selected id
newSelectedUserIds.push(userId);
} else {
// Remove the selected id using the previously found index
newSelectedUserIds.splice(userIdIndex, 1);
}
return {
selectedUserIds: newSelectedUserIds
};
});
};
render() {
return (
<div>
<ul>
{this.state.users.map(user => {
return (
<li
key={user.id}
onClick={() => this.userIdHandler(user.id)}
className={
this.state.selectedUserIds.includes(user.id)
? "hideTodos"
: ""
}
>
{user.name}
</li>
);
})}
</ul>
<ul>
{this.state.todos
.filter(
todos => !this.state.selectedUserIds.includes(todos.user_id)
)
.map(items => {
return (
<li key={items.id}>
{items.todos.map(todo => (
<li key={todo.id}>{todo}</li>
))}
</li>
);
})}
</ul>
</div>
);
}
}
render(<App />, document.getElementById("root"));
As some answers already stated, your state doesn't allow to have the expected behavior.
I don't know if it's an option to you, but I modified your state, from that point on it is much easier. It could have been done in many different way (I don't know the state you want) but here is an example:
import React, { Component } from 'react';
import { render } from 'react-dom';
import Hello from './Hello';
import './style.css';
class App extends Component {
constructor() {
super();
this.state = {
users: [{id:1, name: 'Martin' }, {id:3, name: 'Gregor' }, {id:2, name: 'Paul' }],
showingTodos: [{user_id:3, todos:['swim', 'feed']}, {user_id:1, todos:['sleep', 'read']}, {user_id:2, todos:['drinking', 'dancing']} ],
hidenTodos: [],
hiddenUserIds: []
};
}
filterTodos = (userId) => {
console.log(userId);
const isHidden = this.state.hiddenUserIds.find(id => id === userId);
if(isHidden) { // user was hiden
const toBeShownTodos = this.state.hidenTodos.filter(item => item.user_id === userId);
this.setState({
users: this.state.users,
hiddenUserIds: this.state.hiddenUserIds.filter(stateUserId => stateUserId !== userId),
showingTodos: this.state.showingTodos.concat(toBeShownTodos),
hidenTodos: this.state.hidenTodos.filter(item => item.user_id !== userId)
})
} else { // user was shown
const toBeHidenTodos = this.state.showingTodos.filter(item => item.user_id === userId);
this.setState({
users: this.state.users,
hiddenUserIds: this.state.hiddenUserIds.concat(userId),
showingTodos: this.state.showingTodos.filter(item => item.user_id !== userId),
hidenTodos: this.state.hidenTodos.concat(toBeHidenTodos)
})
}
}
render() {
return (
<div>
<ul>
{this.state.users.map(user => {
return <li key={user.id} onClick={() => this.filterTodos(user.id)} className={this.state.hiddenUserIds.find(id => id === user.id) ? 'hideTodos' : '' }>{user.name}</li>
})}
</ul>
<ul>
{this.state.showingTodos.map(items => {
return <li key={items.id}>{items.todos.map(todo => <li key={todo.id}>{todo}</li>)}</li>
})}
</ul>
</div>
);
}
}
render(<App />, document.getElementById('root'));
You anyway need to keep track of hide/show status of a user. That is the only info you need to mutate.
Change type of hideTodosUserId to array of ids. On click, toggle the inclusion of selected user id in this array, i.e. add to array if absent, remove from array if present.
Use the hideTodosUserId array to filter out hidden user from complete list.
import React, { Component } from 'react';
import { render } from 'react-dom';
import Hello from './Hello';
import './style.css';
class App extends Component {
constructor() {
super();
this.state = {
users: [{id:1, name: 'Martin'}, {id:3, name: 'Gregor'}, {id:2, name: 'Paul'}],
todos: [{user_id:3, todos:['swim', 'feed']}, {user_id:1, todos:['sleep', 'read']}, {user_id:2, todos:['drinking', 'dancing']} ],
hideTodosUserId: []
};
}
filterTodos = (userId) => {
this.setState(({hideTodosUserId}) => {
hideTodosUserId = [...hideTodosUserId]
const index = hideTodosUserId.indexOf(userId)
if(index >= 0) {
hideTodosUserId.splice(index,1)
} else {
hideTodosUserId.push(userId)
}
return {hideTodosUserId}
})
}
render() {
return (
<div>
<ul>
{this.state.users.map(user => {
return <li key={user.id} onClick={() => this.filterTodos(user.id)} className={this.state.hideTodosUserId.includes(user.id) ? 'hideTodos' : '' }>{user.name}</li>
})}
</ul>
<ul>
{this.state.todos
.filter(ui => !this.state.hideTodosUserId.includes(ui.user_id))
.map(items => {
return <li key={items.id}>{items.todos.map(todo => <li key={todo.id}>{todo}</li>)}</li>
})}
</ul>
</div>
);
}
}
render(<App />, document.getElementById('root'));
https://stackblitz.com/edit/react-ruflba?file=index.js
Here is my solution. It removes some useless states and uses React.PureComponet for better performance.
import React, { Component } from "react";
import { render } from "react-dom";
import Hello from "./Hello";
import "./style.css";
const userList = [
{ id: 1, name: "Martin" },
{ id: 3, name: "Gregor" },
{ id: 2, name: "Paul" }
];
const todoList = [
{ user_id: 3, todos: ["swim", "feed"] },
{ user_id: 1, todos: ["sleep", "read"] },
{ user_id: 2, todos: ["drinking", "dancing"] }
];
class App extends React.PureComponent {
constructor() {
super();
const users = userList.map(user => ({
...user,
selected: true
}));
this.state = {
users,
todos: todoList,
};
}
filterTodos = userId => {
const { users } = this.state;
users.forEach(user => {
if (user.id === userId) {
user.selected = !user.selected;
}
});
this.setState({
users: [...users],
});
};
render() {
const { todos, users } = this.state;
const validTodos = todos.filter(item => users.some(user => user.id === item.user_id && user.selected));
return (
<div>
<ul>
{users.map(user => (
<li
key={user.id}
onClick={() => this.filterTodos(user.id)}
className={user.selected ? "" : "hideTodos"}
>
{user.name}
</li>
))}
</ul>
<ul>
{validTodos.map(item => (
<li key={item.id}>
{item.todos.map(todo => (
<li key={todo.id}>{todo}</li>
))}
</li>
))}
</ul>
</div>
);
}
}
render(<App />, document.getElementById("root"));
And todos state doesn't change in any case. We don't need to put it in the state, and so we'd be better to receive it via props from its parent component. Or we can remove todos state and use todoList directly.
You can try something like this:
import React, { Component } from 'react';
import { render } from 'react-dom';
import Hello from './Hello';
import './style.css';
class App extends Component {
constructor() {
super();
this.state = {
users: [{id:1, name: 'Martin'}, {id:3, name: 'Gregor'}, {id:2, name: 'Paul'}],
todos: [{user_id:3, todos:['swim', 'feed']}, {user_id:1, todos:['sleep', 'read']}, {user_id:2, todos:['drinking', 'dancing']} ],
hideTodosUserId: '',
hiddenUsers: []
};
}
filterTodos = (userId) => {
console.log(userId);
debugger;
const newUsers = [];
if(this.state.hiddenUsers.indexOf(userId) === -1) {
this.setState({
hiddenUsers: [...this.state.hiddenUsers, userId]
})
}
else {
newUsers = [...this.state.hiddenUsers]
newUsers.splice(this.state.hiddenUsers.indexOf(userId), 1)
this.setState({
hiddenUsers: newUsers
})
}
}
render() {
console.log(this.state.hiddenUsers);
return (
<div>
<ul>
{this.state.users.map(user => {
return <li key={user.id} onClick={() => this.filterTodos(user.id)} className={this.state.hiddenUsers.includes(user.id) ? 'hideTodos' : '' }>{user.name}</li>
})}
</ul>
<ul>
{this.state.todos.filter(item => this.state.hiddenUsers.indexOf(item.user_id) === -1).map(items => {
return <li key={items.id}>{items.todos.map(todo => <li key={todo.id}>{todo}</li>)}</li>
})}
</ul>
</div>
);
}
}
render(<App />, document.getElementById('root'));
Add an Array to the state which will contain the users to hidden hiddenUsers. Use this array to filter the todo results in render function.
try:
class App extends Component {
constructor() {
super();
this.state = {
users: [{id:1, name: 'Martin'}, {id:3, name: 'Gregor'}, {id:2, name: 'Paul'}],
todos: [{user_id:3, todos:['swim', 'feed']}, {user_id:1, todos:['sleep', 'read']}, {user_id:2, todos:['drinking', 'dancing']} ],
originaltodos: [{user_id:3, todos:['swim', 'feed']}, {user_id:1, todos:['sleep', 'read']}, {user_id:2, todos:['drinking', 'dancing']} ],
hideTodosUserId: ''
};
}
filterTodos = (userId) => {
if (userId !== this.state.hideTodosUserId) {
console.log(userId);
const hideTodos = this.state.todos.filter(item => item.user_id !== userId);
console.log(hideTodos);
this.setState({
todos: hideTodos,
hideTodosUserId: userId
})
} else {
this.setState({
todos: this.state.originaltodos,
hideTodosUserId: ''
})
}
}
render() {
return (
<div>
<ul>
{this.state.users.map(user => {
return <li key={user.id} onClick={() => this.filterTodos(user.id)} className={this.state.hideTodosUserId === user.id ? 'hideTodos' : '' }>{user.name}</li>
})}
</ul>
<ul>
{this.state.todos.map(items => {
return <li key={items.id}>{items.todos.map(todo => <li key={todo.id}>{todo}</li>)}</li>
})}
</ul>
</div>
);
}
}

Toggling a classname for one button in a list of buttons

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

Categories