im currently trying to figure out how to solve this problem:
As you can see, the search bar value is initialized to "TSLA", however, instead of replacing the TSLA data widget with an AAPL data widget, below the TSLA widget it shows a new widget for "A", "AA", "AAP" and finally "AAPL", rendering a new widget as I type each letter of the ticker into the search bar.
Here is the code for the search bar, with the state variable "value" being what I pass to my widgets:
class OneStockData extends React.Component {
constructor(props) {
super(props);
this.state = {
value: 'TSLA',
clicked: false
}
this.handleChange = this.handleChange.bind(this)
}
toggleBtnHandler = () => {
return this.setState({
clicked:!this.state.clicked
})
}
handleChange (event) {
this.setState({value: event.target.value })
}
render() {
const styles = ['button'];
let text = 'Search';
return (
<div>
<Container fluid>
<Row>
<Card>
<CardTitle className="text-uppercase text-muted h6 mb-0">Enter Stock Ticker: </CardTitle>
<input type="text" value={this.state.value} onChange={this.handleChange} />
<div>
<button className="button" onClick={this.toggleBtnHandler}>{text}</button>
</div>
</Card>
<Card>
<SSIWidget value = {this.state.value}/>
</Card>
</Row >
And here is the code in the SSIWidget:
class SSIWidget extends React.Component {
constructor(props){
super(props);
}
AddWidget = () => {
const script = document.createElement('script');
script.src ="https://s3.tradingview.com/external-embedding/embed-widget-symbol-info.js";
script.async = true;
script.innerHTML = JSON.stringify(
{
"symbol": this.props.value,
"width": 1000,
"locale": "en",
"colorTheme": "light",
"isTransparent": false
}
)
document.getElementById("myContainer6").appendChild(script);
}
componentDidMount() {
this.AddWidget();
}
componentDidUpdate(prevProps) {
if(prevProps.value !== this.props.value) {
this.AddWidget();
}
}
render() {
return(
<div id="myContainer6">
<div className="tradingview-widget-container">
<div className="tradingview-widget-container__widget">
</div>
</div>
</div>
);
}
}
There are 2 problems going on:
It renders an new "SSIWidget" as each letter of the ticker is typed, and stacks them below each other. This is a big no-no, I want the widget where TSLA data is being shown to be the ONLY SSIWidget, and the data replaced by each new ticker after I click "Search"
As I clear the search bar, I would want the widget to show the value of "TSLA" as it was initialized to be.
I have tried different search bars and a few resourced on "onClick", but haven't yet found a solution to my issue. Can someone here point me in the right direction?
Maintain another state say searchedValue and update it upon search button click.
Refactored code
class OneStockData extends React.Component {
constructor(props) {
super(props);
this.state = {
value: "TSLA",
searchedValue: "TSLA", //<------- here
clicked: false,
};
this.handleChange = this.handleChange.bind(this);
}
toggleBtnHandler = () => {
return this.setState({
clicked: !this.state.clicked,
searchedValue: this.state.value === "" ? "TSLA" : this.state.value, //<------- here
});
};
handleChange(event) {
this.setState({ value: event.target.value });
}
render() {
const styles = ["button"];
let text = "Search";
return (
<div>
<Container fluid>
<Row>
<Card>
<CardTitle className="text-uppercase text-muted h6 mb-0">
Enter Stock Ticker:{" "}
</CardTitle>
<input
type="text"
value={this.state.value}
onChange={this.handleChange}
/>
<div>
<button className="button" onClick={this.toggleBtnHandler}>
{text}
</button>
</div>
</Card>
<Card>
<SSIWidget value={this.state.searchedValue} /> //<------- here
</Card>
</Row>
</Container>
</div>
);
}
}
Edit: Follow up qn based on comment.
You are appending the child. Before appending you need to clear children.
AddWidget = () => {
const script = document.createElement('script');
script.src ="https://s3.tradingview.com/external-embedding/embed-widget-symbol-info.js";
script.async = true;
script.innerHTML = JSON.stringify(
{
"symbol": this.props.value,
"width": 1000,
"locale": "en",
"colorTheme": "light",
"isTransparent": false
}
)
document.getElementById("myContainer6").innerHTML = ''; //<----here
document.getElementById("myContainer6").appendChild(script);
}
Related
Hello peepz of the web,
I've ran into mysterious corridor, which's too dark for me to see what the hell I'm going.. would love someone's flashlight to shine the way.
I have created a simple, poor to do list program.
It's combined from Task.js, TaskList.js and NewTaskForm.js.
From the NewTaskForm.js I'm retrieving input, passing it to the parent, TaskList.js.
On TaskList.js I'm adding a key to the task object:
handleSubmit(task2add){
let keyid = v4();
console.log(keyid);
let task2addWithID = { ...task2add, id: {keyid}, key: {keyid}};
this.setState(st => ({
todoArr: [...st.todoArr, task2addWithID],
}));
}
Now, in TaskList.js, in my render function, I'm creating Tasks:
render() {
let tasklist = this.state.todoArr.map(task => (
<div>
<Task
taskTitle={task.title}
taskText={task.text}
key={task.key}
id={task.id}
handleRemove={this.handleRemove}
handleEdit={this.handleEdit}
/>
</div>
));
return (
<div>
<h1>TaskList</h1>
<div className='TaskList'>
<div className='TaskList-title'>
{tasklist}
</div>
<NewTaskForm key={1} handleSubmit={this.handleSubmit}/>
</div>
</div>
)
}
now, what is so confusing for me is why when I'm doing in my Tasks.js class:
console.log(this.props.id);
it prints me an object?
I would expect it to.. print me a value? where along the way did it wrap it with an object?
Full (shitty) code below.
Plus #1, if anyone knows to tell me why still I get the warning the key, even though, at least for my poor experience, I have given it a key?
Plus #2, why even when I send the handleRemove function in TaskList.js the proper id, it doesn't erase the bloody task? :-(
Regards!
Task.js
import React, { Component } from 'react'
import './Task.css';
export default class Task extends Component {
constructor(props){
super(props);
this.handleRemove = this.handleRemove.bind(this);
}
handleRemove(evt){
evt.preventDefault();
console.log("MY ID MANNN");
console.log(this.props.id);
this.props.handleRemove(this.props.id.keyid);
console.log("CALLED");
}
render() {
return (
<div className='Task'>
<div className='Task-title'>
{this.props.taskTitle}
</div>
<div className='Task-text'>
{this.props.taskText}
</div>
<div>
<button className='Task-buttons'>Edit</button>
<button className='Task-buttons' onClick={this.handleRemove}>Delete</button>
</div>
</div>
)
}
}
NewTaskForm.js:
import React, { Component } from 'react';
import {v4} from 'uuid';
export default class NewTaskForm extends Component {
constructor(props){
super(props);
this.state = {
title: "", text: "", editing: false
}
this.handleSubmit = this.handleSubmit.bind(this);
this.handleChange = this.handleChange.bind(this);
// this.handleRemove = this.handleRemove.bind(this);
}
handleSubmit(evt){
evt.preventDefault();
// let stateWithID = { ...this.state, id: useId()};
this.props.handleSubmit(this.state);
this.setState({
title: "",
text: ""
})
}
handleChange(evt){
evt.preventDefault();
this.setState({
[evt.target.name]: evt.target.value
});
}
render() {
return (
<div>
<h2>Insert new Task:</h2>
<form onSubmit={this.handleSubmit}>
<label htmlFor="title">Title</label>
<input
name='title'
id='title'
type='text'
onChange={this.handleChange}
value={this.state.title}
/>
<div>
<label htmlFor="text">text</label>
<input
name='text'
id='text'
type='text'
onChange={this.handleChange}
value={this.state.text}
/>
</div>
<button>Submit</button>
</form>
</div>
)
}
}
TaskList.js
import React, { Component } from 'react'
import NewTaskForm from './NewTaskForm';
import Task from './Task';
import './TaskList.css';
import {v4} from 'uuid';
export default class TaskList extends Component {
constructor(props){
super(props);
this.state = {
todoArr: [
// {title: "test", text: "this shit", key: "", id: ""},
// {title: "test2", text: "this shi2", key: "", id: ""},
// {title: "test3", text: "this shi3", key: "", id: ""}
],
isEditing: false,
}
this.handleSubmit = this.handleSubmit.bind(this);
this.handleRemove = this.handleRemove.bind(this);
this.handleEdit = this.handleEdit.bind(this);
}
handleSubmit(task2add){
let keyid = v4();
console.log(keyid);
let task2addWithID = { ...task2add, id: {keyid}, key: {keyid}};
this.setState(st => ({
todoArr: [...st.todoArr, task2addWithID],
}));
}
handleRemove(keyid){
console.log("IN HANDLE REMOVE");
console.log(keyid);
this.setState(st => ({
todoArr: st.todoArr.filter(n => n.keyid !== keyid )
}));
}
handleEdit(id){
}
render() {
let tasklist = this.state.todoArr.map(task => (
<div>
<Task
taskTitle={task.title}
taskText={task.text}
key={task.key}
id={task.id}
handleRemove={this.handleRemove}
handleEdit={this.handleEdit}
/>
</div>
));
return (
<div>
<h1>TaskList</h1>
<div className='TaskList'>
<div className='TaskList-title'>
{tasklist}
</div>
<NewTaskForm key={1} handleSubmit={this.handleSubmit}/>
</div>
</div>
)
}
}
Because you have created an object here:
{ ...task2add, id: {keyid}, key: {keyid}};
{keyid}
is the same as
{keyid: keyid}
You wanted to do this:
{ ...task2add, id: keyid, key: keyid};
I'm assuming its because you're setting the state like this:
let task2addWithID = { ...task2add, id: {keyid}, key: {keyid}};
The id attribute is assigned an object. If you did:
let task2addWithID = { ...task2add, id: keyid, key: {keyid}};
console logging id should print out a string
class Services extends Component {
constructor(props) {
super(props);
this.state = {showoffer: false};
}
showOffers=( )=>{
this.setState({showoffer: !this.state.showoffer});
}
render() {
return (
<div className="OSServicesContainer">
<img className="OSlogomark" src={logomark} alt="logo mark" />
<article className="OssHeadingText">OOM INTERIORS OFFERS</article>
{offersdata.map((offers,index)=>{
return ( <div key={index} className="OssoffersContainermain">
<div className="OssoffersContainer">
<div className="OssofferHeadingmain">
<article className="OssofferHeading">{offers.heading}</article>
</div>
<article className="OssofferText">{offers.subheading}</article>
<div className="OssofferViewbtnmain">
<article key={index} className="OssofferViewbtn" onClick={this.showOffers}>{this.state.showoffer?"View Less":"View More"}</article>
</div>
</div>
{!this.state.showoffer?
null:
<div className="OssOfferSubCompmain">
{offers.offersub.map((offer,key) =>{
return <OssOfferSubComp ofrtext={offer.text} ofrsubtext={offer.subtext} />
})}
</div>}
</div>
)
})}
</div>);
}
}
export default Services;
Above is my code
i want to call showoffer function and update only that element clicked
please what shall i do it is triggering all elements
how to trigger single element??
You can try something like this:
`class Services extends Component {
constructor(props) {
super(props);
this.state = {showoffer: 0};
}
showOffers = ( offerIndex ) => {
this.setState({showoffer: offerIndex});
}
hideOffers = () => {
this.setState({showoffer: 0});
}
render() => {
...
<div className="OssofferViewbtnmain">
<article key={index} onClick={ () => this.showOffers(index) }>
{this.state.showoffer?"View Less":"View More"}
</article>
</div>
...
{
this.state.showOffer && this.state.showOffer === index
? // then show it
: ''
}
}`
Hey if you wish to have multiple items open at the same time you can do something like this where you mutate the mapped item to track show hide state. I have added a visible property to the list item that keeps track if the item is open or closed:
import React, { Component } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
class App extends Component {
state = {
items: [
{ header: "Test 1", extra: "Some extra content A" },
{ header: "Test 2", extra: "Some extra content B" },
{ header: "Test 3", extra: "Some extra content C" }
]
};
onItemClick(index) {
const selected = this.state.items[index];
this.setState({
items: [
...this.state.items.slice(0, index),
{ ...selected, visible: !selected.visible },
...this.state.items.slice(index + 1)
]
});
}
render() {
return (
<div>
<ul>
{this.state.items.map((item, index) => {
return (
<li
key={index}
style={{ cursor: "pointer" }}
onClick={() => this.onItemClick(index)}
>
<h3>{item.header}</h3>
{item.visible ? <div>{item.extra}</div> : null}
</li>
);
})}
</ul>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
https://codesandbox.io/s/busy-germain-hdmrn
I tried a lot of ways to make it works but it didn't. Probably I don't understand the React idea or I missed something. My child via callback try to modify the global/parent state and then I got the well known error.
index.js:1452 Warning: Can't call setState (or forceUpdate) on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.
I try to use variable _isMounted so before setState I did:
if (this.isMounted()) {
this.setState({...});
}
Unfortunately it doesn't work.
The problem is when the function this.handleChangeAlgorithms is invoked from the child.
My code
class ConfigurationForm extends Component {
constructor(props) {
super(props);
this.state = {
choosenAlgorithm: null,
htmlType: null,
customHtml: null,
isOpenModal: false,
generatedHTMlConfig: {
headers: 1,
paragraphs: 1,
buttons: 1,
links: 1,
inputs: 1,
images: 1,
},
};
}
handleChangeAlgorithms = event => {
let algorithmName = event.target.value;
let newChoosenAlgorithm = this.props.algorithms.filter(
e => e.value === algorithmName,
)[0];
// this.setState({choosenAlgorithm: newChoosenAlgorithm});
this.props.callbackConfigurationForm(algorithmName);
};
handleChangeHtmlType(event) {
let newHtmlType = event.target.value;
console.log(newHtmlType);
if (newHtmlType === "default") {
this.setState({ isOpenModal: true });
} else {
this.setState({ htmlType: newHtmlType });
}
}
handleUserFile(event) {
let file = event.target.files[0];
this.setState({ customHtml: file });
this.props.callbackConfigurationFormPreview(file);
}
handleSubmit(event) {
event.preventDefault();
let htmlContent = null;
let htmltypeKey = Object.keys(HtmlType).find(
key => HtmlType[key] === HtmlType.default,
);
console.log(htmltypeKey);
// if (this.state.htmlType === 'default'){
// htmlContent = this.loadTemplate();
htmlContent = generateHTML(this.state.generatedHTMlConfig);
console.log(htmlContent);
// } else {
// htmlContent=this.state.customHtml;
// }
let config = {
algorithm: this.state.choosenAlgorithm,
html: htmlContent,
};
console.log(config);
this.props.callbackConfigurationForm(config);
}
loadTemplate() {
let loadedTemplate = null;
let file = "html-templates/example.html";
let request = new XMLHttpRequest();
request.open("GET", file, false);
request.send(null);
if (request.status === 200) {
loadedTemplate = request.responseText;
}
return loadedTemplate;
}
renderHtmlTypesList = () => {
let list = [];
for (const htmlKey of Object.keys(HtmlType)) {
list.push(
<option key={htmlKey} value={htmlKey}>
{HtmlType[htmlKey]}
</option>,
);
}
return list;
};
generateHTMLFromPopup() {
this.setState({ isOpenModal: false });
}
changeHeaders(event, type) {
console.log(type);
console.log(event.target.value);
let prevConfig = { ...this.state.generatedHTMlConfig };
switch (type) {
case "Headers":
prevConfig.headers = event.target.value;
this.setState({ generatedHTMlConfig: prevConfig });
break;
case "Paragraphs":
prevConfig.paragraphs = event.target.value;
this.setState({ generatedHTMlConfig: prevConfig });
break;
case "Buttons":
prevConfig.buttons = event.target.value;
this.setState({ generatedHTMlConfig: prevConfig });
break;
case "Links":
prevConfig.links = event.target.value;
this.setState({ generatedHTMlConfig: prevConfig });
break;
case "Inputs":
prevConfig.inputs = event.target.value;
this.setState({ generatedHTMlConfig: prevConfig });
break;
case "Images":
prevConfig.images = event.target.value;
this.setState({ generatedHTMlConfig: prevConfig });
break;
}
}
render() {
return (
<Row>
{/* todo 12.11.2018 extract to another component! */}
<Modal
open={this.state.isOpenModal}
header="Generate Html"
actions={
<div>
<Button
modal="close"
waves="light"
className="red lighten-2"
>
Cancel
</Button>
<Button
modal="close"
waves="light"
className="blue"
onClick={this.generateHTMLFromPopup.bind(this)}
>
<Icon left>build</Icon>Generate
</Button>
</div>
}
>
<p>Choose HTML elements for generated HTML.</p>
<Input
type="number"
label="Headers"
value={this.state.generatedHTMlConfig.headers}
onChange={e => this.changeHeaders(e, "Headers")}
/>
<Input
type="number"
label="Paragraphs"
value={this.state.generatedHTMlConfig.paragraphs}
onChange={e => this.changeHeaders(e, "Paragraphs")}
/>
<Input
type="number"
label="Buttons"
value={this.state.generatedHTMlConfig.buttons}
onChange={e => this.changeHeaders(e, "Buttons")}
/>
<Input
type="number"
label="Links"
value={this.state.generatedHTMlConfig.links}
onChange={e => this.changeHeaders(e, "Links")}
/>
<Input
type="number"
label="Inputs"
value={this.state.generatedHTMlConfig.inputs}
onChange={e => this.changeHeaders(e, "Inputs")}
/>
<Input
type="number"
label="Images"
value={this.state.generatedHTMlConfig.images}
onChange={e => this.changeHeaders(e, "Images")}
/>
</Modal>
<h2>Algorithm</h2>
<Row>
<Input
s={12}
type="select"
label="Select algorithm"
defaultValue=""
onChange={this.handleChangeAlgorithms}
>
<option value="" disabled>
Choose an algorithm
</option>
{this.props.algorithms.map(item => (
<option key={item.value} value={item.value}>
{item.name}
</option>
))}
</Input>
</Row>
{this.state.choosenAlgorithm ? (
<Collapsible popout>
<CollapsibleItem header="Details" icon="notes">
<ol>
{this.state.choosenAlgorithm.details.steps.map(
(step, index) => (
<li key={index}>{step}</li>
),
)}
</ol>
</CollapsibleItem>
</Collapsible>
) : null}
<h2>HTML to obfuscate</h2>
<Row>
<Input
s={12}
type="select"
label="HTML type"
defaultValue=""
onChange={this.handleChangeHtmlType}
>
<option value="" disabled>
Choose HTML
</option>
{this.renderHtmlTypesList()}
</Input>
</Row>
{this.state.htmlType === "custom" ? (
<Row>
<Input
type="file"
label="File"
onChange={this.handleUserFile}
/>
</Row>
) : null}
<div className="center-align">
<Button type={"button"} onClick={this.handleSubmit}>
Process<Icon left>autorenew</Icon>
</Button>
</div>
</Row>
);
}
}
class App extends Component {
_isMounted = false;
constructor(props) {
super(props);
this.state = {
isMounted: false,
//export to enum
algorithms: [
{
name: "Html to Javascript",
value: "1",
details: {
steps: [
"Split HTML file line by line.",
"Replace white characters.",
"Create function which add lines to document using document.write function.",
],
},
},
{
name: "Html to Unicode characters",
value: "2",
details: {
steps: [
"Create js function encoding characters to Unicode characters.",
"Create decoding function.",
"Add output from decoding function to HTML.",
],
},
},
{
name: "Html to escape characters",
value: "3",
details: {
steps: [
"Change endcoding using escape javascript function.",
"Decode using unescape javascript function.",
"Add element to HTML.",
],
},
},
{
name:
"Using own encoding and decoding function. [NOT IMPLEMENTED YET]",
value: "4",
details: {
steps: [
"Encode HTML using own function.",
"Save encoded content into js variable.",
"Decode using own decoding function.",
"Add element to HTML document.",
],
},
},
{
name: "Combine above methods [NOT IMPLEMENTED YET]",
value: "5",
details: {
steps: ["To be done..."],
},
},
],
previewHtml: null,
obfuscationConfig: null,
activeTab: 1,
};
}
componentDidMount() {
this._isMounted = true;
}
componentWillUnmount() {
this._isMounted = false;
}
processDataFromConfigurationForm = config => {
console.info(config);
if (this._isMounted) {
this.setState({
test: Date.now(),
// obfuscationConfig: config,
// doObfuscation: true,
// activeTab:3,
// previewHtml: config.html
});
}
};
render() {
return (
<div>
<header className="App">
<h1>HTML obfuscator</h1>
</header>
<Tabs>
<Tab
title="Configuration"
active={this.state.activeTab === 1}
>
<ConfigurationForm
algorithms={this.state.algorithms}
config={this.state.obfuscationConfig}
callbackConfigurationForm={
this.processDataFromConfigurationForm
}
/>
</Tab>
<Tab
title="HTML Preview"
active={this.state.activeTab === 2}
disabled={!this.state.previewHtml}
>
<HTMLPreview previewHtml={this.state.previewHtml} />
</Tab>
<Tab
title="Result"
active={this.state.activeTab === 3}
disabled={!this.state.obfuscationConfig}
>
{this.state.obfuscationConfig ? (
<ObfuscationOutput
config={this.state.obfuscationConfig}
/>
) : null}
</Tab>
</Tabs>
</div>
);
}
}
export default App;
The error
Try to add a constructor to your parent class, indeed, you can initialize the component state before the first modification:
class App extends Component {
constructor(props) {
super(props);
this.state = {
test1: Date.now()
};
}
// here you can update the state as you want, for example
foobar(event) {
this.setState({test1: event.target.value });
}
}
Hope it can help you...
Setting the state inside of the lifecycle method componentDidMount() should do the trick.
Oh I didn't see that someone already suggested that, glad you got it working
I'm trying to load items from JSON and toggle a dropdown div with description on click. While I can display elements sequentially (ex: loc1 & desc1, loc2 & desc2) on static divs I'm having trouble finding out how to render it properly when the second part (desc) is hidden and only shows when the loc div is clicked.
What would be the best way to map the result so it doesn't show as loc1 & loc2, desc1 & desc2 but as loc1 & desc1, loc2 & desc2?
Code:
var places = {
library: {
location: [
{
loc_name: "library1",
"desc": "desc1 : Modern and spacious building"
},
{
loc_name: "library2",
"desc": "desc2 : A cosy small building"
}
]
}
};
function contentClass(isShow) {
if (isShow) {
return "content";
}
return "content invisible";
}
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = { isShow: false };
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(function (prevState) {
return { isShow: !prevState.isShow };
});
}
render() {
const libraries_desc = places.library.location.map((libr) =>
<div>
<p>{libr.desc}</p>
</div>
);
const lib_names = places.library.location.map((libr) =>
<div>
<p>{libr.loc_name}</p>
</div>
);
return (
<div>
<div className='control' onClick={this.handleClick}>
<h4>{lib_names}</h4>
<div className={contentClass(this.state.isShow)}>{libraries_desc}</div>
</div>
</div>
);
}
}
render((
<Toggle />
), document.getElementById('root'));
Current result:
library1
library2
desc1 : Modern and spacious building
desc 2 : A cosy small building
Desired Result:
library1
desc1 : Modern and spacious building (hidden but shown when clicked)
library2
desc 2 : A cosy small building (hidden but shown when clicked)
Codesandbox
I might try extracting a location into a separate component. By extracting it, each location is responsible for knowing its state. In your case, that means its visibility (controlled by this.state.isShow).
Here's how you could do it:
import React from 'react';
import { render } from 'react-dom';
var places = {
library: {
location: [
{
loc_name: "library1",
"desc": "Modern and spacious building"
},
{
loc_name: "library2",
"desc": "A cosy small building"
}
]
}
};
class Location extends React.Component {
constructor(props) {
super(props);
this.state = { isShow: false };
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(function (prevState) {
return { isShow: !prevState.isShow };
});
}
contentClass(isShow) {
if (isShow) {
return "content";
}
return "content invisible";
}
render() {
return (
<div className='control' onClick={this.handleClick}>
<h4>{this.props.desc}</h4>
<div className={this.contentClass(this.state.isShow)}>{this.props.loc_name}</div>
</div>
)
}
}
class Toggle extends React.Component {
constructor(props) {
super(props);
}
render() {
const locations = places.library.location.map(location => {
return <Location {...location} />
})
return (
<div>
{locations}
</div>
);
}
}
render((
<Toggle />
), document.getElementById('root'));
Your Toggle Component should be like this.
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {
isShow: false,
id: -1, // initial value
};
}
handleClick = (id) => {
this.setState({
isShow: !this.state.isShow,
id: id
});
}
render() {
const { location } = places.library;
const { isShow, id } = this.state;
return (
<div className="control">
{location.map((libr, index) => (
<div key={index} onClick={() => { this.handleClick(index) }}>
<p>{libr.loc_name}</p>
{(isShow && (id === index)) && <p>{libr.desc}</p>}
</div>
))}
</div>
);
}
}
So when you click on the div element. A click event will be triggered called handleClick which will pass the index as a param to the function. which will set isShow to false or truth and vice versa along with the current element you want to show which will be selected through this.state.id. So everytime isShow is true and this.state.id matched index element of the array. Your description will show otherwise it will be hidden as you want.
So your desired result will be something like this.
library1
desc1 : Modern and spacious building (hidden but shown when clicked)
library2
desc 2 : A cosy small building (hidden but shown when clicked)
Let me preface that I'm in the process of learning React and I'm still pretty green at this.
I'm going to give the necessary parts of the code:
I have built a counter with increment and decrement buttons that are utilized by ways of state, they work just fine until I introduce and array and map over it. Then things start to break down. I know my code is wrong, I know there's something amiss however I'm completely ignorant as to what to even look for.
In my counting.js I have:
const players = [
{
name: "Jon Smith",
score: 10,
id: 1,
},
{
name: "Jon Doe",
score: 40,
id: 2,
},
{
name: "John Ham",
score: 30,
id: 3,
},
];
Which I have mapped in here:
class Counting extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0,
nameof: 'Full Name',
}
this.incrementCount = this.incrementCount.bind(this)
this.decrementCount = this.decrementCount.bind(this)
}
incrementCount(e) {
this.setState({
count: (this.state.count + 1),
})
}
decrementCount(e) {
this.setState({
count: (this.state.count - 1),
})
}
render() {
const listPlayers = players.map((players) =>
<Counter
key={players.id}
incrementCount={this.incrementCount}
decrementCount={this.decrementCount}
nameof={players.name}
count={players.score}
/>
);
return (
<div className="wrap">
<Titles header="Counter" subhead="A setState() project" subtitle="this will change" />
<h3>This doesn't work correctly</h3>
<ul>{listPlayers}</ul>
<ScoreList>
<h3> works</h3>
<li>
<Counter
incrementCount={this.incrementCount}
decrementCount={this.decrementCount}
nameof={this.state.nameof}
count={this.state.count}
/>
</li>
<li>
<Counter
incrementCount={this.incrementCount}
decrementCount={this.decrementCount}
nameof={this.state.nameof}
count={this.state.count}
/>
</li>
</ScoreList>
</div>
)
}
}
I have imported my Counter.jswhich is comprised of:
class Counter extends Component {
render() {
const { count } = this.props
const { decrementCount } = this.props
const { incrementCount } = this.props
const { nameof } = this.props
return (
<div>
<CountCell>
<Row style={{alignItems: 'center'}}>
<Col>
<CountButton
onClick={incrementCount}>
<Icon
name="icon" className="fa fa-plus score-icon"
/>
</CountButton>
</Col>
<Col >
<ScoreName>{nameof}</ScoreName>
</Col>
<Col >
<Score>{count}</Score>
</Col>
<Col>
<CountButton
onClick={decrementCount}>
<Icon
name="icon" className="fa fa-minus score-icon"
/>
</CountButton>
</Col>
</Row>
</CountCell>
</div>
)
}
}
So the increment and decrement buttons are only working globally and only for my static <li>, not my ones generated from the array. If I'm making any sense at all, how do I individually map my inc/dec buttons to each <li> and not globally?
Thank you!
You need to keep the state also be an array of objects, each storing data for a corresponding user
class Counting extends React.Component {
constructor(props) {
super(props);
this.state = {
countInfo: []
}
this.incrementCount = this.incrementCount.bind(this)
this.decrementCount = this.decrementCount.bind(this)
}
incrementCount(index) {
var countInfo = [...this.state.countInfo];
if(countInfo[index]) {
countInfo[index].count = countInfo[index].count + 1
countInfo[index].nameOf = players[index].name
}
else {
countInfo[index] = {count: 1, nameOf: players[index].name}
}
this.setState({
countInfo
})
}
decrementCount(index) {
var countInfo = [...this.state.countInfo];
if(countInfo[index]) {
countInfo[index].count = countInfo[index].count - 1
countInfo[index].nameOf = players[index].name
}
else {
countInfo[index] = {count: -1, nameOf: players[index].name}
}
this.setState({
countInfo
})
}
render() {
const listPlayers = players.map((players, index) =>
<Counter
key={players.id}
incrementCount={() => this.incrementCount(index)}
decrementCount={() => this.decrementCount(index)}
nameof={players.name}
count={players.score}
/>
);
return (
<div className="wrap">
<Titles header="Counter" subhead="A setState() project" subtitle="this will change" />
<h3>This doesn't work correctly</h3>
<ul>{listPlayers}</ul>