node.current is not a function - reactjs

I am trying to create a function close modal when click outside but I am keep getting this error:
TypeError: node.current is not a function
Here is my following code in MemberCard.js:
const [modalStatus, setModalStatus] = useState(false);
const node = useRef(null);
const openModal = () => {
setModalStatus(!modalStatus);
};
const handleClick = (e) => {
if (node.current(e.target)) {
return;
}
// outside click
setModalStatus(false);
};
useEffect(() => {
document.addEventListener("mousedown", handleClick);
return () => {
document.removeEventListener("mousedown", handleClick);
};
}, []);
return (
<div className="member-card">
<div className="member-edit" onClick={openModal}>
<Symlink />
</div>
{modalStatus && (
<TeamStatusModal
active={modalStatus}
ref={node}
tab={tab}
member={member}
/>
)}
...
}
Here is my modal that I open after click:
const TeamStatusModal = (props) => {
const { active, tab, member, ref } = props;
console.log(ref);
return (
<div
className={`team-status-modal-container ${active ? "ACTIVE_CLASS" : ""}`}
>
<button className="status">
<ProfileIcon /> <span>View Profile</span>
</button>
<hr />
<button className="status">
<MessageIcon /> <span>Message Me</span>
</button>
</div>
);
};
How can I implement this feature?

In react, there are some good libraries that can help you with modals, one of them is called react-modal, you can give it a check.
If you want to implement a modal by yourself, we can follow some steps.
First we need to define a context, because the modal state needs to be accesed by more than one component or page in your app.
In the context, you could store the modal in a isModalOpen state, and add functions to manipulate it, such as openModal and closeModal. It really depends on the amount of features you want to add to this implementation.
Finally, you make the context globally accessible wrapping your app around a provider.
an example implementation
const ModalContext = createContext({})
export const ModalContextProvider = ({children}) => {
const [isModalOpen, setIsModalOpen] = useState(false)
const toggleModalState = () => {
setIsModalOpen(state => !state)
}
return <ModalContext.Provider value={{isModalOpen, toggleModalState}}>{children}<ModalContext.Provider>
}
export const useModal = () => {
return useContext(ModalContext)
}
Now the modal will be available globally

Related

Rerender FunctionComponent when prop gets set with same value

I'm currently trying to implement some kind of modal (I'm aware that there is a bunch of libraries for that). The real code is much more complex because of a bunch of animation stuff, but it boils down to this (also see this Stackblitz):
const Modal: React.FunctionComponent<{ visible?: boolean }> = ({
visible,
}) => {
const [isVisible, setIsVisible] = React.useState(visible);
React.useEffect(() => setIsVisible(visible), [visible]);
if (!isVisible) {
return null;
}
return (
<div>
I'm visible <button onClick={() => setIsVisible(false)}>Close</button>
</div>
);
};
const App: React.FunctionComponent = () => {
const [showModal, setShowModal] = React.useState(false);
return (
<div>
<button onClick={() => setShowModal(true)}>Show modal</button>
<Modal visible={showModal} />
</div>
);
}
The first time the parent component sets the visible property it works without a problem. But when I close the "modal" and want to set the property again it does not show up again, because the property from the point of view of the "modal" didn't actually change.
Is there a way to always rerender a FunctionComponent when a property gets touched even if the value didn't change?
Have you try this:
const Modal: React.FunctionComponent<{ visible?: boolean }> = ({
visible,
setIsVisible
}) => {
if (!isVisible) {
return null;
}
return (
<div>
I'm visible <button onClick={() => setIsVisible(false)}>Close</button>
</div>
);
};
const App: React.FunctionComponent = () => {
const [showModal, setShowModal] = React.useState(false);
return (
<div>
<button onClick={() => setShowModal(true)}>Show modal</button>
<Modal visible={showModal} setIsVisible={setShowModal} />
</div>
);
}
It will then re-render also your parent component, because they share the same state
you're trying changing the value in the child element, this does not get reflected in the parent
My suggestion is that to close the modal from parent itself
which reduces the code complexity and there is only single source of data here
export const Modal: React.FunctionComponent<{ visible?: boolean , onClose }> = ({
visible,onClose
}) => {
const [isVisible, setIsVisible] = React.useState(visible);
React.useEffect(() => setIsVisible(visible), [visible]);
if (!isVisible) {
return null;
}
return (
<div>
I'm visible <button onClick={() => onClose()}>Close</button>
</div>
);
};
<Modal visible={showModal} onClose={()=>setShowModal(false)} />
working example https://stackblitz.com/edit/react-ts-heiqak?file=Modal.tsx,App.tsx,index.html

