using a .put to edit item from an API-generated array - javascript

I'm attempting to use a .put call to edit a color from an array of colors that I am pulling from an API. However, my .put call is not working for some reason. I am unable to get the response from the .put call to log to the console. When I try to submit by clicking the save button, I get an error that says colors.map is not a function. Does anyone know how I can resolve this?
import React, { useState } from "react";
import axios from "axios";
import { axiosWithAuth } from "../utils/axiosWithAuth";
const initialColor = {
color: "",
code: { hex: "" },
};
const ColorList = ({ colors, updateColors }) => {
console.log(colors);
const [editing, setEditing] = useState(false);
const [colorToEdit, setColorToEdit] = useState(initialColor);
const editColor = color => {
setEditing(true);
setColorToEdit(color);
};
const saveEdit = e => {
e.preventDefault();
// Make a put request to save your updated color
// think about where will you get the id from...
// where is is saved right now?
axiosWithAuth().put(`/colors/${colorToEdit.id}`, colorToEdit)
.then(res => {
console.log(res);
updateColors(res.data);
})
};
const deleteColor = color => {
// make a delete request to delete this color
};
return (
<div className="colors-wrap">
<p>colors</p>
<ul>
{colors.map(color => (
<li key={color.color} onClick={() => editColor(color)}>
<span>
<span className="delete" onClick={e => {
e.stopPropagation();
deleteColor(color)
}
}>
x
</span>{" "}
{color.color}
</span>
<div
className="color-box"
style={{ backgroundColor: color.code.hex }}
/>
</li>
))}
</ul>
{editing && (
<form onSubmit={saveEdit}>
<legend>edit color</legend>
<label>
color name:
<input
onChange={e =>
setColorToEdit({ ...colorToEdit, color: e.target.value })
}
value={colorToEdit.color}
/>
</label>
<label>
hex code:
<input
onChange={e =>
setColorToEdit({
...colorToEdit,
code: { hex: e.target.value }
})
}
value={colorToEdit.code.hex}
/>
</label>
<div className="button-row">
<button type="submit">save</button>
<button onClick={() => setEditing(false)}>cancel</button>
</div>
</form>
)}
<div className="spacer" />
{/* stretch - build another form here to add a color */}
</div>
);
};
export default ColorList;

if you are getting error "that says colors.map is not a function" mean colors in not an array type. You may check using Array.isArray(colors). It will return true if colors will array type.

Related

Adding Child (Comment) part to every Parent (Answer) Component in React.js

