I am working on my blog and am trying to implement Sanity. I was able to get my posts to show up with the json object returned from query with useState
I am trying to populate my React-Modal with the correct contents based on the post I have clicked with its _id or some kind of key. I simplified the code so it wouldn't be too long:
export default function News() {
// Json objects stored in posts
const [posts, setPosts] = useState([]);
// Used to toggle Modal on and off
const [isOpen, setIsOpen] = useState(false);
function toggleModal() {
setIsOpen(!isOpen);
}
return (
<>
{posts.map((posts) => (
<div key={posts._id}>
<h3 className="title" onClick={toggleModal}>
{posts.title}
</h3>
<div">
<a>
<span onClick={toggleModal}>Read More</span>
</a>
</div>
// Clicking on either span or a tag shows the Modal
<Modal
isOpen={isOpen}
onRequestClose={toggleModal}>
// Closes modal
<button className="close-modal" onClick={toggleModal}>
<img
src="assets/img/svg/cancel.svg"
alt="close icon"/>
</button>
// Want to show content based on _id
<h3 className="title">{posts.title}</h3>
<p className="body">{posts.body}</p>
</div>
)
</>
)
}
Whenever I click on a certain post, it always toggles on the first object.
Click to see gif demo
Edit: I was able to get it to work based on the answer given
const [state, setState] = useState({ isOpen: false, postId: null });
const openModal = React.useCallback(
(_key) => () => {
setState({ isOpen: true, postId: _key });
},
[]
);
function closeModal() {
setState({ isOpen: false, postId: null });
}
And with Modal tag I added
key={post.id == state.postId}
Now every divs and tags that renders the correct content.
However, I'm facing a slight issue. If I click on post[2] and it renders out post[0] content and in a blink of an eye changes to the correct content. Then when I click on post1, it renders and post[2] content and changes to the correct one. It keeps rendering the previous post. It's all in a blink of an eye, but still visible.
I can suggest using react hooks to solve your problem.
You can pass a function to useCallback's return, you can then call your function normally in the render by passing params to it.
See more: https://reactjs.org/docs/hooks-reference.html#usecallback
import * as React from 'react';
export default function News() {
// Json objects stored in posts
const [posts, setPosts] = useState([]);
// Used to toggle Modal on and off
const [isOpen, setIsOpen] = useState(false);
// React.useCallback.
const toggleModal = React.useCallback((id) => () => {
setIsOpen(!isOpen);
console.log(`Post id: ${id}`);
}, []);
return (
<>
{posts.map((post) => (
<div key={post._id}>
<h3 className="title" onClick={toggleModal(post._id)}>
{post.title}
</h3>
<div">
<a>
<span onClick={toggleModal(post._id)}>Read More</span>
</a>
</div>
// Clicking on either span or a tag shows the Modal
<Modal
isOpen={isOpen}
onRequestClose={toggleModal(post._id)}>
// Closes modal
<button className="close-modal" onClick={toggleModal(post._id)}>
<img
src="assets/img/svg/cancel.svg"
alt="close icon"/>
</button>
// Want to show content based on _id
<h3 className="title">{post.title}</h3>
<p className="body">{post.body}</p>
</div>
)
</>
)
}
Related
I want to be able to change what is the "feed" in this with a button, not having to swap to a new page. I have the two values, with the "home" being the one that shows all blogs, and personal being just the ones with the author value of "mario". What would I have to do add to have the button onClick switch to using the personal filtered blogs. (and defaulting to the home, and if needed a button that changes the current listed blogs back to the home)
(I apologize for any like, poor conventions or anything, I am new to javascript, and well it is javascript)
const Home = () => {
const [viewCount, setViewCount] = useState(0);
const {data: blogs, isPending } = useFetch();
const history = useHistory();
const [newBlog, setNewBlog] = useState([]);
useEffect(() => {
const getNewBlog = async () => {
const home = blogs
const personal = blogs.filter((blog) => blog.author === 'mario')
setNewBlog(home)
}
getNewBlog()
},[]);
return (
<div className="home">
<div className="profile">
<h2>Hello, User!</h2>
<div className="profile-picture"> </div>
<p>Profile Views: {Math.round(viewCount / 2)}</p>
<a href="/">
<button onClick={null}>Manage Your Blog</button> // <-- the onClick that I mention
</a>
</div>
<div className="feed">
{isPending && <div>Loading... </div>}
{ blogs && <BlogList blogs={newBlog} title = "Your Feed"/> }
</div>
</div>
);
}
This isn't the full extent of what I have tried, I tinkered with some other stuff but looking back at it I was going at it with poor logic.
To cover what is expected to happen:
The default "feed" shows all blogs, onClick of the button, it switches over to just the blogs of the author 'mario'. Returning to the default feed could be done through another button, or just a refresh of page.
You just have to add one line in order to filter the blogs on click.
<button onClick={()=>setNewBlog([...blogs.filter((blog) => blog.author === 'mario')])}>Manage Your Blog</button>
I hope this helps, or comment if your expectation is different
Here is the relevant code:
const Members = () => {
// array of each video in selected grade
const videosMap = (videos) => {
return videos.map((video) => (
<VideoCard
key={video.id}
thumbnail={video.thumbnail}
title={video.title}
description={video.description}
onClick={() => {
handleVideoClick();
}}
/>
));
};
// updates state of shown videos & page heading
const handleGradeButtonClick = (videos, heading) => {
setShowVideos(videosMap(videos));
setVideosHeading(heading);
};
const handleVideoClick = () => {
console.log("test");
};
// controls state of which grade's videos to show
const [showVideos, setShowVideos] = useState(videosMap(kinder_videos));
// controls states heading to display depending on selected grade
const [videosHeading, setVideosHeading] = useState("Kindergarten");
const [showVideoDetails, setShowVideoDetails] = useState(null);
The handleVideoClick is the function that is not working when I click on one of the mapped VideoCard components.
Here is the full code if you want to see that:
https://github.com/dblinkhorn/steam-lab/blob/main/src/components/pages/Members.js
When I look in React DevTools at one of the VideoCard components, it shows the following:
onClick: *f* onClick() {}
If I don't wrap it in an arrow function it does execute, but on component load instead of on click. I have a feeling it has something to do with my use of .map to render this component, but haven't been able to figure it out.
Thanks for any help!
There's no problem with your mapping method, you just need to pass the onClick method as a prop to your VideoCard component :
On your VideoCard component do this :
const VideoCard = (props) => {
const { thumbnail, description, title, onClick } = props;
return (
<div className="video-card__container" onClick={onClick}>
<div className="video-card__thumbnail">
<img src={thumbnail} />
</div>
<div className="video-card__description">
<div className="video-card__title">
<h3>{title}</h3>
</div>
<div className="video-card__text">{description}</div>
</div>
</div>
);
};
export default VideoCard;
I'm learning react at the moment and currently, making a todo app so that I can understand react more easily.
So here's what I'm trying to do:
The user clicks a button
The click fires a prompt which asks the user for the todo title (only title at the moment)
Then, that title is added to an array of all todos
And then, use that array to display each todo on the page
Code:
const [check, setCheck] = useState(false);
const [todo, setTodo] = useState([]);
function handleClick() {
let toAdd = prompt('Title: ')
setTodo([...todo, {
title: toAdd
}]);
}
useEffect(()=> {
if(todo.length !== 0) {
setCheck(true);
}
})
return (
<div className="wholeContainer">
<div className="tododiv">
<span className="todos">Todos: </span>
<hr/>
{
check ?
todo.forEach((eachTodo)=> {
<TodoItems title={eachTodo}/>
})
: <span>Nothing</span>
}
</div>
<button className="add" onClick={handleClick}>
<i className="fas fa-plus"></i>
Add a Todo
</button>
</div>
);
The const [check, setCheck] = useState(false); is written so that I can access the array if todo.length !== 0;
The problem comes in the rendering part. I can't figure out a way to display each and every todo in their own <TodoItems/> component, and also when using forEach(), nothing happens because I think that someone told me that setState() works asynchronously.
I really need some advice!
Thanks...
You are using
todo.forEach((eachTodo)=> {
<TodoItems title={eachTodo}/>
})
When you should be using
todo.map((eachTodo)=> {
return <TodoItems title={eachTodo}/>
})
Or
todo.map((eachTodo)=> (
<TodoItems title={eachTodo}/>
))
Also you have an infinite loop in your component:
useEffect(()=> {
if(todo.length !== 0) {
setCheck(true);
}
})
Each time the component updates, when the todo list isn't empty, you setCheck to true which triggers a new render.
Also, you don't need to use state for every variable, only the ones were a change should trigger a re-render.
Also your new todo-list state depends on the previous state so you should use a functional update.
https://reactjs.org/docs/hooks-reference.html
const [todoList, setTodoList] = useState([]);
function handleClick() {
let toAdd = prompt('Title: ');
setTodoList((prevTodoList) => [...prevTodoList, toAdd]);
}
const isTodoListEmpty = todoList.length === 0
return (
<div className="wholeContainer">
<div className="tododiv">
<span className="todos">Todos: </span>
<hr />
{!isTodoListEmpty ? (
todoList.forEach((todoItem) => {
<TodoItems title={todoItem} />;
})
) : (
<span>Nothing</span>
)}
</div>
<button className="add" onClick={handleClick}>
<i className="fas fa-plus"></i>
Add a Todo
</button>
</div>
);
I am trying to show a message when user try to leave current page, so I am using history.block like this:
import { useHistory } from "react-router-dom";
const ProfilerCreate = ({ pageType }) => {
const history = useHistory();
const [isDisabled, setIsDisabled] = useState(true);
const [openModalUnsave, setOpenModalUnsave] = useState(false);
useEffect(() => {
history.block(validateChange);
}, []
);
//Function to validate changes and open modal
function validateChange(txt) {
if (!isDisabled) {
toggleModalUnsave();
return false;
}
}
//Function to open or close modal
function toggleModalUnsave() {
setOpenModalUnsave(!openModalUnsave);
}
//Function to return landing page
function returnPage() {
history.push("/");
}
return (
...
<div style={{ display: "none" }}>
<Modal
id="myModal"
heading="You have unsaved changes"
description="Do you want to save or discard them?"
isOpen={openModalUnsave}
onRequestClose={(detail) => toggleModalUnsave()}
actionsRight={
<>
<Button display="text" onClick={() => returnPage()}>
Discard
</Button>
<Button
display="primary"
onClick={(evt) => saveAudienceData(evt)}
>
Save and exit
</Button>
</>
}
>
<p>Modal Children</p>
</Modal>
</div>
);
export default ProfilerCreate;
when it is detecting unsaved changes, it shows a modal with a warning and two buttons, one for save and the other for discard, when the user hit discard button it should return to home page, but history.push is not working.
I tried to find the solution or I don't know if I am using the history.block in a wrong way.
I hope that you can help me, thanks!
I think you are missing the unblock() method in validateChange(txt)
I have a <details> tag, on click of it toggles some content. Now I have an <a> tag underneath it, on click of the <a> tag I'd like to toggle the same content, i.e. clicking the <a> should be equivalent to clicking the <details>. Here is a code snippet I've tried:
import React, { useState } from "react";
import ReactDOM from "react-dom";
const Menu = ({ toggleDetails }) => {
return (
<div>
<a href="/#" onClick={toggleDetails}>
Open
</a>
</div>
);
};
const Details = (isOpen) => {
return (
<details>
<summary>Hello</summary>
{isOpen ? <div>Hi</div> : null}
</details>
);
};
const App = () => {
const [isOpen, setIsOpen] = useState(false);
const toggleDetails = () => {
setIsOpen(isOpen ? false : true);
};
return (
<div>
<Details isOpen={isOpen} />
<Menu toggleDetails={toggleDetails} />
</div>
);
};
ReactDOM.render(<App />, document.getElementById("container"));
Here on click of 'Hello', it toggles 'Hi'. I'd like to do the same thing on click of 'Open', i.e. toggles 'Hi'. How can I do it? The conditional rendering does not work. Should I use a ref to access the 'open' property of the <details> tag?
EDIT:
I also tried the ref solution as follows but it didn't work:
const Details = (isOpen) => {
const detailsRef = useRef();
// detailsRef.current.open = isOpen
return (
<details ref={detailsRef}>
<summary>Hello</summary>
<div>Hi</div>
</details>
);
};
I assume you are trying to use the details tag's native toggle functionality. In order to do that, you need to control the open/closed state via the open attribute. You should then use the onToggle event to detect when the summary element is clicked, so you can keep your component's state in sync with the actual DOM.
const Menu = ({ setIsOpen }) => {
return (
<div>
<a
href="#"
onClick={() => {
setIsOpen((prev) => !prev);
}}
>
Open
</a>
</div>
);
};
const Details = ({ isOpen, setIsOpen }) => {
return (
<details
open={isOpen}
onToggle={(event) => {
setIsOpen(event.target.open);
}}
>
<summary>Hello</summary>
<div>Hi</div>
</details>
);
};
const App = () => {
const [isOpen, setIsOpen] = useState(false);
return (
<div>
<Details isOpen={isOpen} setIsOpen={setIsOpen} />
<Menu setIsOpen={setIsOpen} />
</div>
);
};
You need to transfer information between both components to do that you either needs to:
1: State penetration,
2: Redux.
You are attempting to change a component that is not connected to the one you are calling. The Hi div is on the Details component which is not in direct relationship with the Menu component. Now regarding your specific problem, you can do it by pushing the state on a higher component which in this case is App.js.
Now I do not understand if you are trying to make the app work in this way as a coding challenge or if you do not know better. If it is the latter please reply in the comments so I can provide a direct solution.
Pretty sure all you need to do is ensure that details open attribute is set to true or false depending on if you want it open or.not.
<details open={isOpen}>...</details>