How to set a state that is on a component? - reactjs

On reactjs, how can I setNotify again a state that is on a component?
import React, { useState } from 'react'
const NotificationError = (props) => {
const [notify, setNotify] = useState(false);
// if (props.message === "") {
// props.message = "Some Error"
// }
// if (props.message !== "") {
// setNotify(false)
// }
// if (props) {
// const [notify] = useState(true)
// }
console.log("notify.state:", props)
const closeNotification = (e) => {
console.log("Should be closing notification")
setNotify(e)
}
return (
<div className="notification is-danger" style={notify ? {display: 'none'} : {display: 'block'}}>
<button className="delete" onClick={() => closeNotification(true)}></button>
Error: {props.message}
</div>
)
}
export default NotificationError
If I use the following:
if (props) {
const [notify] = useState(true)
}
I get the error,
Line 17:26: React Hook "useState" is called conditionally. React Hooks must be called in the exact same order in every component render react-hooks/rules-of-hooks
If I use the following
if (props.message !== "") {
setNotify(true)
}
It throws the following...
Error: Too many re-renders. React limits the number of renders to
prevent an infinite loop.
Simply, I am not understanding this. Can you please help? :(

Rewrite you logic to something like:
const NotificationError = (props) => {
const [notify, setNotify] = useState(false);
useEffect(() => {
if (props.message === "") {
props.setMessage('Some Error');
}
setNotify(false);
}, [props.message]);
return (
<div
className="notification is-danger"
style={notify ? { display: "none" } : { display: "block" }}
>
<button className="delete" onClick={() => setNotify(true)}></button>
Error: {props.message}
</div>
);
};
Props are immutable so if you want to change a message you should pass a callback.
Also, take a read about Rules of Hooks.

Use a useEffect hook for such cases. It works similar to componentDidMount and componentDidUpdate in class component. It means the function that you pass as the first argument of useEffect hook triggers the first time when your component mounts and then every time any of the elements of the array changes that you pass as the second argument.
Here is the code example:
const NotificationError = (props) => {
const [notify, setNotify] = useState(false);
useEffect(() => {
if(props.message != '') {
setNotify(false);
}
}, [props.message])
const closeNotification = (e) => {
console.log("Should be closing notification")
setNotify(e)
}
return (
<div className="notification is-danger" style={notify ? {display: 'none'} : {display: 'block'}}>
<button className="delete" onClick={() => closeNotification(true)}></button>
Error: {props.message}
</div>
)
}

Related

why useRef current value , isn't sharing trough custom hook?

I wanted to calculate the user scroll height , so I created a custom hook. and I wanted to share this value to another component. but it doesnt work.
code:
const useScroll = () => {
let scrollHeight = useRef(0);
const scroll = () => {
scrollHeight.current =
window.pageYOffset ||
(document.documentElement || document.body.parentNode || document.body)
.scrollTop;
};
useEffect(() => {
window.addEventListener("scroll", scroll);
return () => {
window.removeEventListener("scroll", () => {});
};
}, []);
return scrollHeight.current;
};
export default useScroll;
the value is not updating here.
but if I use useState here , it works. but that causes tremendous amount of component re-rendering. can you have any idea , how its happening?
Since the hook won't rerender you will only get the return value once. What you can do, is to create a useRef-const in the useScroll hook. The useScroll hook returns the reference of the useRef-const when the hook gets mounted. Because it's a reference you can write the changes in the useScroll hook to the useRef-const and read it's newest value in a component which implemented the hook. To reduce multiple event listeners you should implement the hook once in the parent component and pass the useRef-const reference to the child components. I made an example for you.
The hook:
import { useCallback, useEffect, useRef } from "react";
export const useScroll = () => {
const userScrollHeight = useRef(0);
const scroll = useCallback(() => {
userScrollHeight.current =
window.pageYOffset ||
(document.documentElement || document.body.parentNode || document.body)
.scrollTop;
}, []);
useEffect(() => {
window.addEventListener("scroll", scroll);
return () => {
window.removeEventListener("scroll", scroll);
};
}, []);
return userScrollHeight;
};
The parent component:
import { SomeChild, SomeOtherChild } from "./SomeChildren";
import { useScroll } from "./ScrollHook";
const App = () => {
const userScrollHeight = useScroll();
return (
<div>
<SomeChild userScrollHeight={userScrollHeight} />
<SomeOtherChild userScrollHeight={userScrollHeight} />
</div>
);
};
export default App;
The child components:
export const SomeChild = ({ userScrollHeight }) => {
const someButtonClickHandlerWhichPrintsUserScrollHeight = () => {
console.log("userScrollHeight from SomeChild", userScrollHeight.current);
};
return (
<div style={{
width: "100vw",
height: "100vh",
backgroundColor: "aqua"
}}>
<h1>SomeChild 1</h1>
<button onClick={() => someButtonClickHandlerWhichPrintsUserScrollHeight()}>Console.log userScrollHeight</button>
</div>
);
};
export const SomeOtherChild = ({ userScrollHeight }) => {
const someButtonClickHandlerWhichPrintsUserScrollHeight = () => {
console.log("userScrollHeight from SomeOtherChild", userScrollHeight.current);
};
return (
<div style={{
width: "100vw",
height: "100vh",
backgroundColor: "orange"
}}>
<h1>SomeOtherChild 1</h1>
<button onClick={() => someButtonClickHandlerWhichPrintsUserScrollHeight()}>Console.log userScrollHeight</button>
</div>
);
};
import { useRef } from 'react';
import throttle from 'lodash.throttle';
/**
* Hook to return the throttled function
* #param fn function to throttl
* #param delay throttl delay
*/
const useThrottle = (fn, delay = 500) => {
// https://stackoverflow.com/a/64856090/11667949
const throttledFn = useRef(throttle(fn, delay)).current;
return throttledFn;
};
export default useThrottle;
then, in your custom hook:
const scroll = () => {
scrollHeight.current =
window.pageYOffset ||
(document.documentElement || document.body.parentNode || document.body)
.scrollTop;
};
const throttledScroll = useThrottle(scroll)
Also, I like to point out that you are not clearing your effect. You should be:
useEffect(() => {
window.addEventListener("scroll", throttledScroll);
return () => {
window.removeEventListener("scroll", throttledScroll); // remove Listener
};
}, [throttledScroll]); // this will never change, but it is good to add it here. (We've also cleaned up effect)

React state doesnt update, drilling seems ok though

I've got a modal that I want to be able to auto-shut itself using a drilled function. The console.log does work, but the state isn't actually updating. What am I doing wrong? Triggering the state via the dev tools works fine, so it's not the state itself. Is drilling within a component the problem?
index.js:
export default function Home() {
const [modalOpen, setModalOpen] = useState(false)
const handleModalOpen = () => {
console.log ("Setting modal to true")
setModalOpen (true)
}
const handleModalClose = () => {
console.log ("Setting modal to false")
setModalOpen (false)
}
// all the normal app body
{modalOpen ?
(<Modal handleModalClose={handleModalClose} height='30vh'>
<h4>Thank you for your contact request.</h4>
<h4>Keep in mind that this is only a demo website, not an actual business.</h4>
</Modal>): null}
</div>
)
}
Modal.js:
import { createPortal } from "react-dom";
import { useEffect, useState } from "react";
import styles from '../styles/Modal.module.css'
const Backdrop = (props) => {
return <div onClick={() => props.handleModalClose()} className={styles.backdrop} />
}
const Message = (props) => {
let width = '70vw'
let height = '80vh'
if (props.width) width = props.width
if (props.height) height = props.height
return (
<div style={{ width: width, height: height }} className={styles.message}>
{props.children}
</div>
)
}
const Modal = (props) => {
const [backdropDiv, setBackdropDiv] = useState(null)
const [modalDiv, setModalDiv] = useState(null)
useEffect(() => {
if (typeof (window) !== undefined) {
let backdropDiv = document.getElementById('backdrop')
setBackdropDiv(backdropDiv)
let modalDiv = document.getElementById('modal')
setModalDiv(modalDiv)
}
}, [])
return (
<>
{backdropDiv !== null && modalDiv !== null ? (
<>
{createPortal(<Backdrop handleModalClose = {props.handleModalClose} />, backdropDiv)}
{createPortal(<Message children={props.children} width={props.width} height={props.height} />, modalDiv)}
</>
) : null
}
</>
)
}
export default Modal

