React does not rerender instantly using setState - javascript

I have this issue that I'm trying to fix, I have this piece of code that allows me to create a new blog:
import React, { useState, useEffect, useRef } from 'react'
import Blog from './components/Blog'
import Notification from './components/Notification'
import LoginForm from './components/LoginForm'
import BlogForm from './components/BlogForm'
import Togglable from './components/Togglabe'
import blogService from './services/blogs'
import loginService from './services/login'
import './App.css'
const App = () => {
const [blogs, setBlogs] = useState([])
const [user, setUser] = useState(null)
const [errorMessage, setErrorMessage] = useState(null)
const [showAll, setShowAll] = useState(true)
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
const blogFormRef = useRef()
useEffect(() => {
blogService
.getAll()
.then(initialBlogs => {
setBlogs(initialBlogs)
})
}, [])
// ----- Add blog function ------
const addBlog = async (blogObject) => {
blogFormRef.current.toggleVisibility()
const toAdd = blogService.createBlog(blogObject)
const returned = await toAdd
setBlogs(blogs.concat(returned))
}
// ----- Update Blog -----
const updateBlog = async (id) => {
const blog = blogs.find(blog => blog.id === id)
const toUpdate = { ...blog, likes: blog.likes + 1 }
const updateo = blogService.update(id, toUpdate)
const returned = await updateo
setBlogs(blogs.map(blog => blog.id !== id ? blog : returned))
}
// ----- Delete Blog -----
const delBlog = async (id) => {
const blogToDelete = blogService.deleteBlog(id)
const deleted = await blogToDelete
return deleted
}
// ----- Forms -------
const blogForm = () => (
<Togglable buttonLabel='new blog' ref={blogFormRef}>
<BlogForm
createBlog={addBlog}
/>
</Togglable>
)
return (
<div className="App">
<h2>Blogs</h2>
<Notification message={errorMessage}/>
{user === null ?
loginForm() :
<div>
<p>
{user.name} logged-in
<button onClick={(() => logOut())}>Log out</button>
</p>
{blogForm()}
</div>
}
<div>
<button onClick={() => setShowAll(!showAll)}>
{showAll ? 'hide' : 'show'}
</button>
</div>
<ul>
{showAll === true ?
blogs.map((blog, i) =>
<Blog
key={i}
blog={blog}
updateBlog={updateBlog}
delBlog={delBlog} />
)
: null
}
</ul>
</div>
)
}
export default App
and this other part is the form of the app that allows you to add a new blog
import React, { useState } from 'react'
const BlogForm = ({ createBlog }) => {
const [newTitle, setNewTitle] = useState('')
const [newAuthor, setNewAuthor] = useState('')
const [newContent, setNewContent] = useState('')
const handleTitle = (event) => {
setNewTitle(event.target.value)
}
const handleAuthor = (event) => {
setNewAuthor(event.target.value)
}
const handleContent = (event) => {
setNewContent(event.target.value)
}
const addBlog = (event) => {
event.preventDefault()
createBlog({
title: newTitle,
author: newAuthor,
content: newContent,
likes: 0
})
setNewTitle('')
setNewAuthor('')
setNewContent('')
}
return (
<div className="formDiv">
<form onSubmit={addBlog}>
<div>
<div>
Title:
<input
id="title"
type="text"
value={newTitle}
onChange={handleTitle}
/>
</div>
<div>
Author:
<input
type="text"
value={newAuthor}
onChange={handleAuthor}
/>
</div>
<div>
Content:
<input
type="text"
value={newContent}
onChange={handleContent}
/>
</div>
<button type="submit">Add</button>
</div>
</form>
</div>
)
}
export default BlogForm
So the problem is that I'm trying to add a new blog but it doesn't rerender the component and it doesn't show the new blog instantly. I have to refresh the page and then it appears. The funny thing is that it works with the updateBlog component which allows you to give a "LIKE" to some blog, but it doesn't react instantly with the AddBlog component.

Related

While rendering a component it is showing an error- "Cannot update a component (`App`) while rendering a different component (`EventList`). "

