How to display different tables on click in React? - javascript

I am making a simple todo. I am fetching data from an API and I want to show all the items in a table by default. There will be 3 buttons - All, Complete and Incomplete which will show All, Completed and Incompleted todos table respectively. I have set states for completed and incompleted todos but can't wrap my head around how to perform conditional rendering and display different tables on different button clicks.
Below is my code -
import React, { useState, useEffect } from "react";
import axios from "axios";
import "./style.css";
export default function App() {
const URL = 'https://jsonplaceholder.typicode.com/todos';
const [todo, setTodo] = useState([]);
const [completed, setCompleted] = useState([]);
const [incomplete, setIncomplete] = useState([]);
useEffect(()=>{
axios.get(URL)
.then(res=>setTodo(res.data));
},[])
const showCompleted = () =>{
const completeTask = todo.filter((items)=>items.completed===true);
setCompleted(completeTask);
}
const showIncomplete = () =>{
const incompleteTask = todo.filter((items)=>items.completed===false);
setIncomplete(incompleteTask);
}
return (
<div>
<h1>ToDos!</h1>
<button type="button">All</button>
<button type="button" onClick={showCompleted}>Completed</button>
<button type="button" onClick={showIncomplete}>Incomplete</button>
<hr />
<table>
<tr>
<th>ID</th>
<th>Title</th>
<th>Completed</th>
</tr>
{todo.map((items)=>
<tr key={items.id}>
<td>{items.id}</td>
<td>{items.title}</td>
<td><input type="checkbox" defaultChecked={items.completed ? true : false} /></td>
</tr>
)}
</table>
</div>
);
}