How to Use componentDidMount() in Functional Component to execute a function

I have a functional component which had a button to call a method in it. Now i want to get rid of the button and call that method without any actions once the component loads.
I am making API calls inside this method and passing on the results to another component.
Also I am replacing the button with a progress bar meaning when a "search" is taking place, display the progress bar but I am having no luck. What am I doing wrong ?
export const Search = (props) => {
const { contacts, setContacts, onSearchComplete } = props;
const [msgBox, setMsgBox] = useState(null);
const [loading, setLoading] = useState(false);
const onSearch = async () => {
setLoading(true);
const emails = contacts
.filter(x => x.isChecked)
.map(item => item.emailAddress);
try {
const searchResults = await AppApi.searchMany(emails);
let userList = [];
for (let i = 0; i < searchResults.length; i++) {
//process the list and filter
}
userList = [...userList, ..._users];
}
onSearchComplete(userList); //passing the results.
} catch (err) {
console.log({ err });
setMsgBox({ message: `${err.message}`, type: 'error' });
}
setLoading(false);
}
return (
<Box>
{loading ? <LinearProgress /> : <Box>{msgBox && (<a style={{ cursor: 'pointer' }} onClick={() => setMsgBox(null)} title="Click to dismiss"><MessageBox type={msgBox.type || 'info'}>{msgBox.message}</MessageBox></a>)}</Box>}
/*{onSearch()}*/ // function that was executed onclick.
</Box>
);
}
You will want to use the useEffect hook with an empty dependency array which will make it act as componentDidMount source.
export const Search = (props) => {
const { contacts, setContacts, onSearchComplete } = props;
const [msgBox, setMsgBox] = useState(null);
const [loading, setLoading] = useState(false);
const onSearch = async () => {
...
}
useEffect(() => {
onSearch();
}, []);
return (
<Box>
{loading ? <LinearProgress /> : <Box>{msgBox && (<a style={{ cursor: 'pointer' }} onClick={() => setMsgBox(null)} title="Click to dismiss"><MessageBox type={msgBox.type || 'info'}>{msgBox.message}</MessageBox></a>)}</Box>}
</Box>
);
}

