React having onClick work only once on a component - reactjs

Hello I am trying to build a forum site. I have simple categories and comments. I have a button that displays a text box to enter a comment on a category, but when I click the button it opens text boxes on every category. I just want one text box. Here is the code I have so far. Any help would be appreciated. Thanks
state = { showing: true };
renderLists(categories) {
const { showing } = this.state;
if (categories == null) return <div />;
return (
<ul className="ul">
{categories.map(category => {
return (
<li id={category._id} className="categories" key={category._id}>
{category.name}
<Posts categ={category._id} />
<button
className="label"
onClick={() => this.setState({ showing: !showing })}
>
Add Comment
</button>
{showing ? (
<div>
<form method="post" action="/post/create-posts">
<input type="text" name="body" />
<input type="hidden" name="cat" value={category._id} />
<input type="submit" value="Submit" />
</form>
</div>
) : null}
</li>
);
})}
</ul>
);
}
render() {
return (
<div>
<main className="categories">
{this.renderLists(this.state.category)}
</main>
</div>
);
}

Since you are controlling render of the form with a single state value, ever form for every item renders the form when the state value changes.
You should set a unique value on state to show single form every time.
Saving currently active items id to state and checking if that item is active or not can be a simple solution. This also ensures to only single form to be active. If you need to enable multiple forms to be active, you can change below code to hold an array of ids and checking if the id exist in array or not. This implementation also requires you to remove the id from array on a second click to remove form for that item.
Sample
state = { showing: ''};
renderLists(categories) {
const { showing } = this.state;
if (categories == null) return <div />;
return (
<ul className="ul">
{categories.map(category => {
return (
<li id={category._id} className="categories" key={category._id}>
{category.name}
<Posts categ={category._id} />
<button
className="label"
{/* Save item's id to state */}
onClick={() => this.setState({ showing: category._id })}
>
Add Comment
</button>
{/* check item's id */}
{(showing === category._id) ? (
<div>
<form method="post" action="/post/create-posts">
<input type="text" name="body" />
<input type="hidden" name="cat" value={category._id} />
<input type="submit" value="Submit" />
</form>
</div>
) : null}
</li>
);
})}
</ul>
);
}

You can set the dynamic state for each of your category. I have made it below. Initially it was not there in state. On clicking the button, it will update the state with new key(such as categoryName) as true. I have fixed it as toggle. If the required key is true then it will become false.
item.state = { showing: true };
renderLists(categories) {
const { showing } = this.state;
if (categories == null) return <div />;
return (
<ul className="ul">
{categories.map(category => {
let categoryName = category.name // get the categoryName in variable
return (
<li id={category._id} className="categories" key={category._id}>
{category.name}
<Posts categ={category._id} />
//You can also access the object values by using bracket ([]) notation. You can set the dynamic state with unique key, may be we use name as key here
<button
className="label"
onClick={() => {(this.state[categoryName] != undefined) ? (this.state[categoryName] ? this.setState({ [categoryName]: false }) : this.setState({ [categoryName]: true })) : this.setState({ [categoryName]: true })} }
>
Add Comment
</button>
{this.state[categoryName] ? (
<div>
<form method="post" action="/post/create-posts">
<input type="text" name="body" />
<input type="hidden" name="cat" value={category._id} />
<input type="submit" value="Submit" />
</form>
</div>
) : null}
</li>
);
})}
</ul>
);
}
render() {
return (
<div>
<main className="categories">
{this.renderLists(this.state.category)}
</main>
</div>
);
}

Related

onClick load react component in the same place

