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

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.

Related

Is it possible to use css transition in React?

For example I have this code.
And I want to use CSS transitionfor Button when showButton and when !showButton. Now it's just removed and add Button when showButton changes.
{showButton && (
<Button
onClick={() => setShowMessage(true)}
size="lg"
>
Show Message
</Button>
)}
Is it possible make by some events or appending classNames like active?
Append the className with the ternary operator.
But, for example, this code will only adjust the class of the button specified (effectively doing the same thing you described, hiding & showing the button):
import React, { useState } from 'react';
export const Component = () => {
const [showButton, setShowButton] = useState(false);
const handleClick = () => {
setShowButton(true);
}
return (
<button
onClick={handleClick}
className={showButton ? 'showButtonClass' : 'hideButtonClass'}
>
Show Message
</button>
);
};
For content to show once the button is clicked, you'll need something like:
import React, { useState } from 'react';
export const Component = () => {
const [showMessage, setShowMessage] = useState(false);
const handleClick = () => {
setShowMessage(true);
}
return (
<div>
<button
onClick={handleClick}
>
Show Message
</button>
{showMessage && <h1>
The message you'll see when clicking!
</h1>}
</div>
);
};

Cant remove specific React components

This is my Add_Blog.js file
import React, { useState } from "react";
function AddBlogs() {
const ADD = () => {
return (
<div>
<label>Topic</label>
<input></input>
<label>Content</label>
<textarea></textarea>
<button type="button" onClick={deleteContent} value={state.length}> Delete</button>
</div>
);
}
const [state, setState] = useState([<ADD key="first"></ADD>]);
const addblog = () => {
setState([...state, <ADD key={state.length}></ADD>])
}
const deleteContent = (event, key) => {
setState([...state, state.splice(event.target.value, 1)]);
}
return (
<div>
<form>
<div>
<label>Add Title</label>
<input></input>
</div>
<div>
{
state
}
</div>
<div>
<button type="button" onClick={addblog}>Add another topic</button>
</div>
<input type="submit" value="publish"></input>
</form>
</div>
);
}
export default AddBlogs;
My expectation from this code is to remove component left of delete button when I press that delete button. But when I press delete button it removes that component and every components below it.
Any solution for this?
It does not work because you are using the splice method wrong. It returns the deleted value.
You can change your deleteContent function like this:
const deleteContent = (event, key) => {
let indexToRemove = Number(event.target.value);
setState((prevState) => prevState.filter((elem, index) => index !== indexToRemove));
}
That will be enough for the program to work as expected

Transfering id of place to another component as props inside popup

