onClick like button likes all objects in array React JS - reactjs

I'm super new to web development but I'm trying to implement a like button to an API array of the mars rover images in react js. The problem I'm having is when I click the like button for one of my images all of the like buttons click. I've tried using id's, classNames, and keys for my button but nothing seems to be working. Any help would be appreciated :)
class Rover extends React.Component {
state = {
loading: true,
images: [],
};
async componentDidMount() {
const url = 'https://api.nasa.gov/mars-photos/api/v1/rovers/curiosity/photos?sol=1000&page=2&api_key='
const response = await fetch(url);
const data= await response.json();
this.setState({ images: data.photos, loading: false });
console.log(this.state);
}
render() {
if (this.state.loading) {
return <div>loading...</div>;
}
if (!this.state.images) {
return <div>didn't find image</div>
}
return (
<IconContext.Provider value={{ color: '#a9b3c1', size: 64 }}>
<RoverSection>
<RoverWrapper>
<RoverHeading>Mars Rover</RoverHeading>
<div>
{this.state.images.map((image, idx) => (
<div className={`some-image-${idx}`} key={`some-image${idx}`}>
<RoverContainer>
<RoverCard to='/'>
<RoverCardInfo>
<RoverCardID>Photo ID: {image.id}</RoverCardID>
<RoverCardFeatures>
<Img src={image.img_src} />
<RoverCardFeature>{image.rover.name} Rover</RoverCardFeature>
<RoverCardFeature>{image.camera.full_name}</RoverCardFeature>
<RoverCardFeature>{image.earth_date}</RoverCardFeature>
</RoverCardFeatures>
<Button primary id={image.id} className={`some-button-${idx}`} onClick={() => {
this.setState({
isLiked: !this.state.isLiked
})
}}>{this.state.isLiked ? <FaHeart/> : <FaRegHeart/>}</Button>
</RoverCardInfo>
</RoverCard>
</RoverContainer>
</div>
))}
</div>
</RoverWrapper>
</RoverSection>
</IconContext.Provider>
);
}
}
export default Rover

You are mapping through all of the images (you have an array of images); However, you do not have an array for your likes you just have a single boolean value. You need to create an array for your likes of equal size to your image array and then use the same index (you can get the current index in your map (you already did an named it idx))
Your initial like state should be similar to:
async componentDidMount() {
const url = 'https://api.nasa.gov/mars-photos/api/v1/rovers/curiosity/photos?sol=1000&page=2&api_key='
const response = await fetch(url);
const data= await response.json();
this.setState({ images: data.photos, isLiked:new Array(data.photos.length).fill(false), loading: false });
console.log(this.state);
}
and then in you component
this.setState((current)=>{
const newState={...current};
const isLiked = newState.isLiked;
isLiked[idx] = !isLiked[idx];
return {
...newState,
isLiked:[...isLiked]
}
})
and
{this.state.isLiked[idx] ? <FaHeart/> : <FaRegHeart/>}

