How to highlight matches within a string with JSX? - javascript

I have a custom autocomplete, so when you type, it will display a list of suggestions based on the input value. In the list, I would like to bold the characters that are the same as the input value.
So if I have a list of suggestions: "alligator", "lima", "lime", and I typed "li", then the suggestions would look like this:
alligator
lima
lime
I have this simple map in my jsx file:
<ul>
{matches.map(function(match, idx){
let re = new RegExp(value, 'g');
let str = match.replace(re, '<b>'+ value +'</b>');
return <li key={idx}>{str}</li>
})}
</ul>
where value is the input value. It displays the list but in this string format
al<b>li</b>gator
<b>li</b>ma
<b>li</b>me
Not sure how to go about with React. I thought of using dangerouslyinnerhtml or something like that, but I think that's a last resort thing. I would like to avoid that if possible.
This my autocomplete component:
class Autocomplete extends Component{
constructor(props){
super(props);
this.state = {
value: '',
matches: [],
showMatches: false
}
}
searchListing(){
api.call {
that.setState({
showMatches: true,
matches: a
});
})
}
}
handleOnChangeInput(e){
let value = e.target.value;
this.setState({ value: value})
if(value !== ''){
this.searchListing(e);
}else{
// console.log("value", e.target.value);
this.setState({
showMatches: false,
matches: []
})
}
}
render(){
let matches = this.state.matches;
let value = this.state.value;
let matchesHtml;
if(this.state.showMatches){
matchesHtml = <ul>
{matches.map(function(match, idx){
let re = new RegExp(value, 'g');
let str = match.replace(re, '<b>'+ value +'</b>');
return <li key={idx} dangerouslySetInnerHTML={{__html: str}}></li>
})}
</ul>
}
return(
<div>
<input placeholder="type a name" onChange={this.handleOnChangeInput}/>
{matchesHtml}
</div>
);
}
}