I have a panel with 3 buttons, i want to make onclick on every button, a different component will appear in the same place. How can this logic be done?
<AddNotification />
<EditNotification />
<DeleteNotification />
const AdminPanel = () => {
return (
<Card className={classes.input}>
<div><h1>Notification Panel</h1></div>
<form>
<div className="form-group">
<Button type="submit">Add Notification</Button>
</div>
<div className="form-group">
<Button type="submit">Edit Notification</Button>
</div>
<div className="form-group">
<Button type="submit">Delete Notification</Button>
</div>
</form>
</Card>
)
}
#MLDC No i don't have another divs, i want to replace the buttons with the crossponding component. For example: onclick on Add, then Add component will appears instead of the buttons.
In that case, create a boolean state for every Panel that you have (I created 3 so that you could open the panels simultaneously),
const [isAddPanelOpen, setIsAddPanelOpen] = useState(false);
const [isEditPanelOpen, setIsEditPanelOpen] = useState(false);
const [isDeletePanelOpen, setIsDeletePanelOpen] = useState(false);
Next, apply this to every button
<Button onClick={setIsAddPanelOpen(prevState=>!prevState)}>Add Notification</Button>
<Button onClick={setIsEditPanelOpen(prevState=>!prevState)}>Edit Notification</Button>
<Button onClick={setIsDeletePanelOpen(prevState=>!prevState)}>Delete Notification</Button>
Lastly, Refactor your html to
<div className="form-group">
{isAddPanelOpen ? <AddNotification/> : <Button type="submit">Add Notification</Button>}
</div>
<div className="form-group">
{isEditPanelOpen ? <EditNotification/> : <Button type="submit">Edit Notification</Button>}
</div>
<div className="form-group">
{isDeletePanelOpen ? <DeleteNotification/> :<Button type="submit">Delete Notification</Button>}
</div>
Try this if you want to display one component at a time and hide the others when you click a button
const AdminPanel = () => {
const [componentToDisplay, setComponentToDisplay] = useState("")
return (
<>
<Card className={classes.input}>
<div><h1>Notification Panel</h1></div>
<form>
<div className="form-group">
{componentToDisplay !== "add ? (
<Button type="submit" onCLick={() => setComponentTodisplay("add")}>Add Notification</Button>)
:(<AddNotification />)}
</div>
<div className="form-group">
{componentToDisplay !== "edit ? (
<Button type="submit" onCLick={() => setComponentTodisplay("edit")}>Edit Notification</Button>)
:(<EditNotification />)}
</div>
<div className="form-group">
{componentToDisplay !== "delete ? (
<Button type="submit" onCLick={() => setComponentTodisplay("delete")}>Delete Notification</Button>)
:(<DeleteNotification />)}
</div>
</form>
</Card>
</>
)
}
Or if you want to be able to replace every buttons, use this logic with one state per button
const AdminPanel = () => {
const [addNotif, setAddNotif] = useState(false)
const [editNotif, setEditNotif] = useState(false)
const [deleteNotif, setDeleteNotif] = useState(false)
return (
<>
<Card className={classes.input}>
<div><h1>Notification Panel</h1></div>
<form>
<div className={`form-group ${editNotif || deleteNotif ? "display: none" : "display: flex"}`}>
{!addNotif ? (
<Button type="submit" onCLick={() => setAddNotif(true)}>Add Notification</Button>)
:(<AddNotification setAddNotif={setAddNotif} />)}
</div>
<div className={`form-group ${addNotif || deleteNotif ? "display: none" : "display: flex"}`}>
{!editNotif ? (
<Button type="submit" onCLick={() => setEditNotif(true)}>Edit Notification</Button>)
:(<EditNotification setEditNotif={setEditNotif} />)}
</div>
<div className={`form-group ${addNotif || editNotif ? "display: none" : "display: flex"}`}>
{!deleteNotif ? (
<Button type="submit" onCLick={() => setDeleteNotif(true)}>Delete Notification</Button>)
:(<DeleteNotification setDeleteNotif={setDeleteNotif} />)}
</div>
</form>
</Card>
</>
)
}
Then in your component
const AddNotification = ({setAddNotif}) => {
...
return (
...
<button onCLick={() => setAddNotif(false)}>back</button>
...
)
}
Same logic for the other components
To achieve this logic you need to manage which component is displayed using a state.
This means:
Attribute an arbitrary id to each component.
Store the id of the active component in a useState hook.
Use conditional rendering to display the component that match the current state.
Update the state to the corresponding Id when clicking on each button.
A small example
const [activePanel, setActivePanel] = React.useState(0)
let currentPanel = <Panel0 />
switch(activePanel){
case 0:
currentPanel = <PanelO />
case 1:
currentPanel = <Panel1 />
// Continue as needed
}
return (
<div>
<CurrentPanel/>
<button onClick={() => setActivePanel (0)}> Panel 0 </button>
<button onClick={() => setActivePanel (1)}> Panel 1 </button>
// And so on
</div>
)
You can further refine this by extracting the switch statement into its own component that takes the activePanel as a prop.

