The code below is only working when I remove the componentWillMount that uses localStorage. With usage localStorage it gives a mistake
this.state.interests.map is not a function
I tried to move usage of localStorage out of component but it won't help. I suppose that using local storage somehow changes this.state.interests that they stop being an array.
let interests = ["Музыка", "Компьютеры", "Радио"]
let ListOfInterest = React.createClass({
getInitialState: function() {
return {value: '', interests: interests};
},
componentWillMount() {
let local = localStorage.getItem('interests')
if (local) {
this.setState({interests: local});
} else {
localStorage.setItem('interests', this.state.interests)}
},
deleteInterest(key) {
delete interests[key]
this.setState(this.state) // without this line the page will not re-render
},
addInterest() {
interests.unshift(this.state.value)
this.setState({value: ''})
},
handleChange(event) {
this.setState({value: event.target.value})
},
render() {
return <div className="interests">
<b>Интересы</b>
<br/>
{this.state.interests.map((int, index) => {
return <button onClick={() => {
this.deleteInterest(index)
}} key={index} className="btn-interest">{int}</button>
})}
<input type='text' placeholder="Add" value={this.state.value} onChange={(e) => this.handleChange(e)}/>
<button onClick={() => {
this.addInterest()
}} className="add">Add interest</button>
</div>
}
})
<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>
You have several issues in your example
in localStorage.setItem second argument have to be a String, you can not store Array(when you do it, in storage will be string separated by coma because called method toString - [1, 2, 3].toString() ), you need stringify array before set to Storage
keyValue A DOMString containing the value you want to give the
key you are creating/updating.
localStorage.setItem(
'interests', JSON.stringify(this.state.interests)
)
and parse when get value
let local = JSON.parse(localStorage.getItem('interests'));
this.setState(this.state) this is not good way to update state, you need update state like so
deleteInterest(key) {
this.setState({
interests: this.state.interests.filter((el, i) => i !== key)
})
},
addInterest() {
this.setState({
value: '',
interests: this.state.interests.concat(this.state.value)
});
},
Example
Related
I'm trying to map through my state, where I have collected data from an external API.
However, when I do i get this error here:
TypeError: this.state.stocks.map is not a function
I want to render the results to the frontend, through a function so that the site is dynamic to the state.favorites.
Though the console.log(), I can see that the data is stored in the state.
I have found others with a similar issue, but the answers did not work out.
UPDATE:
I have changed the componentDidMount() and it now produces an array. The issue is that I get no render from the renderTableData() functions.
console.log shows this array:
0: {symbol: "ARVL", companyName: "Arrival", primaryExchange: "AESMSBCDA)LNKOS/ TLTE(N GAEQLGAR ", calculationPrice: "tops", open: 0, …}
1: {symbol: "TSLA", companyName: "Tesla Inc", primaryExchange: " RNK EAASGTDACLN)LE/OGMELAQSTB (S", calculationPrice: "tops", open: 0, …}
2: {symbol: "AAPL", companyName: "Apple Inc", primaryExchange: "AMTGS/C) AALGDRSTNLEOEL(S BAE NQK", calculationPrice: "tops", open: 0, …}
length: 3
__proto__: Array(0)
This is my code:
import React, { Component } from 'react'
import './Table.css';
class Table extends Component {
constructor (props) {
super(props);
this.state = {
favorites: ['aapl', 'arvl', 'tsla'],
stocks: []
};
}
componentDidMount() {
this.state.favorites.map((favorites, index) => {
fetch(`API`)
.then((response) => response.json())
.then(stockList => {
const stocksState = this.state.stocks;
const stockListValObj = stockList;
console.log(stocksState)
console.log(stockListValObj)
this.setState({
stocks: [
... stocksState.concat(stockListValObj)
]
}, () => { console.log(this.state.stocks);});
})
})
}
renderTableData() {
this.state.stocks.map((stocks, index) => {
const { companyName, symbol, latestPrice, changePercent, marketCap } = stocks //destructuring
return (
<div key={symbol} className='headers'>
<div className='first-value'>
<h4>{companyName}</h4>
<h4 className='symbol'>{symbol}</h4>
</div>
<div className='align-right'>
<h4>{latestPrice}</h4>
</div>
<div className='align-right'>
<h4 className='changePercent'>{changePercent}</h4>
</div>
<div className='align-right'>
<h4>{marketCap}</h4>
</div>
</div>
);
})
}
render() {
return (
<div className='table'>
<h1 id='title'>Companies</h1>
<div className='headers'>
<h4 className='align-right'></h4>
<h4 className='align-right'>Price</h4>
<h4 className='align-right'>+/-</h4>
<h4 className='align-right'>Market Cap</h4>
</div>
<div>
{this.renderTableData()}
</div>
</div>
)
}
}
export default Table;
You should always aim in making single state update, try reducing the state update to single update.
I suggest 2 solution:
Move the data fetch section into a separate function, update a temporary array variable return the variable at the end of the execution.
async dataFetch() {
const sampleData = this.state.stocks || [];
await this.state.favorites.forEach((favorites, index) => {
fetch(`API`)
.then((response) => response.json())
.then((stockList) => {
sampleData.push(stockList);
// const stocksState = this.state.stocks;
// const stockListValObj = stockList;
// console.log(stocksState);
// console.log(stockListValObj);
// this.setState({
// stocks: [
// ... stocksState.concat(stockListValObj)
// ]
// }, () => { console.log(this.state.stocks);});
});
});
return Promise.resolve(sampleData);
}
componentDidMount() {
this.dataFetch().then((stockValues) => {
this.setState({ stocks: stockValues });
});
}
Use Promise.all() this return a single array value which would be easier to update on to the stock state.
Suggestion : When not returning any values from the array try using Array.forEach instead of Array.map
Return keyword is missing in renderTableData
renderTableData() {
this.state.stocks.map((stocks, index) => { ...});
to
renderTableData() {
return this.state.stocks.map((stocks, index) => { ...});
I would say that this is happening because on the first render, the map looks into state.stock and it's an empty array. After the first render, the componentDidMount method is called fetching the data.
I would suggest to just wrap the map into a condition. If stock doesn't have any object, then return whatever you wish (null or a loader/spinner for example).
It's enough to add it like this for not returning anything in case the array is empty (it will be filled after the first render, but this is useful as well to return error message in case the fetch fails):
this.state.stocks.length > 0 && this.state.stocks.map((stocks, index) => {
I have a list and it returns the transaction ID's. Currently this list is not useful since you need to copy the transaction ID and paste it into the input.
How this list is rendered:
{this.state.all_transactions.map(tx => (
<li key={tx.key}>{tx}</li>
))}
componentDidMount() {
if (window.sessionStorage.getItem("wallet")) {
(async () => {
const arweave = Arweave.init();
var address = window.sessionStorage.getItem("WalletAddress");
const txids = await arweave.arql({
op: "and",
expr1: {
op: "equals",
expr1: "from",
expr2: address
},
expr2: {
op: "equals",
expr1: "App-Name",
expr2: "arshard"
}
});
console.log(txids);
this.setState({ all_transactions: txids });
})();
}
}
For example, when I clicked on the one transaction it should take it's place on the "Enter TX" input below. So users won't have to be copy and paste.
I tried adding onClick before the list item however it didn't work.
More code:
state = {
all_transactions: [],
value: ''
};
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.setState({value: e.target.value});
window.sessionStorage.setItem("transactionID", e.target.value);
}
You could do something like
{this.state.all_transactions.map(tx => (
<li key={tx.key} onClick={() => this.setState({ value: tx })}>{tx}</li>
))}
Just guessing here what you want to set it too since you didn't share the whole code. You basically need to use the same state what you use for your text input.
I'm trying to make a react component that can filter a list based on value chosen from a drop-down box. Since the setState removes all data from the array I can only filter once. How can I filter data and still keep the original state? I want to be able to do more then one search.
Array list:
state = {
tree: [
{
id: '1',
fileType: 'Document',
files: [
{
name: 'test1',
size: '64kb'
},
{
name: 'test2',
size: '94kb'
}
]
}, ..... and so on
I have 2 ways that I'm able to filter the component once with:
filterDoc = (selectedType) => {
//way #1
this.setState({ tree: this.state.tree.filter(item => item.fileType === selectedType) })
//way#2
const myItems = this.state.tree;
const newArray = myItems.filter(item => item.fileType === selectedType)
this.setState({
tree: newArray
})
}
Search component:
class SearchBar extends Component {
change = (e) => {
this.props.filterTree(e.target.value);
}
render() {
return (
<div className="col-sm-12" style={style}>
<input
className="col-sm-8"
type="text"
placeholder="Search..."
style={inputs}
/>
<select
className="col-sm-4"
style={inputs}
onChange={this.change}
>
<option value="All">All</option>
{this.props.docTypes.map((type) =>
<option
value={type.fileType}
key={type.fileType}>{type.fileType}
</option>)}
</select>
</div>
)
}
}
And some images just to get a visual on the problem.
Before filter:
After filter, everything that didn't match was removed from the state:
Do not replace original data
Instead, change what filter is used and do the filtering in the render() function.
In the example below, the original data (called data) is never changed. Only the filter used is changed.
const data = [
{
id: 1,
text: 'one',
},
{
id: 2,
text: 'two',
},
{
id: 3,
text: 'three',
},
]
class Example extends React.Component {
constructor() {
super()
this.state = {
filter: null,
}
}
render() {
const filter = this.state.filter
const dataToShow = filter
? data.filter(d => d.id === filter)
: data
return (
<div>
{dataToShow.map(d => <span key={d.id}> {d.text}, </span>)}
<button
onClick={() =>
this.setState({
filter: 2,
})
}
>
{' '}
Filter{' '}
</button>
</div>
)
}
}
ReactDOM.render(<Example />, document.getElementById('root'))
<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>
<body>
<div id='root' />
</body>
Don't mutate local state to reflect the current state of the filter. That state should reflect the complete available list, which should only change when the list of options changes. Use your filtered array strictly for the view. Something like this should be all you need to change what's presented to the user.
change = (e) => {
return this.state.tree.filter(item => item.fileType === e.target.value)
}
I have a search and select filters on my page. The issue that I am having is that I can't seem to make the search work with multiple json values.
Example value is { "id": "1", "role": "teacher", "subject": "mathematics", "name": "Jonathan Kovinski" } and I want to be able to use key and values.
I've tried using some other question about combining json key and value into a single array and passing it to the search filter but it didn't work.
text = data.filter(info => {
return Object.keys(info).map(function(key) {
var singleOne = JSON.stringify(info[key]);
console.log(info, "This is the json one")
}).toLowerCase().match(searchString);
});
Here is a link to a JS Fiddle that I've created with all of my code.
I am trying to set my search bar to use all keys and values for searching and sorting data.
i would suggest you put the filtered data in a seperate key in the state in case you want to revert to the original result,
use the Obeject.values instead of Object.keys and filter the data in the handleChange function,
here's a working code :
class Hello extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoading: false,
data: [],
searchString: "",
filtered: []
};
this.handleChange = this.handleChange.bind(this);
}
componentDidMount() {
this.fetchData();
}
handleChange(e) {
var value = e.target.value;
this.setState({
searchString: value,
filtered: this.state.data.filter(e =>
Object.values(e)
.join(" ")
.toLowerCase()
.match(value)
)
});
}
fetchData() {
fetch("https://api.myjson.com/bins/lo3ls")
.then(response => response.json())
.then(json => {
this.setState({
isLoaded: true,
data: json,
filtered: json
});
})
.catch(error => console.log("parsing failed", error));
}
render() {
var { isLoaded, data } = this.state;
const searchString = this.state.searchString.trim().toLowerCase();
let text = this.state.data;
console.log(text);
if (searchString.length > 0) {
text = text.filter(info => {
return info.role.toLowerCase().match(searchString);
});
}
return (
<div>
<input
type="text"
id="searchbar"
value={this.state.searchString}
onChange={this.handleChange}
placeholder="Search"
name="device"
/>
<select className="category-select" name="categories" onChange={this.handleChange}>
{data.map(info => (
<option value={info.role}>{info.role}</option>
))}
</select>
{/* map through the filtered ones*/}
{this.state.filtered.map(info => (
<div className="display">
<span className="role">Role: {info.role}</span>
<span> Name: {info.name}</span>
<span>, Subject: {info.subject}</span>
</div>
))}
</div>
);
}
}
ReactDOM.render(<Hello name="World" />, document.getElementById("container"));
Actually, I read all of your code in Fiddle, But I proffer Fuse to you. Use it inside your code in componentDidMount and implement your search. it is very easy and handy.
const options = {
shouldSort: true,
threshold: 0.6,
location: 0,
distance: 100,
maxPatternLength: 32,
minMatchCharLength: 1,
keys: [
"title",
"author.firstName"
]
};
const fuse = new Fuse(list, options); // "list" is the item array
const result = fuse.search(""); // put your string inside double quotation
The result is your answer.
I'm rendering a list of inputs and I want to bind each input's value to a link's href. My current attempt renders https://twitter.com/intent/tweet?text=undefined:
class App extends React.Component {
tweets = [
{ id: 1, link: 'example.com' },
{ id: 2, link: 'example2.com' }
];
render() {
return (
<div>
{this.tweets.map(tweet =>
<div key={tweet.id}>
<input type="text" placeholder="text" onChange={e => tweet.text = e.target.value} />
<a href={`https://twitter.com/intent/tweet?text=${tweet.text}`}>Tweet</a>
</div>
)}
</div>
);
}
}
This probably needs to involve setState but I have no idea how to achieve that when rendering a list. I've tried to do some research on this but didn't found anything helpful.
JSFiddle: https://jsfiddle.net/nunoarruda/u5c21wj9/3/
Any ideas?
You can move the tweets variable to the state to maintain consistency in that array.
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
tweets: [
{ id: 1, link: 'example.com' },
{ id: 2, link: 'example2.com' }
]
};
};
setTweets = index => e => {
const { tweets } = this.state
tweets[index].text = e.target.value
this.setState({ tweets })
}
render() {
const { tweets } = this.state
return (
<div>
{tweets.map((tweet, index) =>
<div key={tweet.id}>
<input type="text" placeholder="text" onChange={this.setTweets(index)} />
<a href={`https://twitter.com/intent/tweet?text=${tweet.text}`}>Tweet</a>
</div>
)}
</div>
);
}
}
Updated Jsfiddle: https://jsfiddle.net/u5c21wj9/6/
You can reach the desired result using state.
return (
<div>
{tweets.map(({ id, link }) =>
<div key={id}>
<input type="text" placeholder="text" onChange={({ target }) => this.setState({ [id]: target.value })} />
<a href={`https://twitter.com/intent/tweet?text=${this.state[id] || link}`}>Tweet</a>
</div>
)}
</div>
);
Note: I would move tweets outside the component and implement few ES6 features.
Updated Jsfiddle: https://jsfiddle.net/u5c21wj9/7/
You really should use a state here and make your tweets variable be part of it. To do that, add a constructor:
constructor() {
super();
this.state = {
tweets: [
{ id: 1, link: 'example.com' },
{ id: 2, link: 'example2.com' }
]
};
}
Then you need to mutate each linkwhenever you type in one of the inputs. There are a few pitfalls here, so let me go through them one-by-one:
changeTweet = (id, e) => {
let arr = this.state.tweets.slice();
let index = arr.findIndex(i => i.id === id);
let obj = Object.assign({}, arr[index]);
obj.link = e.target.value;
arr[index] = obj;
this.setState({tweets: arr});
}
First, you need to create a copy of your state variable. This gives you something to work with, without mutating the state directly which is anti-pattern. This can be done with slice().
Since you are sending in the id of the object to modify, we need to find it in our array (in case the items are unordered). This is done with findIndex(). You might want to handle the scenario in which such index is not found (I have not done that).
Now we know where in the array the object with the given id key is. Now, create a copy of that item (which is an object). This is also to prevent mutating the state directly. Do this with Object.assign().
Now change the link to the input value we typed in. Replace the old item object with the new one (obj) and replace the old tweets array with the new one (arr).
Here's the full example:
class App extends React.Component {
constructor() {
super();
this.state = {
tweets: [
{ id: 1, link: 'example.com' },
{ id: 2, link: 'example2.com' }
]
};
}
changeTweet = (id, e) => {
let arr = this.state.tweets.slice();
let index = arr.findIndex(i => i.id === id);
let obj = Object.assign({}, arr[index]);
obj.link = e.target.value;
arr[index] = obj;
this.setState({tweets: arr});
}
render() {
return (
<div>
{this.state.tweets.map(tweet =>
<div key={tweet.id}>
<input type="text" placeholder="text" onChange={(e) => this.changeTweet(tweet.id, e)} />
<a href={`https://twitter.com/intent/tweet?text=${tweet.link}`}>Tweet</a>
</div>
)}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<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="root"></div>
You need to save the text from the input in the state (using setState), not in the tweets array. Then you can render it getting the text from the state.
class App extends React.Component {
tweets = [
{ id: 1, link: 'example.com' },
{ id: 2, link: 'example2.com' }
];
state = {
tweetsText :{}
}
handleTextChange = (event, tweetId) => {
const tweetsTextCopy = Object.assign({}, this.state.tweetsText)
tweetsTextCopy[tweetId] = event.target.value
this.setState({tweetsText: tweetsTextCopy})
}
render() {
return (
<div>
{this.tweets.map(tweet =>
<div key={tweet.id}>
<input type="text" placeholder="text" onChange={e => this.handleTextChange(e, tweet.id)} />
<a href={`https://twitter.com/intent/tweet?text=${this.state.tweetsText[tweet.id]}`}>Tweet</a>
</div>
)}
</div>
);
}
}
Links info is in the link property of your tweets array. The property text is not defined.
So, your render function should look like this
render() {
return (
<div>
{this.tweets.map(tweet =>
<div key={tweet.id}>
<input type="text" placeholder="text" onChange={e => tweet.text= e.target.value} />
<a href={`https://twitter.com/intent/tweet?text=${tweet.link}`}>Tweet</a>
</div>
)}
</div>
);
}