react.js buttons that display different items from array - arrays

I didn't know how to ask this but basically I just want list of buttons that display different text. When you click a button it shows the text. But instead of hardcoding every button I want it to be dynamic. So far when I click any of my buttons, everything shows instead of individually. (one button should show one definition" I think it is the way I am using state probably.
import React, { useState } from "react";
import terms from "./TermsComponentData.js";
import "./TermsComponent.css";
function TermsComponent() {
const [showTerm, setShowTerm] = useState(false);
const buttonList = terms.map((term, index) => (
<div>
{showTerm && <h1>{term.definition}</h1>}
<button
key={index}
className="buttons-container-button"
onClick={() => {
setShowTerm(!showTerm);
}}
>
{term.name}
</button>
</div>
));
return (
<div className="terms-container">
<div className="buttons-container">{buttonList}</div>
</div>
);
}
MY DATA
const terms = [
{
name: "Term1",
definition: "This is the definition1",
},
{
name: "Term2",
definition: "This is the definition2",
},
{
name: "Term3",
definition: "This is the definition 3",
},
{
name: "Term4",
definition: "This is the definition 4",
}
];
export default terms;

Currently your state variable showTerm is common for all of the buttons. So to achieve the result you are describing, you need to encapsulate the state for each button.
The easiest way to do this, is to create a separate component which contains the showTerm state, and then map a list of this component with the given data based on the terms:
// 👇 added separate term button with it's own state
function TermButton({ term }) {
const [showTerm, setShowTerm] = useState(false);
return (
<div>
{showTerm && <h1>{term.definition}</h1>}
<button
className="buttons-container-button"
onClick={() => {
setShowTerm(!showTerm);
}}
>
{term.name}
</button>
</div>
);
}
function TermsComponent() {
return (
<div className="terms-container">
<div className="buttons-container">
{ // 👇 for each term, create a TermButton with the given term
terms.map((term, index) => <TermButton key={index} term={term} />)}
</div>
</div>
);
}

Related

react-beautiful-dnd: destination is equal to null