import React, { useState, useEffect } from 'react'
import { Link } from 'react-router-dom'
import moment from 'moment'
import Avatar from '../../components/Avatar/Avatar'
import { useSelector, useDispatch} from 'react-redux'
import { useParams } from 'react-router-dom'
import { deleteAnswer } from '../../actions/question'
const DisplayAnswer = ( { question, handleShare } ) => {
const User = useSelector((state) => (state.currentUserReducer))
const dispatch = useDispatch()
const { id } = useParams()
const [button, setButton] = useState(false);
const handleDelete = (answerId, noOfAnswers) => {
dispatch(deleteAnswer(id, answerId, noOfAnswers-1))
}
const handleComment = (e) => {
setButton(!button)
alert(e.target.id)
}
return (
<div>
{
question.answer.map( (ans) => (
<div className="display-ans" key={ans._id}>
<p>{ans.answerBody}</p>
<div className="question-actions-user">
<div>
<button type="button" onClick={handleShare}>Share</button>
{
User?.result?._id === ans?.userId && (
<button type='button' onClick={ () => handleDelete(ans._id, question.noOfAnswers) }>Delete</button>
)
}
<div>
</div>
<button id = {ans._id} type='button' onClick = { (e) => handleComment(e) }> Add Comment </button>
{
button &&
(
<div id = {ans._id}>
<textarea rows='5' cols='30'> </textarea> <br />
<button type='button'> Post </button>
</div>
)
}
</div>
<div>
<p>answered { moment(ans.answeredOn).fromNow()}</p>
<Link to={`/Users/${ans.userId}`} className='user-link' style={{color:'#0086d8'}}>
<Avatar backgroundColor="lightgreen" px='8px' py='5px' borderRadius='4px'>{ans.userAnswered.charAt(0).toUpperCase()}</Avatar>
<div>
{ans.userAnswered}
</div>
</Link>
</div>
</div>
</div>
))
}
</div>
)
}
export default DisplayAnswer
I want to add a comment part under every answer
to do that i added a " Add Comment " button under every Answer and i have a button click on that button
and what i want is whenever the button is clicked the addcomment (textbox) should be added under it
but when i click the button the addcomment (textbox) is getting added under every answer
like if 10 answers are their then Addcommment box is getting added under every 10 answers
Currently there is only a single button state that all the mapped answers render a button for. A simple solution would be to instead store the answer id of the answer you want to add a comment for.
Example:
const DisplayAnswer = ({ question, handleShare }) => {
...
const [commentId, setCommentId] = useState(null); // <-- initially null
...
const handleComment = (e) => {
setCommentId(e.target.id); // <-- set answer id here
alert(e.target.id);
};
return (
<div>
{question.answer.map((ans) => (
<div className="display-ans" key={ans._id}>
<p>{ans.answerBody}</p>
<div className="question-actions-user">
<div>
...
<button
id={ans._id}
type="button"
onClick={handleComment}
>
Add Comment
</button>
{commentId === and._id && ( // <-- conditionally render match by id
<div id={ans._id}>
<textarea rows="5" cols="30" />
<br />
<button type="button">Post</button>
</div>
)}
</div>
...
</div>
</div>
))}
</div>
);
};
When the "Post comment" button is clicked and the entered comment is handled don't forget to also set the commentId value back to null to conditionally hide the input.
Each answer must have his own 'state' to display his own textArea, so you have to extract the code of the 'answer' in a new Answer component, and render a new component in the map method.
Each Answer will thus use a "useState" with a "[isTextAreaVisible, setIsTextAreaVisible] = useState(false);" state.

How to create dynamic (conditional) placeholder in React.js

So I have a submit form where the user needs to create a task by typing in a task name. I want it to be empty at the beginning and have a placeholder of "you must enter a task" when the user click add without entering anything. Now I can achieve it to display the placeholder but it's either always there or I encounter unreachable code. I know how to clean the submission & return to the add function, just need to be able to display the placeholder conditionally. Here's what my code looks like atm:
import { useState } from "react";
export default function Todos() {
const [todos, setTodos] = useState([{ text: "hey" }]);
const [todoText, setTodoText] = useState("");
const [isEmpty, setEmpty] = useState("false");
const addTodo = (e) => {
e.preventDefault();
if (todoText){
setTodos([...todos, { text: todoText }]);
setTodoText("");
} else {
setEmpty(true)
setTodoText("");
return
}
}
return (
<div>
{todos.map((todo, index) => (
<div key={index}>
<input type="checkbox" />
<label>{todo.text}</label>
</div>
))}
<br />
<form onSubmit={addTodo}>
<input
value={todoText}
onChange={(e) => setTodoText(e.target.value)}
type="text"
></input>
<button type="submit">Add</button>
{isEmpty &&<span style={{ color: "red" }}>Enter a task</span>}
</form>
</div>
);
}
I could change your code with the following:
You need to initialize isEmpty by false instead of string "false".
And you can use this flag on showing placeholder texts.
Note that I renamed isEmpty by showError.
import { useState } from "react";
export default function Todos() {
const [todos, setTodos] = useState([{text: "hey"}]);
const [todoText, setTodoText] = useState("");
const [showError, setShowError] = useState(false);
// #ts-ignore
const addTodo = (e) => {
e.preventDefault();
if (todoText) {
setTodos([...todos, {text: todoText}]);
setTodoText("");
setShowError(false);
} else {
setTodoText("");
setShowError(true);
return
}
}
return (
<div>
{todos.map((todo, index) => (
<div key={index}>
<input type="checkbox"/>
<label>{todo.text}</label>
</div>
))}
<br/>
<form onSubmit={addTodo}>
<input
value={todoText}
onChange={(e) => setTodoText(e.target.value)}
type="text"
></input>
<button type="submit">Add</button>
{(showError && !todoText) && <span style={{color: "red"}}>Enter a task</span>}
</form>
</div>
);
}