I Can't render my events. Its showing this error -
"Cannot update a component (App) while rendering a different component (EventList). To locate the bad setState() call inside EventList, follow the stack trace as described in https://reactjs.org/link/setstate-in-render"
Here is EventList Component code -
import { useEffect, useState } from "react";
import EventList from "../../event-list";
import EventForm from "../event-form";
const EventAction = ({
getEventsByClockID,
addEvent,
updateEvent,
clockID,
deleteEvent,
deleteEventsByClockID,
}) => {
const [isCreate, setIsCreate] = useState(false);
const [isToggle, setIsToggle] = useState(false);
const [eventState, setEventState] = useState(null)
const handleCreate = () => {
setIsCreate(!isCreate);
}
useEffect(() => {
setEventState(getEventsByClockID(clockID, true));
}, [isToggle])
const handleToggle = () => {
setIsToggle(!isToggle);
}
return (
<div>
<div>
<button onClick={handleCreate}>Create Event</button>
<button onClick={handleToggle}>Toggle Events</button>
</div>
{isCreate && (
<>
<h3>Create Event</h3>
<EventForm
clockID={clockID}
handleEvent={addEvent}
/>
</>
)}
{isToggle && (
<>
<h3>Events of this clock</h3>
<EventList
clockID={clockID}
eventState={eventState}
deleteEvent={deleteEvent}
updateEvent={updateEvent}
deleteEventsByClockID={deleteEventsByClockID}
/>
</>
)}
</div>
)
}
export default EventAction;
Here is my App Component Code -
import ClockList from "./components/clock-list";
import LocalClock from "./components/local-clock";
import useApp from "./hooks/useApp";
import { localClockInitState } from "./initialStates/clockInitState";
const App = () => {
const {
localClock,
clocks,
updateLocalClock,
createClock,
updateClock,
deleteClock,
getEventsByClockID,
addEvent,
deleteEvent,
updateEvent,
deleteEventsByClockID,
} = useApp(localClockInitState);
return (
<div>
<LocalClock
clock={localClock}
updateClock={updateLocalClock}
createClock={createClock}
/>
<ClockList
clocks={clocks}
localClock={localClock.date}
updateClock={updateClock}
deleteClock={deleteClock}
getEventsByClockID={getEventsByClockID}
addEvent={addEvent}
deleteEvent={deleteEvent}
updateEvent={updateEvent}
deleteEventsByClockID={deleteEventsByClockID}
/>
</div>
)
}
export default App;
and Here is my useApp hook -
import { useState } from "react";
import deepClone from "../utils/deepClone";
import generateID from "../utils/generateId";
import useEvents from "./useEvents";
const getID = generateID('clock');
const useApp = (initValue) => {
const [localClock, setLocalClock] = useState(deepClone(initValue));
const [clocks, setClocks] = useState([]);
const {
// events,
// getEvents,
getEventsByClockID,
addEvent,
deleteEvent,
deleteEventsByClockID,
updateEvent,
} = useEvents();
const updateLocalClock = (data) => {
setLocalClock({
...localClock,
...data,
})
}
const createClock = (clock) => {
clock.id = getID.next().value;
setClocks((prev) => ([
...prev, clock
]))
}
const updateClock = (updatedClock) => {
setClocks(clocks.map(clock => {
if(clock.id === updatedClock.id) return updatedClock;
return clock;
}));
}
const deleteClock = (id) => {
setClocks(clocks.filter(clock => clock.id !== id));
}
return {
localClock,
clocks,
updateLocalClock,
createClock,
updateClock,
deleteClock,
getEventsByClockID,
addEvent,
deleteEvent,
updateEvent,
deleteEventsByClockID,
}
}
export default useApp;
I want to show all events incorporated with each individual clock.

Pagination with React doesn't work, all items are still displayed on screen