Writing your own highlighting code could lead down a rabbit hole. In my answer, I assume only simple text (no HTML within the strings, no charset edge cases) and valid non-escaped RegExp pattern string.
Instead of building a new string, you could build a new array, in which you could put JSX.
A React component can also return an array of elements:
render() {
// No need to wrap list items in an extra element!
return [
// Don't forget the keys :)
<li key="A">First item</li>,
<li key="B">Second item</li>,
<li key="C">Third item</li>,
];
}
The logic behind
As a simple proof of concept, here's the logic we could use:
const defaultHighlight = s => <em>{s}</em>;
// Needed if the target includes ambiguous characters that are valid regex operators.
const escapeRegex = v => v.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&");
/**
* Case insensitive highlight which keeps the source casing.
* #param {string} source text
* #param {string} target to highlight within the source text
* #param {Function} callback to define how to highlight the text
* #returns {Array}
*/
const highlightWord = (source, target, callback) => {
const res = [];
if (!source) return res;
if (!target) return source;
const regex = new RegExp(escapeRegex(target), 'gi');
let lastOffset = 0;
// Uses replace callback, but not its return value
source.replace(regex, (val, offset) => {
// Push both the last part of the string, and the new part with the highlight
res.push(
source.substr(lastOffset, offset - lastOffset),
// Replace the string with JSX or anything.
(callback || defaultHighlight)(val)
);
lastOffset = offset + val.length;
});
// Push the last non-highlighted string
res.push(source.substr(lastOffset));
return res;
};
/**
* React component that wraps our `highlightWord` util.
*/
const Highlight = ({ source, target, children }) =>
highlightWord(source, target, children);
const TEXT = 'This is a test.';
const Example = () => (
<div>
<div>Nothing: "<Highlight />"</div>
<div>No target: "<Highlight source={TEXT} />"</div>
<div>Default 'test': "<Highlight source={TEXT} target="test" />"</div>
<div>Multiple custom with 't':
"<Highlight source={TEXT} target="t">
{s => <span className="highlight">{s}</span>}
</Highlight>"
</div>
<div>Ambiguous target '.':
"<Highlight source={TEXT} target=".">
{s => <span className="highlight">{s}</span>}
</Highlight>"
</div>
</div>
);
// Render it
ReactDOM.render(
<Example />,
document.getElementById("react")
);
.highlight {
background-color: yellow;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="react"></div>
No need to use dangerouslySetInnerHTML here.
This highlightWord function can take any function to wrap the matched string.
highlight(match, value) // default to `s => <em>{s}</em>`
// or
highlight(match, value, s => <span className="highlight">{s}</span>);
I'm doing minimal regex string escaping based on another answer on Stack Overflow.
The Highlight component
As shown, we can create a component so it's "more react"!
/**
* React component that wraps our `highlightWord` util.
*/
const Highlight = ({ source, target, children }) =>
highlightWord(source, target, children);
Highlight.propTypes = {
source: PropTypes.string,
target: PropTypes.string,
children: PropTypes.func,
};
Highlight.defaultProps = {
source: null,
target: null,
children: null,
};
export default Highlight;
It uses a render prop, so you'd have to change your rendering to:
<ul>
{matches.map((match, idx) => (
<li key={idx}>
<Highlight source={match} target={value}>
{s => <strong>{s}</strong>}
</Highlight>
</li>
))}
</ul>

Just use dangerouslySetInnerHTML but take attention it make to inadvertently expose your users to a cross-site scripting (XSS) attack
...
const valueToBold = (match: string) => {
const regex = new RegExp(searchFilterValue, 'g');
return match.replace(regex, '<b>$&</b>');
};
return (
...
<ul>
{matches.map((match, idx)=> (
<li key={idx}>
<span dangerouslySetInnerHTML={{ __html:valueToBold(match) }} />
</li>
))}
</ul>
...
)

You just append your mapper as children inside your auto complete component.
<CustomAutocomplete>
<ul>
{
matches.map(function(match, idx){
let re = new RegExp(value, 'g');
let str = match.replace(re, '<b>'+ value +'</b>');
return (<li key={idx}>{str}</li>)
})
}
</ul>
</CustomAutocomplete>

Related

List items with inputs - how to add list object to an array when input has value?

I have created a list of objects and each list item has input within:
{flowers_data?.map((flower) => {
return (
<>
<div className={classes.Nested_Flower_Container} key={flower.id}>
<div className={classes.Nested_Flower_Name}>
{flower.name}
</div>
<div className={classes.Nested_Flower_Input} style={{ marginRight: '0.2em' }}>
<TextField
id="Amount"
label="Amount"
variant="outlined"
size="small"
type="number"
onChange={(e) => {
setAmount(e.target.value);
handleAddList(e.target.value, flower);
}}
className={classes_2.root}
/>
</div>
</div>
</>)
})}
How can I add an object that has a value in input to an array? I tried to do this using a function that I created, but each time I change one element's target.value and move on to the next item to change its input value, there is only one element in the array with the latest target.value. And after modifying the inputs, when I try to output the values outside that function with e.g. a button, the add_flowers_tab array is empty.
handleAddList function:
let temp_flower: Flower;
let add_flowers_tab: Flower[] = [];
const handleAddList = (targetValue: string, flower: Flower) => {
temp_flower = {
"id": flower.id,
"name": flower.name,
"price": flower.price,
"amount": Number(targetValue),
"creation_date": flower.creation_date
}
if (targetValue === '') {
/* Delete flower when input is empty */
add_flowers_tab.forEach(tabFlower => {
if (tabFlower.id === temp_flower.id) {
const indexOfDelete = add_flowers_tab.indexOf(tabFlower);
add_flowers_tab.splice(indexOfDelete, 1);
}
})
}
if (targetValue !== '') {
/* Add flower to tab when input has value */
if (add_flowers_tab.length > 0) {
/* When input changes, delete flower with old input value and add the new one */
add_flowers_tab.forEach(tabFlower => {
if (tabFlower.id === temp_flower.id) {
const indexOfDelete = add_flowers_tab.indexOf(tabFlower);
add_flowers_tab.splice(indexOfDelete, 1);
add_flowers_tab.push(temp_flower);
}
})
}
else {
/* Add new flower as a first element in the array */
add_flowers_tab.push(temp_flower);
}
/*
Displays an array with only the most recently added temp_flower, even though
several inputs in list have values
*/
console.log(add_flowers_tab);
}
}
Here is the minimum solution for generating a list on Inputs that each update a single element in a list on state.
export const ManyInputs = ({inputs}) => {
const [list, setList] = useState(inputs);
const setIndividual = (value, id) =>
// Update a single field in the object in the list that matches the given id
setList(list.map(l => l.id === id ? ({...l, value}) : l));
return list.map(i =>
// Send the id back to help match the object
<TextField onChange={(e) => setIndividual(e.target.value, i.id)} />
);
};
You can have one React state, which has an array of objects that you enter through your "mapped" component.
const [tmpAmount, setTmpAmount] = React.useState<{flower:Flower,amount:number}[]>([])
// fill the temporary state with generated flowers only once
React.useEffect(()=>{
setTmpAmount(flowers_data.map((flwr) =>{flower:flwr, amount:0}))
},[])
and replace the onChange()
onChange={(e) => {
// find existing flower to edit the amount of it
let indexToChange = flowers_data.findIndex((element) => element.id === flower.id)
// update and replace the array variable with updated object
setAmount2((prev) =>
[
...prev.slice(0, indexToChange),
{
...prev[indexToChange],
amount: e.target.value,
},
...prev.slice(indexToChange + 1),
]);
}
You can also check if the array changes and print it:
React.useEffect(()=>console.log(tmpAmount),[tmpAmount])

Handle populating state from multiple checkboxes

I've got a form which builds a list of checkboxes from some data:
<fieldset className="visibility">
<div className="input-container checkbox">
<span className="label">Visible to</span>
<ul>
{
allForces.map(force => {
if (force.name !== 'White' && force.name !== currentMarkerForce) {
return (
<li key={force.uniqid}>
<label>
<input onChange={handleVisibilityChange} name={`visibility-${_.kebabCase(force.name)}`} type="checkbox" value={force.name} checked={markerVisibleTo.includes(force.name) }/>
{force.name} cell
</label>
</li>
)
}
})
}
</ul>
</div>
</fieldset>
As it is usually at least 2 items that will appear and can be checked, I wrote a handler for it which populates an array before posting back to the state, the contents of this array is initially populated from the existing state:
const visibilityChecked = [...markerVisibleTo]
const handleVisibilityChange = ({ target }) => {
const { checked, value } = target
checked ? visibilityChecked.push(value) : visibilityChecked.pop(value)
setMarkerVisibleTo(visibilityChecked)
}
The last line is a call to a useState hook this, mostly works but sometimes I get an odd behaviour where the wrong checkbox is selected:
Can anyone please help shed some light on what is causing this problem?
I may guess that happens because state update is asynchronous and by the time you attempt to apply changes with setMarkerVisibleTo() your state is different from the one you assume it is, you may try to put const visibilityChecked = [...markerVisibleTo] into handleVisibilityChange() body:
const handleVisibilityChange = ({ target }) => {
const visibilityChecked = [...markerVisibleTo]
const { checked, value } = target
checked ? visibilityChecked.push(value) : visibilityChecked.pop(value)
setMarkerVisibleTo(visibilityChecked)
}
Or, as I would write that:
const handleVisibilityChange = ({target:{checked,value}}) => {
const visibilityChecked = checked ?
[...markerVisibleTo, value] :
[...markerVisibleTo].filter(val => val != value)
setMarkerVisibleTo(visibilityChecked)
}
You may find full-blown demo over here:
//dependencies
const { render } = ReactDOM,
{ useState } = React
//mocking source data
const checkItems = [...'abcd']
//check list component
const CheckList = ({items}) => {
const [visibleMarkers, setVisibleMarkers] = useState(checkItems),
onVisibilityChange = ({target:{checked,value}}) => {
const visibilityChecked = checked ?
[...visibleMarkers, value] :
[...visibleMarkers].filter(val => val != value)
setVisibleMarkers(visibilityChecked)
}
return (
<div>
<ul>
{
items.map((item,key) => (
<li {...{key}}>
<label>
Option {item}
<input
type="checkbox"
value={item}
checked={visibleMarkers.includes(item)}
onChange={onVisibilityChange}
/>
</label>
</li>
))
}
</ul>
<span>visibleMarkers: {JSON.stringify(visibleMarkers)}</span>
</div>
)
}
//render
render (
<CheckList items={checkItems} />,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><div id="root"></div>

Array map into string with line break - React

I have a function which I need to return a string with line breaks, or divs that will render properly
export const showPeople = (peopleArray = []) => {
let peopleString = peopleArray ? peopleArray.map((people) => {
<div>
`${people.name && people.name},
${people.last_name && people.last_name}`
</div>
}).join('') : ''
return peopleString
}
I tried with divs, without divs, with return without return, and without join and i get [Object] for each person, in this particular case an empty string
I have seen similar questions but was not able to solve my problem
If the goal is to reduce a list of items to a single string that is rendered as a multi-line string, then you can use the white-space:pre-wrap; CSS rule to achieve this:
body {
white-space: pre-wrap;
}
Once you've done that you should be able to render a multiple line string with line breaks on \n characters, as follows:
export const showPeople = (peopleArray = []) => {
let peopleString = peopleArray ? peopleArray.map((people) => (
`${people.name ? people.name : ''} ${people.last_name ? people.last_name : ''}`
})
.join('\n') : ''
return peopleString
}
I've posted a working version here for you to see also
Hope this helps!
You dont want to return a string you want to return an array of elements:
let peopleString = peopleArray.map((people) => (
<div>
{people.name || ""},
{people.last_name || ""}
</div>
));
return peopleString
export const showPeople = (peopleArray = []) => {
let peopleString = peopleArray ? peopleArray.map(people =>
`<div>
${people.name}, ${people.last_name}
</div>`
).join('') : ''
return peopleString
}
var peoples = [{name: 'Jhon', last_name: 'Doe'},{name: 'Mario', last_name: 'Peach'}]
var peopleString = peoples.map(people =>
`<div>
${people.name}, ${people.last_name}
</div>`
).join('')
console.log(peopleString)
document.getElementById('container').innerHTML = peopleString
<div id="container"></div>
Since you're not doing string concatenation with variables. You need to remove Template Strings inside your divs.
It appears to me that you are not returning anything in that map function:
(people) => { ...string here... }
I would suggest, simply:
(people) => ( ...string here... )
or:
(people) => { return ...string here... }
Another issue might be that what you seem to intend to return in the map function is NOT a string, and probably(?) should be; it's hard to say without seeing the function in context.
So if you specifically want to use the HTML Line Break element to separate the strings you can use this.
import { Fragment, ReactNode } from "react";
const separateWithLineBreaks = (strings: string[]): ReactNode => {
return (
<Fragment>
{strings.map((str, index) => (
<Fragment key={index}>
{str}
{index < strings.length - 1 && <br />}
</Fragment>
))}
</Fragment>
);
};

How can I grab the key of a list item generated from a map function?

So I am learning React, and I've tried searching for solutions to my problem both on stackoverflow and on React's own documentation, but I am still stumped.
Essentially, I have a list of 10 subreddits that is being mapped to list items in the form of the subredditsArray variable.
I render the results, and try to pass the selected item when I click that list item to my getSubredditInfo function. However, this doesn't work - event.target.key is undefined. (To clarify, I am looking to grab the key of the single list element that I have clicked).
When I try to just get event.target, I get the actual htmlElement (ex: <li>Dota2</li>), where as I want to get the key, or at least this value into a string somehow without the tags. I also tried putting my onClick method in the list tag of the map function, but that did not work.
Here is the relevant code:
//this is where I get my data
componentDidMount(){
fetch('https://www.reddit.com/api/search_reddit_names.json?query=dota2')
.then(results => {
return results.json();
})
.then(redditNames => {
//this is there I set my subreddits state variable to the array of strings
this.setState({subreddits: redditNames.names});
})
}
getSubredditInfo(event){
//console.log(event.target.key); <-- DOESNT WORK
}
render() {
var subredditsArray = this.state.subreddits.map(function(subreddit){
return (<li key={subreddit.toString()}>{subreddit}</li>);
});
return (
<div className="redditResults">
<h1>Top 10 subreddits for that topic</h1>
<ul onClick={this.getSubredditInfo}>{subredditsArray}</ul>
</div>
);
}
My questions essentially boil down to:
How do I grab the key value from my list object?
Additionally, is there a better way to generate the list than I currently am?
Thank you in advance.
EDIT: Added my componentDidMount function in hopes it clarifies things a bit more.
try the following code:
class App extends React.Component {
constructor(props){
super(props);
this.state = {subreddits:[]};
}
componentDidMount(){
fetch('https://www.reddit.com/api/search_reddit_names.json?query=dota2')
.then(results => {
return results.json();
})
.then(redditNames => {
//this is there I set my subreddits state variable to the array of strings
this.setState({subreddits: redditNames.names});
})
}
getSubredditInfo(subreddit){
console.log(subreddit);
}
render() {
return <div className="redditResults">
<h1>Top 10 subreddits for that topic</h1>
<ul>
{
this.state.subreddits.map((subreddit)=>{
return (<li key={subreddit.toString()} onClick={()=>this.getSubredditInfo(subreddit)}>{subreddit}</li>);
})
}
</ul>
</div>;
}
}
ReactDOM.render(
<App/>,
document.getElementById('container')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="container">
<!-- This element's contents will be replaced with your component. -->
</div>
please check the onClick event handler now. its an arrow function and its calling the getSubredditInfo function with your subreddit now. so you will get it there.
so its basically different way of calling the handler to pass data to the handler.
it works as you expect it to.
You can use lamda function or make component for item list which have own value for getSubredditInfo function
getSubredditInfo(value) {}
render() {
var subredditsArray = this.state
.subreddits.map((subreddit, i) =>
(<li key={i}
onClick={() => this.getSubredditInfo(subreddit)}>{subreddit}</li>));
return (
<div className="redditResults">
<h1>Top 10 subreddits for that topic</h1>
<ul>{subredditsArray}</ul>
</div>
);
}
1) Key should be grabbed either by the id in your object in array. Or you can combine the 2 properties to create a unique key for react to handle re-renders in a better way.
If you have a string array, you may use a combination of string value + index to create a unique value, although using index is not encouraged.
Given a quick example for both below.
2) A better way could be to move your map function into another function and call that function in render function, which will return the required JSX. It will clean your render function.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
subredditsObjArray: [
{ id: 1, value: 'A'},
{ id: 2, value: 'B'},
{ id: 3, value: 'C'},
{ id: 4, value: 'D'}
],
subredditsArray: ['A', 'B', 'C', 'D'],
selectedValue: ''
};
}
getSubredditInfo = (subreddit) => {
console.log(subreddit)
this.setState({
selectedValue: ((subreddit && subreddit.id) ? subreddit.value : subreddit),
});
}
render() {
return (
<div className="redditResults">
<p>Selected Value: {this.state.selectedValue}</p>
<h1>Top {this.state.subredditsArray.length || '0'} subreddits for that topic</h1>
<p>With Objects Array</p>
<ul>
{
this.state.subredditsObjArray
&& this.state.subredditsObjArray.map(redditObj => {
return (<li key={redditObj.id}><button onClick={() => this.getSubredditInfo(redditObj)}>{redditObj.value || 'Not Found'}</button></li>);
})
}
</ul>
<br />
<p>With Strings Array</p>
<ul>
{
this.state.subredditsArray
&& this.state.subredditsArray.map((reddit, index) => {
return (<li key={reddit + '-' + index}><button onClick={() => this.getSubredditInfo(reddit)}>{reddit || 'Not Found'}</button></li>);
})
}
</ul>
</div>
);
}
}
ReactDOM.render(
<App etext="Edit" stext="Save" />,
document.getElementById('container')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="container">
<!-- This element's contents will be replaced with your component. -->
</div>
Are you trying to do this? I'm not sure what you want to do.
getSubredditInfo(e, subreddit) {
console.log(subreddit)
}
render() {
const { subreddits } = this.state
var subredditsArray = subreddits.map(subreddit => (
<li
key={subreddit.toString()}
onClick={(e) => {
this.getSubredditInfo(e, subreddit)
}}
>
{subreddit}
</li>
))
return (
<div className="redditResults">
<h1>Top 10 subreddits for that topic</h1>
<ul>{subredditsArray}</ul>
</div>
);
}
The key purpose is to pass your subreddit to the onClick function so you will receive the value while you click the item.
If you still get error try this and tell me what's happened.
render() {
const { subreddits } = this.state
var subredditsArray = subreddits.map(subreddit => (
<li
key={subreddit.toString()}
onClick={(e) => {
console.log(subreddit.toString())
}}
>
{subreddit}
</li>
))
return (
<div className="redditResults">
<h1>Top 10 subreddits for that topic</h1>
<ul>{subredditsArray}</ul>
</div>
);
}

Can I combine these two array into one object using React and Javascript?

While working on my application I found out that the way I have it set up now is not really ideal. I am currently saving the values of two dynamicly created input fields in two seperate arrays. Now as I understand this is not really smart to do since both input fields have the same index and actually belong together.
Now I was wondering how I can change my code so I can save both values in the same Object.
This is my code for my dynamic form:
class MediaInput extends React.Component {
render() {
const linkName = `link${this.props.index}`;
const contentName = `content${this.props.index}`;
return (
<div>
<ControlLabel>Media (optional)</ControlLabel>
<input
onChange={(event) => this.props.handleChangeUrl(event, this.props.index)}
name={ linkName }
value={ this.props.mediaUrls[this.props.index]}
className="form-control"
placeholder="Add your media url. We accept YouTube, Vimeo and SoundCloud links"
type="text"
/>
<input
name={ contentName }
onChange={(event) => this.props.handleChangeContent(event, this.props.index)}
value={ this.props.mediaContents[this.props.index]}
className="form-control"
placeholder="Add your media content"
type="text"
/>
</div>
);
}
}
This is my current state:
this.state ={
mediaFields: [],
content: [],
mediaUrls: [ null, null, null ],
mediaContents: ['', '', ''],
};
These are my functions:
// Add/remove media fields
add() {
event.preventDefault();
const mediaFields = this.state.mediaFields.concat(MediaInput);
if (i < 3) {
this.setState({ mediaFields });
i++
} else {
Bert.alert('Only 3 media links are allowed', 'danger');
}
}
remove() {
event.preventDefault();
const lastElement = this.state.mediaFields.pop();
const mediaFields = this.state.mediaFields;
this.setState({ mediaFields });
i--
}
// Handle change media fields
handleChangeUrl(e, index) {
// Shallow copy of array
const mediaUrls = this.state.mediaUrls.slice();
let url = e.target.value
if (!/^https?:\/\//i.test(url)) {
url = 'http://' + url;
}
mediaUrls[index] = url;
this.setState({ mediaUrls});
}
handleChangeContent(e, index) {
// Shallow copy of array
const mediaContents = this.state.mediaContents.slice();
mediaContents[index] = e.target.value;
this.setState({ mediaContents });
}
And this is the part of the form where I add the input fields:
[...]
const mediaFields = this.state.mediaFields.map((Element, index) => {
return <Element key={ index } index={ index } mediaUrls={this.state.mediaUrls} mediaContents={this.state.mediaContents} handleChangeUrl={this.handleChangeUrl} handleChangeContent={this.handleChangeContent} />
})
[...]
<div>
{ mediaFields }
<Button onClick={ () => this.add() }>Add media field</Button>
<Button onClick={ () => this.remove() }>Remove media field</Button>
</div>
As you can see I have almost the same function and I understand that this is not great, so I want to learn how I can do it better.
I want to save both the values in the same Object. Like so:
const mediaContent = [ link1: content1, link2: content2, link3: content3 ]
Hope someone can push me in the right direction.
In Javascript, an array can only be indexed by a number (it is not like PHP).
That being said, you can have both values, like you want, in an object (json).
So, you would have something like:
const mediaContent = { link1: content1, link2: content2, link3: content3 }
Then, you just need to treat your object like an array. An object doesn't have the methods pop and concat, but it can be easily achieved using Object.Assign for adding, and Object.keys + map reducing functions to remove (actually treating your object again as array).
Extra tip: You can remove the i variable and use Object.keys(this.state.mediaFields).length instead.
add() {
event.preventDefault();
const mediaFields = Object.assign(this.state.mediaFields, MediaInput);
if (i < 3) {
this.setState({ mediaFields });
i++;
} else {
Bert.alert('Only 3 media links are allowed', 'danger');
}
}
remove() {
event.preventDefault();
const mediaFields = Object.keys(this.state.mediaFields)
.map(k => this.state.mediaFields[k])
.pop()
.reduce((items, curr) => ({
...items,
curr
}), {});
this.setState({ mediaFields });
i--
}
Be aware that keeping the order for adding and removing might not work properly.

Categories

Resources