Updating displayed data on mouse enter - reactjs

I would like to update text which is displayed inside a <div> element. I would love to do it when the cursor enters the <div> element.
Basically I'm fetching some data from the API and I display only one parameter (name). If a user enters the <div> with the name displayed I would like to show some details, i.e. description and price.
This is my code which I tried to complete my task.
import {useEffect, useState} from "react";
import requestOptionsGet from "../utilities/requestOptions";
import validateResponse from "../utilities/validators";
const Warehouse = () => {
const [items, setItems] = useState([]);
const [texts, setTexts] = useState([]);
const getItems = async () => {
const url = "http://localhost:8000/api/items/"
return await fetch(url, requestOptionsGet)
.then((response) => validateResponse(response, url))
.then((response) => response.json())
.then((data) => setItems(data))
};
useEffect(() => {
getItems();
}, []);
useEffect(() => {
setTexts(items.map((item) => (
{
id: item.id,
name: item.name,
description: item.description,
price: item.price,
currentDisplay: <h2>{item.name}</h2>,
})
))
}, [items]);
const displayName = (data) => {
console.log(
"displayName"
);
};
const displayDetails = (data) => {
const itemID = parseInt(data.currentTarget.getAttribute("data-item"));
const displayInfo = texts.find(text => text.id === itemID);
displayInfo.currentDisplay = <p>{displayInfo.description}</p>
setTexts(texts);
console.log(texts);
console.log(
"displayDetails"
);
return(
displayInfo.currentDisplay
);
};
return(
<div className="container">
<h1>Your warehouse.</h1>
<h2>All your items are listed here.</h2>
<hr />
{texts.map((text) => (
<button className="container-for-single-item" id={text.id} key={text.id}
onMouseEnter={displayDetails} onMouseLeave={displayName} data-item={text.id}>
{text.currentDisplay}
</button>
))}
</div>
);
}
export default Warehouse;
The functions work (everything is displayed in the console as it should be) and even the texts change. However the paragraph does not appear. How can I fix my code? Thanks!

Never modify state directly
const newTexts = texts.map(text => text.id === itemID ? { ...text, currentDisplay: <p>{text.description}</p> } : text);
setTexts(newTexts);

Related

data rendering issue after button is clicked in react

