react Creating dynamic components - javascript

I have li key={Info.id}>
I want to make <div>{NumberStatus} </div> output the data corresponding to the id value when the corresponding id value is clicked.
In summary, when 'one li tag text' is clicked, the div will output '{NumberStatus}' value corresponding to 'one text', and {functionInfolabels} corresponding value.
How would you like to write code?
let functionInfolabels = ProductDetail && ProductDetail.chart?.functionalsInfo?.[0].materialsInfo?.[0].ingredientsInfo?.map(array => array.ingredientDisplayText);
let NumberStatus = ProductDetail && ProductDetail.chart?.functionalsInfo?.[0].materialsInfo?.[0].ingredientsInfo?.map(array => array.chartStatus)
return (
{ProductDetail && ProductDetail.chart?.functionalsInfo?.length ?
ProductDetail.chart?.functionalsInfo.map(Info => (
<li key={Info.id}>{Info.seedShapeDisplayText}</li>
)) : <li>There is not</li>}
// seedShapeDisplayText; // one two three four five ...
// <li key===1 onClick <div> Hi Number Minsu, Bar : 1 </div>
// <li key===2 onClick <div> Hi Number Jenny, Bar : 3 </div>
....
<div>
Hi Number {NumberStatus} // one : Minsu, two : Jenny, three : Youngmin, four : Jiho ...
<Bar
labels={functionInfolabels} // one : 1, two: 3, three: 124 ....
/>
</div>
)

The most idiomatic way is to set state value when your <li> element is clicked, and then render that state value.
Using a functional component, you can use the useState hook in a straight forward manner.
Please note: I took some liberty with your code sample, and added a button tag, and a helper function to set the value.
const MyComponent = () => {
const [infoText, setInfoText] = React.useState('');
const onButtonClick = (text) => {
setInfoText(text);
}
return (
{ProductDetail && ProductDetail.chart?.functionalsInfo?.length ?
ProductDetail.chart?.functionalsInfo.map(Info => (
<li key={Info.id}><button onClick={e=>onButtonClick(Info.seedShapeDisplayText)}>{Info.seedShapeDisplayText}</button></li>
)) : <li>There is not</li>}
// seedShapeDisplayText; // one two three four five ...
<div>
Hi Number {infoText} // one : Minsu, two : Jenny, three : Youngmin, four : Jiho ...
</div>
)
}

If you could explain a bit more on your question, I think it will be easy to understand and answer.
Here is the solution as I got your question,
in here I'm using the state as numberStatus and when your click on any of the li i'm setting the numberStatus state with the numberStatus data of the selected li.
also in the bottom rendering part I'm checking that the state of numberStatus is having a value and if only I make it display.
function MyComponent() {
const [numberStatus, setNumberStatus] = React.useState("");
const dataList = [
{ title: "Title 01", numberStatus: "one" },
{ title: "Title 02", numberStatus: "two" },
{ title: "Title 03", numberStatus: "three" },
];
return (
<div>
<ul>
{dataList.map((val, key) => {
return (
<li key={key} onClick={() => setNumberStatus(val.numberStatus)}>
{" "}
{val.title}{" "}
</li>
);
})}
</ul>
{numberStatus && <div>Hi Number {numberStatus}</div>}
</div>
);
}

Related

React component function call only updates one component instance

