I need to opet child component by clicked item. FIrst check code:
<div className="d-flex">
{boardList.map((list) => (
<div className="card m-3 p-3" key={list.id}>
<div className="d-flex flex-column">
<h6> {list.name} </h6>
<ul className="list-group">
{list.cards.map((card) => (
<li className="list-group-item" key={card.id}>
{card.name}
</li>
))}
</ul>
{isVisible ? (
<TodoForm onCloseForm={onCloseForm} />
) : (
<small
className="mt-2"
onClick={showInput}
>
Add new task +
</small>
)}
</div>
</div>
))}
</div>
This is work but when I click on 'Add new task +' a child component opens up to me everywhere. i want only the component with the selected id or index to open.
also component for this :
const [isVisible, setIsVisible] = useState(false);
const [boardList, setBoardList] = useState([]);
useEffect(() => {
axiosInstance
.get("")
.then((res) => {
setBoardList(res.data);
console.log("resp", boardList);
})
.catch((err) => {
console.log(err);
});
}, []);
const showInput = () => {
setIsVisible(true);
};
const onCloseForm = () => {
setIsVisible(false);
};
All the items of the resultant array from boardList.map are depending on the same state isVisible, that's why when you click on one of them all the items mimic the same behaviour.
What you need is to create a component with its own state to encapsulate this part of your code
{isVisible ? (
<TodoForm onCloseForm={onCloseForm} />
) : (
<small
className="mt-2"
onClick={showInput}
>
Add new task +
</small>
)}
This way every instance of this new component would have its own isVisible so they no longer would affect their siblings state.
The component would look like this.
const NewComponent = () => {
const [isVisible, setIsVisible] = useState(false);
return <>
{isVisible ? (
<TodoForm onCloseForm={onCloseForm} />
) : (
<small className="mt-2" onClick={() => setIsVisible(true)}>
Add new task +
</small>
)}
</>
};
Related
I am building a simple todo-esk feature where if a user clicks the edit icon, only that item is editable. I implement this currently with a useState hook, const [editingMemberName, setEditingMemberName] = useState(false), but when I call a function, editMemberName all instances of items show an input field. This is not the experience I am going for.
Here are some screen shots that should make this more clear:
As you can see, I have two cards, but when I click the tool icon, both input boxes are displayed.
Here is the code:
const [editingMemberName, setEditingMemberName] = useState(false)
const [memberName, setMemberName] = useState('')
const handleChangeName = (e) => {
setMemberName(e.target.value)
}
// Update member name
const editMemberName = async (e) => {
setEditingMemberName(true)
}
const memberItems = members.map((member) => {
return (
<div
key={member.id}
>
<div className="flex items-center gap-4 w-full">
{editingMemberName ? (
<input
type="text"
placeholder="Johnny Appleseed"
onChange={handleChangeName}
/>
) : (
<>
<div>
{member.name}
</div>
<h3>{member.name}</h3>
</>
)}
</div>
<div>
{editingMemberName ? (
<button
onClick={() => updateMemberName(member.id)}
>
<CgCheckO size=".75em" />
</button>
) : (
<button
onClick={() => editMemberName(member.id)}
>
<FiTool size=".75em" />
</button>
)}
</div>
</div>
)
I've realized that editingMemberName hook operates on all instances, but I am not sure how to only target a single item.
Note: you can assume that the members array has a unique id for each item.
members: [
{
name: "Johnny",
id: 123
},
{
name: "George",
id: 456
}
]
That's because you are referring the boolean to all the boxes and not an individual element, use
const [editingMemberName, setEditingMemberName] = useState(members.map(e => false))
Something along the lines
const editMemberName = async (memberID, index) => {
let new_editing_members_state = members.map(e => false)
new_editing_members_state[index] = true
setEditingMemberName(new_editing_members_state)
}
const memberItems = members.map((member, index) => {
return (
<div
key={member.id}
>
<div className="flex items-center gap-4 w-full">
{editingMemberName ? (
<input
type="text"
placeholder="Johnny Appleseed"
onChange={handleChangeName}
/>
) : (
<>
<div>
{member.name}
</div>
<h3>{member.name}</h3>
</>
)}
</div>
<div>
{editingMemberName[index] ? (
<button
onClick={() => updateMemberName(member.id)}
>
<CgCheckO size=".75em" />
</button>
) : (
<button
onClick={() => editMemberName(member.id,index)}
>
<FiTool size=".75em" />
</button>
)}
</div>
</div>
)
I have a simple to do app in react every thing is okay when my app.js is a class component but when I changed it to a functional component occured an error = todos.filter is not a function
my files : Todo.js(functional) --> TodoList.js(functional) --> app.js(functional)
function TodoList(props) {
const [statusDone, setDone] = useState(false);
let { todos } = props;
console.log(todos);
let filterTodos = todos.filter((item) => item.done === statusDone);
return (
<>
<nav className="col-6 mb-3">
<div className="nav nav-tabs" id="nav-tab" role="tablist">
<a
className={`nav-item nav-link font-weight-bold ${
!statusDone ? "active" : ""
}`}
id="nav-home-tab"
onClick={() => setDone(false)}
>
undone{" "}
<span className="badge badge-secondary">
{todos.filter((item) => item.done === false).length}
</span>
</a>
<a
className={`nav-item nav-link font-weight-bold ${
statusDone ? "active" : ""
}`}
id="nav-profile-tab"
onClick={() => setDone(true)}
>
done{" "}
<span className="badge badge-success">
{todos.filter((item) => item.done === true).length}
</span>
</a>
</div>
</nav>
{filterTodos.length === 0 ? (
<p>there isn`t any todos</p>
) : (
filterTodos.map((item) => (
<Todo
key={item.key}
item={item}
delete={props.delete}
done={props.done}
edit={props.edit}
/>
))
)}
</>
);
}
main app class
function App() {
const [todos, settodos] = useState([]);
let addTo = (text) => {
settodos((prevState) => {
return {
todos: [prevState.todos, { key: Date.now(), done: false, text }],
};
});
};
return (
<div className="App">
<main>
<section className="jumbotron">
<div className="container d-flex flex-column align-items-center">
<h1 className="jumbotron-heading">Welcome!</h1>
<p className="lead text-muted">
To get started, add some items to your list:
</p>
<FormAddTodo add={addTo} />
</div>
</section>
<div className="todosList">
<div className="container">
<div className="d-flex flex-column align-items-center ">
<TodoList
todos={todos}
// delete={this.deleteTodo.bind(this)}
// done={this.toggleTodo.bind(this)}
// edit={this.editTodo.bind(this)}
/>
</div>
</div>
</div>
</main>
</div>
);
}
I've tried
let filterTodos =Object.values(todos).filter(item => item.done === statusDone)
and error fixed but my code dosen't work true
I hope u understand what I said :)
this functional component is for adding a todo
function FormAddTodo(props) {
const [text, setText] = useState("");
let formHandler = (e) => {
e.preventDefault();
props.add(text);
setText("");
};
let inputHandler = (e) => setText(e.target.value);
return (
<form className="form-inline mb-5" onSubmit={formHandler}>
<div className="row form-group">
<div className="col-8">
<input
type="text"
className=" form-control mx-sm-3"
placeholder="i want to do ..."
value={text}
onChange={inputHandler}
/>
</div>
<div className="col-4">
<button type="submit" className=" btn btn-primary">
add
</button>
</div>
</div>
</form>
);
}
The problem is on addTo function. You are not adding an element to todos array but you are setting todos as an object with a key called todos that contains an array. Try to modify addTo function in this way:
const addTo = (text) => {
let newElement = { key: Date.now(), done: false, text: text };
settodos(prevState => [...prevState, newElement]);
};
You have an error here:
function App() {
const [todos, settodos] = useState([]);
let addTo = (text) => {
settodos((prevState) => {
return {
todos: [prevState.todos, { key: Date.now(), done: false, text }],
};
});
};
your mutation funtion in settodos can try to concat prevState.todos with a new todo.
in fact with a useState setter, you get the value directly:
settodos(currentTodos => ...)
then return the value that you want (you return an object instead of an array)
also, if you want to concat two arrays, use a spread operator:
const newArray = [...someArray, newValue];
so to sum up, here's a fixed version of that piece of code:
function App() {
const [todos, settodos] = useState([]);
let addTo = (text) => {
settodos((prevTodos) => [
...prevTodos,
{ key: Date.now(), done: false, text }
]);
};
I have 3 components.
In ListCard.js, I map cards array and based on the card the user click on, I call handleChangeCardData to update the modal's text.
My question is: How do I update/change the modal's text when my handleChangeCardData function is inside ListCard.js and my modal is on the same level. (Both are in Board.js)
Board.js
const [cardTitle, setCardTitle] = useState("");
return (
{columns.map((column, index) => (
<div className="column__container" key={index}>
<div className="column__header">
<div className="columnHeader__name">
<p>{column.name ? column.name : "..."}</p>
</div>
<div className="columnHeader__button">
<button
className="btn btn-sm --create-card-btn"
data-bs-toggle="modal"
data-bs-target="#modal-card"
onClick={() => setColumnId(column.id)}
>
New item
</button>
</div>
</div>
<Droppable droppableId={column.id}>
{(provided, snapshot) => (
<div
className="column"
ref={provided.innerRef}
{...provided.droppableProps}
>
<ListCard columnId={column.id} />
{provided.placeholder}
</div>
)}
</Droppable>
</div>
))}
<ViewCardModal cardTitle={cardTitle} />
)
LisCard.js
const handleChangeCardData = (cardTitle) => {
setCardTitle(cardTitle);
}
return (
{cards.map((card, index) => (
<>
<div key={index}>
<Draggable draggableId={card.id} index={index}>
{(provided, snapshot) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
<div
className="card --listcard-card"
onClick={() => handleChangeCardData(card.title)}
data-bs-toggle="modal"
data-bs-target="#modal-card-details"
style={{ border: `2px solid ${card.color}` }}
>
<div className="card-body">
<p>{card.title}</p>
</div>
</div>
</div>
)}
</Draggable>
</div>
</>
))}
)
ViewCardModal.js
function ViewCardModal(props) {
return (
<div>{props.cardTitle}</div>
)
}
In general, lift state up. In this case, it sounds like that means moving the state into Board and then passing that state to whatever child components need it (as a prop), and the state setter to whatever (other) child components need it.
Here's a minimal example of lifting state up. I haven't tried to recreate the full complexity of your example, just to provide an example of Parent having state that ChildA uses and ChildB sets:
const {useState} = React;
const ChildA = React.memo(({counter}) => {
console.log("ChildA rendered");
return <div>Counter = {counter}</div>;
});
const ChildB = React.memo(({setCounter}) => {
console.log("ChildB rendered");
return <input
type="button"
value="Increment Counter"
onClick={() => setCounter(c => c + 1)}
/>;
});
const Parent = () => {
const [counter, setCounter] = useState(0);
return (
<div>
<ChildA counter={counter} />
<ChildB setCounter={setCounter} />
</div>
);
};
ReactDOM.render(<Parent />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.development.js"></script>
If there are several levels of hierarchy between where the state is being held and a descendant component that needs it, you might use context instead of props (although you might also look at component composition instead). See those links for details.
you cant do that directly, but must use props.
in list:
onClick={() => props.onClick(card.title)}
in board:
handleChangeCardData = (cardTitle) => {
setCardTitle(cardTitle);
}
<ListCard columnId={column.id} onClick={(e)=>handleChangeCardData(e)}/>
Inside ListCard:
const ListCard = ({setCardTitle}) => {...}
onClick={() => setCardTitle(card.title)}
In the parent:
<ListCard columnId={column.id} setCardTitle={setCardTitle} />
I am creating a challenge tracking app in React. I would like to, after clicking on the challenge button and approving it, be able to add it and save it to the local storage (as a value to save the name of the chosen challenge) and later to print it in the dashboard.
Could anyone please help me with that.
I have 3 classes I am working now and will paste them below.
ChooseChallenge.js
function Challange() {
const [isPopped, setPop] = useState(false);
const pop = () => {
setPop(!isPopped);
};
return (
//Fragments
<>
{isPopped && <Dialog />}
<div className="chooseChallenge">
{/* <Leaf/> */}
<h1 className="newchallenge">New Challange</h1>
<hr />
<div className="challanges">
<button className="challangeBtn" onClick={pop}>
Eat Vegetarian (31days)
</button>
<button className="challangeBtn" onClick={pop}>
Take the bike to work (14days)
</button>
<button className="challangeBtn" onClick={pop}>
Recycle your plastic bottles (31days)
</button>
<button className="challangeBtn" onClick={pop} >
Use public transport to commute (31days)
</button>
<button className="challangeBtn" onClick={pop}>
Don't fly an airplane (365days)
</button>
</div>
<br />
</div>
</>
);
}
export default Challange;
Dialog.js
function Dialog (){
const [isOpen, setOpennes] = useState(true);
const Close = () => {
setOpennes(false);
}
const [value, setValue] = React.useState(
localStorage.getItem('challengeName') || ''
);
React.useEffect(() => {
localStorage.setItem('challengeName', value);
}, [value]);
const onChange = event => setValue(event.target.value);
return(
<div className={isOpen ? 'dialogBox' : 'dialogHide'}>
<h3 id="header">Do you accept the challange?</h3>
<div className="approvalButtons">
<button className= "approvalButton" onClick = {Close} value={value} onChange={onChange}> Approve </button>
<button className= "approvalButton" onClick = {Close}> Decline </button>
</div>
</div>
)
}
export default Dialog;
Dashboard.js
export default function Dashboard() {
// const challengelist = document.querySelector('#challange-list')
const [challs, setChalls] = useState([]);
useEffect(() => {
const fetchData = async () => {
var challs = [];
await database
.collection("Challenges")
.get()
.then((snapshot) => {
snapshot.docs.forEach((doc) => {
challs.push(doc.data().ChallengeName);
});
});
setChalls(challs);
};
fetchData();
}, []);
return (
<div className="Dashboard">
<Header />
<div className="circle">
<img id="leafpicture" src={leafpic} alt="eco-picture" />
<div className="textIn">
<h1> You saved </h1>
<h5>0.00 CO2</h5>
</div>
</div>
<div>
<ul id="challange-list">
{challs.map((ch) => (
<li key={ch}>{ch}</li>
))}
</ul>
</div>
<div className="progressbar">
<h3>Track your challenges!</h3>
{testData.map((item, idx) => (
<ProgressBar
key={idx}
bgcolor={item.bgcolor}
completed={item.completed}
/>
))}
</div>
<br />
</div>
);
}
on dialog.js the value of the button starts with an empty string and this value never changes, so you are always storing and empty string.
I have a parent component for a navigation bar and would like to test an imported Toggle component that acts as a button with an onClick prop.
const Header = ({ brandLogo, links }: HeaderProps) => {
const [navOpen, setNav] = useState(false);
return (
<Container>
<div className='toggle-container'>
<Toggle active={navOpen} onClick={() => setNav(!navOpen)} />
</div>
<div className={navOpen ? 'menu open' : 'menu closed'}>
{links ? (
<ul>
{links.map(link => (
<li key={uuid.v4()} className='page-links'>
<a href={link.href}>{link.textKey}</a>
</li>
))}
</ul>
) : null}
<div className='menu-buttons'>
<Button text='Log In' />
<Button text='Register' />
</div>
</div>
{brandLogo ? (
<a className='brand-logo' href={brandLogo.href}>
{brandLogo.image}
</a>
) : null}
{links ? (
<ul className='links-container'>
{links.map(link => (
<li key={uuid.v4()} className='page-links'>
<a href={link.href}>{link.textKey}</a>
</li>
))}
</ul>
) : null}
<div className='button-container'>
<Button text='Log In' />
<Button text='Register' />
</div>
</Container>
);
};
export default Header;
Segment I would like to test
<Toggle active={navOpen} onClick={() => setNav(!navOpen)} />
I have seen much documentation on testing the onClick functionality on the parent component but not on a child component.
current tests im running are -
describe('Footer Component', () => {
it('Component should render with all config set', () => {
const { asFragment } = render(<Header {...config} />);
expect(asFragment()).toMatchSnapshot();
});
it('Component should only render links if they are present', () => {
const { asFragment } = render(<Header {...onlyLinks} />);
expect(asFragment()).toMatchSnapshot();
});
it('Component should only render links if they are present', () => {
const { asFragment } = render(<Header {...onlyBrandLogo} />);
expect(asFragment()).toMatchSnapshot();
});
});
Any help would be appreciate thanks :)