useState passed to useContext not updating state - javascript

I am trying to use useContext to create a generic Tooltip component that passes a close() function to the content inside the Tooltip. I have written my Tooltip like this
export function Tooltip(props) {
const [active, setActive] = useState(false);
const close = () => {
setActive(false);
}
return (
<div className="tooltip-wrapper"
onClick={() => setActive(true)}
>
{props.children}
<TooltipContext.Provider value={{close}}>
{active && (
<div className='tooltip-tip bottom' ref={node}>
{props.content}
</div>
)}
</TooltipContext.Provider>
</div>
)
}
I create the Tooltip in a different class component as follows
function Category(props) {
return (
<Tooltip content={<AddCategoryInnerTooltip name={props.name}/>}>
<p className="tooltip-name-opener">{props.name}</p>
</Tooltip>
);
}
function AddCategoryInnerTooltip(props) {
const {close} = useContext(TooltipContext);
return(
<div className="inner-tooltip-wrapper">
<input
className="tooltip-custom-input"
type="text"
defaultValue={props.name}
/>
<div className="button-end">
<button onClick={close}>Cancel</button>
<button>Ok</button>
</div>
</div>
)
}
When I attempt to call close within the AddCategoryInnerTooltip, the state passed from the Tooltip component doesn't update. When I console.log the state, it always comes as true without changing. What am I doing wrong?

should be a callback function
<button onClick={()=>close}>Cancel</button>

Related

Passing React State

I have a modal that I want to appear on a button click. This works fine, however, in order for it to be positioned correctly, I need to move the modal component to be rendered in a parent component.
I'm unsure how to do this as I'm not sure how I would pass the state down to the component where the button click is created. I'm also unsure of whether this is about passing a state or using a button onClick outside the component the state is defined in.
My code works and does what I want, but, I need <DeleteWarehouseModal show={show} /> to be in the parent component below. How can I do this?
The parent component where I want to render my modal:
function WarehouseComponent(props) {
return (
<section>
<div>
<WarehouseListItems warehouseData={props.warehouseData} />
//Modal component should go here
</div>
</section>
);
}
The component (WarehouseListItems) where the modal is currently being rendered:
function WarehouseListItem(props) {
const [show, setShow] = useState(false);
return (
<>
//some code necessary to the project, but, irrelevant to this issue
<Link>
<div onClick={() => setShow(true)}></div>
</Link>
<DeleteWarehouseModal show={show} />
</>
);
}
The modal:
const DeleteWarehouseModal = (props) => {
if (!props.show) {
return null;
}
return (
//some elements here
);
};
Yes, you can move the state and it's handler in WarehouseComponent component and pass the handler down to child component, which can change the state in parent component.
WarehouseComponent :-
function WarehouseComponent(props) {
const [show, setShow] = useState(false);
const toggleShow = () => {
setShow(state => !state);
}
return (
<section>
<div>
<WarehouseListItems
warehouseData={props.warehouseData}
toggleShow={toggleShow}
/>
<DeleteWarehouseModal show={show} />
</div>
</section>
);
}
WarehouseListItems : -
function WarehouseListItem(props) {
const {toggleShow} = props;
return (
<>
//some code necessary to the project, but, irrelevant to this issue
<Link>
<div onClick={() => toggleShow()}></div>
</Link>
</>
);
}

Modal component does not render on a custom button component