I am having a data rendering issue in react. Somehow, data is not automatically updated after it's updated in the server side. I can't put all the code in here, cuz the code is kind of lengthy. so i pasted/renamed some variables. Even if some variables are missing, please understand. Basically, I have a button on the page and when the button is clicked, the status changes to 'UPLOADING' and the function checkIfDataExists is called to fetch data from the server side and data should be automatically updated without page refresh, but when I test this, data is successfully retrieved from the server side, but the updated data is not rendered. I see 'successful...' on the Console. Is there anything wrong?
const Settings: React.FC<IProps> = props => {
const { orgId } = props
const password = 'dummy'
const { data } = httpCall(`/${orgId}/${userId}/settings`)
return (
<div>
{data && <SettingsForm data={data} password={password} {...props} />}
</div>
)
}
const SettingsForm: React.FC<Settings & IProps> = ({
data,
password
}) => {
const [status, setStatus] = useState<'ERROR' | 'DONE' | 'UPLOADING'>()
const service = getServiceInstance(data.organizationId)
function checkIfDataExists(user: any) {
return () => {
httpCall
.getClient(user.id)
.then(value => {
console.log('successful...')
data.modeUsername = value.modeUsername
data.modePassword = value.modePassword
})
.catch(() => {
setStatus('ERROR')
})
}
}
useEffect(() => {
if (!status) return
switch (status) {
case 'UPLOADING': {
const timer = setInterval(
checkIfDataExists({ id: data.id }),
2000
)
return () => clearInterval(timer)
}
}
}, [status, client
])
<div className="info-section">
<p className="detail">Username</p>
<p>{data.modeUsername}</p>
</div>
<div className="info-section">
<p className="detail">Password</p>
<p>{data.modePassword}</p>
</div>
The problem I see is that after you setInterval an API you didn't set in the state to trigger the component to rerender. You don't need to be explicit to define resData to data because if you define data already useState already it types.
const SettingsForm: React.FC<Settings & IProps> = ({
data,
password
}) => {
const [resdata,setResData] = useState(data)
const [status, setStatus] = useState<'ERROR' | 'DONE' | 'UPLOADING'>()
const service = getServiceInstance(data.organizationId)
function checkIfDataExists(user: any) {
return () => {
httpCall
.getClient(user.id)
.then(value => {
console.log('successful...')
setResData({
modeUsername: value.modeUsername,
modePassword: value.modePassword,
})
// data.modeUsername = value.modeUsername
// data.modePassword = value.modePassword
})
.catch(() => {
setStatus('ERROR')
})
}
}
useEffect(() => {
if (!status) return
switch (status) {
case 'UPLOADING': {
const timer = setInterval(
checkIfDataExists({ id: data.id }),
2000
)
return () => clearInterval(timer)
}
}
}, [status, client
])
<div className="info-section">
<p className="detail">Username</p>
<p>{resdata.modeUsername}</p>
</div>
<div className="info-section">
<p className="detail">Password</p>
<p>{resdata.modePassword}</p>
</div>

Text field should only change for one value and not over the entire list

I have a list and this list has several elements and I iterate over the list. For each list I display two buttons and an input field.
Now I have the following problem: as soon as I write something in a text field, the same value is also entered in the other text fields. However, I only want to change a value in one text field, so the others should not receive this value.
How can I make it so that one text field is for one element and when I write something in this text field, it is not for all the other elements as well?
import React, { useState, useEffect } from 'react'
import axios from 'axios'
function Training({ teamid }) {
const [isTrainingExisting, setIsTrainingExisting] = useState(false);
const [trainingData, setTrainingData] = useState([]);
const [addTraining, setAddTraining] = useState(false);
const [day, setDay] = useState('');
const [from, setFrom] = useState('');
const [until, setUntil] = useState('');
const getTrainingData = () => {
axios
.get(`${process.env.REACT_APP_API_URL}/team/team_training-${teamid}`,
)
.then((res) => {
if (res.status === 200) {
if (typeof res.data !== 'undefined' && res.data.length > 0) {
// the array is defined and has at least one element
setIsTrainingExisting(true)
setTrainingData(res.data)
}
else {
setIsTrainingExisting(false)
}
}
})
.catch((error) => {
//console.log(error);
});
}
useEffect(() => {
getTrainingData();
}, []);
const deleteTraining = (id) => {
axios
.delete(`${process.env.REACT_APP_API_URL}/team/delete/team_training-${teamid}`,
{ data: { trainingsid: `${id}` } })
.then((res) => {
if (res.status === 200) {
var myArray = trainingData.filter(function (obj) {
return obj.trainingsid !== id;
});
//console.log(myArray)
setTrainingData(() => [...myArray]);
}
})
.catch((error) => {
console.log(error);
});
}
const addNewTraining = () => {
setAddTraining(true);
}
const addTrainingNew = () => {
axios
.post(`${process.env.REACT_APP_API_URL}/team/add/team_training-${teamid}`,
{ von: `${from}`, bis: `${until}`, tag: `${day}` })
.then((res) => {
if (res.status === 200) {
setAddTraining(false)
const newTraining = {
trainingsid: res.data,
mannschaftsid: teamid,
von: `${from}`,
bis: `${until}`,
tag: `${day}`
}
setTrainingData(() => [...trainingData, newTraining]);
//console.log(trainingData)
}
})
.catch((error) => {
console.log(error);
});
}
const [editing, setEditing] = useState(null);
const editingTraining = (id) => {
//console.log(id)
setEditing(id);
};
const updateTraining = (trainingsid) => {
}
return (
<div>
{trainingData.map((d, i) => (
<div key={i}>
Trainingszeiten
<input class="input is-normal" type="text" key={ d.trainingsid } value={day} placeholder="Wochentag" onChange={event => setDay(event.target.value)} readOnly={false}></input>
{d.tag} - {d.von} bis {d.bis} Uhr
<button className="button is-danger" onClick={() => deleteTraining(d.trainingsid)}>Löschen</button>
{editing === d.trainingsid ? (
<button className="button is-success" onClick={() => { editingTraining(null); updateTraining(d.trainingsid); }}>Save</button>
) : (
<button className="button is-info" onClick={() => editingTraining(d.trainingsid)}>Edit</button>
)}
<br />
</div>
))}
)
}
export default Training
The reason you see all fields changing is because when you build the input elements while using .map you are probably assigning the same onChange event and using the same state value to provide the value for the input element.
You should correctly manage this information and isolate the elements from their handlers. There are several ways to efficiently manage this with help of either useReducer or some other paradigm of your choice. I will provide a simple example showing the issue vs no issue with a controlled approach,
This is what I suspect you are doing, and this will show the issue. AS you can see, here I use the val to set the value of <input/> and that happens repeatedly for both the items for which we are building the elements,
const dataSource = [{id: '1', value: 'val1'}, {id: '2', value: 'val2'}]
export default function App() {
const [val, setVal]= useState('');
const onTextChange = (event) => {
setVal(event.target.value);
}
return (
<div className="App">
{dataSource.map(x => {
return (
<div key={x.id}>
<input type="text" value={val} onChange={onTextChange}/>
</div>
)
})}
</div>
);
}
This is how you would go about it.
export default function App() {
const [data, setData]= useState(dataSource);
const onTextChange = (event) => {
const id = String(event.target.dataset.id);
const val = String(event.target.value);
const match = data.find(x => x.id === id);
const updatedItem = {...match, value: val};
if(match && val){
const updatedArrayData = [...data.filter(x => x.id !== id), updatedItem];
const sortedData = updatedArrayData.sort((a, b) => Number(a.id) - Number(b.id));
console.log(sortedData);
setData(sortedData); // sorting to retain order of elements or else they will jump around
}
}
return (
<div className="App">
{data.map(x => {
return (
<div key={x.id}>
<input data-id={x.id} type="text" value={x.value} onChange={onTextChange}/>
</div>
)
})}
</div>
);
}
What im doing here is, finding a way to map an element to its own with the help of an identifier. I have used the data-id attribute for it. I use this value again in the callback to identify the match, update it correctly and update the state again so the re render shows correct values.

One variable is not rendered on page

I have a NewBook Form in which i enter data, like this:
const meetupData = {
title: enteredTitle,
image_url: enteredImage,
description: enteredDescription,
author: enteredAuthor,
genre: enteredGenre,
release_date: enteredRelease_date,
};
Later on i am fetch the data, and render it on page where all books shoudl be. It all works, but I also want to render one value. The rate of the book. The difference is that I dont specify the rate of the book in NewBookForm. I am doing it after the book was added in a special component using just a simple:
const ratingHandler1 = () => {
axios.post(`http://localhost:5000/api/book/${props.id}/rate`, { score: 3 });
};
on
<button onClick={ratingHandler1}>Rate 3 </button>
It all works and the data is added to Database ( postgres). But when I am trying to render it on page as i do with other values:
<h3>Title: {props.title}</h3>
<p>Author: {props.author}</p>
......................
<p>Gatunek: {props.score}</p>
The score is not shown. I assume that this is because I dont fetch data after i Add a Book, and i want to change it, but I don't know how.
If anyone could help me that would be awesome.
The code which fetch data, and later on directs it to the BooksList:
import { useState, useEffect } from "react";
import BookList from "../components/meetups/BookList";
function AllBooksPage() {
const [isLoading, setIsLoading] = useState(true);
let [loadedBooks, setloadedBooks] = useState([]);
useEffect(() => {
setIsLoading(true);
fetch("http://localhost:5000/api/book")
.then((response) => {
return response.json();
})
.then((data) => {
const books = [];
for (const key in data) {
const book = {
id: key,
...data[key],
};
books.push(book);
}
setIsLoading(false);
setloadedBooks(books);
});
}, []);
let [data, setData] = useState([]);
const [sortType, setSortType] = useState("title");
useEffect(() => {
const sortArray = (type) => {
const types = {
title: "title",
release_date: "release_date",
score: "score",
};
const sortProperty = types[type];
const sorted = [...loadedBooks].sort((a, b) =>
("" + a[sortProperty]).localeCompare("" + b[sortProperty])
);
setData(sorted);
};
sortArray(sortType);
}, [loadedBooks, sortType]);
if (isLoading) {
return (
<section>
<p>Loading...</p>
</section>
);
}
return (
<section>
<h1>All Books</h1>
<select defaultValue="Sort" onChange={(e) => setSortType(e.target.value)}>
<option disabled value="Sort">
Sortuj
</option>
<option value="title">Alfabetycznie</option>
<option value="release_date">wg. daty</option>
<option value="score">wg. score</option>
</select>
<BookList books={data} />
</section>
);
}
export default AllBooksPage;
Of course everything is added to BookItem.
import BookItem from "./BookItem";
import classes from "./BookList.module.css";
function BookList(props) {
return (
<div className={classes.list}>
{props.books.map((book) => (
<BookItem
key={book.id}
author={book.author}
id={book.id}
image_url={book.image_url}
title={book.title}
release_date={book.release_date}
description={book.description}
genre={book.genre}
score={book.score}
rating_sum={book.rating_sum}
rating_count={book.rating_count}
/>
))}
</div>
);
}
export default BookList;

Set value to textfield with hooks and redux material ui

I'm building an app using react, redux, and redux-saga.
The situation is that I'm getting information from an API. In this case, I'm getting the information about a movie, and I will update this information using a basic form.
What I would like to have in my text fields is the value from the object of the movie that I'm calling form the DB.
This is a brief part of my code:
Im using 'name' as an example.
Parent component:
const MovieForm = (props) => {
const {
movie,
} = props;
const [name, setName] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
onSubmit({
name,
});
};
const handleSetValues = () => {
console.log('hi');
console.log(movie, name);
setName(movie.name);
setValues(true);
};
useEffect(() => {
if (movie && values === false) {
handleSetValues();
}
});
return (
<Container>
<TextField
required
**defaultValue={() => {
console.log(movie, name);
return movie ? movie.name : name;
}}**
label='Movie Title'
onChange={(e) => setName(e.target.value)}
/>
</Container>
);
};
export default MovieForm;
....
child component
const MovieUpdate = (props) => {
const { history } = props;
const { id } = props.match.params;
const dispatch = useDispatch();
const loading = useSelector((state) => _.get(state, 'MovieUpdate.loading'));
const created = useSelector((state) => _.get(state, 'MovieUpdate.created'));
const loadingFetch = useSelector((state) =>
_.get(state, 'MovieById.loading')
);
const movie = useSelector((state) => _.get(state, 'MovieById.results'));
useEffect(() => {
if (loading === false && created === true) {
dispatch({
type: MOVIE_UPDATE_RESET,
});
}
if (loadingFetch === false && movie === null) {
dispatch({
type: MOVIE_GET_BY_ID_STARTED,
payload: id,
});
}
});
const updateMovie = (_movie) => {
const _id = id;
const obj = {
id: _id,
name: _movie.name,
}
console.log(obj);
dispatch({
type: MOVIE_UPDATE_STARTED,
payload: obj,
});
};
return (
<div>
<MovieForm
title='Update a movie'
buttonTitle='update'
movie={movie}
onCancel={() => history.push('/app/movies/list')}
onSubmit={updateMovie}
/>
</div>
);
};
export default MovieUpdate;
Then, the actual problem is that when I use the default prop on the text field the information appears without any problem, but if i use defaultValue it is empty.
Ok, I kind of got the answer, I read somewhere that the defaultValue can't be used int the rendering.
So I cheat in a way, I set the properties multiline and row={1} (according material-ui documentation) and I was able to edit this field an receive a value to display it in the textfield

Reactjs- How to fix after posting a new note via axios to the server, the render displays nothing?

I am learning to alter data in the backend, json server. The data gets posted if I refresh the server but the render of the new list disappears from the screen after the submit button. Why did the displaying of the notes stop after I submitted a new note ?
const Note = ({note, toggleImportance}) => {
const label = note.important ? 'make not important' : 'make important'
return(
<li >
{note.content}
<button onClick = {toggleImportance}>{label}</button>
</li>
)
}
export default Note
const App = () =>{
const [notes,setNotes] = useState([])
const [newNote, setNewNote] = useState('')
const [showAll, setShowAll] = useState(true)
useEffect(() => {
console.log('effect')
axios
.get('http://localhost:3002/notes')
.then(response => {
console.log('promise fulfilled')
setNotes(response.data)
})
},[])
console.log('render', notes.length, 'notes')
const toggleImportanceOf = id => {
const url = `http://localhost:3002/notes/${id}`
const note = notes.find(n => n.id === id)
const changedNote = {...note, important : !note.important}
axios
.put(url,changedNote)
.then(response => {
setNotes(notes.map(note => note.id !== id ? note : response.data))
})
}
const addNote =(event) =>{
event.preventDefault()
const newObject = {
content: newNote,
date: new Date().toISOString(),
important: Math.random() > 0.5
}
axios
.post('http://localhost:3002/notes', newObject)
.then(response => {
setNotes(notes.concat(response.data))
setNewNote('')
})
}
const handleNoteChange = (event) => {
setNewNote(event.target.value)
}
const notesToShow = showAll ? notes : notes.filter(note => note.important)
const rows = () => {
notesToShow.map(note =>
<Note
key={note.id}
note={note}
toggleImportance = {() => toggleImportanceOf(note.id)}
/>)
}
return(
<div>
<h1>Notes</h1>
<div>
<button onClick = {() => setShowAll(!showAll)}>
show{showAll ? ' important' : ' all'}
</button>
</div>
<ul>
{rows()}
</ul>
<form onSubmit={addNote}>
<input value={newNote} onChange={handleNoteChange}/>
<button type="submit">save</button>
</form>
</div>
)
}
export default App;
How do I fix this ? I want the new notes list to appeaar on the screen but it doesn't.
I forgot to enclose <Note /> in the rows() function with return()

Resources