Working on a simple project using react js.
What I'm trying to do is to save the "item"(which has an input field for the user to put it) in the localStorage, for the purpose when a user reloads or navigate to other components the item won't be gone, but it will be there.
AddItem.js:
export default class AddItem extends React.Component {
userItems;
constructor(props) {
super(props);
this.state = {
content: "",
items: [],
};
}
update(event) {
this.setState({
message: event.target.value,
});
}
componentDidMount() {
this.userItems = JSON.parse(localStorage.getItem('items'));
if (localStorage.getItem('items')){
this.setState({
items: this.userItems.items,
message: this.userItems.message
})
}else {
this.setState({
items: [],
message: ""
})
}
}
componentWillUpdate(nextProps, nextState){
localStorage.setItem('item', JSON.stringify(nextState));
}
handleClick() {
var items = this.state.items;
items.push(this.state.message);
this.setState({
items: items,
content: "",
});
}
handleChanged(j, event) {
let items = this.state.items;
items[j] = event.target.value;
this.setState({
items: items,
});
}
The problem is that when I type an item in the input field, and I reload the page, the component is not shown at all.
In the application, at the local storage, the value is shown at the google inspect:
But as soon as I navigate to another component or I reload that page, the storage becomes empty like it was in the first time.
How can I make it work, so the value doesn't go away when I navigate to another page?
Remove the logic inside componentWillUpdate and move it to handleItemChanged. Like so:
export default class AddItem extends React.Component {
userItems;
constructor(props) {
super(props);
this.state = {
content: "",
items: [],
};
}
updateMessage(event) {
this.setState({
message: event.target.value,
});
}
handleClick() {
var items = this.state.items;
items.push(this.state.message);
this.setState({
items: items,
content: "",
});
}
handleItemChanged(i, event) {
var items = this.state.items;
items[i] = event.target.value;
this.setState({
items: items,
});
localStorage.setItem('items', JSON.stringify({items, message :""}));
}
componentDidMount() {
this.userItems = JSON.parse(localStorage.getItem('items'));
if (localStorage.getItem('items')){
this.setState({
items: this.userItems.items,
message: this.userItems.message
})
}else {
this.setState({
items: [],
message: ""
})
}
}
renderRows() {
var context = this;
return this.state.items.map(function (o, i) {
return (
<tr key={"item-" + i}>
<td className="2">
<div>
<input
type="text"
value={o}
onChange={context.handleItemChanged.bind(context, i)}
</tr>
Related
I'm trying to pass value from one component to another. First one looks like this:
class ListStation extends Component {
constructor(props) {
super(props)
this.state = {
stations: []
}
this.editStation = this.editStation.bind(this);
}
editStation(id) {
this.props.history.push(`/add-station/${id}`);
}
componentDidMount() {
StationService.getStations().then((res) => {
this.setState({ stations: res.data });
})
}
}
render() {
return (
<div>
<tbody>
{this.state.stations.map(
station =>
<tr key={station.id}>
<td>{station.city}</td>
<td>{station.name}</td>
<td>
<button onClick={() => this.editStation(station.id)} className="btn btn-info">Modify</button>
...
</div>
</div>
);
}
}
export default ListStation;
And another looks like this:
import React, { Component } from 'react';
import StationService from '../services/StationService';
class CreateStationComponent extends Component {
constructor(props) {
super(props)
this.state = {
station: {
id: this.props.match.params.id,
city: '',
name: '',
trains: [
{
number: '',
numberOfCarriages: ''
}
]
}
}
this.changeCityHandles = this.changeCityHandles.bind(this);
this.changeNameHandles = this.changeNameHandles.bind(this);
this.saveStation = this.saveStation.bind(this);
}
componentDidMount() {
if (this.state.station[0].id === '_add') {
return;
} else {
StationService.getStationById(this.state.id).then((res) => {
let station = res.data;
this.setState({ name: station[0].name, city: station[0].city })
});
}
console.log(this.state.station.city + 'dfddddd');
}
But when I try to pass value from one component to another I get error: Property of undefined. The response I get from API looks like this:
I'm trying to edit values based on the id taken from the first component but it seems to fail.
if (this.state.station[0].id === '_add') {
return;
}
Have a look at this if statement from your codebase I think you should remove [0] after this.state.station ... this is because station is an object not an Array
Change it to if (this.state.station.id === '_add') {
I have a doughnut chart component where a user can click the element. onClick, I pass the pathname and id to another component which will render a table. This works fine, however, I need to figure out what happens if a user decides to refresh the second component. Currently, if a user refreshes, it throws an error of undefined because we did not pass any props.
How would I solve this issue if a user decides to refresh the page?
//doughnut component that will pass data
import { withRouter } from 'react-router-dom';
onDonutClick = item => {
try {
this.props.history.push({
pathname: "/component that will receive the id",
state: { id: item[0]._index }
})
} catch(error) {
console.log(error)
}
}
export default withRouter(DoughnutComponent);
//table component that will recieve the data
import { withRouter } from 'react-router-dom';
constructor(props) {
super(props);
this.state = {
data: [some table data in here],
index: "",
sourceData: []
}
componentDidMount = () => {
this.getTableData();
}
getTableData = () => {
let id = this.props.history.location.state.id;
if(id === 6) {
this.setState({
index: 6,
sourceData: this.state.data
})
} else {
const tData = this.state.data.filter(function(item){
return item.id === id;
})
this.setState({
index: id,
sourceData: tData
})
}
}
I have tried the below. id of 6 will show the whole table.
if(!this.props.history.location.state.id) {
this.setState({
index: 6,
sourceData: this.state.data
)} else {
let id = this.props.history.location.state.id;
if(id === 6) {
this.setState({
index: 6,
sourceData: this.state.data
})
} else {
const tData = this.state.data.filter(function(item){
return item.id === id;
})
this.setState({
index: id,
sourceData: tData
})
}
}
}
I have a problem getting state data in the render function.
It works fine and returns the array when I use
console.log(items)
But trying to get first item from the array yields an error
console.log(items[0])
Full code is:
import React from "react";
import StatsSection from "./../components/StatsSection";
import { db } from "./../util/database";
class IndexPage extends React.Component {
constructor(props) {
console.log(props)
super(props);
this.state = {};
}
componentDidMount() {
var data = []
db.collection('test')
.get().then(snapshot => {
snapshot.forEach(doc => {
data.push(doc.data())
})
})
this.setState({
items: data
});
}
render() {
const { items } = this.state
console.log(items[0])
return (
<StatsSection
color="white"
size="medium"
backgroundImage=""
backgroundImageOpacity={1}
items={[
{
title: "Following",
stat: "123"
},
{
title: "Followers",
stat: "456k"
},
{
title: "Likes",
stat: "789"
}
]}
/>
);
}
}
export default IndexPage;
Where am I making the mistake?
You're only setting one item, so items is actually just one item and items[0] fails.
this.setState({
items: data
});
should be inside the .then() so that it only runs after all the items are populated with the .forEach().
Update your componentDidMount() like so:
componentDidMount() {
db.collection('test').get().then(snapshot => {
var data = []
snapshot.forEach(doc => {
data.push(doc.data())
})
this.setState({ items: data })
})
}
I'm not sure what I'm doing wrong, but I have an input field for entering a search term and trying to filter results based on the search term. The problem is that the first value being passed is an empty string and input is offset by 1 item for each keypress after that. For example, if I type 'sea', it would update the search term to be ' se'. Then, when I try to delete the value, it is offset the other direction, so deleting ' se' ends with 's', which can't be deleted.
(Here's a link to the app in progress: https://vibrant-yonath-715bf2.netlify.com/allpokemon. The full search functionality isn't working quite yet. I'm pretty new at this.)
import React, { Component } from 'react';
import Pokemon from './Pokemon';
class PokemonList extends Component {
constructor(props) {
super(props);
this.state = {
pokemonList: [],
searchTerm: '',
fetched: false,
loading: false
};
this.updateResults = this.updateResults.bind(this);
}
componentWillMount() {
this.setState({
loading: true
});
fetch('https://pokeapi.co/api/v2/pokemon?limit=151')
.then(res => res.json())
.then(response => {
this.setState({
pokemonList: response.results,
loading: true,
fetched: true
});
});
}
handleSearchTermChange = (
event: SyntheticKeyboardEvent & { target: HTMLInputElement }
) => {
this.setState({ searchTerm: event.target.value });
this.updateResults();
};
updateResults() {
const filteredList = this.state.pokemonList.filter(
pokemon =>
pokemon.name.toUpperCase().indexOf(this.state.searchTerm.toUpperCase()) >= 0
);
console.log(this.state.searchTerm);
this.setState({
pokemonList: filteredList
});
}
render() {
const { fetched, loading, pokemonList } = this.state;
let content;
if (fetched) {
content = (
<div className="flex-grid">
{pokemonList.map((pokemon, index) => (
<Pokemon key={pokemon.name} id={index + 1} pokemon={pokemon} />
))}
</div>
);
} else if (loading && !fetched) {
content = <p> Loading ...</p>;
} else {
content = <div />;
}
return (
<div>
<input
onChange={this.handleSearchTermChange}
value={this.state.searchTerm}
type="text"
placeholder="Search"
/>
{content}
</div>
);
}
}
export default PokemonList;
setState is asynchronous, so your this.state.searchTerm is not updated when you call updateResults. You could e.g. filter the array in render instead.
Example
class App extends Component {
state = {
pokemonList: [
{ name: "pikachu" },
{ name: "bulbasaur" },
{ name: "squirtle" }
],
searchTerm: ""
};
changeSearchTerm = event => {
this.setState({ searchTerm: event.target.value });
};
render() {
const { pokemonList, searchTerm } = this.state;
const filteredList = pokemonList.filter(pokemon =>
pokemon.name.toUpperCase().includes(searchTerm.toUpperCase())
);
return (
<div>
<input value={searchTerm} onChange={this.changeSearchTerm} />
{filteredList.map(pokemon => <div>{pokemon.name}</div>)}
</div>
);
}
}
I think the problem is that you call this.updateResults();
and then calling this.setState({ searchTerm: event.target.value }); instead of using the callback function for setState.
For example:
this.setState({ searchTerm: event.target.value }, () => this.updateResults());
Hope I got it right.
Update:
Also I see many problems in your code, for example, why you update the list with a filtered list? you don't need to do that:
this.setState({
pokemonList: filteredList
});
Instead of updating the results in the state, you simply need to render the filtered list... meaning your state stay with the original list, also your filterd value, just in the render you pass the filtered list..
I have some problem and am getting furios a little. I want to .map my Array of Objects. And make everyone of them clickable. After click I want the particular object to show only its Avatar:
const stations = [
{
name: 'first',
avatar: './img/1.jpg'
},
{
name: 'second',
avatar: './img/2.jpg'
},
{
name: 'third',
avatar: './img/3.jpg'
},
{
name: 'fourth',
avatar: './img/4.jpg'
},
{
name: 'fifth',
avatar: './img/5.jpg'
}
]
Right now. I can access the value I need from my Database Array. But! I have a problem with:
this.state = {
clicked: false
}
this.handleClick = this.handleClick.bind(this)
}
My objects do not have separate state. So when I want to create some action based on this.state (like hide and show) it always work on EVERY element.
I have code which works in some way. When I render the list and click on any button, action occurs for every of them:
class radiosList extends React.Component {
constructor() {
super();
this.state = {
clicked: false
}
this.handleClick = this.handleClick.bind(this)
}
handleClick(station) {
console.log(this)
this.setState({ clicked: !this.state.clicked })
}
render () {
return (
<div>
{
this.props.stations.map((station, index) => (
<div className='radio_list radio_list--component' style={{cursor: 'pointer'}} onClick={() => this.handleClick(station.avatar)}>
<div className='radio_list radio_list--component radio_list--component-station_name'>{station.name}</div>
</div>
))
}
</div>
)
}
}
export default radiosList
Edit: first answer helped with accessing values I need.
This is the way you can achieve what you want by adding an additional clicked state attribute to data. There could be a better way but his is how I have done it for my purposes so far.
class radiosList extends React.Component {
constructor() {
super();
this.state = {
data: [],
}
this.handleClick = this.handleClick.bind(this)
}
handleClick(index) {
console.log(this)
var data = [...this.state.data];
data[index].clicked = !data[index].clicked;
this.setState({data});
}
render () {
var self = this;
this.props.station.forEach(function(station) {
self.state.data.push({name: station.name, avatar: station.avatar, clicked: false});
self.setState({data: self.state.data});
})
return (
<div>
{
this.state.data.map((station, index) => (
<div className='radio_list radio_list--component' style={{cursor: 'pointer'}} onClick={() => this.handleClick(index)}>
<div className='radio_list radio_list--component radio_list--component-station_name'>{station.name}</div>
</div>
))
}
</div>
)
}
}
export default radiosList