filter firstname from 3 map() in react js

i have this input filed in my code for serching the list i have 3 catagery of list which are displayed by three map() i want to input a name in the input fild and if it exist i want to show the firstname just like whatsapps serach works
<form style={{ display: "flex" }}>
<input
className="w-100 msserserch gradiantblur"
placeholder="seach messages"
onChange={(e) =>
this.setState({ serchinput: e.target.value })
}
type="Search"
/>
</form>
the map() are given below there are 3 i want when i type in input fild fistname mustbe filterd from this
{this.state.tablist == "doctor" ? (
<div
className="chat-avatar gradiantblur"
onClick={() => this.livechat(this.state.list.id)}
>
<div></div>
<div className="alignstart">
{this.state.list.firstname} {this.state.list.lastname}
<p className="margin-0">
{" "}
{this.state.list.speciality}
</p>
<p className="margin-0">
✓ {this.state.list.speciality}
</p>
</div>
<div>152:11</div>
</div>
) : this.state.tablist == "consultant" ? (
<>
<div className="scrollerchatlist">
{console.log(this.state.consulatnt)}
{this.state.consulatnt.map((data) => (
<div
className="chat-avatar gradiantblur"
onClick={() => this.livechat(data.id)}
>
<div>
</div>
<div className="alignstart">
{data.firstname} {data.lastname}
<p className="margin-0"> {data.speciality}</p>
<p className="margin-0">
✓ {data.firstname}
</p>
</div>
<div>152:11</div>
</div>
))}
</div>
</>
) : this.state.tablist == "sales" ? (
<>
<div className="scrollerchatlist">
{this.state.sales.map((data) => (
<div
className="chat-avatar gradiantblur"
onClick={() => this.livechat(data.id)}
>
<div>
</div>
<div className="alignstart">
{data.firstname}
<p className="margin-0">
</p>
</div>
<div>152:11</div>
</div>
))}
</div>
</>
) : null}
my code is big so i put it in codesandbox
https://codesandbox.io/s/intelligent-nova-v7pb1?file=/src/App.js
I see in your code, you have this.state.tablist which is deciding what to render when any tab gets selected. So whenever user types in the input, you can just filter out firstname string from all three array and render them in your list:
onChange={this.onSearchTextChange}
onSearchTextChange(event) {
const query = event.query.target;
if(query){
const dataSet = [this.state.sales, this.state.consultants, ...];
const filteredName = dataSet.map(
data => data.filter(row => row.firstName).filter(Boolean)
).flat();
// now set the state with this data
this.setState({...});
}
}

React component is not re-rendered after the state is changed with a dropdown list [react hooks]

