search parameters in React Router 6 - javascript

I am having issues with react-router#6 when implementing search parameters.
To begin with, the app can search using the search form (for example, if the user searches dark, the app would direct to localhost:3000/search?query=dark to display the results),
can also use the URL in the search bar to be directed to the right page and results (for example, if the user use the URL localhost:3000/search?query=dark, it will direct to the page and display the results). Now, the issue is when the user types in the search form, it changes the URL by adding search parameters instantly. I am aware that this is caused by the setSearchParams() in the handleChange function, but is there any way around this to NOT change the URL when typing in the search form?
import React from 'react'
import Navbar from './Navbar'
import create from 'zustand'
import { useState, useEffect } from 'react'
import { useLocation, useNavigate, useSearchParams } from 'react-router-dom'
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
import { faTriangleExclamation } from '#fortawesome/free-solid-svg-icons'
// Zustand
let store = (set) => ({
// input: '',
// setInput: (value) => set({ input: value }),
allImages: [],
setAllImages: (images) => set({ allImages: images}),
totalResults: null,
setTotalResults: (num) => set({ totalResults: num}),
})
export const useMain = create(store)
function Header() {
const [error, setError] = useState(null)
const [showError, setShowError] = useState(false)
const [fadeOut, setFadeOut] = useState(false)
const [page, setPage] = useState(1)
const [searchParams, setSearchParams] = useSearchParams()
const query = searchParams.get('query') || ''
const allImages = useMain(state => state.allImages)
const setAllImages = useMain(state => state.setAllImages)
// const totalResults = useMain(state => state.totalResults)
const setTotalResults = useMain(state => state.setTotalResults)
function handleChange(event) {
// setInput(event.target.value)
setSearchParams({query: event.target.value})
}
async function fetchImages() {
try {
const res = await fetch(`https://api.unsplash.com/search/photos?&page=${page}&per_page=30&query=${query}&client_id=${process.env.REACT_APP_UNSPLASH_API_KEY}`)
const data = await res.json()
if (data.total !== 0) {
setAllImages(data.results)
setTotalResults(data.total)
}
} catch(error) {
setError(error)
}
}
let navigate = useNavigate()
const handleSubmit = async (event) => {
event.preventDefault()
fetchImages()
navigate(`/search?query=${query}`)
}
const location = useLocation()
useEffect(() => {
if (location.pathname === '/search' && allImages.length === 0) {
fetchImages()
navigate(`/search?query=${query}`)
}
}, [query])
// error
useEffect(() => {
if (error) {
setShowError(true)
setTimeout(() => {
setFadeOut(true)
setTimeout(() => {
setShowError(false)
}, 1000)
}, 5000)
}
}, [error])
return (
<div className='header'>
<Navbar />
<h2 className='header--heading text-center text-light'>Find Images</h2>
<div className='header--form'>
<form onSubmit={handleSubmit}>
<input
className='header--form--input'
autoComplete='off'
type='text'
placeholder='Search'
onChange={handleChange}
name='input'
value={query}
/>
</form>
</div>
{showError && <div className={`network-error ${fadeOut ? 'fade-out' : ''}`}>
<i><FontAwesomeIcon icon={faTriangleExclamation} /></i>
<div className='network-error--message'>
<h5>Network Error</h5>
<p>Please check your Internet connection and try again</p>
</div>
</div>}
</div>
)
}
export default Header
import './App.css';
import Main from './components/Main';
import Search from './components/pages/Search'
import Favorites from './components/pages/Favorites';
import Error from './components/pages/Error';
import { BrowserRouter, Routes, Route } from 'react-router-dom'
import { SkeletonTheme } from 'react-loading-skeleton';
import { useDarkMode } from './components/Navbar';
function App() {
const darkMode = useDarkMode(state => state.darkMode)
let style
if (darkMode === 'light') {
style = 'wrapper'
} else {
style = 'wrapper-dark'
}
return (
<div className={style}>
<SkeletonTheme baseColor="#808080" highlightColor="#b1b1b1">
<BrowserRouter>
<Routes>
<Route path='/' element={<Main />} />
<Route path='search' element={<Search />} />
<Route path='favorites' element={<Favorites />} />
<Route path='*' element={<Error />} />
</Routes>
</BrowserRouter>
</SkeletonTheme>
</div>
);
}
export default App;

