React UI is not updating - reactjs

On every action I am pushing new array of object in the local storage. After that I am mapping over the localstorage data. Now the problem is the UI is not updating autometicaly on every new data inserted to the array of object. But I can see new data on every manual refresh.
Hare is the data I am inserting into the local storage:
const handleSubmit = (e) => {
e.preventDefault();
const input = e.target.input.value;
chatLog.push({ user: "Me", text: `${input}` });
localStorage.setItem("chatLog", JSON.stringify(chatLog));
}
Here I have mapped the data in anorther component:
const chatLog = JSON.parse(localStorage.getItem("chatLog"));
return (
<div>
{chatLog.map((message) => (
<div
className={`chat ${
message.user === "Me" ? "chat-end" : "chat-start"
}`}
>
<div className="chat-image avatar">
<div className="w-10 rounded-full">
<img
src={message.user === "Me" ? Me : Rakib}
alt={message.user === "Me" ? "user" : "rakib"}
/>
</div>
</div>
<div className="chat-header">{message.user}</div>
<div className="chat-bubble">{message.text}</div>
</div>
))}
</div>
);
Now I want that, Everytime the localStorage get new data. Then the UI will update autometicaly.

When new data is added to the localStorage, you need to use some state management solution like React's state or a third-party library like Redux.
One way to achieve this is to use React's state to store the chatLog data and update it whenever a new message is added to the localStorage. Example:
import { useState, useEffect } from "react";
function ChatLog() {
const [chatLog, setChatLog] = useState(JSON.parse(localStorage.getItem("chatLog")) || []);
useEffect(() => {
const handleStorageChange = (e) => {
if (e.key === "chatLog") {
setChatLog(JSON.parse(e.newValue));
}
};
window.addEventListener("storage", handleStorageChange);
return () => {
window.removeEventListener("storage", handleStorageChange);
};
}, []);
const handleSubmit = (e) => {
e.preventDefault();
const input = e.target.input.value;
const newMessage = { user: "Me", text: `${input}` };
chatLog.push(newMessage);
localStorage.setItem("chatLog", JSON.stringify(chatLog));
setChatLog([...chatLog, newMessage]);
e.target.reset();
};
return (
<div>
{chatLog.map((message, index) => (
<div
key={index}
className={`chat ${
message.user === "Me" ? "chat-end" : "chat-start"
}`}
>
<div className="chat-image avatar">
<div className="w-10 rounded-full">
<img
src={message.user === "Me" ? Me : Rakib}
alt={message.user === "Me" ? "user" : "rakib"}
/>
</div>
</div>
<div className="chat-header">{message.user}</div>
<div className="chat-bubble">{message.text}</div>
</div>
))}
<form onSubmit={handleSubmit}>
<input type="text" name="input" />
<button type="submit">Send</button>
</form>
</div>
);
}

Related

React array state is not Iterable