I have the following React component (using hooks), which lists a number of Tasks as a dropdown list. When an item is selected from the list, I want to display an Update form. This works only when an item is selected for the first time. When I select a new item, nothing happens (although console.log(e.target.value); prints the correct value). I store the selected task's id in st_taskId.
I wonder if you see any issues in the code below:
const ManageReviewTasks = props => {
const reviewRoundId = props.match.params.reviewRoundId;
const [st_taskId, set_taskId] = useState();
useEffect(() => {
if (props.loading == false && st_taskId == null)
props.fetchReviewTasksByReviewRound(reviewRoundId);
}, [reviewRoundId, st_taskId]);
if (props.loading == true) {
return <div>Loading...</div>;
}
return (
<>
{props.reviewTasks && (
<div>
<h3>Configure the Review Tasks</h3>
<br />
{
<div>
<div>
<h4>
Tasks for <span className="font-italic">students receiving</span> feedback:
</h4>
<select
className="form-control"
onChange={e => {
e.preventDefault();
console.log(e.target.value);
set_taskId(e.target.value);
}}>
<option>--SELECT--</option>
{Object.keys(props.reviewTasks).map(id => {
const task = props.reviewTasks[id];
{
if (task.isForStudent) {
return (
<option key={id} id={id} value={id}>
{task.title}
</option>
);
}
}
})}
</select>
</div>
{props.reviewTasks[st_taskId] && (
<UpdateReviewTaskForm task={props.reviewTasks[st_taskId]} />
)}
</div>
}
</div>
)}
</>
);
};
Below is the code for the UpdateReviewTaskForm component:
const UpdateReviewTaskForm = (props) => {
const [st_Title, set_Title] = useState(props.task.title);
const [st_Description, set_Description] = useState(RichTextEditor.createValueFromString(props.task.description, 'html'));
const [st_startDate, set_startDate] = useState(new Date(props.task.startDate.replace('-', '/')));
const [st_DueDate, set_DueDate] = useState(new Date(props.task.dueDate.replace('-', '/')));
const handleCancelClick = (event) => {
event.preventDefault();
history.goBack();
}
const onSubmit_saveTask = (e) => {
e.preventDefault();
props.updateReviewTask({
Id: props.task.id,
Title: st_Title,
Description: st_Description.toString('html'),
StartDate: format(st_startDate, 'DD/MM/YYYY'),
DueDate: format(st_DueDate, 'DD/MM/YYYY'),
})
}
if (props.loading)
return <div>Updating...</div>
return (
<div>
<br/>
<br/>
<div className="p-3 bg-light">
<h3 className="text-info">Update the Task:</h3>
{
props.task &&
<form onSubmit={onSubmit_saveTask}>
<div className="form-group">
<label>Enter the title</label>
<input
//placeholder="Enter a title..."
value={st_Title}
onChange={(event) => { set_Title(event.target.value) }}
className="form-control" />
</div>
<div className="form-group">
<label>Enter a description for the assessment</label>
<RichTextEditor
value={st_Description}
onChange={set_Description}
/>
</div>
<div className="form-group">
<label>Start date to start: </label>
<DatePicker
className="form-control"
selected={st_startDate}
onChange={(date) => set_startDate(date)}
/>
</div>
<div className="form-group">
<label>Due date to complete: </label>
<DatePicker
className="form-control"
selected={st_DueDate}
onChange={(date) => set_DueDate(date)}
/>
</div>
<br />
<button type="submit" className="btn btn-primary">Submit</button>
<button type="reset" className="btn btn-light" onClick={handleCancelClick}>Cancel</button>
</form>
}
</div>
</div>
)
}
Because you are using internal state in UpdateReviewTaskForm, even if this component re-render for the second time, its state will not be reset (to the default value props.task.title for example).
One way to force the state to reset is to use a key prop in UpdateReviewTaskForm like this :
{props.reviewTasks[st_taskId] && (
<UpdateReviewTaskForm key={st_taskId} task={props.reviewTasks[st_taskId]} />
)}
Another way is to use useEffect inside UpdateReviewTaskForm to run when props.task change
useEffect(() => {
// reset the state here
}, [props.task])

Why don´´t work onClick on option element