I have a pagination made with a react-paginate package called react-paginate here is the link to the doc. https://www.npmjs.com/package/react-paginate
I have implemented it in my App which is a notes diary, the user creates notes and these are dynamically saved in the localStorage and displayed on screen, well, I have established that there are 6 notes per page, that is, when there is a seventh note, it should not be displayed unless the user goes to page 2, when there are 13 notes page 3 and so ...
The functionality of my component that I have called Pagination works correctly, it is dynamic, I have right now testing 13 notes, so it shows me 3 pages, if I had 12, it would show me 2.
The problem is that although my pagination is correct, the 13 notes are being shown on the screen, when it should be 6 - 6 - 1.
I leave you the code to see if we can find the error, greetings and thanks in advance.
The prop that Pagination receives called data, are basically the notes that are created dynamically in App.js. const [notes, setNotes] = useState([]);
Component Pagination
import React, { useEffect, useState } from 'react'
import ReactPaginate from 'react-paginate';
import '../styles/Pagination.css';
const Pagination = (props) => {
const { data } = props;
// We start with an empty list of items.
const [currentItems, setCurrentItems] = useState([]);
const [pageCount, setPageCount] = useState(0);
// Here we use item offsets; we could also use page offsets
// following the API or data you're working with.
const [itemOffset, setItemOffset] = useState(0);
const itemsPerPage = 6;
useEffect(() => {
// Fetch items from another resources.
const endOffset = itemOffset + itemsPerPage;
console.log(`Loading items from ${itemOffset} to ${endOffset}`);
setCurrentItems(data.slice(itemOffset, endOffset));
setPageCount(Math.ceil(data.length / itemsPerPage));
}, [itemOffset, itemsPerPage, data]);
// Invoke when user click to request another page.
const handlePageClick = (event) => {
const newOffset = (event.selected * itemsPerPage) % data.length;
console.log(
`User requested page number ${event.selected}, which is offset ${newOffset}`
);
setItemOffset(newOffset);
};
return (
<>
<ReactPaginate
breakLabel="..."
nextLabel="next >"
onPageChange={handlePageClick}
pageRangeDisplayed={3}
pageCount={pageCount}
previousLabel="< previous"
renderOnZeroPageCount={null}
containerClassName="pagination"
pageLinkClassName="page-num"
previousLinkClassName="page-num"
nextLinkClassName="page-num"
activeLinkClassName="activee boxx"
/>
</>
);
}
export default Pagination;
Component App
import { useState, useEffect } from "react";
import { nanoid } from 'nanoid';
import NoteList from "./components/NoteList";
import './App.css';
import Search from "./components/Search";
import Header from "./components/Header";
import Pagination from "./components/Pagination";
const App = () => {
const [notes, setNotes] = useState([]);
const [searchText, setSearchText] = useState('');
const [darkMode, setDarkMode] = useState(false);
//Se encarga de mostrar la nota para escribir
const [showNote, setShowNote] = useState(true); //eslint-disable-line
useEffect(() => {
const saveNotes = JSON.parse(localStorage.getItem('notes-data'));
if (saveNotes){
setNotes(saveNotes);
}
}, []);
useEffect(() => {
localStorage.setItem('notes-data', JSON.stringify(notes))
},[notes])
const addNote = (inputText, text) => {
const date = new Date();
const newNote = {
id: nanoid(),
title: inputText,
text: text,
date: date.toLocaleString()
}
const newNotes = [newNote, ...notes];
setNotes(newNotes)
}
const deleteNote = (id) => {
var response = window.confirm("Are you sure that you want to remove the note?");
if (response){
const notesUpdated = notes.filter((note) => note.id !== id)
setNotes(notesUpdated);
}
}
return (
<div className={darkMode ? 'dark-mode' : ''}>
<div className="container">
<Header
handleToggleTheme={setDarkMode}
/>
<Search
handleSearchNote={setSearchText}
setShowNote={setShowNote}
/>
<NoteList
notes={notes.filter((noteText) =>
noteText.title.toLowerCase().includes(searchText)
)}
handleAddNote={addNote}
handleDeleteNote={deleteNote}
/>
<Pagination data={notes}/>
</div>
</div>
)
}
export default App;
The problem is you are not using currentItems and the paginated data is stored in that state.
Codesandbox: https://codesandbox.io/s/sweet-keldysh-2u72vd
Pagination.js
import React, { useEffect, useState } from 'react'
import ReactPaginate from 'react-paginate';
import NoteList from "./components/NoteList";
import '../styles/Pagination.css';
const Pagination = (props) => {
const { data, searchText, handleAddNote, handleDeleteNote } = props;
// We start with an empty list of items.
const [currentItems, setCurrentItems] = useState([]);
const [pageCount, setPageCount] = useState(0);
// Here we use item offsets; we could also use page offsets
// following the API or data you're working with.
const [itemOffset, setItemOffset] = useState(0);
const itemsPerPage = 6;
useEffect(() => {
// Fetch items from another resources.
const endOffset = itemOffset + itemsPerPage;
console.log(`Loading items from ${itemOffset} to ${endOffset}`);
setCurrentItems(data.slice(itemOffset, endOffset));
setPageCount(Math.ceil(data.length / itemsPerPage));
}, [itemOffset, itemsPerPage, data]);
// Invoke when user click to request another page.
const handlePageClick = (event) => {
const newOffset = (event.selected * itemsPerPage) % data.length;
console.log(
`User requested page number ${event.selected}, which is offset ${newOffset}`
);
setItemOffset(newOffset);
};
return (
<>
<NoteList
notes={currentItems.filter((noteText) =>
noteText.title.toLowerCase().includes(searchText)
)}
handleAddNote={handleAddNote}
handleDeleteNote={handleDeleteNote}
/>
<ReactPaginate
breakLabel="..."
nextLabel="next >"
onPageChange={handlePageClick}
pageRangeDisplayed={3}
pageCount={pageCount}
previousLabel="< previous"
renderOnZeroPageCount={null}
containerClassName="pagination"
pageLinkClassName="page-num"
previousLinkClassName="page-num"
nextLinkClassName="page-num"
activeLinkClassName="activee boxx"
/>
</>
);
}
export default Pagination;
App.js
import { useState, useEffect } from "react";
import { nanoid } from 'nanoid';
import './App.css';
import Search from "./components/Search";
import Header from "./components/Header";
import Pagination from "./components/Pagination";
const App = () => {
const [notes, setNotes] = useState([]);
const [searchText, setSearchText] = useState('');
const [darkMode, setDarkMode] = useState(false);
//Se encarga de mostrar la nota para escribir
const [showNote, setShowNote] = useState(true); //eslint-disable-line
useEffect(() => {
const saveNotes = JSON.parse(localStorage.getItem('notes-data'));
if (saveNotes){
setNotes(saveNotes);
}
}, []);
useEffect(() => {
localStorage.setItem('notes-data', JSON.stringify(notes))
},[notes])
const addNote = (inputText, text) => {
const date = new Date();
const newNote = {
id: nanoid(),
title: inputText,
text: text,
date: date.toLocaleString()
}
const newNotes = [newNote, ...notes];
setNotes(newNotes)
}
const deleteNote = (id) => {
var response = window.confirm("Are you sure that you want to remove the note?");
if (response){
const notesUpdated = notes.filter((note) => note.id !== id)
setNotes(notesUpdated);
}
}
return (
<div className={darkMode ? 'dark-mode' : ''}>
<div className="container">
<Header
handleToggleTheme={setDarkMode}
/>
<Search
handleSearchNote={setSearchText}
setShowNote={setShowNote}
/>
<Pagination data={notes} handleAddNote={addNote}
handleDeleteNote={deleteNote} searchText={searchText} />
</div>
</div>
)
}
export default App;