Using state and props between React components

This is my project for business card app.
I have a problem with using state and props between components.
Component tree looks like this.
Editor <- CardEditForm <- ImageFileInput
The url state is in the Editor component. There is a function to update state and give it as props to child components. When I upload an image on ImageFileInput component, url data goes up until the editor component and then using setUrl to url be updated. And then I gave url to CardEditorForm component.
The problem is this, In cardEditorForm, when it comes to using url props, I can't get the updated url. Only gets the initial state. I really need to get an updated url. I also tried to use setTimeout() to get the updated url. But it doesn't work either. What can I do?..
It's my first time to ask a question on stack overflow. Thank you for helping the newb.
Here is the code.
editor.jsx
const Editor = ({ cards, deleteCard, createOrUpdateCard }) => {
const [url, setUrl] = useState('');
const updateUrl = (src) => {
setUrl(src);
};
return (
<section className={styles.editor}>
<h1 className={styles.title}>Card Maker</h1>
{Object.keys(cards).map((key) => (
<CardEditForm
key={key}
card={cards[key]}
onDelete={deleteCard}
onUpdate={createOrUpdateCard}
updateUrl={updateUrl}
url={url}
/>
))}
<CardAddForm onAdd={createOrUpdateCard} updateUrl={updateUrl} url={url} />
</section>
);
};
card_edit_form.jsx
const CardEditForm = ({ card, onDelete, onUpdate, updateUrl, url }) => {
// ...
const changeUrl = () => {
setTimeout(() => {
const newCard = {
...card,
fileURL: url,
};
onUpdate(newCard);
}, 4000);
};
return (
<form className={styles.form}>
// ...
<div className={styles.fileInput}>
<ImageFileInput updateCard={changeUrl} updateUrl={updateUrl} />
</div>
// ...
</form>
);
};
export default CardEditForm;
image_file_input.jsx
const ImageFileInput = ({ updateUrl, updateCard }) => {
const [image, setImage] = useState('');
const upload = new Upload();
const onUpload = (e) => {
e.preventDefault();
upload.uploadImage(image).then((data) => updateUrl(data));
updateCard(e);
};
return (
<div>
<input type="file" onChange={(e) => setImage(e.target.files[0])} />
<button name="fileURL" onClick={onUpload}>
image
</button>
</div>
);
};

React: Assigning array to variable using useState to pass into modal