Filtering data in a list (delete button) isn't working?

So I'm doing a list in which you can add items. When you add them you have two options:
Delete the whole list
Delete a specific item.
But for some reason the "handeDelete" button is not working. Can somebody tell me what did I write wrong in the code?
The link to CodeSandbox is:
codesandbox
import { useState } from "react";
import uuid from "react-uuid";
export default function ItemList() {
const [items, setItems] = useState({ item: "" });
const [groceryList, setGroceryList] = useState([]);
function handleChange(value, type) {
setItems((prev) => {
return { ...prev, [type]: value };
});
}
function handleSubmit(e) {
e.preventDefault();
const newItem = { ...items, id: uuid() };
setGroceryList([...groceryList, newItem]);
setItems({ item: "" });
}
function handleDelete(id) {
setGroceryList(groceryList.filter((items) => items.id !== id));
}
return (
<>
<form autoComplete="off" onSubmit={handleSubmit}>
<input
type="text"
name="item"
id="item"
value={items.item}
onChange={(e) => handleChange(e.target.value, "item")}
/>
</form>
{groceryList.map((list) => {
return (
<div key={list.id}>
<ul>
<li> {list.item}</li>
</ul>
<button onClick={(id) => handleDelete()}>Delete</button>
</div>
);
})}
<button onClick={() => setGroceryList([])}>Clear</button>
</>
);
}
Your delete button definition is wrong:
<button onClick={() => handleDelete(list.id)}>Delete</button>
the parameter you are receiving from the click event is not the id. Since you are not working with the event args itselfy you can safely ignore it. The second mistake was, that you are not passing the id itself to your handleDelete function.
For learning purposes, humor yourself and print the event to the console, while developing:
<button onClick={(evt) => {
console.log(evt)
handleDelete(list.id)
}}>
Delete
</button>
This will show you, that the parameter, that you named id (and I renamend to evt), is in fact reacts Synthetic Event: https://reactjs.org/docs/events.html

How to input text for each user in React?

