ReactJS is not able to differentiate between elements - javascript

Basically, Here I am mapping data coming from backend but when I am trying to get textContent of content of h5 tag through postData function, it only gives textContent of first card element not 2nd and so forth..
Is it has something to do with key while mapping in ReactJS ?
Can anyone tell me where is the problem ?
import React, { Component } from "react";
import "./App.css";
class Cards extends Component {
constructor(){
super()
this.state = {
cData:[]
}
}
postData(){
let ff = document.getElementById('ccc')
let dd = ff.textContent
console.log(dd);
let data = { course: dd}
axios.post('http://localhost:9000/api', data)
.then(res => {
console.log(res);
})
.catch(err =>{
console.log(err);
})
}
render() {
return (
<div>
{ this.state.cData.map(el => (
<div className="card mb-3 jj" key={Math.random()}>
<div className="row g-0">
<div className="col-md-4">
<img
src={el.Image}
alt="..."
className="img-fluid ii"
/>
<button
type="button"
className="btn-sm btn-info btt"
onClick={this.dropDetails}
>
<small>More Details </small>
</button>
<a href="/form" >
<button type="button" className="btn-sm btn-info" **onClick={this.postData}**>
<small> Book Now </small>
</button>
</a>
</div>
<div className="col-md-1"> </div>
<div className="col-md-6">
<div className="card-body">
**<h5 className="card-title" id="ccc">{el.Course}</h5>**
<b> {el.FFS} </b> <br />
<span className="card-text">
<small className="text-muted">{el.FPS}</small>
</span>
<br /> <br />
<span>
By : {el.CP} <br />
Age : {el.Age} <br />
{el.TIW} <br />
{el.ON} | {el.Cen} <br />
{el.Skills}
</span>{" "}
<br /> <br />
) )} );
}
}```

You are using same id inside your map. IDs should be unique in a page.
If you have multiple IDs in a page getElementById will return only the first one.
You could change this line
<h5 className="card-title" id="ccc">{el.Course}</h5>
to
<h5 className="card-title ccc" >{el.Course}</h5>
and get the elemnts array like
let ff = document.querySelectorAll('.ccc');
I don't want to get all the elements at once, I need to differentiate
between the elements so that I can get the textContent of each h5 tag
element
ff.forEach((element) => {
console.log(element.textContent); // prints the textContent of each element.
})
I need one textContent per click, as per your solution when I am clicking on one card its showing me the textContent of all the cards
at once
You need to pass the index to postData and get the corresponding node.
Your map have a second parameter called index.
this.state.cData.map((el,index) => (
Pass this index to postData like this
onClick={()=>this.postData(index)}
Get the corresponding node in postData based on the index.
postData(index){
let ff = document.querySelectorAll('.ccc');
const clickedNode = ff[index];
const clickedNodeTextContent = clickedNode.textContent;

Related

Reactjs - onClick works after first click

I am new to react so I apologize in advance if there is an obvious solution to this. But I am attempting to create a reusable search component, in this search component I have an input field that I would like to change the border when clicked upon.
Currently, this css onClick function is only working after the second click. I am not getting any errors in my console so I am currently lost on what the solution is. I've attempted to do some research and I've had no luck.
Here is my code :
const searchBorderChange= () => {
var textareas = document.getElementsByTagName('input');
for (let i=0; i<textareas.length; i++){
// you can omit the 'if' if you want to style the parent node regardless of its
// element type
if (textareas[i].parentNode.tagName.toString().toLowerCase() == 'div') {
textareas[i].onfocus = function(){
this.parentNode.style.borderColor = '#FF0000';
}
textareas[i].onblur = function(){
this.parentNode.style.borderColor = '#B9C0D0';
}
}
}
}
const SearchBar= () => (
<div className="search-nav">
<h1 className="padding-bottom">Search
<br /> Here <span className="text-bold">Lookup Keywords</span></h1>
<div>
<div className="content">
<div className="search-bar">
<i className="fas search fa-search"></i>
<input type="text" name="search" placeholder="Search by Keyword" onClick={searchBorderChange} />
</div>
</div>
</div>
</div>
);
export default SearchBar;
I am expecting the border of my search div to change on the first onClick function.
It's generally a good idea not to alter the document manually in React if you can avoid it. You could keep the input border color in a state variable and change it when it is focused or blurred.
Example
const { useState } = React;
const defaultColor = "#B9C0D0";
const SearchBar = () => {
const [inputColor, setInputColor] = useState(defaultColor);
return (
<div className="search-nav">
<h1 className="padding-bottom">
Search
<br /> Here <span className="text-bold">Lookup Keywords</span>
</h1>
<div>
<div className="content">
<div className="search-bar">
<i className="fas search fa-search"></i>
<input
type="text"
name="search"
placeholder="Search by Keyword"
style={{ border: `1px solid ${inputColor}` }}
onFocus={() => setInputColor("#FF0000")}
onBlur={() => setInputColor(defaultColor)}
/>
</div>
</div>
</div>
</div>
);
};
ReactDOM.render(<SearchBar/>, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Is passing a piece of data from child component to parent component an anti pattern in react?

consider the following example...
I have a component called ChemVisualisation.jsx it is as follows
const ChemVisualization = props => {
const { getChemists, chemists } = props;
useEffect(() => {
getChemists();
}, [getChemists]);
// useState hook is used here
// handleChange sets the name property
// i check for chemists based on name property here and pass it as props to
// SearchableDropdown component where it renders the list
const getChemistId = id => {
console.log(id);
// showing undefined
};
return(
<SearchableDropdown
getName={ChemistAnalytics}
onChange={e => handleChange(e)}
name="name"
value={name}
onSubmit={e => handleSubmit(e)}
entities={result}
handleClick={getChemistId}
/>
);
}
SearchableDropdown.jsx
const SearchableDropdown = props => {
// destructure props here
return(
<div className="control has-icons-left w-3/5 ">
<input
type="text"
placeholder="search"
value={value}
name={name}
onChange={onChange}
className="input"
autoComplete="off"
/>
<span className="icon is-left">
<FontAwesomeIcon icon={faSearch}></FontAwesomeIcon>
</span>
</div>
{entities && (
<div className="dropdown">
<div className="dropdown-content">
{entities.map(r => (
<div
className="dropdown-item text-xl hover:bg-gray-400 w-full"
key={r._id}
onClick={r => handleClick(r._id)}
>
{r.chem_name}
</div>
))}
</div>
</div>
)}
);
}
When I click the drop down item, I'm not getting the id in its parent component.
My question is how do I get the id of the drop down item that I clicked?
Is passing the data from child component to its parent an anti-pattern?
It's normal for child components to call functions their parents provide to them in response to events. The parent then rerenders as necessary with the new information. This keeps the state information fairly "high." More in Lifting State Up in the documentation.
In your example with
<div className="dropdown-content">
{entities.map(r => (
<div
className="dropdown-item text-xl hover:bg-gray-400 w-full"
key={r._id}
onClick={r => handleClick(r._id)}
>
{r.chem_name}
</div>
))}
</div>
the problem is that you're shadowing r (the parameter of the map callback) with a different r (the parameter of the onClick). The first argument the click handler is called with is an event object. If you don't want it, just don't accept the parameter:
<div className="dropdown-content">
{entities.map(r => (
<div
className="dropdown-item text-xl hover:bg-gray-400 w-full"
key={r._id}
onClick={() => handleClick(r._id)}
>
{r.chem_name}
</div>
))}
</div>
The only change there is replacing r with () in onClick={() => handleClick(r._id)}.
onClick={e => handleClick(r._id)}
Use e instead of r.

How to properly search in a list in ReactJS

I am trying to set a simple search operation in a user interface as shown below:
I have a total of 70 react-strap cards and each card contain a vessel with name, type and an image. I would like to search the name of the vessel and have the card related to that vessel to pop-up. All my images are currently contained inside the external database Contentful. Below the fields of interests:
The problem is that I don't know how to write a search function that locate a specific value of a list.
Below the code:
SideBar.js
import React from 'react';
import Client from '../Contentful';
import SearchVessel from '../components/SearchVessel';
class Sidebar extends React.Component {
state = {
ships: [],
};
async componentDidMount() {
let response = await Client.getEntries({
content_type: 'cards'
});
const ships = response.items.map((item) => {
const {
name,
slug,
type
} = item.fields;
return {
name,
slug,
type
};
});
this.setState({
ships
});
}
getFilteredShips = () => {
if (!this.props.activeShip) {
return this.state.ships;
}
let targetShip = this.state.ships.filter(
(ship) => this.props.activeShip.name === ship.name
);
let otherShipsArray = this.state.ships.filter((ship) => this.props.activeShip.name !== ship.name);
return targetShip.concat(otherShipsArray);
};
render() {
return (
<div className="map-sidebar">
{this.props.activeShipTypes}
<SearchVessel />
<pre>
{this.getFilteredShips().map((ship) => {
console.log(ship);
return (
<Card className="mb-2">
<CardImg />
<CardBody>
<div className="row">
<img
className="image-sizing-primary"
src={ship.companylogo.fields.file.url}
alt="shipImage"
/>
</div>
<div>
<img
className="image-sizing-secondary"
src={ship.images.fields.file.url}
alt="shipImage"
/>
</div>
<CardTitle>
<h3 className="thick">{ship.name}</h3>
</CardTitle>
<CardSubtitle>{ship.type}</CardSubtitle>
<CardText>
<br />
<h6>Project Details</h6>
<p>For a description of the project view the specification included</p>
</CardText>
<Row style={{ marginTop: '20px' }}>
<div className="buttoncontainer">
<div className="btn btn-cards">
<a
className="buttonLink"
download
href={ship.projectnotes.fields.file.url}
>
Project Notes
</a>
</div>
<div className="btn btn-cards">
<a className="buttonLink" href={ship.abstract.fields.file.url}>
Abstract
</a>
</div>
</div>
</Row>
</CardBody>
</Card>
);
})}
</pre>
</div>
);
}
}
export default Sidebar;
VesselSearch.js
import React, { Component } from 'react';
export default class SearchVessel extends Component {
render() {
const { value, handleSubmit, handleChange } = this.props;
return (
<React.Fragment>
<div className="container">
<div className="row">
<div className="col-10 mx-auto col-md-8 mt-5 text-center">
<h4 className="text-slanted text-capitalize">Search for Vessel</h4>
<form className="mt-4" onSubmit={handleSubmit}>
<label htmlFor="search" className="text-capitalize">
type vessel separated by comma
</label>
<div className="input-group">
<input
type="text"
name="search"
placeholder="Type name of vessel here"
className="form-control"
value={value}
onChange={handleChange}
/>
<div className="input-group-append">
<button type="submit" className="input-group-text bg-primary text-white">
<i className="fas fa-search" />
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</React.Fragment>
);
}
}
What I have done so far:
1) I tried different combination with the filter function and I think I am close. The problem is that when I operate the search nothing happens and in order to find the card of the vessel I want, I have to scroll down until I find it.
I am running out of ideas and if you see something I didn't catch point me in the right direction for solving this issue.
You're close! I would add a field to your state called 'searchText' and then create a method to filter based on that searchText state item.
getFilteredShips = () => this.state.ships.filter(s => s.name.includes(this.state.searchText)
Then just map over those values to render the cards that match the search text. The cards will update each time the searchText value updates.
this.getFilteredShips().map(ship => ..........
React is famous for re-usable component. You will have all the data of these vessels in an array. You will loop through the array and render the items with card component.And when you search for the specific card you want that vessel to pop out on top.
There are two ways to do it:
You have to run through the array, find the index of that vessel and do whatever it takes to manipulate your array and to make that item at top and re-render your list.
Alternatively render one more component on top of your vessel list as user clicks the search button. You just have to find the item index and render it. This way you don't have to deal with array manipulation. It doesn't matter if you have 80 or 1000 cards.
Please checkout official documentation for array methods, for array slicing and splice.
Hope this is what you are looking for. If you need further help, comment please.

Issues passing data up React tree to change DOM

** This may be a simple answer, I'm new to React, thank you for any help ! **
Back story
I have a modal(bootstrap4) hidden inside the main app with a form inside that, when it rendered, the form is filled out based on the information from the selected recipe (more on that later.)
The recipes are stored locally and with this.state.currentRecipe I know which recipe is being selected (it is used as the index and is set to 0 by default.)
So, using this.state.currentRecipe as the index the first render naturally puts in the first recipe's information.
I attempted to solve this by making a function and passing it down the child components. The recipe-card has all the information and the edit button inside of it. So when the recipe-cards are all rendered by .map() I pass in their index and the function that was passed down in order to change the state of this.state.currentRecipe and re-render the DOM with the form having the new information.
What's wrong
Everything loads however, when I click the edit button the modal pops up with the first recipe always. It will even change this.state.currentRecipe but the DOM doesn't re-render with the proper recipe's information.
How do I get the form's information to update based on which recipe-card I'm in when I click the 'Edit' button?(there is a button in each card).
(and even if it did, would it just hide the modal again?)
Here is the link to the component folder of the repo https://github.com/JeremyWeisener/React-Recipe-box/tree/master/src/components
in case the code below isn't enough information
Here is the inside of the 4 main files I believe matter (cleaned up a bit and put here to make life easier)
app.js
import React, { Component } from 'react';
import RecipeCard from './recipe-card';
import RecipeBook from './recipe-book';
import AddRecipe from './add-recipe';
import RecipeEditer from './edit-recipe';
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
currentRecipe: 0,
recipes: JSON.parse(localStorage.getItem('cookBook')),
updateKey: 0,
counter: 0
}
}
changeRecipe = (arg) => {
this.setState({currentRecipe:arg});
}
render() {
return (
<div>
<div>
<RecipeBook changeRecipe={this.changeRecipe.bind(this)} recipes={this.state.recipes} />
</div>
<div id="popUps">
<AddRecipe />
<RecipeEditer index={this.state.currentRecipe} forglory={this.state} />
</div>
<div id="editPopup">
</div>
</div>
);
}
}
recipe-book.js
import React from 'react';
import RecipeCard from './recipe-card';
const RecipeBook = (props) => {
var changeRecipe = props.changeRecipe;
console.log(props);
const DisplayRecipes = props.recipes.map((recipe, index) => {
return <RecipeCard index={index} key={index+1} recipe={recipe} changeRecipe={changeRecipe.bind(this)} />
})
return (
<div id="accordion" role="tablist" aria-multiselectable="true">
{DisplayRecipes}
<div>
<button className="btn btn-primary" type="button" data-toggle="modal" data-target="#addRecipe"> Add Recipe </button>
</div>
</div>
);
}
export default RecipeBook;
recipe-card.js
import React from 'react';
import Ingredients from './recipe-ingredients';
import Steps from './recipe-steps';
import RecipeEditer from './edit-recipe';
const RecipeCard = (props) => {
const changeRecipe = props.changeRecipe;
return (
<div>
{/*Card Start*/}
<div className="recipe card">
{/*Header*/}
<div className="card-header" role="tab" id={`heading${props.index}`}>
<h5 className="mb-0">
<a data-toggle="collapse" data-parent="#accordion" href={`#collapse${props.index}`} aria-expanded="true" aria-controls={`collapse${props.index}`}>
{props.recipe.title}
</a>
</h5>
</div>
{/*End Header*/}
{/*Collapse Div*/}
<div id={`collapse${props.index}`} className="collapse" role="tabpanel" aria-labelledby={`heading${props.index}`}>
{/*Card IMG*/}
<img className="card-img-top" src="./img/Fried Chik'n-edit1.jpg" />
{/*Card Block*/}
<div className="card-block">
<p className="card-text">
</p>
{/* Ingredients */}
<h3>Ingredients</h3>
<Ingredients parts={props.recipe.ingredients} />
{/* Steps */}
<h3>Steps</h3>
<Steps levels={props.recipe.steps} />
Print Recipe
{/*Edit Button is here*/}
<button onClick={() => {changeRecipe(props.index)}} className="btn btn-success" type="button" data-toggle="modal" data-target="#editRecipe"> Edit Recipe </button>
{/*Edit Button is here*/}
Delete Recipe
</div>
{/*End Card Block*/}
</div>
{/*End Collapsable*/}
</div>
{/*End Card*/}
</div>
);
}
export default RecipeCard;
edit-recipe.js
import React from 'react';
const RecipeEditer = (props) => {
var index = props.index;
var cookBook = JSON.parse(localStorage.getItem("cookBook"));
var editMe = cookBook[props.forglory.currentRecipe];
const UpdateRecipe = () => {
var formData = $('#recipeEditer').serializeArray();
var newRecipe = {};
newRecipe.title = formData[0]['value'];
newRecipe.image = formData[1]['value'];
newRecipe.ingredients = formData[2]['value'].split(',');
newRecipe.steps = formData[3]['value'].split(',');
cookBook[index] = newRecipe;
localStorage.setItem("cookBook", JSON.stringify(cookBook));
}
return (
<div className="modal fade" id="editRecipe" tabIndex="-1" role="dialog" aria-labelledby="editRecipeLabel" aria-hidden="false">
<div className="modal-dialog" role="document">
<div className="modal-content">
<div className="modal-header">
{/* Title */}
<h5 className="modal-title" id="exampleModalLabel">
Edit Recipe
</h5>
<button type="button" className="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="false">×</span>
</button>
</div>
<div className="modal-body">
<form id="recipeEditer" name="editRecipe">
<label htmlFor="name"><h4>Name</h4></label>
<input id="name" className="form-control" name="title" type="text" defaultValue={editMe.title} />
<label htmlFor="image"><h4>Image</h4></label>
<input id="image" className="form-control" name="image" type="text" defaultValue={editMe.image} />
<label htmlFor="ingredients"><h4>Ingredients</h4></label>
<textarea id="ingredients" className="form-control" name="ingredients" rows="4" cols="48" defaultValue={editMe.ingredients}></textarea>
<label htmlFor="steps"><h4>Steps</h4></label><br/>
<textarea id="steps" className="form-control" name="steps" cols="48" rows="4" defaultValue={editMe.steps} ></textarea>
</form>
</div>
<div className="modal-footer">
<button type="button" className="btn btn-secondary" data-dismiss="modal">Close</button>
{/*Submit Button*/}
<button onClick={UpdateRecipe} type="button" className="btn btn-success" data-dismiss="modal">Change Recipe</button>
{/*Submit Button*/}
</div>
</div>
</div>
</div>
);
}
export default RecipeEditer;
thank you very much if you even glanced, if there is any more information that can help please don't hesitate to ask !
You are binding changeRecipe to this in recipe-book.js, thus setting its context to RecipeBook.
Try changing
<RecipeCard index={index} key={index+1} recipe={recipe} changeRecipe={changeRecipe.bind(this)} />
to
<RecipeCard index={index} key={index+1} recipe={recipe} changeRecipe={changeRecipe} />
You may not even have to use bind in app.js, since changeRecipe is defined as an arrow function.

send data through onclick event from child to parent React js?

I am new to React js I am pretty confused that how to send data through onclick event from child to parent component.
Parent Component
...
onTurn(id){
console.log(id)//undefined
}
renderCardsList(){
const {cardsList} = this.props
return cardsList.get('cards').map(({id,front_image}) => {
return <Card
image={front_image}
onTurn={this.onTurn}
/>
})
}
...
Child Component
const Card = (props) => {
return(
<div className="singleCard">
<div className="imageDiv">
<img src={props.image} alt="work" />
</div>
<div className="bottombar">
<span onClick={e => props.onTurn(props.id)} className="glyphicon glyphicon-refresh" />
<span className="glyphicon glyphicon-pencil" />
<span className="glyphicon glyphicon-pushpin" />
</div>
</div>
)
};
Your onClick expects an id property on props:
onClick={e => props.onTurn(props.id)}
But you're not providing one:
return <Card
image={front_image}
onTurn={this.onTurn}
/>
So naturally, props.id in the card's render is undefined.
If you want the card to have an id on props, you'll need to specify one, e.g.:
return <Card
image={front_image}
onTurn={this.onTurn}
id={/*something*/}
/>

Categories

Resources