How to search drinks based on a checkbox value

I created a search component to get the cocktails by name, but I want to add another search option based on a checkbox(so the cocktail is alcoholically or not).
I have a context.js file:
import React, { useState, useContext, useEffect } from 'react'
import { useCallback } from 'react'
const url = 'https://www.thecocktaildb.com/api/json/v1/1/search.php?s='
const AppContext = React.createContext()
const AppProvider = ({ children }) => {
const [loading, setLoading] = useState(true)
const [searchTerm, setSearchTerm] = useState('a')
const [searchCheckbox, setSearchCheckbox] = useState(false)
const [cocktails, setCocktails] = useState([])
const fetchDrinks = useCallback(async () => {
setLoading(true)
setSearchCheckbox(false)
try {
const response = await fetch(`${url}${searchTerm}`)
const data = await response.json()
const {drinks} = data
if(!drinks) {
setCocktails([])
} else {
const searchedCocktails = drinks.map((drink) => {
const {idDrink, strDrink, strDrinkThumb, strInstructions,strAlcoholic, strIngredient1,strIngredient2} = drink
return {
id: idDrink,
name: strDrink,
image: strDrinkThumb,
isAlcoholic: strAlcoholic,
info: strInstructions,
ingredient1: strIngredient1,
ingredient2: strIngredient2
}
})
setCocktails(searchedCocktails)
}
setLoading(false)
} catch (error) {
console.log(error)
setLoading(false)
}
}, [searchTerm])
useEffect(() => {
fetchDrinks()
}, [searchTerm, fetchDrinks])
return <AppContext.Provider
value={{loading,
cocktails,
setSearchTerm,
setSearchCheckbox
}}>
{children}
</AppContext.Provider>
}
export const useGlobalContext = () => {
return useContext(AppContext)
}
export { AppContext, AppProvider }
The searchbar component is the following:
import React from 'react'
import { useGlobalContext } from '../helpers/context'
export default function SearchBar() {
const searchValue = React.useRef('')
const searchCheckbox = React.useRef(false)
const {setSearchTerm} = useGlobalContext()
const {setSearchCheckbox} = useGlobalContext()
const searchCocktail = () => {
setSearchTerm(searchValue.current.value)
setSearchCheckbox(searchCheckbox.current.checked)
}
const handleSubmit = (e) =>{
e.preventDefault()
}
//setup auto focus on input
React.useEffect(() => {
searchValue.current.focus()
searchCheckbox.current.focus()
}, [])
return (
<div className="container">
<div className="row">
<div className="col-12">
<div className="input-group">
<input className="form-control border-secondary py-2" type="search" ref={searchValue} onChange={searchCocktail}/>
<div className="input-group-append">
<button onClick={handleSubmit} className="btn btn-outline-secondary" type="button">
<i className="fa fa-search"></i>
</button>
</div>
</div>
</div>
<div className="col-12">
<div className="form-check">
<input className="form-check-input" type="checkbox" ref={searchCheckbox} onChange={searchCocktail} id="flexCheckDefault"/>
<label onClick={handleSubmit} className="form-check-label" htmlFor="flexCheckDefault">
Alcoholic
</label>
</div>
</div>
</div>
</div>
)
}
Can somebody can path me to a way to solve my problem? I haven't coded in React for some time and I think I m doing something hugely wrong with the useState hook on the checkbox
You need to create reference to state in parent element and pass it to child.
const [ alcoholicFilter, setAlcoholicFilter ] = useState(false) // false is default value
// here you can use `alcoholicFilter` variable and it will be updated
...
<Searchbar filter={setAlcoholicFilter}/>
After that in Searchbar component you can use this reference to update parent state
export default (props) => {
...
const handleSubmit = () => {
...
props.filter(input.checked) // you can set here to whatever you need
}
...
}