In Instagram clone project, I created comment box for each user, submitting comments works properly but, inputting text shows text for all users. Following is the code required.
Home.js
const [data, setData] = useState([])
const [userProfile, setUserProfile] = useState(null)
const {state, dispatch} = useContext(UserContext)
/* Comment function */
const makeComment = (text,postId)=>{
fetch('/comment',{
method:"put",
headers:{
"Content-Type":"application/json",
"Authorization":"Bearer "+localStorage.getItem("jwt")
},
body:JSON.stringify({
postId,
text
})
}).then(res=>res.json())
.then(result=>{
console.log(result)
const newData = data.map(item=>{
if(item._id==result._id){
return {...item,comments: result.comments}
}else{
return item
}
})
setData(newData)
}).catch(err=>{
console.log(err)
})
}
return (
<div className="home" >
{
data.map(item => {
return (
<div className="card home-card" key={item._id}>
<div className="profile-card">
/* Profile Image */
<img style={{width: '50px', height:'50px', borderRadius:'80px'}}
src={item.postedBy.image} />
<span style={{display:'flex', flexWrap:'wrap', width:'85%'}}>
/* Profile Link */
<h5><Link to={item.postedBy._id !== state._id ? "/profile/"+item.postedBy._id : "/profile" } > {item.postedBy.name} </Link></h5>
</span>
</div>
/* Post Image */
<div className="card-image">
<img style={{width: '100%', height:'260px'}} src={item.photo} alt=""/>
</div>
<hr/>
/* Like Button */
<div className="like-section">
{ item.likes.includes(state._id) ?
<FavoriteIcon className="like-heart" style={{ fontSize: 25 }} onClick={()=>{unlikePost(item._id)}} /> :
<FavoriteBorderIcon className="unlike-heart" style={{ fontSize: 25 }} onClick={()=>{likePost(item._id)}} />
}
<h6>{item.likes.length}</h6>
</div>
<br/>
/* Comments Section */
<div className="card-content">
<h6><b>{item.title}</b></h6>
<p>{item.body}</p>
{
item.comments.map(record => {
return (
<>
<h6 key={record._id}><b className="posted-by">{record.postedBy.name}</b>{record.text}</h6>
</>
)
})
}
{
/* Comment Box */
<form onSubmit={(e) => {
e.preventDefault();
makeComment(e.target[0].value, item._id)
setNewText("")
}}>
<input type="text" value={newtext} onChange={onChange} placeholder="add a comment" />
<button type="submit" disabled={!newtext}>Post</button>
</form>
}
</div>
</div>
)
})
}
</div>
);
};
export default Home;
When typing comments in particular comment box only that should be focused and display text, but all other comments are displayed
Below is the image reference
Above image you can see 'Hello' text when typed is displayed in both comment box
Here you can see 'Hello' text is submitted properly to the that particular id
So, any appropriate solution?
That's because you are using the same state for each comment boxes.
To overcome this, you need to create as many state as users.
You can do this easily by using an array for comments
const [newText, setNewText] = useState([])
data.map((item, index) => (
...
<input value={newText[index]} onChange={(e) => setNewText(replaceByIndex(newText, index, e.target.value))} />
...
)
FYI, here's replaceByIndex function:
const replaceByIndex = (originArray, index, newItem) =>
originArray.map((item, i) => i === index ? newItem: item)
Additional enhancements to your code:
Performance enhancements: use useCallback react hook to define functions in function components.
Use meaningful naming conventions. It's hard to understand what the data and item means...

How can i add and remove elements from array on click in reactjs?

I'm trying to create a function that renders an array of links and i want to create a text input and a button that adds value from input in the array. I got the links saved in the state in the object that looks like this:
sourceLinks: {
0: "https://www.w3schools.com/html/"
1: "https://www.apachefriends.org/docs/"
2: "https://docs.moodle.org/38/en/Windows_installation_using_XAMPP"
}
I've managed to render the links like this:
renderLinks() {
let sessionLinks = this.state.sessionLinks;
let links = [];
Object.values(sessionLinks).map((link) => {
links.push(<div className="column">
<span>
<InputPreview inputValue={link} classes="w-300" />
</span>
</div>)
})
return links;
}
InputPreview is the component i use for displaying links. I'm tryin to add a text input and a button bellow the rendered links that adds the value to the array, and an icon next to every link that removes it from an array. I'm trying to do it all in one function renderLinks() and then call it in render. I know i have to push and slice items from an array and update the state but i'm strugling cause i just started learning react. Please help :)
You can add and render links with below code.
import React from "react";
class ItemList extends React.Component {
state = {
links: ["item1"],
newItem: ""
};
submit(e, newLink) {
e.preventDefault();
let updatedLinks = this.state.links;
updatedLinks.push(newLink);
this.setState({ links: updatedLinks });
}
render() {
return (
<React.Fragment>
<ul>
{this.state.links?.map((link, i) => (
<li key={i}>
<p>{link}</p>
</li>
))}
</ul>
<form onSubmit={(e) => this.submit(e, this.state.newItem)}>
<input
type="text"
value={this.state.newItem}
onChange={(e) => this.setState({ newItem: e.target.value })}
/>
<button type="submit">ADD</button>
</form>
</React.Fragment>
);
}
}
export default ItemList;
Let me know for further clarificaton.
This is a example with functional components and hooks
import React, { useState } from 'react';
const sourceLinks = [
'https://www.w3schools.com/html/',
'https://www.apachefriends.org/docs/',
'https://docs.moodle.org/38/en/Windows_installation_using_XAMPP',
];
export const ListLinks = () => {
const [links, setLinks] = useState(sourceLinks);
const [newLink, setNewLink] = useState('');
const handleAdd = () => {
setLinks(links => [...links, newLink]);
};
const handleChangeNewLink = e => {
const { value } = e.target;
setNewLink(value);
};
return (
<div>
<div style={{ display: 'flex', justifyContent: 'center' }}>
<input type='text' value={newLink} onChange={handleChangeNewLink} />
<button onClick={handleAdd}>Add</button>
</div>
<br />
{links.map((link, index) => (
<p key={index}>{link}</p>
))}
</div>
);
};
This is the result:
Lastly, read the documentation, managing the state is essential.

Categories