I'm developing a plugin to search streets with elastic search, ok?
I have a datalist to show my options to select.
When I receive info from database I create all html option elemtns and add click event to capture and handle.
But I dont know why not works onClick event that I've added to each option element.
Here is my code EDITED:
render() {
const { value, streets, error, labelError } = this.state;
return (
<div className="w-100 d-flex flex-column">
<div className="plug-search plug-search__content">
<div className="plug-inner-addon">
<input onKeyDown={this.handlePressEnter.bind(this)} onChange={this.handleSearch.bind(this)} type="text" placeholder={PLACEHOLDER} value={value} list="streets" autoComplete="on" />
<datalist id="streets">
{ streets && streets.length > 0 && streets.map((street, index) => {
return (
<Item street={street} position={index} key={index} />
);
})}
</datalist>
</div>
<div className="plug-btn-search plug-btn-search__content">
<i onClick={this.handleGetGeometry} className={`icon-search plug-search-icon`}></i>
</div>
</div>
{error &&
<div className={`plug-error plug-error__content ${(error) ? 'slideDown' : 'slideUp'}`}>
<label className="plug-label">{labelError}</label>
</div>
}
</div>
);
}
Now I've created a Item component, but still doesnt work:
class Item extends Component {
clickedOption = (event, index) => {
console.log('clicked');
console.log('value: ', event.target.value);
console.log('index: ', index);
};
render() {
return (
<div className="option" onClick={(event) => this.clickedOption(event, this.props.position)}>
<option value={this.props.street.nombre} />
</div>
)
}
}
export default Item;
Currently, <datalist /> don't support onClick events in his <options />, you could see this question, in order to apply an option in this kind of cases. Hope this help.
Thanks to Alberto Perez
Solved:
clickedOption = (event) => {
console.log('clicked');
console.log('value: ', event.target.value);
};
render() {
const { value, streets, error, labelError } = this.state;
return (
<div className="w-100 d-flex flex-column">
<div className="plug-search plug-search__content">
<div className="plug-inner-addon">
<input onInput={this.clickedOption} onChange={this.handleSearch.bind(this)} type="text" placeholder={PLACEHOLDER} value={value} list="streets" autoComplete="on" />
<datalist id="streets">
{ streets && streets.length > 0 && streets.map((street, index) => {
return (
<option value={street.nombre} key={index} />
);
})}
</datalist>
</div>
<div className="plug-btn-search plug-btn-search__content">
<i onClick={this.handleGetGeometry} className={`icon-search plug-search-icon`}></i>
</div>
</div>
{error &&
<div className={`plug-error plug-error__content ${(error) ? 'slideDown' : 'slideUp'}`}>
<label className="plug-label">{labelError}</label>
</div>
}
</div>
);
}
handleSearch = (e) => {
this.setState({
value: e.target.value
})
}
render() {
const { value, streets, error, labelError } = this.state;
return (
<div className="w-100 d-flex flex-column">
<div className="plug-search plug-search__content">
<div className="plug-inner-addon">
<input onKeyDown={this.handlePressEnter.bind(this)} onChange={this.handleSearch.bind(this)} type="text" placeholder={"placeholder"} value={value} list="streets" autoComplete="on" />
<datalist id="streets">
{ streets && streets.length > 0 && streets.map((street, index) => {
return (
<div key={index} className="option">
<option value={street.nombre} />
</div>
);
})}
</datalist>
</div>
<div className="plug-btn-search plug-btn-search__content">
<i onClick={this.handleGetGeometry} className={`icon-search plug-search-icon`}></i>
</div>
</div>
{error &&
<div className={`plug-error plug-error__content ${(error) ? 'slideDown' : 'slideUp' }`}>
<label className="plug-label">{labelError}</label>
</div>
}
</div>
);
}

redux-form renderField type radio undefined value

I try to implement renderField for radio buttons, i can display it but when i click on radio field does not fill.
When i check the console, value of input is equal to undefined, always !
This is my code :
render(){
const {input, array, meta, label} = this.props;
return(
<div className="input-row">
<div className="radio-box">
{array.map((option,i) =>
<div className="radio-field" key={i}>
<input type='radio' name={option.name} onChange={() => {input.onChange('oui')}} value={option.value} checked={input.value === option.value} />
{/* Try this before <input type='radio' {...input} name={option.name} value={option.value} checked={input.value == option.value}/> */}
<label>{option.name}</label>
</div>
)}
</div>
</div>
)
}
I call this with :
<Field name="courtage" type="radio" label="Courtage ?" component={renderRadioField} array={selectOptions.courtage} {...courtage} />
SelectOptions.courtage is an array of object.
After few hours, I find a solution, I play with the state :
setRadioState(a) {
const obj = {};
obj[a.target.name] = a.target.value;
this.setState(obj);
}
render(){
const {input, array, meta, label, name} = this.props;
return(
<div className="input-row">
<div className="label-box">
<label>{label}</label>
</div>
<div className="radio-box">
{array.map((option,i) =>
<div className="radio-field" key={i}>
<input type='radio' name={name} {...input} onChange={this.setRadioState.bind(this)} value={option.value} checked={this.state.name} />
<label>{option.name}</label>
</div>
)}
{/* <div className="errors">{meta.touched && ((error && <span>{error}</span>))}</div> */}
</div>
</div>
)

Resources