I'm new to React/ES6 and I'm working on my first components. I have a PuzzleContainer component that contains a Puzzle component which displays images. The container component makes an AJAX call to determine what images to display and then passes that data down to the child component. I want to do something in the container component when one of the images is clicked.
Here is my code, I didn't include the componentWillMount() function in the container component, but cards is just an array of strings.
class PuzzleContainer extends React.Component {
cardClicked(cardsrc) {
console.log(cardsrc);
}
render() {
return (
<div>
<section className="wrapper site-min-height" id="main-wrapper">
<Puzzle cards={this.state.cards} cardClicked={this.cardClicked}></Puzzle>
</section>
<Infobar></Infobar>
</div>
);
}
}
class Puzzle extends React.Component {
render() {
var cards = this.props.cards;
var html = cards.map((card) =>
<div className="col-lg-2 col-md-4 col-sm-6 col-xs-12 desc">
<div className="photo-wrapper">
<div className="photo">
<img className="img-responsive" src={card} onClick={(card) => this.props.cardClicked(card)}></img>
</div>
</div>
</div>
);
return (
<div className="row mt gutter">{html}</div>
);
}
}
The problem is, whenever I click an image, a Proxy(?) Object gets printed to the console. It seems to be some object from React. However, my understanding was that it should just print the card value that gets passed to the function, which is a string.
Why is an object getting logged instead of a string? How do I make it so the container component can receive the identity of the image that gets clicked?
You use card as both the parameter for the map function as well as the parameter for the onClick function. As a result, the onClick event object is being passed to this.props.cardClicked rather than the string you desire.
Changing your img tag to
<img className="img-responsive" src={card} onClick={() => this.props.cardClicked(card)}></img>
will do what you want.
The first parameter of an onClick handler is the event object.
onClick={(card) => this.props.cardClicked(card)} When you do this, the event object is assigned the name card and you are actually passing the event object to this.props.cardClicked instead of the card variable that you are looping through in map.
Do this instead:
<img className="img-responsive"
src={card}
onClick={this.props.cardClicked.bind(this, card)}
/>
Btw, <img> is self-closing and you can combine the opening and closing tag into one.
Related
Who can help with that? I need to write everything in a normal algorithm, not as it is now. I have three elements. I wanted to make it so that when I'm on the last one, the next button takes me back to the first element. To make a infinity loop carrousel. project at the react
import React, { useEffect, useState } from "react";
import { factory_img, factory_bg_svg } from "#/img_video";
export default function Factory_Video() {
const arr_items = []
useEffect(() => {
const items = document.querySelectorAll('.item')
for (let item of items) {
arr_items.push(item)
}
})
function next_slide() {
const el_1 = arr_items[0].classList.contains("active")
const el_2 = arr_items[1].classList.contains("active")
if (el_1) {
arr_items[0].classList.remove('active')
arr_items[0].classList.add("transform")
arr_items[1].classList.add("active")
arr_items[1].classList.remove('transform')
} else if (el_2) {
arr_items[1].classList.remove('active')
arr_items[1].classList.add("transform")
arr_items[2].classList.add("active")
arr_items[2].classList.remove('transform')
}
}
}
return (
<section className="section_factory" >
<img id="bg_section_factory" src={factory_bg_svg} alt="" />
<div className="container_factory">
<h1 className="h1_section_title" >О производстве <br /> Венарус</h1>
<div className="wrapper">
<div className="window">
<div className="item active" >
<img src={factory_img} />
</div>
<div className="item transform" >
<img src={factory_img} />
</div>
<div className="item transform" >
<img src={factory_img} />
</div>
</div>
<div className="navigation" >
<div className="btn_prev" ></div>
<div onClick={next_slide} className="btn_next" ></div>
</div>
</div>
</div>
</section>
);
}
I tried using array methods, but it didn't work.
The code is not complete so I can't tell the exact changes you need to do, however here are a list of recommendations I can see your code lacks or are wrong.
Never manipulate the dom directly with React.
Modifying the classlist in a function is not the React way and is prone to errors as it will be cleared if a re-render is triggered. Instead change the classes directly in the html.
It would be something like <div className={"item " +active===0?'active':''} >
You're declaring const arr_items = [] directly above useEffect. This varible will be cleared on every render. There are other ways to keep the info around in React, however in this case there is no point to keep the array of dom elements.
Your useEffect has no dependencies (the array sent as second parameter). This makes it execute every time the component runs so there's no point in using a useEffect here. (btw you don't need it at all for this to work)
Finally the "React" way (at least one approach) would be to have a state with the index of the active element. You just have to change this index with its corresponding setter and render the classes of each element conditionally.
I'm using React to view my pages.
I came across this problem if I try to call a function from a .js file nothing happens.
Basically, I have a small program that has two columns. Each column has a <p> tag that contains Column 1 and Column 2. There is a button below that once you click on it, both Columns should switch.
index.js
import "../style.css";
//import "./java.js";
class index extends React.Component {
render() {
return(
<div className="container">
<div className="columns" id="columnsToSwitch">
<div className="column1" id="column1_id">
<p>Column1</p>
</div>
<div className="column2" id="column2_id">
<p>Column 2</p>
</div>
</div>
<div className="switch" id="switch_id" onClick={this.switchColumns}>Switch</div>
</div>
);
}
}
export default index;
java.js
const switchCol = document.querySelectorAll("div.columns");
const el = document.getElementById("switch_id");
if(el) {
el.addEventListener("click", switchColumns, false);
}
switchCol.forEach(switches => switches.addEventListener('click', switchColumns));
function switchColumns(){
const makeSwitch1 = document.getElementById('column1_id');
document.getElementById('column2_id').appendChild(makeSwitch1);
const makeSwitch2 = document.getElementById('column2_id');
document.getElementById('column1_id').appendChild(makeSwitch2);
}
Method 1:
I tried to import the .js file that contains the function.
Nothing is happening after clicking "Switch".
Method 2:
Using onClick within a tag.
<div className="switch" id="switch_id" onClick={this.switchColumns}>Switch</div>
I get a couple of errors,
Uncaught TypeError: Cannot read properties of undefined (reading 'switchColumns')
The above error occurred in the <index> component:
On This line:
const switchCol = document.querySelectorAll(".switchCol");
There's no elements with the class of 'switchCol' so you're going to get an empty NodeList which causes the forEach loop to not execute so there are no click events on the columns themselves.
In the forEach block:
switchCol.forEach(switches => {
switches.addEventListener("column1", switchColumns);
switches.addEventListener("column2", switchColumns);
});
"column1" and "column2" are not valid event listeners, and there doesn't need to be two event listeners for one element. I think you mean to write the following:
switchCol.forEach(switch => switch.addEventListener('click', switchColumns))
Now onto your main switching column function:
function switchColumns(){
const makeSwitch1 = document.getElementById('column1');
document.getElementById('column2').appendChild(makeSwitch1);
const makeSwitch2 = document.getElementById('column2');
document.getElementById('column1').appendChild(makeSwitch2);
}
Variables makeSwitch1 and makeSwitch2 are going to be undefined as you do not have any elements with an id of column1 and column2 respectfully. Which is causing your issue with the second fix you tried.
I display a list of foos and when i click on some link more results i keep the existing foos and i append to them the new ones from my api like bellow
const [foos, setFoos] = useState([]);
...
// api call with axios
...
success: (data) => {
setFoos([ ...foos, ...data ])
},
Each <Foo /> component run the animation above
App.js
...
<div className="foos-results">
{ foos.map((foo, index) => <Foo {...{ foo, index }} key={foo.id}/>) }
</div>
...
Foo.js
const Foo = ({ foo, index }) => <div className="circle">...</div>
animation.css
.circle {
...
animation: progress .5s ease-out forwards;
}
The problem is when i append the new ones then the animation is triggered for all the lines of <Foo />.
The behavior expected is that the animation is triggered just for the new ones and not starting over with the existing ones too.
UPDATE
We have found the origin of the problem (it's not related to the uniqueness of key={foo.id})
if we change
const Foo = ({ foo, index }) => <div className="circle">...</div>
to
const renderFoo = ({ foo, index }) => <div className="circle">...</div>
And App.js to
...
<div className="foos-results">
{ foos.map((foo, index) => renderFoo({ foo, index })) }
</div>
...
It works
So why is this behavior like this in react ?
here is a sandbox based on #Jackyef code
This is quite an interesting one.
Let's look at the sandbox provided in the question.
Inside App, we can see this.
const renderItems = () => (
<div>
{items.map((item, index) => (
<div className="item" key={item.id}>
<span>
{index + 1}. {item.value}
</span>
</div>
))}
</div>
);
const Items = () => renderItems();
return (
<div className="App">
<h1>List of items</h1>
<button onClick={addItem}>Add new item</button>
<Items />
</div>
);
Seems pretty harmless right? The problem with this is that Items is declared in the App render function. This means that on each render, Items actually is now a different function, even though what it does is the same.
<Items /> is transpiled into React.createElement, and when diffing, React takes into account each components' referential equality to decide whether or not it is the same component as previous render. If it's not the same, React will think it's a different component, and if it's different, it will just create and mount a new component. This is why you are seeing the animation being played again.
If you declare Items component outside of App like this:
const Items = ({ items }) => (
<div>
{items.map((item, index) => (
<div className="item" key={item.id}>
<span>
{index + 1}. {item.value}
</span>
</div>
))}
</div>
);
function App() { /* App render function */}
You will see everything works as expected. Sandbox here
So, to summarise:
Referential equality matters to React when diffing
Components (function or class that returns JSX) should be stable. If they change between renders, React will have a hard time due to point number 1.
I don't think there is a way to disable this re-rendering animation, but I think there is a workaround that could solve this issue.
As we know that each div's css is reloaded every time, so the solution I can think of, is to create another css class rule (let this class be named 'circle_without_anim') with same css as class 'circle' but without that animation and while appending new div, just before appending change class of all divs that have class name 'circle' to 'circle_without_anim' that would make the changes and css to previous divs but just without that animation and the append this new div with class 'circle' making it the only div that have animation.
Formally the algorithm will be like:
Write another css class(different name for example prev_circle) with same rules as 'circle' but without the animation rule.
In Javascript just before appending new div with class 'circle', change class of all previous divs that have class named 'circle' to newly created class 'prev_circle' that do not have animation rule.
Append the new div with class 'circle'.
Result: It would give an illusion that the CSS of previous divs is not being reloaded as the css is same but without animation, but the new div has different css rule (animation rule) which is going to be reloaded.
With this code:
const Items = () => renderItems();
...
<Items />
React has no chance of knowing that Items in the current render is the same component as Items in the previous render.
Consider this:
A = () => renderItems()
B = () => renderItems()
A and B are different components, so if you have <B /> in the current render and <A /> instead of <B /> in the previous render, React will discard the subtree rendered by <A /> and render it again.
You are invoking React.createElement (since <Items /> is just a JSX syntax sugar for React.createElement(Items, ...)) every render, so React scraps the old <Items /> in the DOM tree and creates it again each time.
Check out this question for more details.
There are two solutions:
create Items component outside of the render function (as Jackyef suggested)
use render function ({ renderItems() } instead of <Items />)
In my parent component, I used a component called List as follows.
render() {
return (
<div className="experiments">
<div className="experiments-list-container">
<List rowItems={this.state.employeeData} />
</div>
</div>
);
}
}
In my List component, I am trying to change the style whenever each item of the row is clicked. So what I did is:
render() {
const dateDisplay = moment(this.props.createdAt).format('MMM YYYY');
return (
<tr
className={this.state.isExpanded ? 'testclass' : "experiment-list__row"}
//className="experiment-list__row"
onClick={this.handleRowClick}
>
<td>
{this.props.rowItems.firstName + ' ' + this.props.rowItems.lastName}
</td>
<td>{this.props.rowItems.jobTitle}</td>
<td>{'Email#Email.com'}</td>
<td>{this.props.rowItems.employmentType}</td>
</tr>
);
}
whenever I click a row in the table, it will all a function that changes the this.state.isExpanded to True. However, the style that I actually want to change is <div className="experiments"> or <div className="experiments-list-container">. But I am not sure how to change the style of the upper-level component. Please help.
EDIT
Thanks for the reply. What I have tried is,
const List = props => {
return (
<table className="experiment-list">
<tbody>
<ListHeader />
{props.rowItems.map((data, i) => <ListRow
key={i}
rowItems={data}
onRowClicked={props.onRowClicked} />)}
</tbody>
</table>
);
};
and
toggleEmployerInfo(e) {
alert('dd')
}
in my parent component.
Whenever I click each row, it alerts "dd" correctly.
However, what I eventually want to do is pass in the info of the row clicked.
In my parent component, I use the List by doing
<div className="experiments-list-container">
<List
rowItems={this.state.employeeData}
onRowClicked={this.toggleEmployerInfo.bind(this)}
/>
</div>
This does render all data into each row correctly, but how can I make each row correctly read the id of the item that the row has?
You could pass an event handler to the List component and call it whenever a row is clicked. Here I've defined handleRowClick in the parent component as an ES6 arrow function. Then I pass this function as a callback to the child component via the onRowClicked prop.
// parent.jsx
handleRowClick = (id) => {
// Handle click event, update state, etc.
console.log(id);
}
render() {
return (
<div className="experiments">
<div className="experiments-list-container">
<List rowItems={this.state.employeeData} onRowClicked={this.handleRowClick} />
</div>
</div>
);
}
And then call the onRowClicked function on the onClick event for each element you want to react to.
// list.jsx
render() {
return (
// Extremely simplified example...
<div onClick={() => this.props.onRowClicked('row-id-goes-here')}>row content</div>
);
}
The arrow function syntax here allows us to specify parameters beside the default event parameter that you would get if you just used onClick={this.props.onRowClicked}.
Here is a post explaining this approach better than I can: https://medium.com/#machnicki/handle-events-in-react-with-arrow-functions-ede88184bbb
Just starting off with ReactJS and have a project where I am showing an accordion of issues and including a details area that is hidden on the start.
There is a button in the accordion bar that should pass a prop to the child element to hide or show them. I have refs on the button and on the details child compoment and added a function to call the function and pass the ref of the details area. I am just not sure how to dynamically change the class hidden on one of many areas and not all of them.
Not sure if putting a class on each element and then learning how to toggle the particular child's class is better or changing the prop to the child.
I can get to the change function but am drawing a blank from there and all the googling shows how to do one element with a grand change of state but I need individual elements.
Here is what I have so far.
Parent
...
<AccordionItem key={item.id} className={iconClass} title={`${item.area}`} expanded={item === 1}>
{
item.issues.map(issue => {
let trim = (issue.issue.length>21) ? `${issue.issue.substring(0,22)}...`: issue.issue;
return (
<div className="issue-bar container-fluid">
<div className="row issue-bar-row">
<span className="issue-title"><img src={CriticalRed} alt="Critical"/> {trim}</span>
<span className="btns">
<button className="btn btn-details" onClick={() => this.showDetail(`details-${issue.id}`)}>Details</button>
</span>
</div>
<IssuesDetails ref={`details-${issue.id}`} issue={issue} shouldHide={true} />
</div>
)
})
}
<div>
</div>
</AccordionItem>
...
Child
export default class IssuesDetails extends Component{
render(){
let issueDetails = classNames( 'issue-details', { hidden: this.props.shouldHide } )
return(
<div className={issueDetails}>
<div className="issues-details-title">
<h3>{this.props.issue.issue}</h3>
</div>
<div className="issues-details-details">
{this.props.issue.details}
</div>
<div className="issues-details-gallery">
<ImageGallery source={this.props.issue.photos} showPlayButton={false} useBrowserFullscreen={false} />
</div>
<button className="btn btn-success">Resolve</button>
</div>
)
}
}
Thanks for any help you provide or places you can send me!
If i'm understanding correctly, you need to be able to swap out shouldHide={true} in certain circumstances. To do this, you'll want your parent component to have a state object which indicates whether they should be hidden or not.
Exactly what this state object looks like depends on what sort of data you're working with. If the issues is a single array, then perhaps the state could be an array of booleans indicating whether each issue is expanded or not. I suspect you may have a more nested data structure, but i can't tell exactly since some of the code was omitted.
So assuming you have an array, it might look like this (i've omitted some things from the render method for brevity):
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
hidden: (new Array(props.issues.length)).fill(false),
};
}
showDetail(index) {
let newHidden = this.state.hidden.slice();
newHidden[index] = true;
this.setState({
hidden: newHidden
});
}
render() {
return (
<AccordionItem>
{this.props.issues.map((issue, index) => {
<div>
<button onClick={() => this.showDetail(index))}/>
<IssuesDetails issue={issue} shouldHide={this.state.hidden[index]}/>
</div>
})}
</AccordionItem>
);
}
}
Take a look at these:
https://codepen.io/JanickFischr/pen/xWEZOG
style={{display: this.props.display}}
I think it will help with your problem. If you need more information, please just ask.