Reactjs handleclick binding in a nested map not working - javascript

I am writing a simple blog component in React. The component get the post from a second file and renders the content and the hashtags with map (in particular two nested map). I was trying to create a part in which the hashtag are highlighted when clicked, and hence I was following the snippet of the answer to this question. The fact is that it is not working and in particular what it is not working is the binding through bind (checked with console.log output)
class Blog extends React.Component {
state= {
open: {}
}
handleClick = (k) => {
let linkOpenState = true;
if (this.state.open.hasOwnProperty(k)) {
linkOpenState = !this.state.open[k];
}
this.setState({ open: { [k]: linkOpenState } })
}
render(){
return(
posts.map(i=>(
<div class="box">
<article class="media">
<div class="media-content">
<div class="content">
<h1 class="title">{i.title}</h1>
<p>
{i.content}
</p>
</div>
<div class="tags">
{i.hash.map(k=>(<span id={k} onClick={this.handleClick.bind(this,k)} class={this.state.open[k]? "tag is-primary" : "tag"} >{k}</span>))}
</div>
</div>
</article>
</div>))
)
}
}
export default Blog
Anyone has an idea of what is wrong there? Is it the nested map a problem for the bind?

React expects a single element as a return. You can solve this by wrapping the return in a fragment as <React.Fragment> or <>.
Also, you don't need to bind an arrow function(handleClick when mapping i.hash).
render(){
return(
<>
posts.map(i=>(
<div class="box">
<article class="media">
<div class="media-content">
<div class="content">
<h1 class="title">{i.title}</h1>
<p>
{i.content}
</p>
</div>
<div class="tags">
{i.hash.map(k=>(<span key={k} id={k} onClick={() => this.handleClick(k)} class={this.state.open[k]? "tag is-primary" : "tag"} >{k}</span>))}
</div>
</div>
</article>
</div>))
</>)}}

You don't need to bind the arrow function.
Also, you need to pass unique key to elements when used inside map, you can use index as a key but if you are mutating the array then use some id or hash or anything which will be unique.
class Blog extends React.Component {
state= {
open: {}
}
handleClick = (k) => {
let linkOpenState = false;
if (this.state.open.hasOwnProperty(k)) {
linkOpenState = this.state.open[k];
}
this.setState({ open: { [k]: linkOpenState } })
}
render(){
return(
posts.map((i, index)=>(
<div class="box" key={i.id || index}>
<article class="media">
<div class="media-content">
<div class="content">
<h1 class="title">{i.title}</h1>
<p>
{i.content}
</p>
</div>
<div class="tags">
{i.hash.map(k=>(<span key={k} id={k} onClick={() => this.handleClick(k)} class={this.state.open[k]? "tag is-primary" : "tag"} >{k}</span>))}
</div>
</div>
</article>
</div>))
)
}
}
export default Blog

Related

OnClick() handler in map function is changing the state of all elements instead of only clicked element