Error in Map function: Cannot read property of 'map' undefined

Error in Map function: Cannot read property of 'map' undefined
While using the map function, I went through each documentation but and also copied the same exact code whjich was actual running on the github but was unable to run in the local server. It's always saying the same and same problem everytime as the map function is underdefined.
Please get the following code which I used in my project.
import React, { useState, useEffect } from "react";
import axios from "./axios";
import "./Row.css";
import Youtube from "react-youtube";
const baseImgUrl = "https://image.tmdb.org/t/p/original";
function Row({ title, fetchUrl, isLargeRow }) {
const [movies, setMovies] = useState([]);
const [trailerUrl, setTrailerUrl] = useState("");
// Options for react-youtube
const opts = {
height: "390",
width: "100%",
playerVars: {
autoplay: 1,
},
};
useEffect(() => {
async function fetchData() {
const request = await axios.get(fetchUrl);
setMovies(request.data.results);
return request;
}
fetchData();
}, [fetchUrl]);
const handleClick = async (movie) => {
if (trailerUrl) {
setTrailerUrl("");
} else {
let trailerurl = await axios.get(
`/movie/${movie.id}/videos?api_key=somekeyhere`
);
setTrailerUrl(trailerurl.data.results[0]?.key);
}
};
return (
<div className="row">
<h2>{title}</h2>
<div className="row_posters">
{movies.map(
(movie) =>
movie.backdrop_path !== null && (
<img
className={`row_poster ${isLargeRow && "row_posterLarge"}`}
src={`${baseImgUrl}${
isLargeRow ? movie.poster_path : movie.backdrop_path
}`}
alt={movie.name}
key={movie.id}
onClick={() => handleClick(movie)}
/>
)
)}
</div>
{trailerUrl && <Youtube videoId={trailerUrl} opts={opts} />}
</div>
);
}
export default Row
Eventhough you set [] for movies as initial state using useState it is reassigned by the response of your request.
Make sure request.data.results is not undefined

Error importing custom hooks in React 16.7.0-alpha

Been playing around with the new hook RFC in react and can't get my custom hook working properly. Not sure if what is going on is on my end or a bug with the React alpha itself.
I've been trying to create a click outside hook. I was able to get it working with this code.
./dropdown_builtin_hooks
const DropDownWrapper = React.memo(props => {
const { user, className } = props;
const ref = useRef(null);
const [active, setActive] = useState(false);
useEffect(() => {
const handleDOMClick = event => {
console.log(event.target);
if (active && !!ref && !(event.target === ref.current || ref.current.contains(event.target))) {
console.log("Clicked outside of wrapped component");
setActive(false);
}
};
window.addEventListener("click", handleDOMClick);
return () => {
window.removeEventListener("click", handleDOMClick);
};
});
const handleDropDown = (): void => {
setActive(true);
};
return (
<div ref={ref} className={className} >
<Toggler onClick={handleDropDown}>
{active ? (
<StyledDropUpArrow height="1.5em" filled={false} />
) : (
<StyledDropDownArrow height="1.5em" filled={false} />
)}
</Toggler>
{active && (
<DropDown/>
)}
</div>
);
});
export default DropDownWrapper;
However when I try to wrap this in a custom hook that I can reuse and import it into my component. Something like this...
./hooks
export function useClickedOutside<RefType = any>(
initialState: boolean = false,
): [React.RefObject<RefType>, boolean, Function] {
const ref = useRef(null);
const [active, setActive] = useState(initialState);
useEffect(() => {
const handleDOMClick = event => {
console.log(event.target);
if (active && !!ref && !(event.target === ref.current || ref.current.contains(event.target))) {
console.log("Clicked outside of wrapped component");
setActive(false);
}
};
window.addEventListener("click", handleDOMClick);
return () => {
window.removeEventListener("click", handleDOMClick);
};
});
return [ref, active, setActive];
}
./dropdown_custom_hook
const DropDownWrapper = React.memo(props => {
const { user, className } = props;
const [ref, active, setActive] = useClickedOutside(false);
const handleDropDown = (): void => {
setActive(true);
};
return (
<div ref={ref} className={className} >
<Toggler onClick={handleDropDown}>
{active ? (
<StyledDropUpArrow height="1.5em" filled={false} />
) : (
<StyledDropDownArrow height="1.5em" filled={false} />
)}
</Toggler>
{active && (
<DropDown/>
)}
</div>
);
});
export default DropDownWrapper;
At first I figured it was an issue with hot reloading, but after removing that I am still getting this error:
Uncaught Error: Hooks can only be called inside the body of a function
component.
I only get this issue when I use imports and exports. If I copy the same custom hook function and paste it above my component it works properly.
I assume I'm doing something dumb or haven't read the docs well enough.
Cheers

Resources