If I'm understanding your question correctly, you don't want to update the query queryString parameter in real-time as the form field is being updated, but sometime later, like when the form is submitted. Keep in mind that the setSeaarchParams function works just like the navigate function, but operates on the queryString.
You can manually update the searchParams object, and when the form is submitted, call setSearch params instead of navigate. Remove the value prop from the input element as we'll be updating the searchParams object.
function Header() {
...
const location = useLocation()
const navigate = useNavigate();
const [searchParams, setSearchParams] = useSearchParams()
...
function handleChange(event) {
searchParams.set("query", event.target.value);
}
...
const handleSubmit = async (event) => {
event.preventDefault();
fetchImages();
setSearchParams(searchParams);
}
...
return (
<div className='header'>
<Navbar />
<h2 className='header--heading text-center text-light'>Find Images</h2>
<div className='header--form'>
<form onSubmit={handleSubmit}>
<input
className='header--form--input'
autoComplete='off'
type='text'
placeholder='Search'
onChange={handleChange}
name='input'
/>
</form>
</div>
...
</div>
)
}
export default Header

Related

How can I get the updated data without refresh the page in React?

I have a component with create a blog. It works fine and navigates me to the home after adding the new blog object in the database, but I have to refresh the page to be able to see the updated data. So how can I see the updated blogs after navigation without refreshing the page?
import { useState } from "react";
import { useNavigate } from "react-router-dom";
export const Create = () => {
const [title, setTitle] = useState("");
const [body, setbody] = useState("");
const [author, setAuthor] = useState("mario");
const [isPending, setPending] = useState(false);
const nav = useNavigate();
const newBlog = { title, body, author };
const handelSubmit = (e) => {
e.preventDefault();
console.log(title);
console.log(body);
console.log(author);
setPending(true);
fetch("http://localhost:3001/blogs", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(newBlog),
}).then(() => {
console.log("newBlog Added");
setPending(false);
nav("/");
});
console.log(newBlog);
};
return (
<div className="create">
<h2>Add a New Blog</h2>
<form onSubmit={handelSubmit}>
<label htmlFor="">Blog title:</label>
<input
type="text"
required
value={title}
onChange={(e) => setTitle(e.target.value)}
/>
<label htmlFor="">Blog Body:</label>
<textarea
required
value={body}
onChange={(e) => setbody(e.target.value)}
></textarea>
<label htmlFor="">Blog author:</label>
<select value={author} onChange={(e) => setAuthor(e.target.value)}>
<option value="mario">Mario</option>
<option value="magdy">Magdy</option>
</select>
{!isPending && <button>Add Blog</button>}
{isPending && <button disabled>Adding Blog</button>}
</form>
</div>
);
};
//Home component implementation
import React from "react";
import Blog from "./../blog/Blog";
export const Home = () => {
// const { data, flag, error } = useFetch("http://localhost:3001/blogs");
const [blogs, setBlogs] = useState(null);
const [flag, setFlag] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch("http://localhost:3001/blogs")
.then((res) => {
if (!res.ok) {
throw Error("Error in the API");
}
return res.json();
})
.then((data) => {
console.log(data);
setBlogs(data);
setFlag(!flag);
})
.catch((e) => {
console.log(e.message);
setError(e.message);
setFlag(!flag);
});
return () => {};
}, []);
return (
<div className="home">
{blogs && <Blog blogs={blogs} title="All Blogs" />}
{flag && <p>Loading...</p>}
{error !== null && <p>{error}</p>}
</div>
);
};
//Blog component implementation
import React from "react";
import { Link, Route, Routes } from "react-router-dom";
import { BlogDetails } from "../BlogDetails/BlogDetails";
import { Create } from "../create/Create";
import { Error } from "../Notfound/Error";
function Blog({ blogs, title }) {
return (
<Routes>
<Route
path="/"
element={
<div>
<h2>{title}</h2>
{blogs.map((blog) => {
return (
<div className="blog" key={blog.id}>
<Link to={`blogs/${blog.id}`}>
<h3>{blog.title}</h3>
<p>{blog.body}</p>
<h6>Written By {blog.author}</h6>
</Link>
</div>
);
})}
</div>
}
></Route>
<Route path="/create" element={<Create />}></Route>
<Route path="/blogs/:id" element={<BlogDetails />}></Route>
<Route path="*" element={<Error />}></Route>
</Routes>
);
}
export default Blog;
In order to update the blogs state, which is declared at the main point of the application, which is, for example, App.jsx, you must pass the state blogs and the set-state-action setBlogs as properties to the child component. For example:
The state in App.jsx:
const [blogs, setBlogs] = useState(null)
The child component (on which you will update the blogs state):
<Create blogs={blogs} setBlogs={setBlogs} />
On the fetch's then
fetch("http://localhost:3001/blogs", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(newBlog),
}).then((data) => {
console.log("newBlog Added");
setPending(false);
setBlogs([...blogs, newBlog])
nav("/");
});
console.log(newBlog);