I am creating a Next js site mcqs and answer.
Initially only questions are rendered with the title and options.
There is a button to show the answer of given question but when button is clicked it is changing the state of all elements in the map function instead of element that is clicked.
export default function Chapter({ chapter }) {
const [right, setRight] = useState(false)
function handleOnClick() {
setRight(!right)
}
<main className='main_q'>
<h1>{chapter.items[0].name}</h1>
{chapter.items[0].questions.items.map((question, index) => {
return (
<div key={question.id} className='question_container'>
<div className='question_q_t'>
<div className='question_q'>Q</div>
<div className='question_t'>
<Markdown>{question.title}</Markdown>
</div>
</div>
<div className='option_container'>
<div className='option_a_o'>
<div className='option_a'>A</div>
<div className='option_o'>
<Markdown>{question.optionA}</Markdown>
</div>
</div>
<div className='option_a_o'>
<div className='option_a'>B</div>
<div className='option_o'>
<Markdown>{question.optionB}</Markdown>
</div>
</div>
<div className='option_a_o'>
<div className='option_a'>C</div>
<div className='option_o'>
<Markdown>{question.optionC}</Markdown>
</div>
</div>
<div className='option_a_o'>
<div className='option_a'>D</div>
<div className='option_o'>
<Markdown>{question.optionD}</Markdown>
</div>
</div>
</div>
<a
className='solution_link'
target='_blank'
href={`/${question.chapter.subject.category.slug}/${question.chapter.subject.slug}/${question.chapter.slug}/${question.id}`}
>
See Solution
</a>
<button onClick={handleOnClick}>Answer</button>
{right && <div>{question.rightAnswer}</div>}
</div>
)
})}
</main>
}
Put each question div into its own component, and make a right state in that component:
const Question = ({ question }) => {
const [right, setRight] = useState(false)
return (
<div key={question.id} className='question_container'>
// etc
Or make an array of right states in the parent:
export default function Chapter({ chapter }) {
const qs = chapter.items[0].questions.items;
const [rights, setRights] = useState(qs.map(q => false));
const makeHandleClick = (i) => {
setRights(
rights.map((r, j) => j === i ? r : !r)
);
};
// ...
<button onClick={makeHandleClick(i)}>Answer</button>
{rights[i] && <div>{question.rightAnswer}</div>}

hide a div in react when there is no anchor tag in it

I have a react code (just a snippet, its not a complete code) as shown below which shows the list of programs on the webpage. Line A in the react code below renders all list of program on the webpage.
react code:
const renderPrograms = () => {
return programs.map((program, index)=>{
return (
<a href={program.url} key={index}>
<div className="program" >
<div class="hello-world">{program.name}</div>
</div >
</a>
)
})
}
return(
<div class="parent-div">
<div className ="pqr-xyz">
<h5>Hello World</h5>
</div>
<div className ="abc-def">
<h5>Programs</h5>
{programs && renderPrograms()} {/*Line A*/}
</div>
</div>
)
The above react code renders the following html code at runtime:
<div class="parent-div">
<div className ="pqr-xyz">
<h5>Hello World</h5>
</div>
<div class="abc-def">
<h5>Programs</h5>
<a href="https://www.google.com/">
<div class="program">
<div class="hello-world">TYUV</div>
</div>
</a>
<a href="https://www.twitter.com/">
<div class="program">
<div class="hello-world">SGHS</div>
</div>
</a>
</div>
</div>
Problem Statement:
When Line A does not render anything, my div (<div className ="abc-def">) will look like this at run time:
<div class="abc-def">
<h5>Programs</h5>
</div>
I am wondering what changes I need to make in my react code above so that when Line A doesn't render anything then <div class="abc-def"><h5>Programs</h5></div> should not display on the webpage.
They should be part of the condition:
return(
programs.length > 0 && <div className ="abc-def">
<h5>Programs</h5>
{renderPrograms()}
</div>
)
I changed the condition to check for length, otherwise you'll get a 0 instead of nothing, when empty
Try change this lines:
<div className ="abc-def">
<h5>Programs</h5>
{programs && renderPrograms()} {/*Line A*/}
</div>
into this:
{ programs && (
<div className ="abc-def">
<h5>Programs</h5>
{renderPrograms()} {/*Line A*/}
</div>
) }
Now without programs nothing is displayed.
You can try adding a className like this:
<div className={`abc-def ${programs.length ? "hidden" : ""}`}
Or from the parent componet, you can choose to not render this component with <div>
Instead of trying to hide it using CSS, you also can choose to not render it at all by returning nothing from your function
const RenderPrograms = ({ programs = [] }) => {
if (!programs.length) {
return []
}
return (
<div className="abc-def">
<h5>Programs</h5>
{programs.map((program, index) =>
<a href={program.url} key={index}>
<div className="program">
<div>{program.name}</div>
</div>
</a>
)}
</div>
)
}
ReactDOM.render(
<RenderPrograms />,
document.getElementById('react')
)
<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="react"></div>
You can try putting your class in a template string and using a ternary operator to display a certain class when some state value is true.
<div className={`${stateValue ? "name-of-class-with-hide" : "abc-def"}`}>
Here is a complete solution. All you need to do is move the <h5>Programs</h5> inside the condition and modify the condition slightly as shown in following snippet.
<div className ="abc-def">
{
programs.length > 0 && (
<div>
<h5>Programs</h5>
<RenderPrograms programs = {programs}/>
</div>
)
}
</div>
Note that I have modified the RenderPrograms function as well to accept arguments.
Following is a full snippet.
function RenderPrograms(props) {
return props.programs.map((program, index)=>{
return (
<a href={program.url} key={index}>
<div className="program" >
<div className="hello-world">{program.name}</div>
</div >
</a>
)
});
}
function MyApp() {
const programs=[
{name:'program 1', url: 'https://url1.com'},
{name:'program 2', url: 'url2'},
{name:'program 3', url: 'url3'},
];
const programs1 = []; // empty program list
return(
<div class="parent-div">
<h1>When programs list is not empty</h1>
{/*Above line is just for explanation -- you may remove it*/}
<div className ="pqr-xyz">
<h5>Hello World</h5>
</div>
<div className ="abc-def">
{
programs.length > 0 && (
<div>
<h5>Programs</h5>
<RenderPrograms programs = {programs}/>
</div>
)
}
</div>
{/*Below code is just for explanation -- you may remove it*/}
<hr />
<h1>When programs list is empty</h1>
<div className ="pqr-xyz">
<h5>Hello World</h5>
</div>
<div className ="abc-def">
{
programs1.length > 0 && (
<div>
<h5>Programs</h5>
<RenderPrograms programs = {programs1}/>
</div>
)
}
</div>
</div>
)
}
ReactDOM.render(
<MyApp />,
document.getElementById('app')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app"></div>
You need to move rendering of programs into RenderPrograms component. Make RenderPrograms component purelu functional and pass data to it via props (Instead of using function).
const RenderPrograms = ({ programs }) => {
let disp = programs.map((program, index) => {
return (
<a href={program.url} key={index}>
<div className="program">
<div class="hello-world">{program.name}</div>
</div>
</a>
);
});
return disp || <span />;
};
then use RenderPrograms to render programs in main component (One with logis for loading programs).
// let programs = [
// {
// url: "test1.com",
// name: "test1"
// },
// {
// url: "test2.com",
// name: "test2"
// }
// ];
let programs = [];
return (
<div className="App">
<div class="parent-div">
<div className="pqr-xyz">
<h5>Hello World</h5>
</div>
<div className="abc-def">
<h5>Programs</h5>
<RenderPrograms programs={programs} />
{/* {programs && renderPrograms()} Line A */}
</div>
</div>
</div>
);
You can play around with this sandbox

Add +1 to willWatch when <a> is clicked

Task: add +1 to willWatch when <a> is clicked.
I have an error when <a> is clicked, because MovieItem is not a component. I try to set class MovieItem... but I have a problem with moviesData
import React, { Component } from "react";
import { moviesData } from "../moviesData";
function MovieItem(props) {
let {info : { id, vote_count , video, vote_average, title, popularity, poster_path, original_language, original_title ,backdrop_path, adult, overview} } = props;
return (
<div className="col" id={id} style={{width: "18rem"}}>
<img className="card-img-top" src={'https://image.tmdb.org/t/p/w500' + poster_path}
alt="Card image cap"/>
<div className="card-body">
<h5 className="card-title">Оригинальное название: {original_title}</h5>
<h5 className="card-title">Название: {title}</h5>
<p className="card-text">{overview}</p>
<p className="card-text">Рейтинг: {vote_average}</p>
<p className="card-text">Популярность: {popularity}</p>
<p className="card-text">Наличие видео: {video}</p>
<p className="card-text">Оригинальный язык: {original_language}</p>
<p className="card-text">Возраст 18+: {adult}</p>
<p className="card-text">backdrop_path {backdrop_path}</p>
<p className="card-text">Голоса: {vote_count}</p>
<a
// onClick={this.props.counter}
href="#"
className="btn btn-primary">Will Watch
</a>
</div>
</div>
)
}
class App extends Component {
constructor(state) {
super(state);
this.state = {
willWatch: 0
};
this.counter = this.counter.bind(this)
}
counter(e) {
e.preventDefault();
this.state.willWatch = this.state.willWatch + 1
}
render() {
return (
<div className="container">
<div className="col-12">
<div className="row">
<div className="col-9">
<div className="row">
{
moviesData.map((props) => {
return <MovieItem info={props} counter={this.counter}/>
})
}
</div>
</div>
<div className="col-3 sidebar">
<div className="row">
<p> Хочу посмотреть, фильмов: {this.state.willWatch} </p>
</div>
</div>
</div>
</div>
</div>
)
}
}
export default App;
First of all, you have to destructure count from props like so:
function MovieItem(props) {
let {info : { id, vote_count , video, vote_average, title, popularity, poster_path, original_language, original_title ,backdrop_path, adult, overview}, counter } = props;
return (
<div className="col" id={id} style={{width: "18rem"}}>
<img className="card-img-top" src={'https://image.tmdb.org/t/p/w500' + poster_path}
alt="Card image cap"/>
<div className="card-body">
<h5 className="card-title">Оригинальное название: {original_title}</h5>
<h5 className="card-title">Название: {title}</h5>
<p className="card-text">{overview}</p>
<p className="card-text">Рейтинг: {vote_average}</p>
<p className="card-text">Популярность: {popularity}</p>
<p className="card-text">Наличие видео: {video}</p>
<p className="card-text">Оригинальный язык: {original_language}</p>
<p className="card-text">Возраст 18+: {adult}</p>
<p className="card-text">backdrop_path {backdrop_path}</p>
<p className="card-text">Голоса: {vote_count}</p>
<a
onClick={counter}
href="#"
className="btn btn-primary">Will Watch
</a>
</div>
</div>
)
}
and one more note, you have to set state using setState function:
counter(e) {
e.preventDefault();
this.setState((prevState) => {
return {
willWatch: state.willWatch + 1
}
});
}
You should never mutate/set this.state value directly.
Or else React would not know whether state has been changed or not.
(Refer to this article for details).
So instead of updating willWatch directly,
counter(e) {
e.preventDefault();
this.state.willWatch = this.state.willWatch + 1
}
Use setState
counter(e) {
e.preventDefault();
this.state(prevState => ({willWatch: prevState.willWatch + 1}));
}

React.js - Close and add another row div after X items

I'm learning React.js.
I have this code:
const articles = Object
.keys(this.state.articles)
.map(key => <ArticleThumb key={key} details={this.state.articles[key]} />)
;
return (
<div className="container">
<div className="row">
{ articles }
</div>
</div>
)
Assuming I have 6 items, here is the code after rendering:
<div className="container">
<div className="row">
<div id="item1"></div>
<div id="item2"></div>
<div id="item3"></div>
<div id="item4"></div>
<div id="item5"></div>
<div id="item6"></div>
</div>
</div>
But I want to have only 3 items per line like this:
<div className="container">
<div className="row">
<div id="item1"></div>
<div id="item2"></div>
<div id="item3"></div>
</div>
<div className="row">
<div id="item4"></div>
<div id="item5"></div>
<div id="item6"></div>
</div>
</div>
What is the best way to do this?
The easy answer is to use something like lodash's chunk function https://lodash.com/docs/4.17.4#chunk
However, if you are not using npm and or do not want to add additional dependencies you are going to have to use a good old fashioned javascript for loop to perform the grouping you need.
With the markup you have you could do it via css:
.container {
display: flex;
flex-wrap: wrap;
}
.row {
width: 33%;
box-sizing: border-box;
}
I made a ArticleGrid component:
class ArticleGrid extends Component {
render() {
let articlesRows = Object
.keys(this.props.rows)
.map(key => <ArticleRow key={key} articles={this.props.rows[key]} />)
;
return (
<div id="articles" className="container">
{articlesRows}
</div>
);
}
}
export default ArticleGrid;
A ArticleRow component:
class ArticleRow extends Component {
render() {
let articles = Object
.keys(this.props.articles)
.map(key => <ArticleThumb key={key} details={this.props.articles[key]} />)
;
return (
<div className="row">
{articles}
</div>
);
}
}
export default ArticleRow;
I used lodash's chunk and values function (ty Deadron) and it's done:
let articles = _.values(this.state.articles);
let articlesRows = _.chunk(articles, 3);
return (<ArticleGrid rows={articlesRows} />);

Using odd/even switch for classNames

I want each this.state.title to align according to a different classname.
I tried using css flex boxes/nth-of-type/nth-child, but it did not play nicely with React.
I'm using this.state to get my objects.
My unsuccessful attempt
render: function () {
let className
var newVar = !someVar;
switch(someVar) {
case odd:
className= "post-1 line";
break;
case even:
className = "post-2 right-align line";
break;
}
return (
<article class={I WANT THIS TO FILL FROM SWITCH}>
<div class="s-12 l-6 post-image">
<a href="post-1.html">
<img src="/post1.jpg">
</a>
</div>
<div class="s-12 l-5 post-text">
<a href="#">
<h2>{this.state.title}</h2>
</a>
<p>Testing
</p>
</div>
<div class="s-12 l-1 post-date">
<p class="date">28</p>
<p class="month">feb</p>
</div>
</article>
);
}
});
react rewrite class attribute as className,see react for more details.
render: function () {
let className = ['post-2 right-align line', 'post-1 line'][someVar % 2];
return (
<article className={className}>
<div className="s-12 l-6 post-image">
<a href="post-1.html">
<img src="/post1.jpg"/>
</a>
</div>
<div className="s-12 l-5 post-text">
<a href="#">
<h2>{this.state.title}</h2>
</a>
<p>Testing
</p>
</div>
<div className="s-12 l-1 post-date">
<p className="date">28</p>
<p className="month">feb</p>
</div>
</article>
);
}
The 'someVar' is local variable ? You can try write it in state, Via this.setState mutate it for your need.
here is the whole example like this:
class Item extends Component {
render() {
let {index, title}=this.props;
let className = ['post-2 right-align line', 'post-1 line'][index % 2];
return <li key={index} id={index} className={className}>{title}</li>
}
}
class TodoList extends Component {
render() {
let i = 0;
let itemFactory = (props = {}) => {
return <Item key={i} index={i++} {...props}/>
};
return (<ul>
{this.props.items.map((item) => itemFactory({title: item}))}
</ul>);
}
}
ReactDOM.render(
<TodoList items={['first', 'second', 'last']}/>,
document.getElementById('container')
);

Categories