I made a JSON file for the upcoming NFL season. In this component I have a working fetch method that gets my data, and I've named the variable "squads". Now I want to press a button to filter out the selected team's schedule and display it in a modal. I've hard coded my button in this example. My modal component works fine, and I have {props.children} in the modal's body to accept my data.
In the code below you'll see that I'm trying to assign the filtered team to the selectedTeam variable using useState. The error message I'm getting just says my variables are undefined.
import React, { useState, useEffect } from "react";
import Modal from "./Components/Modal";
export default function App() {
const [show, setShow] = useState(false);
const [title, setTitle] = useState("");
const [squads, setSquads] = useState([]);
const [modalTitleBackground, setModalTitleBackground] = useState("");
const [image, setImage] = useState("");
const [selectedTeam, setSelectedTeam] = useState([]);
const url = "../nfl2021.json";
const fetchData = async () => {
try {
const response = await fetch(url);
const data = await response.json();
setSquads(data.teams);
} catch (error) {
console.log(error);
}
};
useEffect(() => {
fetchData();
}, []);
// const filterTeam = (team) => {
// const theTeam = squads.filter((squad) => squad.name === team);
// setModalTitleBackground(theTeam[0].topBG);
// // setTitle(theTeam[0].name);
// setNickname(theTeam[0].nickname);
// setImage(`./images/${theTeam[0].img}`);
// setShow(true);
// };
const filterTeam = (team) => {
setSelectedTeam(squads.filter((squad) => squad.name === team));
console.log(selectedTeam);
setTitle(selectedTeam[0].name);
setModalTitleBackground(selectedTeam[0].topBG);
setImage(`./images/${selectedTeam[0].img}`);
setShow(true);
};
return (
<div className="App">
<button onClick={() => filterTeam("New England Patriots")}>
Show Modal
</button>
<button onClick={() => filterTeam("Green Bay Packers")}>
Show Modal 2
</button>
<button onClick={() => filterTeam("Cincinnati Bengals")}>
Show Modal 3
</button>
<Modal
image={image}
title={title}
backgroundColor={modalTitleBackground}
onClose={() => setShow(false)}
show={show}
>
<p>
This is the modal body using props.children in the modal component.
</p>
<p>The {title} 2021 schedule.</p>
{selectedTeam[0].schedule.map((schedule, index) => {
return (
<p>
Week {index + 1}: The {selectedTeam[0].nickname} play the{" "}
{selectedTeam[0].schedule[index].opponent}.
</p>
);
})}
</Modal>
</div>
);
}
1- In react, the state is set asynchronously. selectedTeam is not set until next render.
2- You can use find instead of filter and get rid of array access.
const [selectedTeam, setSelectedTeam] = useState({schedule: []});
...
const filterTeam = (team) => {
let temp = squads.find((squad) => squad.name === team);
setSelectedTeam(temp);
console.log(temp);
setTitle(temp.name);
setModalTitleBackground(temp.topBG);
setImage(`./images/${temp.img}`);
setShow(true);
};
...
{selectedTeam.schedule.map((match, index) => {
return (
<p>
Week {index + 1}: The {selectedTeam.nickname} play the {match.opponent}.
</p>
);
})}

Build a dropdown with an eventHandler. How to structure hooks + eventhandler

I'm building a simple dropdown with react and functional components. On strange behavior, I've run into is the way we have to think about conjures and state. This is a simplified version of my component:
export default function App() {
const [show, setShow] = useState(false);
const selectElement = useRef(null);
const handleToggle = (e) => {
if (selectElement) {
if (!selectElement.current.contains(e.target)) {
setShow(!show);
}
}
};
useEffect(() => {
document.addEventListener("click", handleToggle, false);
return () => document.removeEventListener("click", handleToggle, false);
}, []);
return (
<div className="App">
<div ref={selectElement} className="comp">
<h1 onClick={() => setShow(!show)}>Select</h1>
{show && (
<div>
<div>Inner 1</div>
<div>Inner 2</div>
</div>
)}
</div>
</div>
);
}
This component behaves wrong and it's not possible to toggle the dropdown correctly. The effect handler is registered on the first render and encloses the state of the first render (if I'm not wrong here). The registered function will not receive state updates. This is causing the error.
I'm not really sure what's the best way to fix this. Currently, I decided to simply remove the dependency array from the useEffect hook so that the effect handler is created and destroyed on every render/cleanup.
I've also created a Sandbox so my issue becomes more tangible.
I think this code will help you to solve your problem.
export default function App() {
const [show, setShow] = useState(false);
const selectElement = useRef(null);
const handleToggle = (e) => {
if (selectElement) {
if (!selectElement.current.contains(e.target)) {
setShow(false);
document.removeEventListener("click", handleToggle, false);
}
}
};
const handleClick = (e) => {
setShow(true)
document.addEventListener("click", handleToggle, false);
};
return (
<div className="App">
<div ref={selectElement} className="comp">
<h1 onClick={handleClick}>Select</h1>
{show && (
<div>
<div>Inner 1</div>
<div>Inner 2</div>
</div>
)}
</div>
</div>
);
}