lets say this is your image array:
this.state.images = [{id : 0, img : "img1"} ,{id : 1 , img : "img2"} ,{id : 2, img : "img3"} ]
now say you want to like img1 , when you click like on img1, dont you think that you need to only save img1 as liked image. you can do it by modifying the images array object. if you dont want to change data in your original image object,you can keep another array/object map with id and their like status ( you can keep all or you can only push the liked ones.
now when you click on any like button on image , call the function ( also bind it in constructor)
onClick = {(e)=> {this.handleLikeClick(image)}}
and in function handleLikeClick
handleLikeClick(image){
let copyImages = [...this.state.images]
this.state.images.forEach((img) => {
if(img.id === image.id) {
img.liked= !img.liked;
}
});
this.setState({
images :copyImages
})
}
now whenever you are in render , instead of just checking isLIked, you can check the liked property in image object.

Related

setState not returned from render when using Axios

I'm using axios to get data from an endpoint. I'm trying to store this data inside the state of my React component, but I keep getting this error:
Error: Results(...): Nothing was returned from render. This usually means a return statement is missing. Or, to render nothing, return null.
I've struggled with many approaches: arrow functions etc., but without luck.
export default class Map extends Component {
constructor() {
super();
this.state = {
fillColor: {},
selectedCounty: "",
dbResponse: null,
};
}
getCounty(e) {
axios.get("/getWeatherData?county=" + e.target.id)
.then((response) => {
this.setState(prevState => {
let fillColor = {...prevState.fillColor};
fillColor[prevState.selectedCounty] = '#81AC8B';
fillColor[e.target.id] = '#425957';
const selectedCounty = e.target.id;
const dbResponse = response.data;
return { dbResponse, selectedCounty, fillColor };
})
}).catch((error) => {
console.log('Could not connect to the backend');
console.log(error)
});
}
render() {
return (
<div id="map">
<svg>big svg file</svg>
{this.state.selectedCounty ? <Results/> : null}
</div>
)
}
I need to set the state using prevState in order to update the fillColor dictionary.
Should this be expected? Is there a workaround?

Modify a property of an element that is inside of react state

I have this program that brings an article from my data base in componentDidMount(), fragmentedArticle() grabs each word and put it in this.state.fragmented and each word is put it in a span tag in this.state.fragmentedTags
I print the article in grey color text, but I want to change the style color property of the text (with a setTimeout every 1000 milliseconds) but I don't know if it's posible to changed a property of a tag that is save it in the react state.
import React, { Component } from 'react';
import axios from 'axios';
import { Link } from 'react-router-dom';
export default class ArticleDetails extends Component {
constructor(props) {
super(props);
this.state = {
id: '',
title: '',
article: '',
date: new Date(),
lenguages: [],
articles: [],
fragmented: [],
fragmentedTags: [],
showSpans: false,
counterSpaces: 0,
}
this.deleteArticle = this.deleteArticle.bind(this);
this.fragmentedArticle = this.fragmentedArticle.bind(this);
this.coloredArticle = this.coloredArticle.bind(this);
}
componentDidMount() {
this.setState({
id: this.props.match.params.id
})
// get individual exercise.
axios.get('http://localhost:5000/articles/'+ this.props.match.params.id)
.then(response => {
this.setState({
title: response.data.title,
article: response.data.article,
duration: response.data.duration,
date: new Date(response.data.date)
})
})
.catch(function (error) {
console.log(error);
})
// get all lenguages.
axios.get('http://localhost:5000/lenguages/')
.then(response => {
if (response.data.length > 0) {
this.setState({
lenguages: response.data.map(lenguage => lenguage.lenguage),
})
}
}).catch( error => console.log(error) )
}
deleteArticle( id ) {
axios.delete( 'http://localhost:5000/articles/' + id )
.then( res => console.log( res.data ) );
this.setState({
articles: this.state.articles.filter( el => el._id !== id )
}
)
}
fragmentedArticle = () => {
let length = this.state.article.length;
let word = [];
let fragmentedArticle = [];
let counter = 0;
let p1, p2 = 0;
for (let x = 0; x <= length; x++) {
word[x] = this.state.article[x];
if( this.state.article[x] === ' ' || this.state.article[x] === "\n" ){
p2 = x;
fragmentedArticle[counter] = word.join('').substr(p1,p2);
p1 = p2
p2 = 0;
counter++;
}
}
// we save each word
this.setState({
fragmented: fragmentedArticle,
counterSpaces: counter,
showSpans: !this.state.showSpans,
})
// we save each word wrapped in a span tag with a property of color grey.
this.setState( prevState => ({
fragmentedTags: prevState.fragmented.map( (name, index) =>
<span key={ index } style={{color:'grey'}} >{name}</span>
)
}))
}
coloredArticle = () => {
console.log(this.state.fragmentedTags[0].props.style.color);
// I see the actual value color style property of the span tag (grey) but I want to change it on green from the this.state.fragmentedTags[0] to the last word within a x period of time with the setTimeout js method.
// this code bellow change the color but not one by one.
this.setState( prevState => ({
fragmentedTags:
// map all the elements
prevState.fragmented.map( (name, index) =>
// with a delay of 1500 milliseconds
setTimeout(() => {
<span key={ index } style={{color:'green'}} >{name}</span>
}, 1500)
)
})
)
}
render(props) {
const displaySpan = this.state.showSpans ? 'inline-block' : 'none';
const {fragmentedTags} = this.state
return (
<div>
<h6>{ this.state.title }</h6>
{/* this show/hide the article text */}
<p onClick={ this.fragmentedArticle }>Show</p>
{/* I want to changed the text color one by one within a period of time (velocity, setTimeout) */}
<p onClick={ this.coloredArticle }>Play</p>
{/* Show us the full article (each word wrapped in a span with its property) */}
<div style={{ display:displaySpan }}>
{ fragmentedTags }
</div>
</div>
)
}
}
You shouldn't be transforming state like that. It gets very difficult to debug your application and makes it much more difficult to do simple things.
Download your articles and save them into state but if you need to make any other changes save it into a new part of state rather than overwriting current state. Most likely you do not need to save transformations into state though.
To answer your question, I would set a timestamp for each article and once its downloaded set a timer that will rerender the article with the new changes if sufficient time has passed.

Display data from API

i have the following code in parent.js file which gets data from API using axios which is working fine
//Parent.js
componentDidMount() {
axios.get('URL', {
method: 'GET',
headers: {
'key': 'apikeygoeshere'
}
})
.then((response) => {
this.success(response);
})
}
successShow(response) {
this.setState({
person: response.data.data.Table
});
}
render() {
return (
<div class="row">
{this.state.person.map(results => (
<h5>{results.first_name}</h5>
)
)
}
and the above code display data from api perfect. i want to display api data in child component instead of displaying data from json file. In child component i have the following code which display data from local.json file
//
The issue is in the successShow function the you cannot change the array state value like that. According to the answer from here :
Correct modification of state arrays in ReactJS
You can update the state like this:
this.setState(prevState => ({
person: [...prevState.person,response.data.data.Table]
});
or
this.setState({
person: this.state.person.concat([response.data.data.Table])
});
Try using
const data = this.state.person && this.state.person.find(item => item.id === id);
const relatedTo = this.state.person && this.state.person.filter( item => item.manager_id === data.manager_id && item.id !== data.id );

How to Add filter into a todolist application in Reactjs with using .filter

im new to react, trying to make an todolist website, i have the add and delete and displaying functionality done, just trying to add an search function, but i cant seem to get it working, where as it doesn't filter properly.
i basically want to be able to filter the values on the todos.title with the search value. such as if i enter an value of "ta" it should show the todo item of "take out the trash" or any item that matches with that string.
when i try to search, it gives random outputs of items from the filtered, i am wondering if my filtering is wrong or if i am not like displaying it correctly.
ive tried to pass the value into todo.js and display it there but didn't seem that was a viable way as it it should stay within App.js.
class App extends Component {
state = {
todos: [
{
id: uuid.v4(),
title: "take out the trash",
completed: false
},
{
id: uuid.v4(),
title: "Dinner with wife",
completed: true
},
{
id: uuid.v4(),
title: "Meeting with Boss",
completed: false
}
],
filtered: []
};
// checking complete on the state
markComplete = id => {
this.setState({
todos: this.state.filtered.map(todo => {
if (todo.id === id) {
todo.completed = !todo.completed;
}
return todo;
})
});
};
//delete the item
delTodo = id => {
this.setState({
filtered: [...this.state.filtered.filter(filtered => filtered.id !== id)]
});
};
//Add item to the list
addTodo = title => {
const newTodo = {
id: uuid.v4(),
title,
comepleted: false
};
this.setState({ filtered: [...this.state.filtered, newTodo] });
};
// my attempt to do search filter on the value recieved from the search field (search):
search = (search) => {
let currentTodos = [];
let newList = [];
if (search !== "") {
currentTodos = this.state.todos;
newList = currentTodos.filter( todo => {
const lc = todo.title.toLowerCase();
const filter = search.toLowerCase();
return lc.includes(filter);
});
} else {
newList = this.state.todos;
}
this.setState({
filtered: newList
});
console.log(search);
};
componentDidMount() {
this.setState({
filtered: this.state.todos
});
}
componentWillReceiveProps(nextProps) {
this.setState({
filtered: nextProps.todos
});
}
render() {
return (
<div className="App">
<div className="container">
<Header search={this.search} />
<AddTodo addTodo={this.addTodo} />
<Todos
todos={this.state.filtered}
markComplete={this.markComplete}
delTodo={this.delTodo}
/>
</div>
</div>
);
}
}
export default App;
search value comes from the header where the value is passed through as a props. i've checked that and it works fine.
Todos.js
class Todos extends Component {
state = {
searchResults: null
}
render() {
return (
this.props.todos.map((todo) => {
return <TodoItem key={todo.id} todo = {todo}
markComplete={this.props.markComplete}
delTodo={this.props.delTodo}
/>
})
);
}
}
TodoItem.js is just the component that displays the item.
I not sure if this is enough to understand the issue 100%, i can add more if needed.
Thank you
Not sure what is wrong with your script. Looks to me it works fine when I am trying to reconstruct by using most of your logic. Please check working demo here: https://codesandbox.io/s/q9jy17p47j
Just my guess, it could be there is something wrong with your <TodoItem/> component which makes it not rendered correctly. Maybe you could try to use a primitive element such as <li> instead custom element like <TodoItem/>. The problem could be your logic of markComplete() things ( if it is doing hiding element works ).
Please let me know if I am missing something. Thanks.

React trying to use .map to populate data from multiple APIs into the same mapped container

I'm trying to practice integrating data from separate APIs, but I'm having trouble conceptualizing what the right way is to do so.
Basically, I have two sample APIs I'm fetching from:
1. const API = 'https://hn.algolia.com/api/v1/search?query='; (this.state.hits)
const DEFAULT_QUERY = 'redux';
and
2. https://randomuser.me/api/?results=50 (this.state.randomPeople)
I pull them into their own arrays in state via a method called in componentDid Mount.
this.state = {
hits: [],
randomPeople: [],
};
Ideally, I'd like to map over both of them and have data available from each .map result to populate in a single container, something like:
<div>
<img src={random.thumbnailPic}/>
<h3>{random.name.first}</h3>
<h3>{random.name.last}</h3>
<p>{hit.title}</p>
</div>
Just not sure how to approach this the best way. I have only mapped over one data source when populating the results to a container. Should I combine the two arrays and store them together in state? I looked at Lodash, would that work here? Or is there a better way to accomplish this that I just haven't found?
Right now I just have them right on top of another in render() :
{hits.map(hit => (
<div key={hit.objectID}>
<a href={hit.url}>{hit.title}</a>
</div>
))}
{randomPeople.map(rando => (
<div key={random.email}>
<img src={random.picture.medium} />
<h3>Author: {random.name.first} {random.name.last}</h3>
</div>
))}
And here are my methods:
fetchHits = () => {
fetch(API + DEFAULT_QUERY)
.then(response => {
if (response.ok) {
return response.json();
} else {
throw new Error('Something went wrong ... ');
}
})
.then(data => this.setState({ hits: data.hits, isLoading: false }))
.catch(error => this.setState({ error, isLoading: false }));
};
fetchRandomPeople = () => {
fetch('https://randomuser.me/api/?results=50')
.then(results => results.json())
.then(data => this.setState({ randomPeople: data.results }));
};
componentDidMount() {
this.fetchRandomPeople();
this.fetchHits();
this.setState({ isLoading: true });
}
Thanks!
If you're assuming that hits and randompeople are going to be the same length, or if you can somehow align the two arrays, you could add the index parameter to your .map() function:
{randomPeople.map((rando, i) => (
<div key={rando.email}>
<img src={rando.thumbnailPic}/>
<h3>{rando.name.first}</h3>
<h3>{rando.name.last}</h3>
<p>{hits[i].title}</p>
</div>
)}

Resources