I have a component called RightTab like this
const RightTab = ({ data }) => {
return (
<div className="RightTab flex__container " onClick={data.onClick}>
<img src={data.icon} alt="Dashboard Icon" />
<p className="p__poppins">{data.name}</p>
{data.dropDown === true ? (
<div className="dropdown__icon">
<img src={Assets.Arrow} alt="Arrow" />
</div>
) : (
<div className="nothing"></div>
)}
</div>
);
};
export default RightTab;
The tab has an active state in its CSS like this
.RightTab.active {
background-color: var(--primaryGreen);
}
as you have seen it changes the color when an active class is added. I have an array in the parent component that I pass down to the child component as props. Here is the array
const dataArray = [
{
name: "Dashboard",
icon: Assets.Dashboard,
dropDown: false,
onClick: handleDashBoardClick,
},
{
name: "Inventory",
icon: Assets.Inventory,
dropDown: true,
onClick: handleInventoryClick,
},
{
name: "Reports",
icon: Assets.Reports,
dropDown: true,
onClick: handleReportsClick,
},
];
Here is how I pass the props down.
<RightTab data={dataArray[0]} />
<RightTab data={dataArray[1]} />
<RightTab data={dataArray[2]} />
The data prop passed into the component is an object containing a function call as one of its properties like this. I have an onclick attribute on the child components' main container that is supposed to call the respective function.
The function is what adds the active class to make the background change color. However each time I click on the component it only changes the background of the first occurrence. And as you may have noticed I call the component thrice. No matter which component I click only the first ones background changes.
Here is an example of the function that is on the prop object.
const handleDashBoardClick = () => {
const element = document.querySelector(".RightTab");
element.classList.toggle("active");
};
I don't get what I'm doing wrong. What other approach can I use?
Although you use the component 3 times, it doesn't mean that a change you make in one of the components will be reflected in the other 2, unless you specifically use a state parameter that is passed to all 3 of them.
Also, the way you add the active class is not recommended since you mix react with pure js to handle the CSS class names.
I would recommend having a single click handler that toggles the active class for all n RightTab components:
const MainComponent = () => {
const [classNames, setClassNames] = useState([]);
const handleClick = (name) =>
{
const toggledActiveClass = classNames.indexOf('active') === -1
? classNames.concat(['active'])
: classNames.filter((className) => className !== 'active');
setClassNames(toggledActiveClass);
switch (name) {
case 'Dashboard';
// do something
break;
case 'Inventory':
// ....
break;
}
}
const dataArray = [
{
name: "Dashboard",
icon: Assets.Dashboard,
dropDown: false,
onClick: handleClick.bind(null, 'Dashboard'),
},
{
name: "Inventory",
icon: Assets.Inventory,
dropDown: true,
onClick: handleClick.bind(null, 'Inventory'),
},
{
name: "Reports",
icon: Assets.Reports,
dropDown: true,
onClick: handleClick.bind(null, 'Reports'),
},
];
return (
<>
{dataArray.map((data) =>
<RightTab key={data.name}
data={data}
classNames={classNames} />)}
</>
);
};
const RightTab = ({ data, classNames }) => {
return (
<div className={classNames.concat(['RightTab flex__container']).join(' ')}
onClick={data.onClick}>
<img src={data.icon} alt="Dashboard Icon" />
<p className="p__poppins">{data.name}</p>
{data.dropDown === true ? (
<div className="dropdown__icon">
<img src={Assets.Arrow} alt="Arrow" />
</div>
) : (
<div className="nothing"></div>
)}
</div>
);
};

Map an object by looping through alphabet