How to hide a form after submit in react, currently you have to click a toggle button

Currently I am rendering a list of songs where there is a toggle button I made to render a form to add a song. How can I make it so when that form is submitted it will hide the form without a button click. I attempted to make a useEffect to trigger the function but I couldn't crack it. Thanks in advance.
The list of songs
import { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { deleteSong, getSongs, updateSong } from '../../store/song';
import ReactAudioPlayer from 'react-audio-player';
import { useHistory } from 'react-router';
import SongForm from '../AddSongForm';
import EditSongForm from '../EditSongForm';
import SpecificSong from '../SpecificSong';
const SongList = () => {
const [addShowForm, setAddShowForm] = useState(false);
// const [editShowForm, setEditShowForm] = useState(false);
const history = useHistory()
const dispatch = useDispatch();
const songsObj = useSelector((state) => state.songState.entries);
const songs = Object.values(songsObj)
const user = useSelector((state) => state.session.user);
const CurrentUserId = user?.id
const remove = (e) => {
dispatch(deleteSong(e.target.id));
}
const addFormCheck = (e) => {
if (addShowForm) setAddShowForm(false)
if (!addShowForm) setAddShowForm(true)
}
// const editFormCheck = (e) => {
// if (editShowForm) setEditShowForm(false)
// if (!editShowForm) setEditShowForm(true)
// }
useEffect(() => {
dispatch(getSongs());
}, [dispatch]);
return (
<div>
<div>
<button onClick={addFormCheck}>add a song</button>
{addShowForm ?
<SongForm />
: null}
</div>
<h1>Song List</h1>
<ol>
{songs.map(({ id, songName, songLink, userId }) => (
<div>
<SpecificSong id={id} songName={songName} songLink={songLink} userId={userId} />
</div>
))}
</ol>
</div>
);
};
export default SongList;
And the component that is being rendered
import { useState } from "react";
import { useDispatch } from "react-redux";
import { postSong } from "../../store/song";
import { useSelector } from "react-redux";
import Axios from 'axios'
const SongForm = () => {
const dispatch = useDispatch();
const [songName, setSongName] = useState("");
const [songLink, setSongLink] = useState("");
const [errors, setErrors] = useState([]);
const [songSelected, setSongSelected] = useState("")
const reset = () => {
setSongName("");
setSongLink("");
};
const user = useSelector((state) => state.session.user);
const userId = user?.id
let url;
const handleSubmit = async (e) => {
e.preventDefault();
const formData = new FormData()
formData.append('file', songSelected)
formData.append('upload_preset', 'd3gthd7l')
if (songSelected === '') {
setErrors(['You have to upload an audio file!'])
}
Axios.post("https://api.cloudinary.com/v1_1/dyhfkvy6u/video/upload", formData).then(async (response) => {
if (response.data.url) url = response.data.url
const newSong = {
songName,
songLink: url,
userId
};
const song = await dispatch(postSong(newSong))
.catch(async (res) => {
const data = await res.json()
if (data && data.errors) setErrors(data.errors)
})
})
// reset();
};
return (
<div className="inputBox">
<h1>Add A Song</h1>
<ul>
{errors.map((error, idx) => <li className='errors' key={idx}>{error}</li>)}
</ul>
<form onSubmit={handleSubmit}>
<input
type="text"
onChange={(e) => setSongName(e.target.value)}
value={songName}
placeholder="Song Name"
name="Song Name"
/>
<input
type='file'
onChange={(e) => { setSongSelected(e.target.files[0]) }}
placeholder="Song Link"
name="Audio File"
/>
<button type="submit">Submit</button>
</form>
</div>
);
};
export default SongForm;
You could pass the setAddShowForm function to the form as a prop and update its state once submitted (Note that you can use && for conditional rendering):
<div>
<button onClick={addFormCheck}>add a song</button>
{addShowForm && <SongForm setAddShowForm={setAddShowForm}/>}
</div>
Change your component declaration to
const SongForm = ({ setAddShowForm }) => {
And update the state in the handleSubmit method:
const handleSubmit = async (e) => {
e.preventDefault();
const formData = new FormData();
formData.append('file', songSelected);
formData.append('upload_preset', 'd3gthd7l');
if (songSelected === '') {
setErrors(['You have to upload an audio file!']);
}
Axios.post(
'https://api.cloudinary.com/v1_1/dyhfkvy6u/video/upload',
formData
).then(async (response) => {
if (response.data.url) url = response.data.url;
const newSong = {
songName,
songLink: url,
userId,
};
const song = await dispatch(postSong(newSong)).catch(async (res) => {
const data = await res.json();
if (data && data.errors) setErrors(data.errors);
// Hide the form
setAddShowForm(false);
});
});
};
You cant trigger useEffect with a dispatch variable change. Dispatch is a hook and dont change once invoked. You need to create a state variable useState and change its value on handleChange, when you do that, include that variable on useEffect instead of dispatch and that will trigger the useEffect content.

Why does state only update after invoking twice using React Hooks?

I'm stuck trying to understand why my state won't update until I change the value in the text input twice (calling the handleChange function). What am I doing wrong here?
import React, {useEffect, useState} from "react";
export default function Typeahead(props){
const {list} = props;
const [colorList] = useState(list.map(element => element.toLowerCase()));
const [color,setColor] = useState();
const [showResults, setShowResults]= useState(false);
const [results,setResults]= useState();
let handleChange = (e) =>{
setShowResults(true);
setColor(e.target.value.toLowerCase());
const match = (colorList) =>{
return colorList.startsWith(color,0);
};
const matches = colorList.filter(match);
setResults((matches));
console.log(results);
console.log(showResults);
};
useEffect(() => {
//setResults(list.map(elements => elements.toLowerCase()));
}, [results]);
return(
<div>
<input type= "text" onChange={handleChange}/>
{showResults ?
<div>
{results.map((options) => {
return (
<option key={options} value={options}> {options}</option>
)
})}
</div>
: null }
</div>
);
}

Categories

Resources