Instead of maintaining a separate state for each type have one type state that the buttons update when they're clicked. Add data attributes to the buttons to indicate what type they are and which can be picked up in the click handler.
Instead of mapping over the whole set of todos, call a function that filters out the set of data from the todo state that you need.
const { useEffect, useState } = React;
const URL = 'https://jsonplaceholder.typicode.com/todos';
function Example() {
const [todos, setTodos] = useState([]);
const [type, setType] = useState('all');
useEffect(()=>{
fetch(URL)
.then(res => res.json())
.then(data => setTodos(data));
}, []);
// Filter the todos depending on type
function filterTodos(type) {
switch(type) {
case 'completed': {
return todos.filter(todo => todo.completed);
}
case 'incomplete': {
return todos.filter(todo => !todo.completed);
}
default: return todos;
}
}
// Set the type when the buttons are clicked
function handleClick(e) {
const { type } = e.target.dataset;
setType(type);
}
// Call the filter function to get the
// subset of todos that you need based
// on the type
return (
<div>
<h1>ToDos!</h1>
<button
type="button"
className={type === 'all' && 'active'}
data-type="all"
onClick={handleClick}
>All
</button>
<button
type="button"
className={type === 'completed' && 'active'}
data-type="completed"
onClick={handleClick}
>Completed
</button>
<button
type="button"
className={type === 'incomplete' && 'active'}
data-type="incomplete"
onClick={handleClick}
>Incomplete
</button>
<hr />
<table>
<tr>
<th>ID</th>
<th>Title</th>
<th>Completed</th>
</tr>
{filterTodos(type).map(todo => {
const { id, title, completed } = todo;
return (
<tr key={id}>
<td>{id}</td>
<td>{title}</td>
<td>
<input
type="checkbox"
defaultChecked={completed ? true : false}
/>
</td>
</tr>
);
})}
</table>
</div>
);
}
ReactDOM.render(
<Example />,
document.getElementById('react')
);
button { margin-right: 0.25em; }
button:hover { cursor:pointer; }
.active { background-color: lightgreen; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>

Keep two states, one to store the initial data and another one to keep track of actually displayed data.
Try like this:
function App() {
const URL = "https://jsonplaceholder.typicode.com/todos";
const [todo, setTodo] = React.useState([]);
const [view, setView] = React.useState([]);
React.useEffect(() => {
fetch(URL)
.then((res) => res.json())
.then((result) => {
setTodo(result);
setView(result);
});
}, []);
const showAll = () => {
setView(todo);
};
const showCompleted = () => {
const completeTask = todo.filter((items) => items.completed === true);
setView(completeTask);
};
const showIncomplete = () => {
const incompleteTask = todo.filter((items) => items.completed === false);
setView(incompleteTask);
};
return (
<div>
<h1>ToDos!</h1>
<button type="button" onClick={showAll}>
All
</button>
<button type="button" onClick={showCompleted}>
Completed
</button>
<button type="button" onClick={showIncomplete}>
Incomplete
</button>
<hr />
<table>
<thead>
<tr>
<th>ID</th>
<th>Title</th>
<th>Completed</th>
</tr>
</thead>
<tbody>
{view.map((items) => (
<tr key={items.id}>
<td>{items.id}</td>
<td>{items.title}</td>
<td>
<input
type="checkbox"
defaultChecked={items.completed ? true : false}
/>
</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
ReactDOM.render(<App />, document.querySelector('.react'));
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div class='react'></div>

You can use useMemo to prepare the data to display based on some conditions/filters/search/ordering/ anything else.
So few steps to achieve that:
Optional, declare some object outside of the component to hold some constants. Maybe I choosed a poor name for that but the idea itself should be ok. FILTER_COMPLETED in the code.
Add a useState variable to hold active filter for this specific area. const [filterCompleteMode, setFilterCompleteMode] = useState(...) in the code.
Add a useMemo variable that will prepare the data to display. You can apply some ordering or additinal filtering here. todosToDisplay in the code.
Modify your JSX a bit, change <button>s and todo to todosToDisplay.
const { useState, useMemo, useEffect } = React;
const FILTER_COMPLETED = {
All: "ALL",
Complete: "COMPLETE",
Incomplete: "INCOMPLETE"
};
function App() {
const URL = "https://jsonplaceholder.typicode.com/todos";
const [todos, setTodos] = useState([]);
const [filterCompleteMode, setFilterCompleteMode] = useState(
FILTER_COMPLETED.All
);
const todosToDisplay = useMemo(() => {
if (!todos) return [];
switch (filterCompleteMode) {
case FILTER_COMPLETED.All:
return todos;
case FILTER_COMPLETED.Incomplete:
return todos.filter((x) => x.completed === false);
case FILTER_COMPLETED.Complete:
return todos.filter((x) => x.completed === true);
default:
return todos;
}
}, [todos, filterCompleteMode]);
useEffect(() => {
fetch(URL)
.then((res) => res.json())
.then((data) => setTodos(data));
}, []);
const onCompleteFilterClick = (e) => {
setFilterCompleteMode(e.target.dataset.mode);
};
return (
<div>
<h1>ToDos!</h1>
<button
type="button"
data-mode={FILTER_COMPLETED.All}
onClick={onCompleteFilterClick}
>
All
</button>
<button
type="button"
data-mode={FILTER_COMPLETED.Complete}
onClick={onCompleteFilterClick}
>
Completed
</button>
<button
type="button"
data-mode={FILTER_COMPLETED.Incomplete}
onClick={onCompleteFilterClick}
>
Incomplete
</button>
<hr />
<table>
<thead>
<tr>
<th>ID</th>
<th>Title</th>
<th>Completed</th>
</tr>
</thead>
<tbody>
{todosToDisplay.map((item) => (
<tr key={item.id}>
<td>{item.id}</td>
<td>{item.title}</td>
<td>
<input
type="checkbox"
defaultChecked={item.completed ? true : false}
/>
</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
ReactDOM.createRoot(document.getElementById("root")).render(<App />);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
<div id="root"></div>

Create state:
const [whatShow, setWhatShow] = useState('All').
When you click on button change this state
next:
{todo.map((items)=>
{items.completed === whatShow && <tr key={items.id}>
<td>{items.id}</td>
<td>{items.title}</td>
<td><input type="checkbox" defaultChecked={items.completed ? true : false} /></td>
</tr>}
)}
something like this

Related

Delete Table Row not working properly with search bar

i'm new to reactjs and i'm trying to make a table that shows the information from a array of objects and have a button of delete and an input to search among the users. The delete button is working correctly when i'm not searching anything, but when i'm searching it doesn't delete the corretly row, and deletes only the first one. I see that it is because the arrays that show the table are different with and without the search being used but I don't know how to make it work.
this is the component of the table:
import { formatDate } from "../../utils/formatDate";
import "./table.css";
import { useState } from "react";
function Table(props) {
const { headerData, bodyData, type, removeItem} = props;
const isUser = type === "user";
const buildTableItems = () => {
return bodyData.map((item, index) => (
<tr className="data-tr">
<td>{item.name}</td>
<td>{item.email}</td>
<td>{item.occupation}</td>
<td>{formatDate(item.birthday)}</td>
<td>
<button className="delete-button" onClick={() => removeItem(index)}>
Delete
</button>
</td>
</tr>
));
};
return (
<div className="user-data">
<table className="user-table">
<thead>
<tr className="data-th">
{headerData.map((headerTable) => (
<th >{headerTable}</th>
))}
</tr>
</thead>
<tbody>{buildTableItems()}</tbody>
</table>
</div>
);
}
export default Table;
Here the component of the search bar:
import "./searchBar.css"
function SearchBar({ searchedData, onSearch }) {
return (
<div className="search-bar">
<label>Search</label>
<input type="text" placeholder="Search User" value={searchedData} onChange={e => onSearch(e.target.value)} />
</div>
);
}
export default SearchBar;
and here is the home:
import "./Home.css";
import React, { useEffect, useState } from "react";
import Header from "../components/Header/Header";
import Table from "../components/Table/Table";
import AddData from "../components/AddData/AddData";
import SearchBar from "../components/SearchBar/SearchBar";
import { userArr } from "../mock/users";
const Home = () => {
const headerUser = ["Name", "Email", "Occupation", "Birthday"];
const [newUserArr, setNewUserArr] = useState(userArr);
const [searchedItem, setSearchedItem] = useState("");
const searchedArray = newUserArr.filter((item) => {
if (item.name.toLowerCase().includes(searchedItem.toLowerCase())) {
return true;
}
});
function onSearch(e) {
setSearchedItem(e);
}
const addDataToArr = (form) => {
setNewUserArr([...newUserArr, form]);
};
const deleteData = (indexUserArr) => {
let restOfDataArray = newUserArr.filter(
(element, ind) => ind !== indexUserArr
);
setNewUserArr(restOfDataArray);
};
return (
<>
<Header />
<SearchBar searchedData={searchedItem} onSearch={onSearch} />
<Table
type="user"
headerData={headerUser}
bodyData={newUserArr}
removeItem={(index) => deleteData(index)}
/>
<AddData saveData={(val) => addDataToArr(val)} />
</>
);
};
export default Home;
thank you
If you have ID in your user data then use that instead of index or create id keywords using concatenate with your values here is examples.
import { formatDate } from "../../utils/formatDate";
import "./table.css";
import { useState } from "react";
function Table(props) {
const { headerData, bodyData, type, removeItem} = props;
const isUser = type === "user";
const buildTableItems = () => {
return bodyData.map((item, index) => (
<tr className="data-tr">
<td>{item.name}</td>
<td>{item.email}</td>
<td>{item.occupation}</td>
<td>{formatDate(item.birthday)}</td>
<td>
<button className="delete-button" onClick={() => removeItem(`${item.name}${item.email}${item.occupation}`)}>
Delete
</button>
</td>
</tr>
));
};
return (
<div className="user-data">
<table className="user-table">
<thead>
<tr className="data-th">
{headerData.map((headerTable) => (
<th >{headerTable}</th>
))}
</tr>
</thead>
<tbody>{buildTableItems()}</tbody>
</table>
</div>
);
}
export default Table;
And here is your delete method ${item.name}${item.email}${index}
const deleteData = (data) => {
let restOfDataArray = newUserArr.filter(
(element, ind) => `${element.name}${element.email}${element.occupation}` !== data
);
setNewUserArr(restOfDataArray);
};
This will fixed your problem. If this doesn't work then you need to use ID to resolve this problem. There is a possibility that ${item.name}${item.email}${item.occupation} can be duplicates.
Never use index ever for deleting or any other operations. Use always ID.

React.js POST method issue

I'm doing this React.js project and I have to use POST method. I've managed to make the item appear in the the console and in the json file, but it's not showing in my table. I assume It has to do something with the useEffect hook, but since I'm very new to all of this, I don't know how to implement it correctly.
Table
import React, { useState, useEffect } from "react";
import axios from "axios";
const Table = () => {
//FETCH API
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
const fetchPosts = async () => {
setLoading(true);
const res = await axios.get("http://localhost:3000/movies");
setPosts(res.data);
setLoading(false);
};
fetchPosts();
}, []);
//SORTING
const [order, setOrder] = useState("ascending");
const sorting = (s) => {
if (order === "ascending") {
const sorted = [...posts].sort((a, b) =>
a[s].toLowerCase() > b[s].toLowerCase() ? 1 : -1
);
setPosts(sorted);
setOrder("descending");
}
if (order === "descending") {
const sorted = [...posts].sort((a, b) =>
a[s].toLowerCase() < b[s].toLowerCase() ? 1 : -1
);
setPosts(sorted);
setOrder("ascending");
}
};
//TABLE
return (
<div className="container">
<table className="table table-bordered table-striped">
<thead>
<tr>
<th className="click" onClick={() => sorting("name")}>
Name
</th>
<th>Genre</th>
<th>Rating</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{posts.map((post) => (
<tr key={post}>
<td>{post.name}</td>
<td>{post.genre}</td>
<td>{post.rating}</td>
<td>
{<button className="btn btn-outline-success">delete</button>}
{<button className="btn btn-outline-warning">edit</button>}
</td>
</tr>
))}
</tbody>
</table>
</div>
);
};
export default Table;
AddForm
import React from "react";
import { useState } from "react";
function AddForm() {
const [name, setName] = useState("");
const [genre, setGenre] = useState("");
const [rating, setRating] = useState("");
const handleSubmit = (e) => {
fetch("http://localhost:3000/movies", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
name: name,
genre: genre,
rating: rating,
}),
})
.then((response) => response.json())
.then((response) => {
console.log(response);
});
e.preventDefault();
};
return (
<div className="text-center">
<h2>Add a New movie</h2>
<form onSubmit={handleSubmit}>
<label>Name:</label>
<input
name="name"
type="text"
required="required"
onChange={(e) => setName(e.target.value)}
/>
<label>Genre:</label>
<input
name="genre"
type="text"
required="required"
onChange={(e) => setGenre(e.target.value)}
/>
<label>Rating:</label>
<input
name="rating"
type="text"
required="required"
onChange={(e) => setRating(e.target.value)}
/>
<button type="submit">Add</button>
</form>
</div>
);
}
export default AddForm;

tried a lot but not able to make deletehandler function working. here is my code

This is my librarylist component in which i pass deletehandler function to delete the row from library management. I don't know which part of the code is causing the problem. Any helps/suggestions are welcome.
LibraryBookList.js
const LibraryBookList = (props) => {
const[database, setDatabase]=useState()
const deleteHandler = (bookdataId) => {
const newDatabase=[...database];
const index= database.findIndex((bookdata)=>bookdata.id===bookdataId)
newDatabase.splice(index,1)
setDatabase(newDatabase);
} ;
return (
<ul className={classes.list}>
{props.database.map((bookdata) =>
(<LibraryBook
key={bookdata.key}
id={bookdata.id}
bookname={bookdata.bookName}
author={bookdata.author}
publisher={bookdata.publisher}
pages={bookdata.pages}
serialno={bookdata.serialNo}
onSelect={deleteHandler}
/>
))}
</ul>
)};
here i pass deletehandler via props
LibraryBook.js
const LibraryBook = (props) => {
return (
<li>
<table className={classes.table}>
<tbody>
<tr className={classes.table_row}>
<td className={classes.row_data}>{props.serialno}</td>
<td className={classes.row_data}>{props.pages}</td>
<td className={classes.row_data}>{props.bookname}</td>
<td className={classes.row_data}>{props.author}</td>
<td className={classes.row_data}>{props.publisher}</td>
<td>
<button className={classes.delete_btn} onClick={(props.onSelect(props.id))}>
Delete
</button>
</td>
</tr>
</tbody>
</table>
</li>
export default LibraryBookList;
**BookData.js **
const BookData = (props) => {
const [isLoading, setIsLoading] = useState(true);
const [loadedLibrarydata, setLoadedLibrarydata] = useState();
useEffect(() => {
setIsLoading(true);
fetch(
"https://librarymanagement-70ab2-default-rtdb.firebaseio.com/database.json"
)
.then((response) => {
// console.log('response',response.json())
return response.json();
})
.then((data) => {
const database = [];
console.log("data", data);
for (const key in data) {
const bookdata = {
id: key,
...data[key],
};
database.push(bookdata);
}
setIsLoading(false);
setLoadedLibrarydata(database);
});
}, []);
if (isLoading) {
return (
<section>
<p>Loading.....</p>
</section>
);
}
return (
<section>
<h1>Book Data Base</h1>
<table className={classes.table}>
<thead>
<tr className={classes.table_row}>
<th className={classes.row_heading}>Serial No</th>
<th className={classes.row_heading}>Pages</th>
<th className={classes.row_heading}>Book Name</th>
<th className={classes.row_heading}>Author</th>
<th className={classes.row_heading}>Publisher</th>
</tr>
</thead>
</table>
{loadedLibrarydata && loadedLibrarydata.length && (
<LibraryBooklist database={loadedLibrarydata} />
)}
</section>
);
};
export default BookData;
NewDataBase.js
const NewDataBase = () => {
const history=useHistory();
const addDataHandler = (bookData) => {
console.log('bookData',bookData);
fetch(
"https://librarymanagement-70ab2-default-rtdb.firebaseio.com/database.json",
{
method: "POST",
body: JSON.stringify(bookData),
headers: {
"Content-type": "application/json",
},
}
).then(()=>{
history.replace('/')
})
};
return (
<section>
<DataBaseForm onAddNewData={addDataHandler} />
</section>
);
};
export default NewDataBase;
The code has a few issues: 1) props.onSelect(props.id) inside onClick. Instead you should give a referance to that function. 2) You didn't have anything in database state before you click delete button. That is why ... spread operator didn't work 3) You are displaying props.database instead of database state. That is way the changes didn't show up even after you deleted a bookdata. I also fixed some small issues. Now it is working perfectly:
// !! you can put all the code into one file and run for testing.
// !! I removed stylings as I didn't have the source
import {useState, useEffect} from 'react'
const LibraryBooklist = (props) => {
const[database, setDatabase]=useState(props.database)
const deleteHandler = (bookdataId) => {
const newDatabase=database.filter((bookdata)=>bookdata.id!==bookdataId);
setDatabase(newDatabase);
}
return (
<ul>
{database.map((bookdata) =>
<LibraryBook
key={bookdata.id}
id={bookdata.id}
bookname={bookdata.bookName}
author={bookdata.author}
publisher={bookdata.publisher}
pages={bookdata.pages}
serialno={bookdata.serialNo}
onSelect={deleteHandler}
/>
)}
</ul>
)};
const LibraryBook = (props) => {
const {id, onSelect} = props
return (
<li>
<table>
<tbody>
<tr>
<td>{props.serialno}</td>
<td>{props.pages}</td>
<td>{props.bookname}</td>
<td>{props.author}</td>
<td>{props.publisher}</td>
<td>
<button onClick={() => onSelect(id)}>
Delete
</button>
</td>
</tr>
</tbody>
</table>
</li>
)}
const BookData = (props) => {
const [isLoading, setIsLoading] = useState(true);
const [loadedLibrarydata, setLoadedLibrarydata] = useState();
useEffect(() => {
setIsLoading(true);
fetch(
"https://librarymanagement-70ab2-default-rtdb.firebaseio.com/database.json"
)
.then((response) => {
// console.log('response',response.json())
return response.json();
})
.then((data) => {
const database = [];
for (const key in data) {
const bookdata = {
id: key,
...data[key],
};
database.push(bookdata);
}
setIsLoading(false);
setLoadedLibrarydata(database);
});
}, []);
if (isLoading) {
return (
<section>
<p>Loading.....</p>
</section>
);
}
return (
<section>
<h1>Book Data Base</h1>
<table>
<thead>
<tr>
<th>Serial No</th>
<th>Pages</th>
<th>Book Name</th>
<th>Author</th>
<th>Publisher</th>
</tr>
</thead>
</table>
{loadedLibrarydata && loadedLibrarydata.length && (
<LibraryBooklist database={loadedLibrarydata} />
)}
</section>
);
};
export default BookData;

React update state only when button is clicked rather than updating whenever something is written in input

I've got a react app. And I was making filter section where user puts max price of article.
Now everything works, only thing that is, page is rerendering on every single input in input box, even there is an button.
What would be the way to stop rerendering of a page? I want it to rerender only if button is pressed - so I only want to remove this rerendering when user inputs a price. Eg. User want to input price 200000 but page refreshes when he types 2,0,0 etc. I want to be able to input 200000 without refreshing only if button is pressed.
Thanks!
Here is my dashboard.js
const Dashboard = () => {
const form = useRef();
const checkBtn = useRef();
const pageSearchParam = new URLSearchParams(window.location.search);
const pageParam = Number(pageSearchParam.get('page')) || 1;
const maxPriceSearchParam = new URLSearchParams(window.location.search);
const maxPriceParam = Number(maxPriceSearchParam.get('maxprice'));
const parsedUrl = new URL(window.location.href);
const filterNameInURL = parsedUrl.searchParams.get('filter');
const [content, setContent] = useState([]);
const [maxPrice, setMaxPrice] = useState(maxPriceParam || []);
// eslint-disable-next-line
const [page, setPage] = useState(pageParam);
const [total, setTotal] = useState(0);
const [loading, setLoading] = useState(false);
useEffect(() => {
if (filterNameInURL) {
const fetchFiltered = async () => {
const res = await ArticleService.filterByName(filterNameInURL, page);
const { count, rows } = await res.data;
setTotal(count);
setContent(rows);
};
fetchFiltered();
} else if (maxPrice) {
const fetchWithMaxPrice = async () => {
const res = await ArticleService.filterByMaxPrice(maxPrice, page);
const { count, rows } = await res.data;
setTotal(count);
setContent(rows);
};
fetchWithMaxPrice();
} else {
const fetchPosts = async () => {
const res = await ArticleService.articles(page);
const { count, rows } = await res.data;
setTotal(count);
setContent(rows);
};
fetchPosts();
}
}, [filterNameInURL, page, maxPrice]);
const onChangeMaxPrice = (e) => {
const maxprice = e.target.value;
setMaxPrice(maxprice);
};
const handleFilter = async (e) => {
e.preventDefault();
form.current.validateAll();
setLoading(true);
if (checkBtn.current.context._errors.length === 0) {
try {
onsubmit = () => {
maxPriceSearchParam.set('maxprice', maxPrice);
window.location.search = maxPriceSearchParam.toString();
};
} catch (error) {
console.log(error);
}
} else {
setLoading(false);
}
};
const render = (item, index) => {
return (
<tr key={index}>
<td className='text-center'>
<div key={item.id}>
<img
src={`${item.pictures}`}
alt='picture'
className='rounded'
></img>
</div>
</td>
<td className='text-center'>
<div key={item.id}>
<h4>{item.descr}</h4>
<br></br>
<h6 className='text-left'>No of sqm: {item.sqm}m2</h6>
<div className='text-left'>
<small className='text-left'>
{' '}
<a href={item.link} target='_blank' rel='noopener noreferrer'>
Show on page
</a>
</small>
</div>
</div>
</td>
<td className='text-center'>
<div key={item.id}>
<h4>{item.price}</h4>
<small className='text-left'>Price per m2: {item.ppm2}</small>
</div>
</td>
<td className='text-center'>
<div key={item.id}>
<Link to={`/article/${item.id}`}>
<h4>Show</h4>
</Link>
</div>
</td>
</tr>
);
};
const capitalize = (str) => {
return str.charAt(0).toUpperCase() + str.slice(1);
};
const renderHeader = () => {
if (filterNameInURL) {
return (
<h4 className='text-center'>
Total {total} articles on
{capitalize(filterNameInURL)}
</h4>
);
} else {
return (
<h4 className='text-center'>
Total {total} articles in DB
</h4>
);
}
};
return (
<div>
<div className='container'>
{renderHeader()}
<Form onSubmit={handleFilter} ref={form}>
Filters <br></br> Max price:
<input
type='text'
className='form text-center'
placeholder='npr. 120000'
aria-describedby='basic-addon2'
value={maxPrice}
onChange={onChangeMaxPrice}
/>
<button className='btn btn-primary btn-block w-25' disabled={loading}>
{loading && (
<span className='spinner-border spinner-border-sm'></span>
)}
<span>Filter</span>
</button>
<CheckButton style={{ display: 'none' }} ref={checkBtn} />
</Form>
<div className='table-responsive'>
<table className='table'>
<thead className='thead-dark'>
<tr>
<th className='text-center' scope='col'>
Picture
</th>
<th className='text-center' scope='col'>
Description
</th>
<th className='text-center w-25' scope='col'>
Price
</th>
<th className='text-center' scope='col'>
Offer
</th>
</tr>
</thead>
<tbody>{content.map(render)}</tbody>
</table>
</div>
</div>
<div className='mb-5 text-center'>
<Pagination
totalPages={Math.ceil(total / 10)}
onPageChange={(e, d) => {
pageSearchParam.set('page', d.activePage);
window.location.search = pageSearchParam.toString();
}}
activePage={page}
/>
</div>
</div>
);
};
export default Dashboard;
You are changing state on onChange function. So it forces re-render on every time when you type a letter in input box.
Take a new state and put that in useEffect instead of maxPrice. Set this state on buttonPress.
useEffect(() => {
// all code here
}, [filterNameInURL, page, newState]);
const onButtonPress () => {
setNewState(maxPrice)
}
Following document will help you to understand it in more details.
useEffect doc

react not rerendering after state change

I know there have been similar questions, but I have a weird issue.
This is what I'm doing
import React, {useState} from 'react';
import './App.css';
import {Table, Button, InputGroup, FormControl} from 'react-bootstrap';
function App() {
const [pons, setPons] = useState();
const [translations, setTranslations] = useState([]);
const [isInEditMode, setIsInEditMode] = useState(false);
const [inputValue, setInputValue] = useState('samochod');
const [errors, setErrors] = useState([]);
const [translationsToSave, setTranslationsToSave] = useState([]);
const changeIsInEditMode = () => setIsInEditMode(!isInEditMode);
const handleEditButtonClick = (id) => console.log('Edit', id);
const handleDeleteButtonClick = (id) => console.log('Delete', id);
const handleInputChange = (e) => setInputValue(e.target.value);
const handleFetchOnButtonClick = async () => {
const resp = await fetch(`http://localhost:8080/pons/findTranslation/${inputValue}`).then(r => r.json()).catch(e => console.log(e));
if (resp.ok === true) {
setTranslations(resp.resp[0].hits);
setErrors([]);
} else {
setErrors(resp.errors ? resp.errors : ['Something went wrong. check the input']);
}
};
const handleSaveTranslations = async () => {
const resp = await fetch('localhost:8080/pons/', {method: 'POST', body: {content: translationsToSave}});
if (resp.ok === true) {
setInputValue('');
setTranslations(null);
}
};
return (
<div className="App">
{errors.length > 0 ? errors.map(e => <div key={e}>{e}</div>) : null}
<InputGroup className="mb-3">
<FormControl
value={inputValue}
onChange={handleInputChange}
placeholder={inputValue}
/>
</InputGroup>
<div className="mb-3">
<Button onClick={handleFetchOnButtonClick} disabled={inputValue === '' || errors.length > 0}>Translate</Button>
<Button onClick={changeIsInEditMode}>
{isInEditMode ? 'Exit edit mode' : 'Enter edit mode'}
</Button>
<Button disabled={translationsToSave.length === 0} onClick={handleSaveTranslations}>Save translations</Button>
</div>
<Table striped bordered hover>
<thead>
<tr>
<th>Original</th>
<th>Translation</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{translations ? translations.map(pon => pon.roms.map(rom => rom.arabs.map(arab => arab.translations.map(translation => {
const {source, target} = translation;
return (
<tr>
<td><span dangerouslySetInnerHTML={{__html: source}}/></td>
<td><span dangerouslySetInnerHTML={{__html: target}}/></td>
<td>
{
!translationsToSave.includes(target) ?
<Button onClick={() => {
const tmp = translationsToSave;
tmp.push(target);
setTranslationsToSave(tmp);
}}>
Add translation
</Button>
:
<Button
onClick={() => {
const tmp = translationsToSave;
tmp.splice(tmp.findIndex(elem => elem === target));
setTranslationsToSave(tmp);
}}>
Remove translation
</Button>
}
</td>
</tr>
)
})))) : (
<div>No translations</div>
)}
</tbody>
</Table>
</div>
);
}
export default App;
So it's a basic app, it right now just adds and removes from an array wit setTranslationsToSave. After I click the Add translation button the view stays the same. But it refreshes when I click Enter edit mode. Same with Remove translation. I need to click Enter/Exit edit mode.
Hitting Translate also reloads the view. So the Add/Remove translation buttons are the only ones which do not refresh the page. Why? What am I missing?
The issue is that you are mutating the satte in Add/Remove translation button, so when react check before re-rendering if the state updater was called with the same state it feels that nothing has changed as it does a reference check and ehnce doesn't trigger re-render
Also while updating current state based on previous state use functional callback approach for state updater.
Update your state like below
<Button onClick={() => {
setTranslationsToSave(prev => [...prev, target]);
}}>
Add translation
</Button>
:
<Button
onClick={() => {
setTranslationsToSave((prev) => {
const index = prev.findIndex(elem => elem === target)); return [...prev.slice(0, index), ...prev.slice(index + 1)]
});
}}>
Remove translation
</Button>
In your Add translation click handler, you're mutating the state:
<Button onClick={() => {
// tmp is just a reference to state
const tmp = translationsToSave;
// You are mutating state, this will be lost
tmp.push(target);
setTranslationsToSave(tmp);
}}>
You should duplicate the state and add the new element:
<Button onClick={() => {
setTranslationsToSave([...translationsToSave, target]);
}}>

Categories

Resources