I want to be able to loop through each letter of the alphabet and then if the StoreName in my json object matches that looped letter then map it to an li tag. I want to do this to every letter in the alphabet and if there is no match just display nothing.
Hope this makes sense.
for example:
This is what I have so far.
import { data } from './data/data';
import { useState } from 'react';
export default function Home() {
const [values, setValues] = useState(data);
return (
<div>
{values.filter(store => store.storeName.startsWith('a'))
.map((item, index) => (
<li key={index}>
{item.storeName}
</li>
))}
</div>
)
}
Json object:
export const data = [
{
storeId: 1,
storeName: 'addidas',
},
{
storeId: 2,
storeName: 'axels',
},
{
storeId: 3,
storeName: 'blix',
},
{
storeId: 4,
storeName: 'benis',
},
{
storeId: 5,
storeName: 'clives',
},
];
I know i could filter and map each letter manually but there must be a way to loop through the alphabet and map?
example output:
A
Addidas
axels
B
blix
benis
c
clives
d
So I want the letter to display and then the results for each item thats been looped that starts with that looped letter.
You could make a list of alphabet and map through that
import { data } from "./data/data";
import React, { useState } from "react";
const alphabet = "abcdefghijklmnopqrstuvwxyz";
export default function Home() {
const [values, setValues] = useState(data);
return (
<div>
{alphabet.split("").map((c) => {
return (
<>
<p>{c}</p>
{values
.filter((store) => store.storeName.startsWith(c))
.map((item, index) => (
<li key={index}>{item.storeName}</li>
))}
</>
);
})}
</div>
);
}
It sounds like you want to group your datapoints by initial letter. The algorithm you describe will work, but it's not the one I'd choose.
Here's how I would do it:
Sort all stores alphabetically
Iterate through the sorted stores, keeping track of the most recent initial letter
every time the current store's initial letter is different from the most recent, insert a grouping boundary (e.g. closing the previous <li> and opening a new one)
Finally, don't implement this within the JSX. It may not be the most complex algorithm in the world, but nobody will appreciate it if this data-preparation logic is intermixed with a bunch of display literals. Instead, work with the data as pure data. Bearing that in mind, "inserting a grouping boundary" just means changing which group you insert the store into.
Here's a run at the implementation. (I haven't tested it.)
function MyComponent( props ) {
// group the stores by initial letter
// produces a list of { initialLetter: 'A', stores: [ store1, store2, store3 ] }
let prevInitialLetter = null
let groupedStores = stores
.sort(sort_storeName_alpha)
.reduce(( groups, store ) => {
let myInitialLetter = store.storeName.charAt(0)
let belongsInNewGroup = myInitialLetter !== prevInitialLetter
if(belongsInNewGroup) {
// create a new group and add this store as its first member
groups.push({ initialLetter: myInitialLetter, stores: [ store ] })
} else {
// add this store to the previous group
groups[groups.length - 1].stores.push(store)
}
return groups
}, [])
return (
<ol className="GroupedStores">
{
groupedStores.map(group => (
<li className="storeGroup" key={group.initialLetter}>
{group.initialLetter}
<ol className="stores">
{
group.stores.map(store => (
<li key={store.storeName}>
{store.storeName}
</li>
)
}
</ol>
</li>
))
}
</ol>
)
}
// this is the crudest-possible text sort; depending on your needs,
// you may need to perform a locale-aware comparison
function sort_storeName_alpha = ( a, b ) => {
if(a.storeName < b.storeName) return -1
if(b.storeName < a.storeName) return 1
return 0
}

React dropdown only show value if clicked on specific data using e.target?

So I'm trying to make this accordion and right now it maps through my data and essentially shows all the dropdown values when I click on my h1, but I only want it to show the dropdown for the specific h1 I clicked and not display the entire values
const Accordion = () => {
const [clicked, setClicked] = useState(false);
const toggle = e => {
if (e.target) {
setClicked(!clicked);
}
console.log(e.target);
};
return (
<div>
{Data.map((item, index) => {
return (
<div key={index}>
<h1 onClick={e => toggle(e)}>{item.question}</h1>
{clicked ? (
<div>
<p>{item.answer}</p>
</div>
) : null}
</div>
);
})}
</div>
);
};
My toggle function shows the correct e.target value of the h1 I clicked on, but I don't know how to only display that h1 value when I click on it instead of showing all h1's values
Here is my data file
export const Data = [
{
question: 'What 1?',
answer: 'answer 1'
},
{
question: 'What 2',
answer: 'answer 2'
},
{
question: 'What 3?',
answer: 'answer 3'
}
];
How would I refactor my code to only show question 1 and answer 1, vs showing all questions and answers on click?
You need to track which div has been clicked . You can do that assigning id in your data like this:
const Data = [
{
question: "What 1?",
answer: "answer 1",
id: 0
},
{
question: "What 2",
answer: "answer 2",
id: 1
},
{
question: "What 3?",
answer: "answer 3",
id: 2
}
];
Then send this id on toggle method and check if id is equal to selected one or not like this:
const toggle = (e, id) => {
if (e.target) {
setSelected(id === selected ? null : id); //selected and id are same it means close the toggle
}
console.log(e.target);
};
and inside render check like this:
{selected === item.id ? (
<div>
<p>{item.answer}</p>
</div>
) : null}
Here is full demo and code: https://codesandbox.io/s/toggle-accordian-eplpw
My suggestion would be to a separate component with its own state and use that for each accordion item.
const AccordionItem = (props) => {
const [clicked, setClicked] = useState(false);
return (
<div>
<h1 onClick={() => setClicked(! clicked)}>{props.question}</h1>
{clicked && (
<div>
<p>{item.answer}</p>
</div>
)}
</div>
);
};
const Accordion = () => {
return (
<div>
{Data.map((item, index) => {
return (
<AccordionItem
key={index}
question={item.question}
answer={item.answer}
/>
);
})}
</div>
);
};
You have to put the clicked question in state rather than just whether something was clicked or not. E.g. https://codesandbox.io/s/summer-night-4uulw?file=/src/App.js
const Data = [
{
question: 'What 1?',
answer: 'answer 1',
},
{
question: 'What 2',
answer: 'answer 2',
},
{
question: 'What 3?',
answer: 'answer 3',
},
];
const Accordion = () => {
const [activeQn, setActiveQn] = useState(null);
const toggle = ( index ) => {
// If the clicked qn is already active, then collapse it
if ( activeQn === index ) {
return setActiveQn(null)
}
// Otherwise show the answer to the clicked qn
setActiveQn(index)
};
return (
<div>
{Data.map((item, index) => {
return (
<div key={index}>
<h1 onClick={() => toggle(index)}>{item.question}</h1>
{activeQn === index ? (
<div>
<p>{item.answer}</p>
</div>
) : null}
</div>
);
})}
</div>
);
};
That way, only the clicked answer will show and if the user wishes to collapse the answer, that can also be done.

How to add an image in the first react component and ignore in rest of the components using map function

I'm trying to add content into a component using json file where I require only the first component to have the cover image and rest of the components to ignore it.
Schema: "TrackList": [
{
"CourseNo": "1",
"CourseName": "C++ Programming with DataStructures",
"CoverImg":"example.com/cover.jpg"
},
{
"CourseNo": "2",
"CourseName": "Competitive Programming"
},
{
"CourseNo": "3",
"CourseName": "Java"
}
]
the below code renders as required but it also adds blank image into components except the first one.
render(){
const { Data } = this.props;
const Courses = Data.TrackList.map(course => {
return (
<li>
<span>Course {course.CourseNo}</span>
<a href='#path-10'>{course.CourseName}</a>
<img src={course.CoverImg}/>
</li>
)
});
return(
<div className='col-md-4 right-pannel'>
<ul>
{Courses}
</ul>
</div>
)
}
Array.prototype.map calls the provided callback function with the current array index (as the second argument). The first index of an array is 0, so you just need to add some logic that makes sure the image is only added when the index is equal to 0, like this:
const Courses = Data.TrackList.map((course, i) => {
return (
<li>
<span>Course {course.CourseNo}</span>
<a href='#path-10'>{course.CourseName}</a>
{ i === 0 && <img src={course.CoverImg}/> }
</li>
)
});
And here's a slightly modified version of your code turned into a runnable snippet:
const Data = {"TrackList": [{"CourseNo": "1","CourseName": "C++ Programming with DataStructures","CoverImg":"example.com/cover.jpg"},{"CourseNo": "2","CourseName": "Competitive Programming"},{"CourseNo": "3","CourseName": "Java"}]}
const Courses = Data.TrackList.map((course, i) =>
<li>
<span>CourseNo: {course.CourseNo}</span>
<span> | CourseName: {course.CourseName}</span>
{ i === 0 && <span> | CoverImg: {course.CoverImg}</span> }
</li>
);
ReactDOM.render(
<div className='col-md-4 right-pannel'>
<ul>
{Courses}
</ul>
</div>,
document.body
);
<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>
Or more generic solution can be the below code, as can be seen in your data, other object does not have CoverImg key.
const Courses = Data.TrackList.map(course => {
return (
<li>
<span>Course {course.CourseNo}</span>
<a href='#path-10'>{course.CourseName}</a>
{course.CoverImg && <img src={course.CoverImg}/>}
</li>
)
});
You can put condition with && to render only object which is having image
Optimized
-Unique key for every row
-&& condition for available image path
-another way removing return(){}
Code
render() {
const { Data } = this.props;
const Courses = Data.TrackList.map(course => (
<li key={course.CourseNo}>
<span>Course {course.CourseNo}</span>
{course.CourseName}
{course.CoverImg && <img src={course.CoverImg} alt="imgicon" />}
</li>
));
return (
<div className="col-md-4 right-pannel">
<ul>{Courses}</ul>
</div>
);
}

Convert string to JSON object for use in React.js style tag style={}

I am building a React.js application where I want to allow users to input styles in a text area which will affect the style of another element.
First, I have a row component which looks like this:
function Row(props) {
return (
<tr>
<td width="50%">
<textarea
style={{}}
value={props.style}
onChange={props.onStyleChange}>
</textarea>
</td>
<td><div className="">Row {props.id+1}</div></td>
</tr>
)
};
I am iterating through a list of rowData to populate my rows, which can be found here:
{this.state.rowData.map(function(row, index) {
return (
<Row
id={row.id}
style={row.style}
onStyleChange={function(e) {this.onStyleChange(e, row.id)}.bind(this)}/>
);
}.bind(this))}
Then in my onStyleChange function I have:
onStyleChange: function(e, id) {
this.state.rowData[id].style = e.target.value;
this.setState(this.state);
}
So as a user enters data into the the textarea, it adds to the i'th element in my rowData array. Assuming its the 0th row and the user enters "Hello" into the text area, then rowData[0].style = "Hello".
However, I would like to be able to do something like this: style={{props.style}} inside my Row component. But because it is currently a string it does not work. I have also tried style={JSON.parse(props.style)} which throws an error every time I add a new row because props.style='{}'. The error reads Uncaught SyntaxError: Unexpected token f in JSON at position 1
Always grateful for any help. There's got to be a much easier way to do this. Thank you.
Two steps to convert inline-style toobject-style` as restricted by React :
Parse the string to be a JSON object.
Convert the keys of this object to camel case (z-index becomes zIndex.. so on)
Congrats! i wrote the algorithm , check below :
const example1= "position:absolute;h-index:9001;"
const example2 = "-ms-transform: rotate(20deg);-webkit-transform: rotate(20deg);";
// 2ⁿᵈ step logic
const camelize = (string) => string.replace(/-([a-z])/gi,(s, group) => group.toUpperCase());
// 1ˢᵗ step logic which calls the 2ⁿᵈ step logic
const style2object = (style) => style.split(';').filter(s => s.length)
.reduce((a, b) => {
const keyValue = b.split(':');
a[camelize(keyValue[0])] = keyValue[1] ;
return a;
} ,{});
console.log("Example 1 : ", example1, '\n',
style2object(example1)
)
console.log("Example 2 : ", example2, '\n',
style2object(example2)
)
If it is helpful the style attribute needs an object like {"color": "blue"}
I made a demo with your code the only thing that escapes me is how to update with the onChange event.
function Row(props) {
const styleValue = JSON.stringify(props.style);
return (
<tr>
<td width="50%">
<textarea
style={props.style}
defaultValue={styleValue}
onChange={props.onStyleChange}/>
</td>
<td><div className="">Row {props.id+1}</div></td>
</tr>
)
};
class App extends React.Component {
state = {
rowData: [{
id: 1,
style: {
color: 'blue'
}
}, {
id: 2,
style: {
color: 'red',
backgroundColor:'#000'
}
}]
};
onStyleChange(e, id) {
const rows = this.state.rowData;
const row = rows.find(row => row.id === id);
const index = rows.indexOf(row);
rows[index]['style'] = JSON.parse(e.target.value);
this.setState({
rowData: rows
});
}
render() {
return (
<table>
<tbody>
{
this.state.rowData.map(function(row, index) {
return (
<Row
id={row.id}
key={index}
style={row.style}
onStyleChange={function(e) {this.onStyleChange(e, row.id)}.bind(this)}/>
);
}.bind(this))
}
</tbody>
</table>
)
}
}
ReactDOM.render(<App/>, document.getElementById('app'));
http://codepen.io/GGarciaSeco/pen/vgVEGX?editors=0010
You can take a look to the React documentation in the next link
https://facebook.github.io/react/docs/dom-elements.html#style

Categories