I am trying to render a custom and dynamic modal on button clicks. For example, when a "Game" button is clicked, I would like a modal to render with specfics about the game and when a "Bank" button is clicked, I would like the modal to populate with specfics about a bank.
First, when I add an onClick function to a custom button component, the modal does not render. However, when I put the onClick function on a regular button, the modal does render. How can I simply add an onClick function on any component to render a dynamic modal?
Second, I would like to populate each modal with differnet data. For example, a "Game" button would populate the modal with a title of "Game" and so on. I'm using props to do this, but is that the best solution?
Here is the code I have so far, but it is broken when I add the onClick function to components.
// Navbar.js
import { ModalContext } from '../contexts/ModalContext'
function Navbar() {
const [showModal, updateShowModal] = React.useState(false)
const toggleModal = () => updateShowModal((state) => !state)
return(
<ModalContext.Provider value={{ showModal, toggleModal }}>
<Modal
title="Title"
canShow={showModal}
updateModalState={toggleModal}
/>
</ModalContext.Provider>
)
// does not render a modal
<Button
onClick={toggleModal}
type="navItem"
label="Game"
icon="windows"
/>
// render a modal
<button onClick={toggleModal}>Show Modal</button>
)
}
import { ModalContext } from '../contexts/ModalContext'
// Modal.js
const Modal = ({ title }) => {
return (
<ModalContext.Consumer>
{(context) => {
if (context.showModal) {
return (
<div style={modalStyles}>
<h1>{title}</h1>
<button onClick={context.toggleModal}>X</button>
</div>
)
}
return null
}}
</ModalContext.Consumer>
)
}
// modalContext.js
export const ModalContext = React.createContext()
// Button.js
function Button({ label, type = 'default', icon }) {
return (
<ButtonStyle buttonType={type}>
{setIcon(icon)}
{label}
</ButtonStyle>
)
}
First problem:
I think the onClick prop of the <Button> component is not pointing to the onClick of the actual HTML button inside the component.
Could you please check that? And if you think It's been set up in the right way, then can you share the code of the component?
Second Problem
Yes, there's another way to do that. And I think it's React Composition. You can build the modal as the following:
<Modal
showModal={showModal}
updateModalState={toggleModal}
>
<div className="modal__header">{title}</div>
<div className="modal__body">{body}</div>
<div className="modal__footer">{footer}</div>
</Modal>
I think this pattern will give you more control over that component.
Issue
You are not passing the onClick prop through to the styled button component.
Solution
Given style-component button:
const ButtonStyle = styled.button``;
The custom Button component needs to pass all button props on to the ButtonStyle component.
// Button.js
function Button({ label, type='default', icon, onClick }) {
return (
<ButtonStyle buttonType={type} onClick={onClick}>
{setIcon(icon)}
{label}
</ButtonStyle>
)
}
If there are other button props then you can use the Spread syntax to collect them into a single object that can then be spread into the ButtonStyle component.
// Button.js
function Button({ label, type = 'default', icon, ...props }) {
return (
<ButtonStyle buttonType={type} {...props}>
{setIcon(icon)}
{label}
</ButtonStyle>
)
}
Second Question
For the second issue I suggest encapsulating the open/close/title state entirely in the modal context provider, along with the Modal component.
Here's an example implementation:
const ModalContext = React.createContext({
openModal: () => {},
});
const Modal = ({ title, onClose}) => (
<>
<h1>{title}</h1>
<button onClick={onClose}>X</button>
</>
)
const ModalProvider = ({ children }) => {
const [showModal, setShowModal] = React.useState(false);
const [title, setTitle] = React.useState('');
const openModal = (title) => {
setShowModal(true);
setTitle(title);
}
const closeModal = () => setShowModal(false);
return (
<ModalContext.Provider value={{ openModal }}>
{children}
{showModal && <Modal title={title} onClose={closeModal} />}
</ModalContext.Provider>
)
}
Example consumer to set/open a modal:
const OpenModalButton = ({ children }) => {
const { openModal } = useContext(ModalContext);
return <button onClick={() => openModal(children)}>{children}</button>
}
Example usage:
function App() {
return (
<ModalProvider>
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<OpenModalButton>Modal A</OpenModalButton>
<OpenModalButton>Modal B</OpenModalButton>
</div>
</ModalProvider>
);
}
Demo

React useEffect Problems