ref doesn't have a value inside event handlers

Aimed functionality:
When a user clicks a button, a list shows. When he clicks outside the list, it closes and the button should receive focus. (following accessibility guidelines)
What I tried:
const hideList = () => {
// This closes the list
setListHidden(true);
// This takes a ref, which is forwarded to <Button/>, and focuses it
button.current.focus();
}
<Button
ref={button}
/>
Problem:
When I examined the scope of hideList function, found that ref gets the proper reference to button every where but inside the click event handler, it's {current: null}.
The console outputs: Cannot read property 'focus' of null
Example:
https://codepen.io/moaaz_bs/pen/zQjoLK
- click on the button and then click outside and review the console.
Since you are already using hooks in your App, the only change you need to make is to use useRef instead of createRef to generate a ref to the list.
const Button = React.forwardRef((props, ref) => {
return (
<button
onClick={props.toggleList}
ref={ref}
>
button
</button>
);
})
const List = (props) => {
const list = React.useRef();
handleClick = (e) => {
const clickIsOutsideList = !list.current.contains(e.target);
console.log(list, clickIsOutsideList);
if (clickIsOutsideList) {
props.hideList();
}
}
React.useEffect(function addClickHandler() {
document.addEventListener('click', handleClick);
}, []);
return (
<ul ref={list}>
<li>item</li>
<li>item</li>
<li>item</li>
</ul>
);
}
const App = () => {
const [ListHidden, setListHidden] = React.useState(true);
const button = React.useRef();
const toggleList = () => {
setListHidden(!ListHidden);
}
const hideList = () => {
setListHidden(true);
button.current.focus();
}
return (
<div className="App">
<Button
toggleList={toggleList}
ref={button}
/>
{
!ListHidden &&
<List hideList={hideList} />
}
</div>
);
}
ReactDOM.render(<App />, document.getElementById('root'));
Working demo
The reason that you need it is because on every render of your Functional component, a new ref will be generated if you make use of React.createRef whereas useRef is implemented such that it generates a ref when its called the first time and returns the same reference anytime in future re-renders.
P.S. A a thumb rule, you can say that useRef should be used when you
want to have refs within functional components whereas createRef
should be used within class components.
Create your ref
this.button = React.createRef();
Add Ref to your DOM element
ref={this.button}
Use the Ref as per requirement
this.button.current.focus();
Complete code using forwarding-refs
const Button = React.forwardRef((props, ref) => {
return (
<button
onClick={props.toggleList}
ref={ref}
>
button
</button>
);
})
const List = (props) => {
const list = React.createRef();
handleClick = (e) => {
const clickIsOutsideList = !list.current.contains(e.target);
if (clickIsOutsideList) {
props.hideList();
}
}
React.useEffect(function addClickHandler() {
document.addEventListener('click', handleClick);
return function clearClickHandler() {
document.removeEventListener('click', handleClick);
}
}, []);
return (
<ul ref={list}>
<li>item</li>
<li>item</li>
<li>item</li>
</ul>
);
}
const button = React.createRef();
const App = () => {
const [ListHidden, setListHidden] = React.useState(true);
const toggleList = () => {
setListHidden(!ListHidden);
}
const hideList = () => {
setListHidden(true);
console.log(button)
button.current.focus();
}
return (
<div className="App">
<Button
toggleList={toggleList}
ref={button}
/>
{
!ListHidden &&
<List hideList={hideList} />
}
</div>
);
}
ReactDOM.render(<App />, document.getElementById('root'));

Resources