I am trying to figure out how to pass an item thru the state on the item: [] inside the list state. Whenever I tried this code, an error shows up as lists is not iterable whenever I insert or add item to the array
Is there a way to insert data to the array property of the state? And adding more string arrays in that property?
const [lists, setLists] = useState({
item: [],
});
const addList = () => {
const listItem = document.getElementById("listItem");
if (listItem.value !== "") {
setLists([
...lists,
{
item: listItem.value,
},
]); // >>> [INSERT TO THE ARRAY PROPERTY]
listItem.value = "";
}
};
return (
<div>
<form onSubmit={handleSubmit}>
<input
type="text"
id="listItem"
name="item"
onKeyPress={(e) => (e.key === "Enter" ? addList() : null)}
/>
<button
type="button"
onClick={() => {
addList();
}}
>
Add
</button>
<ul>
LIST
{lists.item.map((val, index) => {
return (
<li key={index}>
<p>{val}</p>
<button type="button" onClick={() => removeList(index)}>
Remove
</button>
</li>
);
})}
</ul>
<button type="submit">submit</button>
</form>
</div>
);
You seem to be having some confusion regarding your data types. lists is an array of objects of the shape {item: ...}.
The useState call should be useState([]).
You'll need lists.map(({item}, index) => (or lists.map(val and val.item) to get at the ....
You can use e.g. console.log(lists), or a debugger, to see what's really happening.)
You shouldn't use document.getElementById() with React, ever. Instead, make the input controlled (or have a ref to it and read the value if you want uncontrolled, but you likely don't).
The setLists call should be the functional form: setLists(lists => [...lists, {item: listItem.value}]).
All in all, something like
function Component() {
const [newItemText, setNewItemText] = React.useState("");
const [todoList, setTodoList] = React.useState([]);
const addList = (event) => {
event.preventDefault();
if (newItemText !== "") {
setTodoList(todoList => [
...todoList,
{
item: newItemText,
},
]);
setNewItemText("");
}
};
return (
<div>
<form onSubmit={addList}>
<input
type="text"
name="item"
value={newItemText}
onChange={e => setNewItemText(e.target.value)}
/>
<button
type="submit"
>
Add
</button>
</form>
<ul>
LIST
{todoList.map(({item}, index) => {
return (
<li key={index}>
<p>{item}</p>
<button type="button">
Remove
</button>
</li>
);
})}
</ul>
</div>
);
}
ReactDOM.render(<Component />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.0/umd/react-dom.production.min.js"></script>
<div id="root">
const [lists, setLists] = useState({
item: [],
});
In the code above you set initial value as an Object not an Array. Try to change code like below.
const [lists, setLists] = useState([]);

How i can empty my state after rendering the result?

i make a to do list and i have 2 problems.
The first one : When i write something on my input and i click on my button (addTask) i need to click 2 times to have the result on my console.log . How i can do to have the result directly whn i click the first time ?
The second one : Nothing is rendered on my .map but all my values are on my array todoList . Did i do something wrong ?
function Task() {
const [task, setTask] = useState("");
const [encours, setEncours] = useState("en cours");
const [todoList, setTodoList] = useState([]);
const switchEnCours = () => {
setEncours("terminé");
};
const deleteTask = () => {
setEncours("supprimée");
};
const handleInput = (e) => {
e.preventDefault();
setTask(e.target.value);
};
const AddTask = (e) => {
setTodoList([...todoList, task]);
console.log(todoList);
};
return (
<div>
<input onChange={handleInput}></input>
<button onClick={AddTask}>Valider</button>
<div className="DivColonne">
<div className="Colonne">
<h1>Tâche à faire</h1>
{todoList !== "" ? (
todoList.map((insertTask) => {
<div>
<p>{insertTask}</p>
<button onClick={switchEnCours}>{encours}</button>
</div>;
})
) : (
<div></div>
)}
</div>
<div className="Colonne">
<h1>Tâche en cours</h1>
{encours === "terminé" ? (
<div>
{todoList.map((insert) => {
return (
<div>
<p>{insert}</p>
<button onClick={deleteTask}>{encours}</button>
</div>
);
})}
</div>
) : (
<div></div>
)}
</div>
<div>
<h1>Tâches terminées</h1>
{encours === "supprimée" ? (
<div>
<p>{todoList}</p>
</div>
) : (
<div></div>
)}
</div>
</div>
</div>
);
}
export default Task;
React state updates are asynchronously processed. This means you cannot console log the state right after an update has been enqueued and expect to see the updated value. Use a useEffect hook with dependency on the state you are updating to log state updates.
useEffect(() => console.log(todoList), [todoList]);
You are also not returning JSX when you are mapping the todos.
{todoList.map((insertTask) => {
<div>
<p>{insertTask}</p>
<button onClick={switchEnCours}>{encours}</button>
</div>;
})}
Should be
{todoList.map((insertTask) => {
return (
<div>
<p>{insertTask}</p>
<button onClick={switchEnCours}>{encours}</button>
</div>
);
})}
or directly returned
{todoList.map((insertTask) => (
<div>
<p>{insertTask}</p>
<button onClick={switchEnCours}>{encours}</button>
</div>
))}
Don't forget that when mapping lists and arrays in React to use a React key. If your tasks don't have a unique property then I highly suggest adding a GUID to each todo when it's created.
Example:
{todoList.map((insertTask) => (
<div key={task.id}>
<p>{insertTask}</p>
<button onClick={switchEnCours}>{encours}</button>
</div>
))}
your 1st problem is that you show data in "console.log()" and the nature of console.log that it shows the previous value/data(state), it's better that you used "alert()" instead.
todoList is array you need iterate here check https://reactjs.org/docs/lists-and-keys.html
<div>
<h1>Tâches terminées</h1>
{encours === "supprimée" ? (
<div>
{todoList.map(value => <p>{value}</p>}
</div>
) : (
<div></div>
)}
</div>
About console.log, you printing results after you change state, state updating is async, so it won't happen instantly.
Can change to
const AddTask = (e) => {
const newList = [...todoList, task]
console.log(newList);
setTodoList(newList);
};
About second part, this condition is redundant {todoList !== "" ? ( todoList is an Array, if it's empty .map never get executed.
Can you comment out this and check {encours === "terminé" ? ( ?
Also can't print Array in jsx
<div>
// <p>{todoList}</p>
<p>{JSON.stringify(todoList, null, 2)</p> --< try this
</div>
There are bunch of wrong things you have done in your code.
1- Since the process of setting a state is Asynchronous, logging the todoList state right after the setTodoList won't give you the latest version of it.
To log the latest version of your state, you have to register an effect:
React.useEffect(() => console.log(todoList), [todoList])
2- It's better to use setTodoList(todoList => [...todoList, task]).
3- Since todoList is an array, checking todoList !== "" is redundant and it will always pass.
4- You missed to return the DOM Node here:
todoList.map((insertTask) => {
<div>
<p>{insertTask}</p>
<button onClick={switchEnCours}>{encours}</button>
</div>;
})
5- Also you missed to use a decent key prop on loops:
todoList.map((insertTask) => {
<div key={insertTask.toString()}>
<p>{insertTask}</p>
<button onClick={switchEnCours}>{encours}</button>
</div>;
})
Check out this article for more information on this matter.
And for your main question "How to empty your state" you have to use a controlled text input instead of uncontrolled one:
<input onChange={handleInput} value={task} />
Then to empty your state, all you have to do is:
const AddTask = (e) => {
setTodoList(state => [...state, task]);
setTask("");
};

React : press X button to delete image from screen

I'm a complete beginner in React and I have an app that returns a few pics from an API . I have inserted an 'X' on each pic to delete a pic if user clicks on 'X' . However I have no idea how to use React Hooks to delete a specific image.
My code :
function City(){
// custom func that returns the pics from the api and the loading state of my app
const {loading , pics ,query , setQuery} = useFetch('nature');
//I believe I need something like this but have no idea how to use it (initial state is all
my pics and deleteImage must delete a single pic)
const [images,deleteImage] = useState(pics);
return (<div className="city-info">
{
//if app is loading display loading else return all pics
!loading ?
(pics.length>0 && pics.map((pic) =>{
return <div className="info" key = {pic.id}>
<span className="close">
//I want to use onClick on this button to delete the specific image
<span className="inner-x">
×
</span>
</span>
<img src = {pic.src.original} alt ="img"/>
</div>
})
):<div> Loading </div>
}
</div>);
}
UPDATE : With the code below I remove the images from my list using hooks but they are not deleted from my picture :
function City(){
const {loading , pics ,query , setQuery} = useFetch('athens');
const [images , setImages] = useState([]);
const removeImage = (id) =>{
//images are inserted correctly cannot delete
setImages((oldState)=>oldState.filter((item)=> item.id !== id))
images.map((im)=>console.log(im.id)) //images are deleted from list not from screen
}
useEffect(()=>{
setImages(pics);
} , [pics])
return (<div className="city-info">
{
!loading ?
(pics.length>0 && pics.map((pic) =>{
return <div className="info" key = {pic.id}>
<span className="close" onClick= {()=>removeImage(pic.id)} >
<span
className="inner-x">
×
</span>
</span>
<img src = {pic.src.original} alt ="img"/>
</div>
})
):<div> Loading ... </div>
}
</div>);
}
export default City;
So this is a simple local state example on how to remove items inside of an array
import React, { useState, useEffect } from "react";
import "./styles.css";
const allImages = [
{
id: 1,
imgUrl: "https://via.placeholder.com/100"
},
{
id: 2,
imgUrl: "https://via.placeholder.com/100"
},
{
id: 3,
imgUrl: "https://via.placeholder.com/100"
}
];
const App = () => {
const [pics, setPics] = useState([]);
const removeImage = (id) => {
// this is the line that you are looking for
setPics((oldState) => oldState.filter((item) => item.id !== id));
};
useEffect(() => {
//fake fetch data
setPics(allImages);
}, []);
return (
<div className="App">
{pics.map((pic) => {
return (
<div style={{ marginBottom: "100px" }}>
{pic.id}
<img
src={pic.imgUrl}
width="100px"
height="100px"
alt="placeholder grey 100px"
/>
<button onClick={() => removeImage(pic.id)}>X</button>
</div>
);
})}
</div>
);
};
export default App;
sandbox link

Async/await React: objects are not valid, [object promise]

It's my first project working with React, and I'm fetching an API. I'm trying to pass on the data but it keeps giving me the same error however I do it.
Also, I can log the data in the async function addTrade, but it logs twice for some reason?
fetch:
async function asyncData() {
const response = await fetch('https://api.pokemontcg.io/v1/cards?setCode=base1');
const data = await response.json();
return data;
}
my render function:
const addTrade = async () => {
const pokemonList = await asyncData();
const cards = pokemonList.cards;
console.log(cards);
return <>
<div className='add-trade-container'>
<div className='trade-add-window'>
<div id={1} onClick={e => handleActiveTab(e)} className={tradeBoard.activeTab === '1' ? 'trade-tab trade-tab-active' : 'trade-tab'}>
<span className='trade-tab-title'>Has</span>
{tradeBoard.has.map(has =>
<>
<div className={tradeBoard.activeTab === '1' ? 'window window-active' : 'window'}>{has.content}</div>
</>
)}
</div>
<img className='divider' src='./src/assets/img/trade-icon.svg' width='30' height='30'></img>
<div id={2} onClick={e => handleActiveTab(e)} className={tradeBoard.activeTab === '2' ? 'trade-tab trade-tab-active' : 'trade-tab'}>
<span className='trade-tab-title'>Wants</span>
{tradeBoard.wants.map(wants =>
<>
<div className={tradeBoard.activeTab === '2' ? 'window window-active' : 'window'}>{wants.content}</div>
</>
)}
</div>
</div>
<article className='list'>
<label className='search-label' htmlFor='search'>Search a Pokémon
<input className='search-field' type='text' id='search'></input>
</label>
<div className='list-grid'>
{cards.map((card, index) =>
<div onClick={() => handleClickContent(<PokeItem pokemon={card} index={index}/>)} key={index}>
<PokeItem pokemon={card} index={index}/>
</div>
)}
</div>
</article>
<button onClick={handleSeedClick} className='button-add'>Add a trade</button>
</div>
</>;
};
App:
const App = () => {
return useObserver(() => (
<>
{addTrade()}
<div className='trades'>
{showTrades()}
</div>
</>
));
};
const roottype = document.getElementById('root');
const headtype = document.getElementById('header');
ReactDOM.render(<App />, roottype);
ReactDOM.render(<Header />, headtype);
you need to use react hooks for this
first you need to import useState, useCallback from react library like this import React, { useState, useCallback } from "react";
then use useEffect like this:
const addTrade = async () => {
const [cards,setCards] = useState(null)
useEffect(() => {
async function getData() {
try {
const pokemonList = await asyncData();
const cardsList = pokemonList.cards;
setCards(cardsList);
console.log(cardsList);
} catch (error) {
console.log(error);
}
}
getData();
}, [cards]);
return <>
<div className='add-trade-container'>
<div className='trade-add-window'>
<div id={1} onClick={e => handleActiveTab(e)} className={tradeBoard.activeTab === '1' ? 'trade-tab trade-tab-active' : 'trade-tab'}>
<span className='trade-tab-title'>Has</span>
{tradeBoard.has.map(has =>
<>
<div className={tradeBoard.activeTab === '1' ? 'window window-active' : 'window'}>{has.content}</div>
</>
)}
</div>
<img className='divider' src='./src/assets/img/trade-icon.svg' width='30' height='30'></img>
<div id={2} onClick={e => handleActiveTab(e)} className={tradeBoard.activeTab === '2' ? 'trade-tab trade-tab-active' : 'trade-tab'}>
<span className='trade-tab-title'>Wants</span>
{tradeBoard.wants.map(wants =>
<>
<div className={tradeBoard.activeTab === '2' ? 'window window-active' : 'window'}>{wants.content}</div>
</>
)}
</div>
</div>
<article className='list'>
<label className='search-label' htmlFor='search'>Search a Pokémon
<input className='search-field' type='text' id='search'></input>
</label>
<div className='list-grid'>
{cards.map((card, index) =>
<div onClick={() => handleClickContent(<PokeItem pokemon={card} index={index}/>)} key={index}>
<PokeItem pokemon={card} index={index}/>
</div>
)}
</div>
</article>
<button onClick={handleSeedClick} className='button-add'>Add a trade</button>
</div>
</>;
};

Reset pagination to the first page by clicking a button outside the component

I'm using material UI usePagination hook to create a custom pagination component, so far so good, the functionality works as expected but I was wondering how I can be able to reset the pagination to the first page by triggering a button that is not part of the pagination component.
Does anyone has an idea on how to trigger that?
This is my component.
import React from "react";
import PropTypes from "prop-types";
import { usePagination } from "hooks";
function arrow(type) {
return (
<i
className={`fa fa-chevron-${
type === "next" ? "right" : "left"
} page-icon`}
/>
);
}
function Pagination({ data, itemCount, onChange }) {
const { items } = usePagination({
count: Math.ceil(data.length / itemCount, 10),
onChange
});
return (
<nav aria-label="Paginator">
<ul className="pagination-component">
{items.map(({ page, type, selected, ...item }, index) => {
let children;
if (type === "start-ellipsis" || type === "end-ellipsis") {
children = "…";
} else if (type === "page") {
children = (
<button
type="button"
automation-tag={`page-${page}`}
className={`page-button ${selected ? "selected" : ""}`}
{...item}
>
{page}
</button>
);
} else {
children = (
<button
automation-tag={type}
className="page-button"
type="button"
{...item}
>
<span className="d-none">{type}</span>
{arrow(type)}
</button>
);
}
return (
// eslint-disable-next-line react/no-array-index-key
<li key={index} className="page-item">
{children}
</li>
);
})}
</ul>
</nav>
);
}
What I'm trying is to create a select component that the onChange function will sort the data, depending on the selection, but when the data is sorted I want to return the pagination component to the first page
const TableVizContainer = props => {
const [currentPage, setCurrentPage] = useState(1);
const [sortColumn, setSortColumn] = useState(1);
const [range, setRange] = useState({
start: 0,
end: 25
});
const onChangePage = (_event, page) => {
setCurrentPage(page);
setRange({
start: 25 * (page - 1),
end: 25 * page
});
};
const onSelectChange = event => {
const { value } = event.target;
setCurrentPage(1);
setSortColumn(parseInt(value, 10));
};
return (
<div
className="table-viz-container container-fluid my-4 float-left"
automation-tag={`table-viz-${automationId}`}
>
<div className="d-flex justify-content-between mb-3 leaderboard-meta">
<span className="leaderboard-title">{visualization.title}</span>
<div className="mr-5">
<label htmlFor="sort-table-select">
Sort By:
<select
id="sort-table-select"
onChange={onSelectChange}
value={sortColumn}
>
{visualization.columns.map((column, index) => {
const uniqueId = uuidv1();
return (
<option key={uniqueId} value={index}>
{setSelectValue(column, visualization.metrics)}
</option>
);
})}
</select>
</label>
</div>
</div>
<div className="d-block d-sm-flex justify-content-between align-items-center my-2 px-2">
<span className="page-items-count" automation-tag="pagination-count">
{`Showing ${range.start === 0 ? 1 : range.start + 1} - ${
range.end <= visualization.rows.length
? range.end
: visualization.rows.length
} of ${visualization.rows.length}.`}
</span>
<Pagination
currentPage={currentPage}
data={visualization.rows}
itemCount={25}
onChange={onChangePage}
/>
</div>
</div>
);
};
Does anyone has an idea on how to reset and move the pagination page to the first one without clicking the component?
There are two ways.
1. Passing Props
Let's just say you have a function called jump() and passing 1 as an argument will reset the pagination. So, you can pass the jump function as a property and reuse that on other components.
function jump(){
setCurrentPage(1)
}
<MyCompnent resetPage={jump} />
// MyComponent
function MyComponent({resetPage}){
return (
<button onClick={resetPage(1)}></button>
)
}
2. On Changing Route
You can reset your pagination when your route will change. For example, you are using a router npm package and that package has a method called onChange or routeChangeStart. With those methods or after creating that method you can implement a function like below.
Router.events.on("routeChangeStart", () => {
jump(1);
});

Resources