So here is the problem which I can't seem to solve. I have an app component, inside of App I have rendered the Show Component. Show component has toggle functionality as well as a outside Click Logic. In the Show component I have a Button which removes an item based on his Id, problem is that When I click on the button Remove. It removes the item but it also closes the Show Component, I don't want that, I want when I press on button it removes the item but does not close the component. Thanks
App.js
const App =()=>{
const[isShowlOpen, setIsShowOpen]=React.useState(false)
const Show = useRef(null)
function openShow(){
setIsShowOpen(true)
}
function closeShowl(){
setIsShowOpen(false)
}
const handleShow =(e)=>{
if(show.current&& !showl.current.contains(e.target)){
closeShow()
}
}
useEffect(()=>{
document.addEventListener('click',handleShow)
return () =>{
document.removeEventListener('click', handleShow)
}
},[])
return (
<div>
<div ref={show}>
<img className='taskbar__iconsRight' onClick={() =>
setIsShowOpen(!isShowOpen)}
src="https://winaero.com/blog/wp-content/uploads/2017/07/Control-
-icon.png"/>
{isShowOpen ? <Show closeShow={closeShow} />: null}
</div>
)
}
Show Component
import React, { useContext } from 'react'
import './Show.css'
import { useGlobalContext } from '../../context'
import WindowsIcons from '../../WindowsIcons/WindowsIcons'
import { GrClose } from 'react-icons/gr'
const Show = ({closeShow}) => {
const {remove, icons }= useGlobalContext()
}
return (
<div className='control__Panel'>
<div className='close__cont'>
<GrClose className='close' onClick={closeShow} />
<h3>Show</h3>
</div>
<div className='control__cont'>
{icons.map((unin)=>{
const { name, img, id} = unin
return (
<li className='control' key ={id}>
<div className='img__text'>
<img className='control__Img' src={img} />
<h4 className='control__name'>{name}</h4>
</div>
<button className='unin__button' onClick={() => remove(id)} >remove</button>
</li> )
})}
</div>
</div>
)
}
export default Show
Try stop propagation function, it should stop the propagation of the click event
<button
className='unin__button'
onClick={(e) => {
e.stopPropagation();
remove(id);
}}
>remove</button>
You have a few typos in your example. Are they in your code? If they are, you're always reach the closeShow() case in your handler, since you're using the wrong ref.
const App =()=>{
const[isShowOpen, setIsShowOpen]=React.useState(false) <<-- Here 'isShowlOpen'
const show = useRef(null) <<-- here 'S'how
function openShow(){
setIsShowOpen(true)
}
function closeShow(){ <<-- Here 'closeShowl'
setIsShowOpen(false)
}
const handleShow =(e)=>{
if(show.current&& !show.current.contains(e.target)){ <<-- here 'showl'
closeShow()
}
}
useEffect(()=>{
document.addEventListener('click',handleShow)
return () =>{
document.removeEventListener('click', handleShow)
}
},[])
return (
<div>
<div ref={show}>
<img className='taskbar__iconsRight' onClick={() =>
setIsShowOpen(!isShowOpen)}
src="https://winaero.com/blog/wp-content/uploads/2017/07/Control-
-icon.png"/>
{isShowOpen ? <Show closeShow={closeShow} />: null}
</div>
)
}

Hiding items in React

So basically I want to hide a Button when I press on it.
const Button=()=>{
const [hideButton, setHideButton]= React.useState(false)
function Button(){
setHideButton(false)
}
return(
<div>
<button onClick={setHideButton}> </button>
</div>
)
}
Initially make it true to show the button. hide it based on click.
const Button=()=>{
const [hideButton, setHideButton]= React.useState(true)
function handleClick(){
setHideButton(false)
}
return(
<div>
{hideButton && <button onClick={handleClick}>Click</button>}
</div>
)}
Or just put !(Not) before hideButton -> !hideButton and make it true on button click.
const Button=()=>{
const [hideButton, setHideButton]= React.useState(false)
function handleClick(){
setHideButton(true)
}
return(
<div>
{!hideButton && <button onClick={handleClick}>Click</button>}
<ChildComponent hideButton={hideButton} handleClick={handleClick}/>
</div>
)}
function ChildComponent ({hideButton, handleClick}) {
return (
<>
<button onClick={handleClick}>Child Button</button>
{!hideButton && <p>
This is content/paragraph which will be hidden
based on based on button click from parent component.
</p>}
</>
)
}
You can change as per your requirement i have passed function and state variable to child component. child button will toggle your content of child component and parent button hide itself once clicked.
Just conditionally render the button so it doesn't render if hideButton is true:
return(
<div>
{!hideButton && <button onClick={setHideButton}> </button>}
</div>
)
It is needed to use React hook for doing this.
const Button=()=>{
const [hideButton, setHideButton]= React.useState(false)
function hide(){
setHideButton(false)
}
return(
<div>
{!hideButton && <button onClick={hide}> </button>}
</div>
)
}
There are several things going wrong here.
This is the way your code should look (one of many ways)
const Button = () => {
const [hideButton, setHideButton] = React.useState(false)
function handleHideButton() {
setHideButton(true)
}
return (
<div>
{!hideButton ? <button onClick={handleHideButton}>Hide</button> : null}
</div>
)
}
What you were doing wrong:
const Button=()=>{
const [hideButton, setHideButton]= React.useState(false)
// You're creating this function called Button, but should be called something esle like handleHideButton
function Button(){
// You should be setting this to true when the button is clicked.
setHideButton(false)
}
return(
<div>
// here you should be calling the handleHideButton function defined above. Calling setHideButton isn't a good option here.
<button onClick={setHideButton}> </button>
</div>
)
}