I have a problem because I try to add place._id to another component as props (line 102), but I need to do it after click on button (line 96 and function editPlace - line 37-41) to display information about place inside popup. However I get totally various id's after refreshing the page I know that it's propably connected with rendering but to be honest I don't know how to do it properly. I've tried to add EditPlaceForm to document but ref's are not my strength yet and also I don't know if it's good strategy.
Numbers of lines are placed in comments.
import React, {
Children,
useContext, useRef, useState,
} from 'react';
import { Link } from 'react-router-dom';
import { removePlace, updatePlaceStatus } from '../../actions/FetchData';
import '../../scss/base/_users-list.scss';
import { PlacesContext } from '../../contexts/PlacesContext';
import EditPlaceForm from './EditPlaceForm/EditPlaceForm';
import '../../scss/base/_places-panel.scss';
function PlacesPanel() {
const places = useContext(PlacesContext);
const popupEl = useRef(null);
const [placeToEdit, setPlaceToEdit] = useState(0);
const handleChangeStatus = (event) => {
const changedStatus = event.target.value;
const changedPlaceId = event.target.id;
updatePlaceStatus(changedPlaceId, changedStatus);
};
const removeSelectedPlace = (event) => {
const removedPlaceId = event.target.value;
const fetchMyData = async () => {
await removePlace(removedPlaceId);
};
fetchMyData();
window.location.reload(true);
};
{/* lines 37-42 here */}
const editPlace = (e, placeId) => {
setPlaceToEdit(placeId);
popupEl.current.style.display = 'block';
console.log(placeId, placeToEdit);
};
return (
<>
<div className="page-container">
<h1>Users list</h1>
{places.map((place) => (
<div className="place-list-item" key={place._id}>
<button className="remove-user-button" value={place._id} type="submit" onClick={removeSelectedPlace}>X</button>
<div>
<Link to={place._id}><img className="place-img" src={place.img} alt="place-img" width="100" height="100" /></Link>
</div>
<div className="place-name">
<h4><Link to={place._id}>{place.name}</Link></h4>
</div>
<div className="place-address">
<h5>
{place.city}
,
{' '}
{place.street}
,
{' '}
{place.houseNo}
,
{' '}
{place.postalCode}
</h5>
</div>
<div className="place-category">
<p>
Kategoria:
{place.category}
</p>
</div>
<div className="place-district">
<p>
Dzielnica:
{place.district}
</p>
</div>
<div className="place-status">
<p>
<b>
Status:
{place.statusPlace}
</b>
</p>
</div>
<p>Zmień status: </p>
<select onChange={handleChangeStatus} id={place._id}>
<option selected value=""> </option>
<option value="pending">pending</option>
<option value="added">added</option>
</select>
{/* line 96 here */}
<input className="edit-place-button" value="Edytuj" onClick={() => editPlace(this, place._id)} type="submit" />
</div>
))}
<div className="popup" ref={popupEl}>
<div className="button-popup-container">
<button type="submit" className="button-popup" onClick={() => { popupEl.current.style.display = 'none'; }}>X</button>
{/* line 102 here */}
<EditPlaceForm placeToEdit={placeToEdit} />
</div>
</div>
</div>
</>
);
}
export default PlacesPanel;
you forgot to change at here, at handleChangeStatus,
setPlaceToEdit(changedPlaceId); adding this will resolve issue,
now after changeing options it will send new values, i hope this is what you are looking for,
const handleChangeStatus = (event) => {
const changedStatus = event.target.value;
const changedPlaceId = event.target.id;
setPlaceToEdit(changedPlaceId);
updatePlaceStatus(changedPlaceId, changedStatus);
};
you can also try to re render using useEffect hook, to ensure latest data changes happened and children rendered, useEffect(()=>{},[placeToEdit, showPop ])
const [placeToEdit, setPlaceToEdit] = useState({});
const [showPop, setShowPop] = useState(false);
useEffect(()=>{},[placeToEdit, showPop ])
const editPlace = (place) => {
setPlaceToEdit(place);
setShowPop(true);
popupEl.current.style.display = 'block';
console.log(placeId, placeToEdit);
};
<input className="edit-place-button" value="Edytuj" onClick={() => editPlace(place)} type="submit" />
then,
{ showPop &&
<div className="popup" ref={popupEl}>
<div className="button-popup-container">
<button type="submit" className="button-popup" onClick={() => { popupEl.current.style.display = 'none'; }}>X</button>
{/* line 102 here */}
<EditPlaceForm placeToEdit={placeToEdit} setShowPop={setShowPop}/>
</div>
</div>
}

How do you remove a CSS class from a certain element of a list with React

