Сode in three files. In setList () you need to pass an array of objects to allocate, but they are generated using map. What is the right thing to do? in general I am trying to adapt my code to this https://codesandbox.io/s/react-select-all-checkbox-jbub2 But there the array for the Checkbox is moved to a separate file, and mine is generated using map.
https://codesandbox.io/s/sweet-butterfly-0s4ff?file=/src/TableBody/TableBody.jsx
1-file)
let Checkbox = () => {
return (
<div>
<label className={s.checkbox}>
<input className={s.checkbox__input} type="checkbox"/>
<span className={s.checkbox__fake}></span>
</label>
</div>
)
}
2-file)
const Tablehead = (handleSelectAll, isCheckAll ) => {
return (
<thead className = {s.header}>
<tr className = {s.area}>
<th ><Checkbox name="selectAll" id="selectAll" handleClick={handleSelectAll} isChecked={isCheckAll}/>
</th>
</tr>
</thead>
)
}
3-file)
const TableBody = ({droplets}) => {
const [isCheckAll, setIsCheckAll] = useState(false);
const [isCheck, setIsCheck] = useState([]);
const [list, setList] = useState([]);
useEffect(() => {
setList();
}, [list]);
const handleSelectAll = e => {
setIsCheckAll(!isCheckAll);
setIsCheck(list.map(li => li.id));
if (isCheckAll) {
setIsCheck([]);
}
};
const handleClick = e => {
const { id, checked } = e.target;
setIsCheck([...isCheck, id]);
if (!checked) {
setIsCheck(isCheck.filter(item => item !== id));
}
};
return (
<>
{droplets.map((droplet, index, id, name ) =>
<tr className={s.area} key={index} >
<td ><Checkbox key={id} name={name} handleClick={handleClick} isChecked={isCheck.includes(id)}/></td>
<td><button type="submit" className={s.button}><Edit /></button></td>
<td><button type="submit" className={s.button}><Trash /></button></td>
</tr>
)
}
</>
)
}
So there are several problems here
Component Checkbox doesn't take any props
const Tablehead = (handleSelectAll, isCheckAll) should be const Tablehead = ({ handleSelectAll, isCheckAll })
And most important one is your TableHead and TableBodyComponents both need this checkbox information so you need to lift your state up from TableBody to Table Component.
Also the example code you are following seems to do a lot of redundant things which are not necessary to implement your feature. Simply storing a checked property in each of your droplets should be enough and two functions to toggle individual and toggle all.
So I made the above changes in your code-sandbox link.
Here is the Link
Related
The edited task reflects on browser only when I delete an existing task or add a new one.
The edited task is even reflected in the prompt as the pre-existing task, but the edited text is not reflected in the task.
import * as React from 'react';
import Card from 'react-bootstrap/Card';
import Add from './Add';
import List from './List';
import Table from 'react-bootstrap/Table';
const Main = () => {
const [listData, setListData] = React.useState([]);
const listDataMani = (text) => {
const listDataObj = {
id: listData.length + 1,
text: text,
}
const finalList = [...listData, listDataObj]
setListData(finalList);
}
const listDataDelete = (id) => {
const finalData = listData.filter(function (el) {
if (el.id === id) {
return false;
} else {
return true;
}
})
setListData(finalData);
}
const editTaskHandler = (t, li) => {
let compData = listData; // this is the function to update text
for (let i = 0; i < listData.length; i++) {
if (listData[i].id === li) {
listData[i].text = t;
} else {
return;
}
}
setListData(compData);
}
return (
<><div className='container'>
<div className='col-lg-12'>
<div className='main-component'>
<div className='title'>
<Card style={{ marginTop: "10em" }}>
<Card.Body>
<Card.Title>My Todo List</Card.Title>
<Card.Subtitle className="mb-2 text-muted">Manages Time</Card.Subtitle>
<Add listDataMani={listDataMani} />
<Table striped bordered hover>
<thead>
<tr>
<th>#</th>
<th>Task Name</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<List callback={listDataDelete} editTask={editTaskHandler} list={listData} />
</tbody>
</Table>
</Card.Body>
</Card>
</div>
</div>
</div>
</div></>
)
}
export default Main;
import * as React from 'react';
const List =(props)=>{
const deleteHandler =(id)=>{
props.callback(id);
}
const editRequestHandler =(data)=>{
let editedText = prompt("Edit Your Task", data.text);
props.editTask(editedText, data.id);
}
return (
<>
{props.list.map((el)=>(<tr>
<td>{el.id}</td>
<td>{el.text}</td>
<td>
<button onClick={function(){
deleteHandler(el.id)
}}>X</button>
<button onClick={()=>{editRequestHandler(el)}}>✍</button>
</td>
</tr>))}
</>
)
}
export default List;
The edited task reflects on browser only when I delete an existing task or add a new one.
The edited task is even reflected in the prompt as the pre-existing task, but the edited text is not reflected in the task.
You are modifying the internals of an object/array without changing its referencial identify.
setState operations only do anything if when React compares the old data to the new, it has changed. In the case of arrays and objects, they are compared by reference (as opposed to numbers, strings, and other primitives which are compared by value).
To set the state using a modified object, you need to reconstruct it into a new object.
Here is a demo of the issue: https://codesandbox.io/s/setstate-unchanged-h249v3?file=/src/App.js
Notice how one button prints to console, while the other doesn't.
You could try doing this:
const editTaskHandler = (t, li) => {
setListData(
listData.map((item) => {
if (item.id === li) {
return { ...item, text: t };
}
return item;
})
);
};
Question about disabled in React.js
I try to disabled a button while loading and then after complete render(or said get the data) it will abled the button.
Here is the example in Codesandbox link: https://codesandbox.io/s/data-fetch-example-oc8eh0?file=/src/App.js
scenario like:
every time I click "first", "previous", "next" and "last" button. all the buttons will be disabled (not just the button which being clicked) when a page of data is currently being loaded.
Thank you
Here is the code if you can't open the link:
import React,{ useState, useEffect } from "react";
import "./styles.css";
export default function App() {
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(false);
const [currentPage, setCurrentPage] = useState(0);
const [postsPerPage, setPostsPerPage] = useState(10);
useEffect(()=>{
getData();
},[])
async function getData(){
setLoading(true);
const response = await fetch('https://stream-restaurant-menu-svc.herokuapp.com/item?category')
let actualData = await response.json();
setPosts(actualData)
setLoading(false);
}
const indexOfLastPost = (currentPage + 1) * postsPerPage;
const indexOfFirstPost = indexOfLastPost - postsPerPage;
const currentPosts = posts.slice(indexOfFirstPost, indexOfLastPost);
const pageNumbers = [];
for (let i = 0; i < Math.ceil(posts.length / postsPerPage); i++) {
pageNumbers.push(i);
}
const incrementPageNumber = () => {
if (currentPage < pageNumbers.length - 1) {
setCurrentPage((previousPageNumber) => previousPageNumber + 1);
}
};
const decrementPageNumber = () => {
if (currentPage > 0) {
setCurrentPage((previousPageNumber) => previousPageNumber - 1);
}
};
// if loading...
if(loading){
return <h2>Loading</h2>
}
return (
<div className="App">
<table className="table">
<thead>
<tr>
<th>ID</th>
<th>First Name</th>
<th>Last Name</th>
</tr>
</thead>
<tbody>
{currentPosts.map((post) => (
<tr>
<td key={`${post.id}-id`}>{post.id}</td>
<td key={`${post.name}-firstName`}>{post.name}</td>
<td key={`${post.short_name}-lastName`}>{post.short_name}</td>
</tr>
))}
</tbody>
</table>
<div>
<button
id='first'
type='button'
className='first-page-btn'
disabled={loading || currentPage === 0}
onClick={() => {setCurrentPage(0)}}
>
first
</button>
<button
id='previous'
type='button'
className='previous-page-btn'
disabled={loading || currentPage === 0}
onClick={decrementPageNumber}
>
previous
</button>
<button
id='next'
type='button'
className='next-page-btn'
disabled={loading || currentPage === pageNumbers[pageNumbers.length - 1]}
onClick={incrementPageNumber}
>
next
</button>
<button
id='last'
type='button'
className='last-page-btn'
disabled={loading || currentPage === 0}
onClick={() => setCurrentPage(pageNumbers[pageNumbers.length - 1])}
>
last
</button>
</div>
</div>
);
}
enter image description here
You seem to only change loading state in getData on initial mount (and not on clicking buttons. And whilte it is loading you return the <h2>Loading</h2> instead of the whole table, so it doesnt matter if buttons inside table disabled or not while loading.
Unfortunately, I cannot open your link. Here is my thought. I guess you might have loading state in main component. If not, you might have data state which is the data you want to download and update in each pagination.
I will use loading as example. Your component might be similar to this.
function Main() {
return(
<div>
<Pagination disabled={loading} {...other props} />
</div>
)
Then, add disabled props to Pagination. I think your pagination are button component, then it has disabled attribute. Or, if you use bootstrap, you can pass disabled className to button or a element.
I new to react to so an explanation would be great to your solution, I have a table that renders data from my database, I have a function delete question seen in the image below, I need the question id the number to the right of the button. The problem is I cant get the questionid based off the button clicked in the table. Say for example I click the delete button I want the login id sent as an argument to my function deletequestion. Any method is ok, Feel free to change any existing code if necessary.
Component that renders the table.
import React, { useState } from 'react';
import { Button } from 'semantic-ui-react';
import { currentloginid } from '../login/loginid.js';
import { deletequestion } from '../question/deletequestion.js';
export const ViewQuestionComponent = () => {
let [state, setState] = useState([]);
const handleViewQuestion = async () => {
try {
const response = await fetch('http://localhost/gotaquestion/api/api.php?action=viewquestion', {
method: 'GET',
credentials: 'include'
});
const data = await response.json();
const result = await data;
setState(data);
} catch (e) {
console.log(e);
}
}
return (
<div>
<ViewQuestion onClick={handleViewQuestion} />
<div id="questions">
<Table rows={state}>
<DeleteButton onClick={deletequestion} />
</Table>
</div>
</div>
);
};
export function ViewQuestion({onClick}) {
return (
<Button onClick={onClick}>View Question</Button>
);
}
export default ViewQuestion;
const Table = ({rows, children}) => (
<table className="ui single line table">
<tbody>
{ rows.map(row =>
<tr key={row.questionid}>
<td>{row.question}</td>
<td>{row.timestamp}</td>
<td>{row.catagories}</td>
<td>{(row.answer === null ? "Not Answered" : row.answer)}</td>
<td>{children}</td>
<td>{row.questionid}</td>
</tr>
)}
</tbody>
</table>
);
const DeleteButton = ({onClick}) => (
<button className="ui negative basic button" onClick={onClick}>Delete Question </button>
);
The deletequestion function
export function deletequestion() {
//I need the question id saved in the varible below
var questionid = ;
console.log(questionid);
fetch('http://localhost/gotaquestion/api/api.php?action=deletequestion',
{
method: 'GET',
credentials: 'include'
}
)
}
Thanks is advance :)
First of all, pass the questionId as props to the children like below,
<td>{React.cloneElement(children, { questionid: row.questionid })}</td>
Table Changes:-
const Table = ({ rows, setIdTobeDeleted, children }) => (
<table className="ui single line table">
<tbody>
{rows.map((row) => (
<tr key={row.questionid}>
<td>{row.question}</td>
<td>{row.timestamp}</td>
<td>{row.catagories}</td>
<td>{row.answer === null ? "Not Answered" : row.answer}</td>
<td>
{React.cloneElement(children, { questionid: row.questionid })} //<<<---Here is change
</td>
<td>{row.questionid}</td>
</tr>
))}
</tbody>
</table>
);
Once the questionid passed as a property to the child (DeleteButton), access the id and pass to the onClick method.
const DeleteButton = ({ questionid, onClick }) => (
<button
className="ui negative basic button"
onClick={() => onClick(questionid)} //<<<<---- see here
>
Delete Question{" "}
</button>
);
Working code - https://codesandbox.io/s/angry-feistel-lxp09?file=/src/App.js
This should work
export function deletequestion(event) {
var questionid = event.target.closest("td").nextElementSibling.innerHTML ;
console.log(questionid);
fetch('http://localhost/gotaquestion/api/api.php?action=deletequestion',
{
method: 'GET',
credentials: 'include'
}
)
}
I have the following code and I'm trying to simplify it using a map function, perhaps on the array: const columns = ['Title', 'Author', 'Rating']
export const BookshelfListRow = (props) => {
return (
<tr className="table-row" >
<td>
<input onChange={(e) => { props.Update(e.target.value) }} placeholder={props.book.Title} />
</td>
<td>
<input onChange={(e) => { props.Update(props.book.Title, e.target.value) }} placeholder={props.book.Author} />
</td>
<td>
<input onChange={(e) => { props.Update(props.book.Title, props.book.Author, e.target.value) }} placeholder={props.book.Rating} />
</td>
</tr>
)}
Please note this is simplified - in my actual code I have 30 columns (meaning 30 separate inputs instead of 3) hence why I'm looking for a way to simplify it as it is currently really long - so essentially what is happening above is the placeholder is iterating through the array [Title,Author,Rating], and simultaneously on each new line we are adding an item from the array (in the form of props.book[item]) to the props.Update function. Any ideas how I could use a map function to carry this out?
You can use map to simplify it. The tricky bit will be the calling of Update with different number of parameters, but that too can be achieved using another map.
const columns = ['Title', 'Author', 'Rating'];
export const BookshelfListRow = (props) => {
return (
<tr className="table-row">
{
columns.map((column, i) => (
<td>
<input onChange={ e =>
props.Update(...[ // the parameters to Update consist of
...columns.slice(0, i).map(column => props.book[column]), // the column values from the start until the current column, map is used here to get the values for those columns
e.target.value // and the input value
])
}
placeholder={ props.book[column] } />
</td>
))
}
</tr>
)
}
Another approach:
The Update function is a mess. It can be a lot simpler if it just takes the column that was changed and the value as there is no need for it to send all those props back to the server if only one was changed, like so (this uses computed property names):
const Update = (column, value) => // takes the column that was changed and the value
axios.put('http://localhost:4001/books/update', { [column]: value }); // update only that column
Then the rendering will be much simpler also, like so:
const columns = ['Title', 'Author', 'Rating'];
export const BookshelfListRow = (props) => {
return (
<tr className="table-row">
{
columns.map((column, i) => (
<td>
<input onChange={ e => props.Update(column, e.target.value) } placeholder={ props.book[column] } />
</td>
))
}
</tr>
)
}
If you're using the keys of props.book, you can try something like this:
import React from "react";
const BookshelfListRow = props => {
const args = [];
return (
<tr className="table-row">
{Object.keys(props.book).map((key, idx) => {
if(idx > 0) {
args.unshift(key);
}
const argsCopy = [...args];
return (
<td>
<input
onChange={e => {
props.Update(...argsCopy, e.target.value);
}}
placeholder={props.book[key]}
/>
</td>
);
})}
</tr>
);
};
export default BookshelfListRow;
Otherwise, you can use an array like the one you suggested (const columns = ['Title', 'Author', 'Rating']) and take each value and add it to a copy with each map loop.
Mapping is a very powerful tool in React. It is most useful when you are try to DRY out some repeated code. In your case you are trying to DRY out your td's by mapping over the array columns.
Your columns array will need a little more info to make mapping useful. For instance,
const columns = ['Title', 'Author', 'Rating']
columns.map(column => console.log(column)) // Title, Author, Rating
That's not very helpful for your td because it needs the onChange, and placeholder and both require more information than just the strings 'Title', 'Author', and 'Rating'.
From what I can tell, your book prop is an object that looks something like this:
book: {
Title: 'some string',
Author: 'some other string',
Rating: 'some number maybe'
}
You can map over that by using Object.keys. But again, that only helps with the placeholder not the onChange.
The data that you have and the data you are trying to use for your inputs do not seem to have a common enough pattern to utilize map here.
Possible Solution
Modify your update function to not require so many parameters to keep the input field generic as possible that way you can map over your columns.
export const BookshelfListRow = (props) => {
// if you are using React without hooks, just replace this with
// normal state
const [state, setState] = useState({
title: '',
author: '',
rating: ''
})
const update = (e) => {
const input = e.currentTarget.value;
const attribute = e.currentTarget.name;
setState({...state, [attribute]: input})
}
const apiRequest = (e) => {
e.preventDefault();
// your state is now your request body
request(state)
}
const columns = ['Title', 'Author', 'Rating']
return (
<tr className="table-row" >
{columns.map(column => (
<td key={column}>
<input name={column.toLowerCase()} onChange={update} placeholder={column} />
</td>
))}
</tr>
)}
const columns = ['Title', 'Author', 'Rating']
const update = (val, column) => {
console.log(`${column}: ${val}`)
}
const BookshelfListRow = () => (<table><tbody><tr className="table-row">{
columns.map((column, i) => {
return (<td key={i}><input type="text" onChange = {e => update(e.target.value, column)} placeholder={column} /></td >)
})
}</tr></tbody></table>
)
ReactDOM.render(
<BookshelfListRow />,
document.getElementById("root")
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
I mapped over a list of objects to render table rows. Each table row has and input with type checkbox.
const [ isChecked, setIsChecked ] = useState(false);
const handleChange = (e) => {
setIsChecked(e.target.checked)
};
const tableRow = clonedUsers.map((user, index) => {
return (
<tr key={index}>
<td className='spacer-left' />
<td className='checkbox'>
<div className='pretty p-svg p-curve'>
<input type='checkbox' onChange={handleChange} checked={isChecked} />
<div className='state p-primary'>
<svg className='svg svg-icon' viewBox='0 0 20 20'>
<path
d='M7.629,14.566c0.125,0.125,0.291,0.188,0.456,0.188c0.164,0,0.329-0.062,0.456-0.188l8.219-8.221c0.252-0.252,0.252-0.659,0-0.911c-0.252-0.252-0.659-0.252-0.911,0l-7.764,7.763L4.152,9.267c-0.252-0.251-0.66-0.251-0.911,0c-0.252,0.252-0.252,0.66,0,0.911L7.629,14.566z'
style={{ stroke: 'white', fill: 'white' }}
/>
</svg>
<label />
</div>
</div>
</td>
<td className='team-name'>{user.name}</td>
<td className='team-lead-name d-none d-xl-flex'>{user.position}</td>
<td className='team-index d-none d-md-flex'>{user.email}</td>
<td className='team-lead-rating'>
<Link to='/people-settings'>Edit team</Link>
</td>
<td className='team-lead-rating'>
<Link to='/people-settings'>Deactivate user</Link>
</td>
</tr>
);
});
When I click on any checkbox, it is marking all of them as checked. What am I doing wrong here? Is there any better way of implementing this?
You have to extract the checkbox to a separate component or i would say you need to create a separate component to use a local state for it:
export default const Checkbox = props => {
const [isChecked, setIsChecked] = useState(false);
const handleChange = e => {
setIsChecked(!isChecked);
}
return (
<input type='checkbox' onChange={handleChange} checked={isChecked} />
);
}
Now you can use this:
<div className='pretty p-svg p-curve'>
<Checkbox />
What am I doing wrong here?
The problem is there's only one state used for all checkboxes.
Is there any better way of implementing this?
You can either use list or object to store the state for each checkbox.
But for quick lookup, I will use object with the following structure:
{ [name]: <checkbox value> }
const [checked, setChecked] = useState({});
const handleChange = (e, name) => {
setChecked({
...checked,
[name]: e.target.checked
})
};
<input type='checkbox'
onChange={(e) => handleChange(e, user.name)}
checked={checked[user.name]}
/>
I'd keep the checked ids in state e.g.:
const [checked, setChecked] = useState([]);
const handleChange = id => () => {
setChecked(prev => {
if (prev.includes(id)) {
return prev.filter(x => x !== id);
} else {
return [...prev, id];
}
});
};
Then in my input:
<input
type='checkbox'
onChange={handleChange(user.id)}
checked={checked.includes(user.id)}
/>
A cleaner way to write your handler is with lodash using xor.
import xor from 'lodash/xor';
///
const handleChange = id => () => {
setChecked(prev => xor(prev, [id]));
};