working on a ReactJs project. It is worth noting I am a uni student is very new to react. Part of the code contains an image that can be clicked like a button. Once clicked a react Modal element is opened and the contents of an array are displayed inside
Screen of arrays printed contents
As you will see from the above image, each array item starts with a different number. The issue is all the array elements are printed on one continuous line without spacing inbetween each element.
here is the code for the Modal
<button className="space boxButton button3" onClick={this.openGreenModal}>
<img className="boxImg" src={greenBox} />
<div onClick={e => e.stopPropagation()}>
<Modal className="ModalGreen" isOpen={this.state.greenVisible}>
<div>
<img className="boxImgPopUp" src={greenBox} />
<h1> Green box testing</h1>
//PRINTING ARRAY ITEMS ON THIS LINE
<p>Items: {this.state.greenArray}</p>
<button onClick={this.closeGreenModal}>Close</button>
</div>
</Modal>
</div>
</button>;
is there a way in which I can display each item on a new line?
if anymore code is needed for the project pls do let me know
Searching for "react render list" put me in the React documentation for exactly this sort of thing: https://reactjs.org/docs/lists-and-keys.html
To give an actual answer though, React is just creating HTML for you so you would want to create HTML tags to render items on different lines just like if you were creating that HTML by hand.
Something like:
{this.state.greenArray.map((item) =>
<p key={item.something_unique}>{item}<p>
)}
Related
I have the following DOM structure:
<item>
<div>
show details
</div>
<div class="details">...</div>
</item>
<item></item>
...
And I want to toggle the details div whenever a is clicked
What's the best way to achieve this in Vue.js ?
Given the fact that you have control over the rendering on the PHP side, I'd say utilizing a simple $index variable from your loop to hold access to a list of ID's would be simple enough. If you can pass $index from the php method to the component, then you can utilize the below:
<Item>
<div>
<a #click="$set(details, $index, !details[$index])">show details</a>
</div>
<div class="details" v-show="details[$index]">...</div>
</Item>
Then hold reference to details in your component's data definition:
data () {
return {
//...
details: {}
}
}
Note the usage of $set in the above allows us to creative a reactive property in our details object. Utilizing this approach means you are not required to define each index property on the details object.
Here's a simple jsFiddle which should help give you a more visual idea of the approach.
I'm working on a project that requires being able to create, save, and play animated graphics on live streams. So far, I've been able to define whatever graphic types I want to let the user build and display on screen, but these are specific to the kinds of graphics we need to display at work. I want to be able to allow the user to create their own using custom fields.
Here's the object that represents a lower-third graphic right now.
{
_id: 'jdksl;alkdfghjd',
type: 'lowerthird',
style: 'cvhp' //this matches the graphic's style with a show for styling
fields: [
{
name: 'Joe Somebody',
info: 'CVHP Digital Bureau'
},
]
}
So far, I've been able to use the fields array as a way to generalize whatever information I want to display on the graphic when it's displayed on the screen but I'm not understanding how I'll be able to use this pattern when I start figuring out how to let users build custom graphics. I can define and display graphics all day long and, as long as I know what's in the fields everything's golden. If I know I need to display the name of the first field, I just grab graphic.fields[0].name and run with it. But how do I start to tackle user-generated objects whose fields I don't have any information about? Moreover, I don't even know if my data is shaped in a way that makes the most sense for this.
Does anyone know of any good references, tutorials, guides that handle this? Thanks in advance!
edit
The code I use for displaying the generated content is pretty basic but it's specific to the framework I'm using, Svelte. In my case, I'm displaying a list of the created graphics objects:
{#each $graphics as graphic}
<article class="graphic-item">
<header>
<h3>{graphic.type}</h3>
<img src={'/assets/images/' + graphic.style + '.png'} />
</header>
<section class="graphic-item-body">
<ul class="graphic-item-field-list">
{#each graphic.fields as field, index}
<li>
<p class="list-title">{Object.values(field)[0]}</p>
{#if Object.values(field)[1]}
<p class="list-subtitle">{Object.values(field)[1]}</p>
{/if}
{#if Object.values(field)[2]}
<p class="list-subtitle">{Object.values(field)[2]}</p>
{/if}
{#if Object.values(field)[3]}
<p class="list-subtitle">{Object.values(field)[3]}</p>
{/if}
</li>
{/each}
</ul>
</section>
</article>
{/each}
Updated My Question based on comments. Not able to make small snippets as its a legacy code with lot of interacting components and I am very new to React to understand all the components.
I have the following GUI:
If you see carefully I have multiple fields with corresponding dropdowns. This combination is represented within a Div in my code under a class within render() function.
The problem is the Div code is repeatedly copy-paste to create a new
field and dropdowns. I don't want to repeat the whole div again and
again and want to replace it with some variable or function which
accepts Field name and DROP_DOWN_TYPE.
I have tried many solutions but it resulted in various errors due to
onchange event.
Repeated div is shown below:
<div className="infoBlock">
<div className="row align-items-center">
<div className="col-6">
<div className="row">
<div className="col-4">
<label className="b_label">Category :</label>
</div>
<div className="col-8">
<Dropdown type="Categories" isMultiple={true} onchange={this.handleDDClick} />
</div>
</div>
</div>
<div className="col-6">
</div>
</div>
</div>
I can't show DropDown code because its a huge file. But its a normal drop down which makes a service call to populate its fields.
Can you give more clarity by making a demo kind of thing on codesandbox.io ?
What I feel is you are repeatedly calling the setState function according to the error.
Either your state is being called repeatedly somewhere , or you need to bind that this.handleDDClick onto that onchange in Dropdown tag. Sometimes it gets triggered without actually any change happening in the dropdown.
I've created a Row and a Col components for an Card in Accordion using Bootstrap - simple, not React.Bootstrap or whatever. Row and Col just show their children, the Card just has more props and also just shows the children. Like that:
class Col extends React.Component {render () {return (
<div className='col' id={'col_'+this.props.colid} key={shortid.generate()}>
{this.props.children}
</div>)}}
class Row extends React.Component {render () { return (
<div className='row' id={'row_'+this.props.rowid} key={shortid.generate()}>
{this.props.children}
</div>)}}
Then I want to insert a textarea, which is provided with a function to make some changes to the text and I display these changes within a div below the textarea. Also I've got a button, that copies the text to buffer, based on clipboard.js
The first version of render function works just fine, but it doesn't give me the needed design, so I came up with the second version, based on Row and Col components, described above.
The contents mainly does not differ - the same textarea, checkbox, button and div. What differs is the layout. In the first version there is no layout at all, in the second I tried my best :)
So:
render () { return (<React.Fragment>
<textarea id='xng0' onChange={this.handleChange} ref={this.xng0ref} />
<CopyButton target="st0" message="Copy text" /> <br />
<input type='checkbox' onChange={this.toggleTranslit}
defaultChecked={this.state.tranParam} /><span>Translit?</span><br />
<div id='st0' border='1' ref={this.st0ref} >
{this.state.st0value}
</div></React.Fragment>)}
The second version:
render () {return ( <React.Fragment><Row><Col>
<textarea id='xng0' onChange={this.handleChange} ref={this.xng0ref} />
</Col><Col><Row><Col>
<CopyButton target="st0" message="Copy text" />
</Col></Row>
<Row><Col>
<input type='checkbox' onChange={this.toggleTranslit}
defaultChecked={this.state.tranParam}
/><span>Translit?</span>
</Col></Row>
</Col>
</Row>
<Row>
<div id='st0' border='1' ref={this.st0ref}>
{this.state.st0value}
</div></Row></React.Fragment>)}
My problem is: while the first render() version keeps focus in the textarea, when I type, the second version throws the focus off the textarea after the first letter was typed AND clears the textarea. That is I have no possibility to input some long text - I get only one letter.
What did I miss? Why this happens and how to make the focus stable?
As you were already prompted in the comments, you have unique keys for each render on the Col and Row components, because key={shortid.generate()}.
After writing a character in the textarea, you most likely change the state, then rerender happens and in the place of the current textarea, a new one appears, naturally without focus.
I recommend that you carefully read why the keys are created in the React. Link https://reactjs.org/docs/lists-and-keys.html#keys.
I must say right away that they were created to identify the list items, which is completely violated when you generate unique keys on each render.
please add 'value' property to your textarea
I'm building out something that allows users to upload N-number of data files. In service of this, I've added a button that will create an additional file upload option. This is done with a simple for loop in the render function (there is a selection option that only appears if certain conditions are met, which is the 'mergeColumnSelection' variable, you can ignore that but I'm including it in case it somehow ends up being relevant):
let renderedEnrichedDataFields = [];
for(let i = 0; i < this.state.enrichedData.length; i++) {
let mergeColumnSelection = ""
if(this.state.enrichedData[i] !== null) {
mergeColumnSelection = <div className="form-item__select form-item">
<label className="form-label" htmlFor="add-target-owner">Merging Column</label>
<div className="form-item__container">
<select onChange={(e) => {this.setEnrichedMergeColumn(e, i)}} defaultValue={this.state.csvColumns[0]}>
{mainDataColumns}
</select>
</div>
</div>
}
renderedEnrichedDataFields.push(
<div className="form-group">
<button onClick={() => {this.removeEnrichmentData(i)}} type="button" className="modal-close">
<Icon name="close" />
</button>
<div className="form-item">
<label className="form-label" htmlFor="add-target-csv">Enrichment Dataset</label>
<input
className="csv-input"
type="file"
accept="text/csv"
onChange={(e) => {this.acceptNewEnrichedDataFile(e, i)}}
/>
</div>
{mergeColumnSelection}
</div>
)
}
Basically, every time the button is pressed a new element is pushed into the enrichedData array in state. This causes the application to render an additional file input. When a user uploads a file, the placeholder element in the array is replaced with the file. When the user eventually submits the form, an array of files will be submitted which is great.
However! I'm having a lot of trouble getting a clean implementation for the ability to REMOVE these input fields. The function
removeEnrichmentData(index) {
let enrichmentData = this.state.enrichedData
let enrichedMergeColumns = this.state.enrichedMergeColumns;
enrichmentData.splice(index, 1);
enrichedMergeColumns.splice(index, 1)
this.setState({enrichedData: enrichmentData, enrichedMergeColumns: enrichedMergeColumns});
}
As you can see this takes the index of the selected input, then splices it from the array that generates the for loop. The appropriate file is spliced from the array, and the number of file inputs is correct. However, there are problems with what file name is displayed. Pictures will help:
Here you can see a sample where someone is preparing to upload three
files, health, cluster, and starbucks
Now I select to remove the cluster item (item 2) from the list. It is
removed from the file list in state, leaving just health and
starbucks. However, the for loop simply runs through twice and drops
the last item - meaning that it appears that the health and cluster
are the remaining two files, even though in actuality they are health
and starbucks
I thought about moving the JSX block itself into state so I can specifically target the JSX input element I want removed - but have had limited success with this approach and read that it's not advisable to put JSX into the state. React doesn't really have built in ways to easily delete the specific input, and I can't set default values in file inputs so I can't easily tie the individual inputs to their counterpart in state.
It feels like it should be such a simple problem and I'm very stuck. Any help is appreciated!
#Miloš Rašić is correct - your initial problem is that you're probably using array indices for the keys for the inputs. So, if you have 10 inputs numbered 0...9, and you delete the input with index 5, you're still rendering items with keys 0..8, and React thinks the last one was removed.
Per your comment about using UUIDs, it sounds like you're generating unique IDs in the render() method itself. DO NOT DO THAT! Never generate random values for keys in render(). When you do that, you're telling React every time that "this item is different than the last time we rendered, please destroy the existing item here and replace it with a new one".
Instead, you should generate these unique IDs when you add a new entry into your state. For example:
class FileInputList extends Component {
state = { inputs : [] }
addNewFileInput = () => {
const inputID = uuid();
const newInputs = this.state.inputs.concat({id : inputID});
this.setState({inputs : newInputs});
}
render() {
const {inputs} = this.state;
const inputList = inputs.map(inputEntry) => {
return <input type="file" key={inputEntry.id} />
});
return inputList;
}
}
It's hard to be sure without a fully working example, but this awfully similar to a "working" example of how React messes up when you don't give arrays of elements a key prop. Don't you get warnings about this from React? Try giving the divs you are pushing to the array a key prop which won't change for an existing element when an element is deleted (so key={i} won't work).
For example, if you are rendering
<input type="file" key={1} />
<input type="file" key={2} />
<input type="file" key={3} />
<input type="file" key={4} />
when you delete the one with key={2} it should be
<input type="file" key={1} />
<input type="file" key={3} />
<input type="file" key={4} />
Some sort of incrementing id like in relational databases or a generated unique id would do the trick.
Have you tried this:
{this.removeEnrichmentData(index)}}
Here is a working example.