I'm trying to remove a CSS class from a specific item when clicking on that item's button. Removing the CSS class will make a menu appear. How would I go about doing this with React? Here's the code.
import "./Homepage.css"
import React, { useState, useEffect, useRef } from "react"
// import { FontAwesomeIcon } from "#fortawesome/react-fontawesome"
// import { faArrowDown } from "#fortawesome/free-solid-svg-icons"
import { Link } from "react-router-dom"
import useFetch from "./useFetch"
import Axios from "axios"
export default function Homepage() {
const [body, setBody] = useState("")
const [sortedData, setSortedData] = useState("")
const [data, setData] = useState("")
const [css, setCss] = useState("")
const [flash, setFlash] = useState(null)
const posts = useFetch("http://localhost:5000/api/data")
const firstRender = useRef(true)
useEffect(() => {
let test = JSON.parse(window.localStorage.getItem("user"))
console.log(test)
setData(posts)
}, [posts])
useEffect(() => {
if (firstRender.current) {
firstRender.current = false
return
}
data.sort(function (a, b) {
return new Date(b.date) - new Date(a.date)
})
setSortedData(data)
}, [data])
const handleSubmit = (e) => {
e.preventDefault()
Axios.post("http://localhost:5000/api/react-create-post", { text: body }, { withCredentials: true })
.then((res) => {
setSortedData((prevArray) => [res.data.post, ...prevArray])
setFlash("Successfully created post.")
setCss("success-msg")
setBody("")
})
.catch((err) => {
setCss("error-msg")
setFlash("Field cannot be left blank.")
})
}
const handleClick = (e) => {
e.preventDefault()
e.target.parentElement.children[1]
}
return (
<div>
<center>
<div className="create-container">
<div className="posts-title">Create Post</div>
<form id="theForm" onSubmit={(e) => handleSubmit(e)}>
<textarea onChange={(e) => setBody(e.target.value)} value={`${body}`} id="theInput" className="post-input" name="text" type="text"></textarea>
<button className="submit-btn">POST</button>
</form>
</div>
<div id="postsContainer" className="posts-container">
<div className="posts-title">Latest Posts</div>
{flash ? <div className={css}>{flash}</div> : console.log()}
<div id="postInput">
{sortedData &&
sortedData.map((item) => {
return (
<div className="post-container" key={item._id}>
<Link className="a" to={`/user/${item.author}`}>
<h3 className="author">{item.author}</h3>
</Link>
<div className="date">{item.date.toLocaleString()}</div>
<div className="options-cont">
<button onClick={(e) => handleClick(e)} id="optionsBtn" className="options-btn">
<i className="fas fa-ellipsis-v"></i>
</button>
<button data-author={`${item.author}`} data-id={`${item._id}`} data-text={`${item.body}`} id="editBtn" className="edit inside-btn invisible">
Edit
</button>
<button data-author={`${item.author}`} data-id={`${item._id}`} id="deleteBtn" className="delete inside-btn invisible">
Delete
</button>
<br></br>
</div>
<p className="body-text">{item.body}</p>
</div>
)
})}
</div>
</div>
</center>
</div>
)
}
As far as I'm concerned using state as the className would remove or alter the CSS of each item in the "sortedData" array and make the menus for all items appear. I only want the menu for one of the items to appear.
As pilchard said, you probably want to make each of those its own component with its own "showing" state, or at least "showing" prop.
As far as I'm concerned using state as the className would remove or alter the CSS of each item in the "sortedData" array and make the menus for all items appear. I only want the menu for one of the items to appear.
That would be true if you used a single flag in state. But instead, use a set of flags, one flag for each menu, perhaps keyed by item._id.
Assuming you don't do the refactoring pilchard (and I) suggest:
You haven't shown us enough code for me to know whether you're using class components or function components, so I'm going to guess function components with hooks. If so, the initial state would be:
const [showing, setShowing] = useState(new Set());
Then when rendering, you'd assign the class:
<theElement className={showing.has(item._id) ? "class-if-any-to-show-it" : "class-if-any-to-not-show-it" ...
To toggle, in the button pass the ID:
<button onClick={(e) => handleClick(e, item._id)}
and then update state as appropriate:
const handleClick = (e, id) => {
e.preventDefault()
setShowing(showing => {
showing = new Set(showing);
if (showing.has(id)) {
showing.delete(id);
} else {
showing.add(id);
}
return showing;
});
};

Creating div in function (React)

I have an exercise where I have to make an input and a button. When I click the button, there has to be created a div/span below, which prints the text which is in input. If I change the text in input, it has to be refreshed in that div/span only when I click the button again. I tried to do it with makeDiv function, but it doesn't do anything. I made console.log(event.target.value) and it handles the text which is in input, but nothing happens then.
My code:
import {useState} from "react"
function About() {
const [initialValue,setInitialValue] = useState('')
const handleValueChange = (event) => {
console.log(event.target.value)
setInitialValue(event.target.value)
}
const makeDiv = () => {
return (<div>Value: {initialValue}</div>)
}
return(
<div>
<button onClick={makeDiv}>click me</button>
<div><input type="text" onChange={handleValueChange} /></div>
</div>
)
}
export default About
edit:
What if I wanted to make an exercise very similar to that, but now, I have to add <li>text in input</li> to <ul> each time I click the button. So when I click the button, I add one li to the list, I tried like this, but it doesn't compile:
import {useState} from "react"
function About() {
const [initialValueLastExercise, setInitialValueLastExercise] = useState([])
const [ValueLE, setValueLE] = useState([])
const handleValueChangeLE = (event) => {
console.log(event.target.value)
setInitialValueLastExercise([...initialValueLastExercise, event.target.value])
}
const showListWithText = () => {
setShouldDisplayText(true)
setValueLE(initialValueLastExercise)
}
return(
<div>
<button onClick={showListWithText}>click me to refresh the list</button>
<div><input type="text" onChange={handleValueChangeLE} /></div>
{shouldDisplayText && <div><ul>
{
for (let i =0; i<initialValueLastExercise.length; i++) {
<li>{initialValueLastExercise[i]}</li>
}
}</div></ul>}
</div>
)
}
export default About
This will refresh the value of the div on button click only as you have mentioned in the question.
import {useState} from "react"
function App() {
const [initialValue,setInitialValue] = useState('')
const [displayText, setDisplayText] = useState(false)
const [Value,setValue] = useState('')
const handleValueChange = (event) => {
setInitialValue(event.target.value)
}
const showText = () => {setDisplayText(true)
setValue(initialValue)};
return(
<div>
<button onClick={showText}>click me</button>
<div><input type="text" onChange={handleValueChange} /></div>
{displayText && <div>Value: {Value}</div>}
</div>
)
}
export default App
Solution for the Edited Question.
import {useState} from "react"
function App() {
const [initialValue,setInitialValue] = useState('')
const [displayText, setDisplayText] = useState(false)
const [Value,setValue] = useState([])
const handleValueChange = (event) => {
setInitialValue(event.target.value)
}
const showText = () => {setDisplayText(true)
setValue([...Value,initialValue])};
return(
<div>
<button onClick={showText}>click me</button>
<div><input type="text" onChange={handleValueChange} /></div>
<ul>{displayText && Value.length > 0 &&
Value.map((i) => {
return <li>Value: {i}</li>
})}</ul>
</div>
)
}
export default App
One way to do it is to create another state variable which indicates whether the div you're trying to make should be displayed and then render it conditionally. Something like
import {useState} from "react"
function About() {
const [initialValue,setInitialValue] = useState('')
const [shouldDisplayText, setShouldDisplayText] = useState(false)
const handleValueChange = (event) => {
console.log(event.target.value)
setInitialValue(event.target.value)
}
const showDivWithText = () => setShouldDisplayText(true);
return(
<div>
<button onClick={showDivWithText}>click me</button>
<div><input type="text" onChange={handleValueChange} /></div>
{shouldDisplayText && <div>Value: {initialValue}</div>}
</div>
)
}
export default About
Your approach is fundamentally wrong.
You should:
Store all the data about the component in the state
Render the output based on the state
So:
You need two state variables:
currentInputValue (because you need to store the value to display and edit in input)
selectedValue (because you need to store the value to be displayed in the div)
When onChange fires, update currentInputValue with the value of the input.
When onClick fires, update selectedValue with the current value of currentInputValue
When you return your data, include something like:
{selectedValue && <div>{selectedValue}</div>}
… to output a div containing the selected value only if there is a truthy value (the default empty string isn't truthy so the div won't be output then)
1st possibility - close to your code source
Don't forget to bind initialValue to the input and to add makeDiv content to the JSX :
return (
<div>
<button onClick={makeDiv}>click me</button>
<input type="text" onChange={handleValueChange} value={initialValue} />
{makeDiv}
</div>
)
2nd possibility - with another approach
return (
<div>
<button onClick={makeDiv}>click me</button>
<input type="text" onChange={handleValueChange} value={initialValue} />
{initialValue && <div>{initialValue}</div>}
</div>
)

Categories