TypeError: Cannot read property 'thumbnail' of undefined

I got access to rest of properties instead of one. I didnt catch what problem is.
I've tried:
this.props.data.imageLinks.thumbnail
this.props.data.imageLinks[thumbnail]
this.props.data.imageLinks["thumbnail"]
But other properties spew out correct value when I tried: {this.props.data.title}, {this.props.data.author}.
class Book extends React.Component {
render() {
console.log('prop',this.props.data.imageLinks)
return (
<div key={this.props.data.id}>
<div className="book">
<div className="book-top">
<div
className="book-cover"
style={{
width: 128,
height: 192,
backgroundImage: `url(${this.props.data.imageLinks.thumbnail})`
}}
></div>
<DropDownList/>
</div>
<div className="book-title">{this.props.data.title}</div>
<div className="book-authors">{this.props.data.author}</div>
</div>
<div>
{/*BooksAPI.getAll().then(function(res){console.log(res)})*/}
</div>
</div>
)
}
}
This object how its look
I guess, thumbnail comes after component mount. You should check first, if there is a thumbnail use thumbnail as a background image or wait until thumbnail load
I have got a new method of making this work. Basically I made a different component for rendering, and passed properties through props. I then used try and catch in the component where i map the array, and before sending the props, I try for error, if there's no error, it passes normally image links. if there's an error, I would pass image property as "", which basically means empty string in img tag. I will attach the code. its rough code, with console.logs, but main thing is inside the try and catch
main driver code
import React, { useEffect, useState } from 'react';
import Details from './bookDetails';
import './books.css'
export default function Books2() {
const [bookName, setName] = useState("a");
const [load, setLoad] = useState(false)
const [itemArray, setArray] = useState([]);
useEffect(() => {
loadData();
}, [])
async function loadData() {
setLoad(true);
// console.log(bookName)
await fetch("https://www.googleapis.com/books/v1/volumes?q=intitle:"+bookName)
.then(response => response.json())
.then(data => setArray(data.items))
setLoad(false)
}
function handleChange(event) {
// console.log(event.target.value)
setName(event.target.value)
}
function handlePress() {
loadData();
}
// console.log(itemArray)
var className = (load===true) ? "loading" : "null"
return (
<div>
<h1>Hello</h1>
<input placeholder="book name" value={bookName} onChange={handleChange} />
<button onClick={handlePress}>Search</button>
<h3 className={className}>Loading...</h3>
{/* <input placeholder="name of book" value={} /> */}
{itemArray.map(book => {
{/* console.log(book.volumeInfo.imageLinks) */}
try{
return(
<Details
key={book.id}
bookName={book.volumeInfo.title}
bookYear={book.volumeInfo.publishedDate}
bookDesc={book.volumeInfo.description}
bookLink={book.volumeInfo.infoLink}
bookImg={book.volumeInfo.imageLinks.smallThumbnail}
/>
)}
catch(err) {
<Details
key={book.id}
bookName={book.volumeInfo.title}
bookYear={book.volumeInfo.publishedDate}
bookDesc={book.volumeInfo.description}
bookLink={book.volumeInfo.infoLink}
bookImg={""}
/>
}
})}
{/* <button onClick={handlePress}>Search</button> */}
</div>
)
}
component for rendering code
import React from 'react';
export default function Details(props) {
return(
<div>
<h1>{props.bookName}</h1>
<a href={props.bookLink} target='_blank'>Link to book</a>
<img src={props.bookImg} className="img"/>
<h3>{props.bookYear}</h3>
{/* <h4>{bookPage}</h4> */}
{/* <h5>{bookRating}</h5> */}
<p>{props.bookDesc}</p>
</div>
)
}

Categories

Resources