React, problem with my Todo list app, my search engine only finds notes on the page I'm on

Hello I am making an application to practice React, my notes app has a pagination which works perfectly, the problem is in the search engine, which only looks for notes from the page I'm on, for example, if I'm on page 2 and I look for a note on page 2, it shows it, however if the note is on a different page, it doesn't show it, it doesn't find it.
I know where the problem is but I'm not sure how to solve it, I'm a bit new to React and I was asking for your help.
I was able to do my pagination with the package react-paginate here is the documentation https://www.npmjs.com/package/react-paginate
My code:
Component 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);
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?");
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;
Component Pagination.js
import React, { useEffect, useState } from 'react'
import ReactPaginate from 'react-paginate';
import '../styles/Pagination.css';
import NoteList from './NoteList';
import { MdSkipPrevious, MdSkipNext } from 'react-icons/md';
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 = 9;
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}
/>
<div className="pagination-wrapper">
<ReactPaginate
breakLabel="..."
nextLabel={<MdSkipNext
className='icons'
/>}
onPageChange={handlePageClick}
pageRangeDisplayed={3}
pageCount={pageCount}
previousLabel={<MdSkipPrevious
className='icons'
/>}
renderOnZeroPageCount={null}
containerClassName="pagination"
pageLinkClassName="page-num"
previousLinkClassName="page-num"
nextLinkClassName="page-num"
activeLinkClassName="activee boxx"
/>
</div>
</>
);
}
export default Pagination;
Component NoteList.js
import React from 'react'
import Note from './Note'
import '../styles/NoteList.css'
import AddNote from './AddNote'
const NoteList = ({ notes, handleAddNote, handleDeleteNote }) => {
return (
<>
<div className="add-notes-wrapper">
<AddNote
handleAddNote={handleAddNote}
/>
</div>
<div className='notes-list'>
{notes.map((note =>
<Note
key={note.id}
id={note.id}
title={note.title}
text={note.text}
date={note.date}
handleDeleteNote={handleDeleteNote}
/>
))}
</div>
</>
)
}
export default NoteList;
Component Search.js
//import React, { useState } from 'react'
import {MdSearch, MdAdd} from 'react-icons/md'
import '../styles/Search.css'
const Search = ({ handleSearchNote, setShowNote }) => {
const handleShowAddNote = () => {
if (setShowNote){
let addNote = document.querySelector('.new');
addNote.classList.add('wobble-horizontal-top')
addNote.style.display='flex';
document.querySelector('.notes-list').style.display='none';
document.querySelector('.pagination').style.display='none';
}
}
return (
<div className='search'>
<div className="input-wrapper">
<MdSearch
className='icon search-icon'
/>
<input
type="text"
placeholder='What note are you looking for?'
onChange={(event) => handleSearchNote(event.target.value) }
/>
</div>
<div className="btn-wrapper-search">
<button
className='btn-addNote'
onClick={handleShowAddNote}>
Nueva Nota
</button>
<MdAdd
className='icon add-icon'
/>
</div>
</div>
)
}
export default Search
The problem is in the component Pagination.js because I'm filtering the notes on each page with the currentItems variable, if I did it with the data variable it would work, but then it would show all the notes, and I don't want that, I currently want to show 9 notes per page.
greetings and thanks in advance.
Edit:
#Newbie I'm doing what you said, but I don't know if you mean this, in my Pagination.js component I did:
useEffect(() => {
const filterNotes=data.filter((noteText) =>
noteText.title.toLowerCase().includes(searchText)
)
setItemOffset(0);
}, [data, searchText])
It doesn't work, do I have to pass a prop to my components additionally?
greetings.
As I suggested to you, search all the notes with searchText in your App.js and pass the results into the Pagination component and it will solve your problem.
Codesandbox: https://codesandbox.io/s/youthful-thompson-xugs0c
Edit
All changes are as per what we talked about in the email.
Codesandbox: https://codesandbox.io/s/green-fast-3k76wx
Search and pagination do not play well together, one of the common solutions is to jump to page 1 each time the filter term changes.
So use an useEffect on searchText to filter data and reset itemOffset to 0, then redo pagination as if the data changed.
The user will jump to page 1 at each keystroke of the search, then he can navigate pages (if there are more than one). This will lead to a less confusing UX.