Here is the thing. I'm learning to React beautiful dnd in by building a todo app. And I have set up the app as far as styling the component and writing the logic to display the todo cards.
The problem is this, as I drag the card in todo to progress or done, the property "destination", in an object logged in the console (check the code to see where it's logged) destination is equal to null. It's supposed to be equal to an object. What seems to be the problem.
The full code is located in the link below:
https://codesandbox.io/s/async-fast-hziot?file=/src/App.js
Preview:
import "./styles.css";
import { useState } from "react";
import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd';
export default function App() {
const [populateStore, setPopulateStore] = useState(
{
todo: {
_id:"todo",
data: [
{
_id: 'ms7690fvs6sd53h328sof-0sdf',
author: 'anonymous',
title: 'Going to the pack',
date: '11th, January 2022'
},
{
_id: 'sdfdf32gf98tddswjk73i2r',
author: 'anonymous',
title: 'Visit my parents for the holiday',
date: '11th, January 2022'
}
]},
inprogress: {
_id: "in progress",
data:[]},
done: {
_id:"done",
data: []
}
}
);
function handleOnDragEnd(result) {
console.log(result, "result")
}
return (
<div className="populate-container">
{Object.entries(populateStore).map(([title, array],index) => {
return (
<div className="todo-container" key={index}>
<div className="todo-headings">
{title}
</div>
<DragDropContext onDragEnd={handleOnDragEnd}>
<Droppable
droppableId={`${array._id}`}
type={`${array._id}`}
>
{
(provided) => {
return (
<div className="_droppable" {...provided.droppableProps} ref={provided.innerRef}>
{
array.data.map((item, id) => {
return (
<Draggable key={id} draggableId={`${id}`} index={id}>
{
(provided) => {
return (
<div className="card" ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps} key={id}>
<div className="card-description">
<h5>{item.title}</h5>
<p>
{
item.date
}
</p>
</div>
</div>
)
}
}
</Draggable>
)
})
}
{provided.placeholder}
</div>
)
}
}
</Droppable>
</DragDropContext>
</div>
)
})}
</div>
);
}
The problem was two things.
The tag "DragDropContext" was meant to be the first tag before mapping out the stored data in the state "populate hook". But instead, I was mapping out the data first with DragDropContext being a child node.
This is the right way:
<DragDropContext onDragEnd={handleOnDragEnd}>
{Object.entries(populateStore).map(([title, array],index) => { ......
Not this:
{Object.entries(populateStore).map(([title, array],index) => { ......
<DragDropContext onDragEnd={handleOnDragEnd}>
Then the second was an unseen problem not asked initially at the question section and that is the id of the Droppaple and the draggable. Always make sure the ids of these tags are in strings, not numbers, and make sure the ids are not alike for example the problem in my code, the id of the draggable card in the "todo", "progress" and "done" columns were both the number 1 (I manually added an object to the "done" array located in the state to test it, and I was using the index in the higher-order function, "map" as an id in the draggable tag), so whenever I try to pick up the first one, in the "todo" column I will end up carrying the first one in the "done" column. So I resolved that by using the string id in the object.
I used this:
id: _id: "ms7690fvs6sd53h328sof-0sdf" in <Draggable key={id}
Not this anymore:
array.data.map((item, id) => { in <Draggable key={id}
when id = 1 or 2 or 3 and so on.
You can view the solution in the javascript file "answer_code.js" in the link provided in the question section.

Accessing a component state from a sibling button

I'm building a page that will render a dynamic number of expandable rows based on data from a query.
Each expandable row contains a grid as well as a button which should add a new row to said grid.
The button needs to access and update the state of the grid.
My problem is that I don't see any way to do this from the onClick handler of a button.
Additionally, you'll see the ExpandableRow component is cloning the children (button and grid) defined in SomePage, which further complicates my issue.
Can anyone suggest a workaround that might help me accomplish my goal?
const SomePage = (props) => {
return (
<>
<MyPageComponent>
<ExpandableRowsComponent>
<button onClick={(e) => { /* Need to access MyGrid state */ }} />
Add Row
</button>
<MyGrid>
<GridColumn field="somefield" />
</MyGrid>
</ExpandableRowsComponent>
</MyPageComponent>
</>
);
};
const ExpandableRowsComponent = (props) => {
const data = [{ id: 1 }, { id: 2 }, { id: 3 }];
return (
<>
{data.map((dataItem) => (
<ExpandableRow id={dataItem.id} />
))}
</>
);
};
const ExpandableRow = (props) => {
const [expanded, setExpanded] = useState(false);
return (
<div className="row-item">
<div className="row-item-header">
<img
className="collapse-icon"
onClick={() => setExpanded(!expanded)}
/>
</div>
{expanded && (
<div className="row-item-content">
{React.Children.map(props.children, (child => cloneElement(child, { id: props.id })))}
</div>
)}
</div>
);
};
There are two main ways to achieve this
Hoist the state to common ancestors
Using ref (sibling communication based on this tweet)
const SomePage = (props) => {
const ref = useRef({})
return (
<>
<MyPageComponent>
<ExpandableRowsComponent>
<button onClick={(e) => { console.log(ref.current.state) }} />
Add Row
</button>
<MyGrid ref={ref}>
<GridColumn field="somefield" />
</MyGrid>
</ExpandableRowsComponent>
</MyPageComponent>
</>
);
};
Steps required for seconds step if you want to not only access state but also update state
You must define a forwardRef component
Update ref in useEffect or pass your API object via useImerativeHandle
You can also use or get inspired by react-aptor.
⭐ If you are only concerned about the UI part (the placement of button element)
Portals provide a first-class way to render children into a DOM node that exists outside the DOM hierarchy of the parent component.
(Mentioned point by #Sanira Nimantha)

React redux saga multiple renders

I'm new to react and I'm having an issue of multiple renders and I was just wondering if I'm doing this right, so I dispatched an action to get a note list, in my list component which looks like this for now :
import React, {useState, useEffect} from 'react';
export default function NoteList (props){
const [ noteList, updateNoteList ] = useState([]);
useEffect(()=>{
updateNoteList(
props.noteList.map(note => {
return {...note, mode : 'title-mode'};
})
)
},[props.noteList])
console.log(noteList);
return (
<div>
Notes come here
</div>
)
}
this component is connected in another container class but that's irrelevant, so what happens is this component renders 4 times, two times without the useEffect hook and two more with it, what I want to achieve is I need to add an item in the object of each note (which is mode : title-mode) in a state for this component which works fine with this code, as to why I'm adding this mode in a state is that I want to change this inside the note array so I can change the view mode for each note , but this component renders 4 times as I mentioned, and in my head no way that this is the correct way to do this.
Please help if you have the time .
We could achieve the display of the notes list by making a display-mode state in the <Note /> component it self so changing the mode won't affect other notes and this way we won't have extra re-renders, also using this approach will allow also modifying the note locally without dispatching it to the store then we could update the store at the end gaining in perfs.
So basically this is the approach (codesandbox):
const Note = ({ title, content }) => {
const [isExpanded, setIsExpanded] = useState(false);
return (
<div
style={{ border: "1px solid", margin: 5 }}
onClick={() => setIsExpanded(!isExpanded)}
>
{!isExpanded ? (
<div>
<h2>{title}</h2>
</div>
) : (
<div>
<h2>{title}</h2>
<p>{content}</p>
</div>
)}
</div>
);
};
function App() {
// this is the notes state, it could be coming from redux store so
// we could interact with it (modifying it if we need)
const [notes, setNotes] = React.useState([
{ id: 1, title: "note 1", content: "this is note 1" },
{ id: 2, title: "note 2", content: "this is note 2" }
]);
return (
<div className="App">
{notes.map((note) => (
<Note key={note.id} {...note} />
))}
</div>
);
}

How do I idiomatically turn a text component into an input component on click?

I am creating a todo app in React that is basically just a list of items, grouped by category. I want to add functionality such that when I click a single to do(which is a paragraph), it brings up an input with the current text that I can edit and save. How can I do that without manually editing the DOM?
Code:
A single todo item:
import React from 'react';
const Item = props => {
return (
<div
className={`item${props.item.purchased ? ' purchased' : ''}`}
onClick={() => props.toggleItem(props.item.id)}
>
<p>{props.item.name}</p>
</div>
);
};
export default Item;
I want to change the toggle to be a radio button and onClick to edit the todo.
sample image of todos
First of all, you will need a prop that updates item.name (this will be needed when you will edit the input)
You didn't explained well how you want it to work, so I made an example where you click on the text to edit it to a text input and also have a button to save the edit.
const Item = props => {
const [isEditing, setIsEditing] = useState(false);
return isEditing ? (
<div>
<input
value={props.item.name}
onChange={e => props.setItemName(e.target.value)}
/>
<button onClick={() => setIsEditing(false)}>Stop Editing</button>
</div>
) : (
<div
className={`item${props.item.purchased ? " purchased" : ""}`}
onClick={() => setIsEditing(true)}
>
<p>{props.item.name}</p>
</div>
);
};
I also created a working codesanbox with the behavior you want.
You will have to maintain a state to change between the TODO Item & TODO Input. Since you are using functional component, you can use useState hook from react to maintain the state as shown below
import React, { useState } from 'react';
const Item = props => {
const [isEditing, setIsEditing] = useState(false);
if (isEditing) {
return (
<div className={`item${props.item.purchased ? ' purchased' : ''}`}>
<input type="text/radio" value={props.item.name}>
</div>
);
} else {
return (
<div
className={`item${props.item.purchased ? ' purchased' : ''}`}
onClick={() => props.toggleItem(props.item.id)}
>
<p>{props.item.name}</p>
</div>
);
}
};
export default Item;
You might need to change the above a bit based on your application structure but this is what you need to follow.

How to pass an object with onClick React?

I have an object in an external file and I want to pass the url into the button onClick but I do not know to pass the value.
Object:
const ProjectLists = [
{
id: "4",
iconImage: "",
name: "Simple",
description: "Simple is a corporate responsive template and the absolutely clean & modern template theme for your business. The theme was built by Foundation Framework and take advantages of it features: grid system, typography, buttons, form, UI element, section and more.",
technologies: "HTML, CSS, JavaScript",
href: "http://striped-dolls.surge.sh"
}
]
export default ProjectLists;
How to pass ProjectLists.map((project, i) => href in map() into <button>
class Projects extends Component {
render() {
return (
<div>
{ProjectLists.map((project, i) =>
<section className='section'>
<div className='row'>
<div className='col-sm'>
<div className='content-left'>
<p key={project.id + i}>
{project.iconImage}
</p>
</div>
</div>
<div className='col-sm-8'>
<div className='content-right text-left'>
<h1>{project.name}</h1>
<p>{project.description}</p>
<p>Technologies: {project.technologies}</p>
<button type='submit' onClick=() => </button>>View live</button>
</div>
</div>
</div>
</section>
)}
</div>
)
}
}
Thank you for your help!
You can pass extra data in your map using arrow functions:
{ProjectLists.map(project =>
<button onClick={onProjectClick(project)}>View live</button>
}
// e.g.
onProjectClick = project => event => {
console.log(project.description);
navigateTo(project.href);
}
I noticed your button has type="submit" so more the correct event to handle is onSubmit on the form element however as this handles pressing the Return key for example (it's not essential though).
You can use "a" tag for this purpose.
<a href={project.href}></a>
If you want to use onClick in your button, then you can try this.
onClick={() => { window.location.href = project.href }}
state = {
redirect: false
}
setRedirect = (href) => {
window.location.href = href;
}
Call the setRedirect function
<button onClick={()=>this.setRedirect(project.href)}>View live</button>

Resources