React, UseContext changes not reflecting immediately

When I log out from the Home page, the state gets updated and the page shows a login option, but after logging in again, the page again shows a login option untill refreshed, after refreshing the page, it displays the username? Why does the context value does not reflect immediately?
Login.js:
const submit = () => {
Axios.post("http://localhost:3001/login", data).then((res) => {
localStorage.setItem("bankDetails", res.data[0].acc_no);
navigate("/Home");
});
};
return (
<form onSubmit={submit}>
<input ... />
...
</form>
)
Home.js:
import { useContext } from "react";
import { LoginContext, DetailsContext } from "../App";
function Account() {
const isValid = useContext(LoginContext);
const userDetails = useContext(DetailsContext);
const load = () => {
localStorage.removeItem("bankDetails");
window.location.reload();
}
return isValid ? (
<div className="personal-info">
<h3>Welcome {userDetails.Username}</h3>
<h3 onClick={load}>LogOut</h3>
</div>
) : (
<h1>You Need to Login First !!!</h1>
);
}
App.js:
export const LoginContext = React.createContext();
export const DetailsContext = React.createContext();
function App() {
const username = localStorage.getItem("bankDetails");
const [userDetails, setUserDetails] = useState();
const [isValid, setisValid] = useState(false);
useEffect(() => {
if (username !== null) {
Axios.post("http://localhost:3001/userDetails", {
username: username,
}).then((res) => {
if (res.data.err) {
console.log("err");
} else {
setUserDetails(res.data.details[0]);
setisValid(true);
}
});
}
}, [username]);
return (
<LoginContext.Provider value={isValid}>
<DetailsContext.Provider value={userDetails}>
<Router>
<Routes>
<Route path="/Login" element={<Login />} />
<Route path="/Home" element={<Home />} />
</Routes>
</Router>
</DetailsContext.Provider>
</LoginContext.Provider>
);
}
That's because inside submit function you didn't ask the context to update anything. Your App component is not watching changes inside the localStorage, the only thing you are updating after login. App as you did checks the localStorage only on load. That's why it works when you log in and then refresh the page.
What you can do that wouldn't change that much your current structure is to change App component as follow (I added comments where I changed things):
export const LoginContext = React.createContext();
export const DetailsContext = React.createContext();
function App() {
const [userDetails, setUserDetails] = useState();
const [isValid, setisValid] = useState(false);
useEffect(() => {
// I moved this line here in the callback
const username = localStorage.getItem("bankDetails");
if (username !== null) {
Axios.post("http://localhost:3001/userDetails", {
username: username,
}).then((res) => {
if (res.data.err) {
console.log("err");
} else {
setUserDetails(res.data.details[0]);
}
});
}
}, [isValid]); // I changed the dependencies array
return (
// I'am passing down setisValid as part of LoginContext
<LoginContext.Provider value={{ isValid, setisValid }}>
<DetailsContext.Provider value={userDetails}>
<Router>
<Routes>
<Route path="/Login" element={<Login />} />
<Route path="/Home" element={<Home />} />
</Routes>
</Router>
</DetailsContext.Provider>
</LoginContext.Provider>
);
}
Then grab both of isValid and setisValid inside Login component. Change your submit function and add an useEffect that would do the redirection. Like so:
const {isValid, setisValid} = useContext(LoginContext);
const submit = () => {
Axios.post("http://localhost:3001/login", data).then((res) => {
localStorage.setItem("bankDetails", res.data[0].acc_no);
setisValid(true);
};
useEffect(()=>{
if(isValid){
navigate("/home")
}
},[isValid])

How can I persist redux state

I'm developing a CRUD and, in one of the components, when the user creates the post, a div is rendered with the title and content values of his post. I need his posts to be saved even when he navigates between pages (without refreshing the page). Currently I can create as many posts as I want and they will be lined up one below the other, but when I go back to the previous page they are deleted. I tried to implement redux in this part the way I implemented it to save the user input (creating a slice to save the posts) but it didn't work. I know normally posts wouldn't be deleted, so I'd like to know where I'm going wrong.
signup screen:
import React, {useState, useEffect} from "react";
import "../_assets/signup.css";
import "../_assets/App.css";
import { useDispatch } from 'react-redux';
import userSlice from '../redux/userslice';
import { useNavigate } from "react-router-dom";
function Signup() {
const navigate = useNavigate();
const dispatch = useDispatch();
const [name, setName] = useState('')
const [buttonGrey, setButtonGrey] = useState('#cccccc')
useEffect(() => {
if (name!== '') {
setButtonGrey("black")
}
else {
setButtonGrey('#cccccc')
}
}, [name])
const handleSubmitForm= (e) => {
e.preventDefault()
dispatch(userSlice.actions.saveUser(name))
navigate("/main")
}
const handleChangeName = (text) => {
setName(text)
}
return (
<div className="container">
<div className="LoginBox">
<form onSubmit={handleSubmitForm}>
<h2>Welcome to codeleap network</h2>
<text>Please enter your username</text>
<input type="text" name="name" value={name} onChange = {e => handleChangeName(e.target.value)} placeholder="Jane Doe" />
<div className="button">
<button type="submit" style={{backgroundColor: buttonGrey}} disabled={!name} >
ENTER
</button>
</div>
</form>
</div>
</div>
);
}
export default Signup;
CRUD screen:
import React, { useState, useEffect } from "react";
import "../_assets/App.css";
import "../_assets/mainscreen.css";
import { MdDeleteForever } from "react-icons/md";
import { FiEdit } from "react-icons/fi";
import { useSelector } from 'react-redux';
import { useDispatch } from 'react-redux';
import { Navigate } from 'react-router-dom';
import postsSlice from '../redux/postsslice'
import Modal from "../components/modal.jsx";
function MainScreen() {
const dispatch = useDispatch();
const user = useSelector((state) => state.user)
const loadPosts = useSelector((state) => state.loadPosts)
const [title, setTitle] = useState("");
const [content, setContent] = useState("");
const [newPosts, setNewPosts] = useState([])
const [buttonGreyOut, setButtonGreyOut] = useState("#cccccc");
useEffect(() => {
if (title && content !== "") {
setButtonGreyOut("black");
} else {
setButtonGreyOut("#cccccc");
}
},[title, content]);
const handleSubmitSendPost = (e) => {
e.preventDefault();
setNewPosts(newPosts.concat({title, content}))
dispatch(postsSlice.actions.savePosts(newPosts))
setTitle('')
setContent('')
};
const handleChangeTitle = (text) => {
setTitle(text);
};
const handleChangeContent = (text) => {
setContent(text);
};
const [openModal, setOpenModal] = useState();
if (user === '') {
return <Navigate to="/" />
} else {
return (
<div className="containerMainScreen">
{openModal && <Modal closeModal={setOpenModal} />}
<div className="bar">
<h1>Codeleap</h1>
</div>
<div className="boxPost">
<h2 style={{ fontWeight: 700 }}>What's on your mind?</h2>
<h2>Title</h2>
<form onSubmit={handleSubmitSendPost}>
<input
type="text"
placeholder="Hello World"
name="name"
value={title}
onChange={(e) => handleChangeTitle(e.target.value)}
></input>
<h2>Content</h2>
<textarea
placeholder="Content"
name="content"
value={content}
onChange={(e) => handleChangeContent(e.target.value)}
></textarea>
<button
className="createButton"
type="submit"
style={{ backgroundColor: buttonGreyOut }}
disabled={!title || !content}
>
CREATE
</button>
</form>
</div>
{newPosts.map((post) => (
<div className="boxPost">
<div className="bar">
<h1>{post.title}</h1>
<MdDeleteForever
className="icon"
onClick={() => {
setOpenModal(true);
}}
/>
<FiEdit
style={{ color: "white", fontSize: "45px", paddingLeft: "23px" }}
/>
</div>
<div id="postowner">
<h3>#{user}</h3>
<h3>25 minutes ago</h3>
<br></br>
<textarea style={{ border: "none" }}>{post.content}</textarea>
</div>
</div>
))}
</div>
);
}
}export default MainScreen;
store.js:
import { configureStore } from '#reduxjs/toolkit';
import userSlice from './userslice';
import postsSlice from './postsslice'
export const store = configureStore({
reducer: {
user: userSlice.reducer,
loadPosts: postsSlice.reducer
},
})
postsSlice:
import { createSlice } from "#reduxjs/toolkit";
const postsSlice = createSlice({
name: "posts",
initialState: "",
reducers: {
savePosts: (state, action) => action.payload
}
});
export default postsSlice
CRUD screenshot:
https://i.stack.imgur.com/YoCJz.png
You are dispatching the savePosts action with the value of newPosts from before the current posts are added to it. The problem is in these lines:
setNewPosts(newPosts.concat({title, content}))
dispatch(postsSlice.actions.savePosts(newPosts))
Try something like this instead:
const posts = newPosts.concat({title, content})
setNewPosts(posts)
dispatch(postsSlice.actions.savePosts(posts))
It does not make sense to store the posts array in Redux and also store it in local component state. In my opinion you should ditch the component newPosts state and access the posts via Redux with useSelector.
I would also recommend dispatching an addPost action which requires just the current post. Let the reducer handle adding it to the array.
The state is an array of posts, so your initialState should be an empty array rather than an empty string.
const postsSlice = createSlice({
name: "posts",
initialState: [],
reducers: {
// add one post to the array.
addPost: (state, action) => {
state.push(action.payload); // modifies the draft state.
},
// replace the entire array.
replacePosts: (state, action) => action.payload
}
});
function MainScreen() {
const dispatch = useDispatch();
const user = useSelector((state) => state.user)
const posts = useSelector((state) => state.loadPosts)
const [title, setTitle] = useState("");
const [content, setContent] = useState("");
/* ... */
const handleSubmitSendPost = (e) => {
e.preventDefault();
dispatch(postsSlice.actions.addPost({title, content}))
setTitle('')
setContent('')
};

What is the best way to pass in props to a react router route?

I have a react component I need to render that takes one argument of a string when it is initialized. I need a button to click on that will redirect and render this new component with the string. It sends the string I want when it console.log(pro). everytinme I click on the button it goes to a blank screen and doesn't load.
My routes.js looks like
const Routes = (props) => {
return (
<Switch>
<Route exact path="/member" component={() => <Member user={props.state.member} />} />
<Route path="/posts" exact component={Posts} />
<Redirect exact to="/" />
</Switch>
)
}
export default Routes
The original component looks like this
const Posts = (props) => {
const dispatch = useDispatch();
const [postCount, setPostCount] = useState(0);
const [member, setMember] = useState({});
const getProfile = async (member) => {
const addr = dispatch({ type: 'ADD_MEMBER', response: member })
console.log(member)
props.history.push('/member'
);
console.log('----------- member------------') // console.log(addr)
return (
<Member user={member}><Member/>
);
}
return (
<div>
{socialNetworkContract.posts.map((p, index) => {
return <tr key={index}>
<button onClick={() => getProfile(p.publisher)}>Profile</button>
</tr>})}
</div>
)
}
export default withRouter(Posts);
The component I'm trying to render from the Posts component needs to be rendered with a string
const Member = (props)=> {
const [user, setUser] = useState({});
const { state } = props.location;
const [profile, setProfile] = useState({});
useEffect(() => {
const doEffects = async () => {
try {
const pro = socialNetworkContract.members[0]
console.log(pro)
const p = await incidentsInstance.usersProfile(pro, { from: accounts[0] });
setProfile(p)
} catch (e) {
console.error(e)
}
}
doEffects();
}, [profile]);
return (
<div class="container">
{profile.name}
</div>
)
}
export default Member;
You can pass an extra data to a route using state attribute with history.push
const getProfile = async (member) => {
const addr = dispatch({ type: 'ADD_MEMBER', response: member })
console.log(member)
props.history.push({
path: '/member',
state: { member }
});
}
Once you do that you can access it in the rendered route from location.state
import {
useLocation
} from "react-router-dom";
const Member = (props)=> {
const [user, setUser] = useState({});
const { state } = useLocation();
console.log(state.member);
const [profile, setProfile] = useState({});
...
}
export default Member;
Also you do not need to pass on anything while rendering the Route
const Routes = (props) => {
return (
<Switch>
<Route exact path="/member" component={Member} />
<Route path="/posts" exact component={Posts} />
<Redirect exact to="/" />
</Switch>
)
}
export default Routes

Categories

Resources