Reactjs update state after call component in functional component - javascript

I created a component called Alertify.js
import React, {useEffect, useState} from "react";
import { Alert } from 'reactstrap';
function Alertify(props){
const [show, setShow] = useState(props.show);
useEffect(
() => {
let timer = setTimeout(() => setShow(false), 3000);
return () => {
clearTimeout(timer);
};
},
[]
);
return (
<Alert color={props.color} className={show ? 'float active' : 'float'}>{props.text}</Alert>
)
}
export default Alertify;
And used in index.js
import Alertify from "../Component/Alertify";
const [showAlert, setShowAlert] = useState(false);
return...
<Alertify text={'hello world'} color={'danger'} show={showAlert}/>
And it will show this alert after a condition is true:
if(condition){
setShowAlert(true)
}
But something is wrong and not show alert on condition, and I'm newbie to reactjs, any idea how to fix this?
All I want is show alert after condition is true, then hide after 3 seconds.
Also it show if I remove useEffect but before condition true, and also not hiding.

Try the following
const [show, setShow] = useState(false);
useEffect(() => {
if (props.show) {
setShow(true)
}
},[props.show])
You can leave your existing useEffect to clear after 3 seconds as is.
EDIT:
Here's a modified approach, your Alertify component looks like so
import React, { useEffect, useState } from "react";
import { Alert } from "reactstrap";
function Alertify(props: {
show: any;
color: string;
text: boolean | React.ReactChild | React.ReactFragment | React.ReactPortal;
setShowAlert: (value: boolean) => void;
}) {
const [show, setShow] = useState(false);
useEffect(() => {
let timer = setTimeout(() => {
return setShow(false);
}, 3000);
return () => {
clearTimeout(timer);
};
});
useEffect(() => {
if (props.show) {
setShow(true);
props.setShowAlert(false);
}
}, [props.show, props.setShowAlert]);
if (show) {
return (
<Alert color={props.color} className={show ? "float active" : "float"}>
{props.text}
</Alert>
);
}
return null;
}
export default Alertify;
Your calling component then looks like so
import "./styles.css";
import Alertify from "./Alertify";
import { useState } from "react";
export default function App() {
const [showAlert, setShowAlert] = useState(false);
return (
<>
<div className="App">
<Alertify
text={"hello world"}
color={"danger"}
show={showAlert}
setShowAlert={setShowAlert}
/>
</div>
<button onClick={() => setShowAlert(true)}>show alert</button>
</>
);
}
Here's the codesandbox link https://codesandbox.io/s/alertify-stackoverflow-x096t

Try this code. You can control rendering Alertify component in index.js. If showAlert value is true, React render Alertify component. When setTimeout executed, showAlert value will be false which means React unmount Alertify component. This is like show and hide effect as what you need.
// index.js
import Alertify from "../Component/Alertify";
const [showAlert, setShowAlert] = useState(false);
if (condition( {
setShowAlert(true); // This makes Alertify component mount immediatly.
setTimeout(setShowAlert(false),3000); // This makes Alertify component unmount in 3,000ms.
}
return...
{showAlert && <Alertify text={'hello world'} color={'danger'}/>}
Therefore, you don't need to use useEffect to make this component hide.
// Alertify.js
import React, {useEffect, useState} from "react";
import { Alert } from 'reactstrap';
function Alertify(props){
return (
<Alert color={props.color} className={'float active'}>{props.text
</Alert>
)
};
export default Alertify;

Related

React elements are removed on state update

I'm supposed to have a modal appear with an image in it. There are next and previous buttons which controls which image you are currently viewing. The modal is rendered in a portal. That in itself is working correctly. However, when I add children, and those childrens are updated, the modal only (not the portal) gets removed from the flow. In the React DevTools, the "isOpen" state of the modal is still set to true. I am using React 17.0.2 with NextJS 12.0.4 and Styled Components 5.3.3.
I have tried:
memoizing my components (as you can see there are some remnants of those trials) but this did not work
extracting the state of the modal to the parent and passing it as props and it didn't work either
I know there must be something wrong that I'm doing here so if you could help me find it that would be much appreciated!
Here is the controller where the modal is rendered:
import { FC, MouseEventHandler, useEffect, useState } from "react";
import { Photo } from "services/Images/Images.interfaces";
import { useGetNextPhoto, useGetPhotos, useGetPreviousPhoto } from "state";
import SlideshowContextProvider from "./Context/SlideshowContext";
import SlideShowModal from "./SlideShowModal";
const SlideshowController: FC = () => {
const photos = useGetPhotos();
const [currentlyViewedPhoto, setCurrentlyViewedPhoto] = useState<Photo | null>(null);
const nextPhoto = useGetNextPhoto(currentlyViewedPhoto?.id);
const previousPhoto = useGetPreviousPhoto(currentlyViewedPhoto?.id);
const onPreviousRequest: MouseEventHandler<HTMLButtonElement> = (event) => {
event.preventDefault();
setCurrentlyViewedPhoto(previousPhoto);
};
const onNextRequest: MouseEventHandler<HTMLButtonElement> = async (event) => {
event.preventDefault();
setCurrentlyViewedPhoto(nextPhoto);
};
useEffect(() => {
setCurrentlyViewedPhoto(photos[0]);
}, [photos]);
return (
<SlideshowContextProvider
currentlyViewing={currentlyViewedPhoto}
onNextSlideRequest={onNextRequest}
onPreviousSlideRequest={onPreviousRequest}
>
<SlideShowModal />
</SlideshowContextProvider>
);
};
export default SlideshowController;
The SlideshowModal:
import { Modal } from "components";
import { FC } from "react";
import SlideshowControlBar from "./SlideshowControlBar";
import SlideshowImage from "./SlideshowImage";
const SlideShowModal: FC = () => {
return (
<Modal uniqueKey="slideshow">
<SlideshowImage />
<SlideshowControlBar />
</Modal>
);
};
export default SlideShowModal;
The modal in itself:
import Portal from "components/Portal/Portal";
import { FC, useEffect, useMemo, useState } from "react";
import { useRegisterModal } from "state";
import styled from "styled-components";
import useWindowScrollLock from "./hook/UseWindowScrollLock";
interface Props {
uniqueKey: string;
isBackgroundOpaque?: boolean;
}
... Styled elements
const Modal: FC<Props> = ({ uniqueKey, isBackgroundOpaque = true, children }) => {
const [isOpen, setIsOpen] = useState(false);
const open = () => setIsOpen(true);
const close = () => setIsOpen(false);
const register = useRegisterModal(uniqueKey);
const isModalOpen = useMemo(() => isOpen, [isOpen]);
useEffect(() => {
register({ open, close });
}, [register]);
useWindowScrollLock(isModalOpen);
return isModalOpen ? (
<Portal>
<Container>
<InnerModal>
<Close onClick={close}>X</Close>
{children}
</InnerModal>
</Container>
<Background onClick={close} opaque={isBackgroundOpaque} />
</Portal>
) : null;
};
export default Modal;

i have an error in my code react js project

The error is, TypeError: Cannot read properties of undefined (reading 'results') please help me i want to finish the project. I have used API from movie database inorder to fetch data of the movies. but in so doing i get the errors, you cannot see them in the code editor, i use visual studio code but when i save, it doesn't show the results and it has challenged me. I am going to quit the project because i can no longer go further more. I need some help. Yes i can understand that i am new in this react but i have practiced it for now 3 months.
I have not posted other codes but if in need i will have to post the whole project. Some help please and i will appriciate.
Home.js:27 Uncaught TypeError: Cannot read properties of undefined (reading 'results')
at Home (Home.js:27)
at renderWithHooks (react-dom.development.js:14985)
at mountIndeterminateComponent (react-dom.development.js:17811)
at beginWork (react-dom.development.js:19049)
home.js
import React, { useState, useEffect } from "react"; //rafce
import reactDom from "react-dom";
//Config
import { POSTER_SIZE, BACKDROP_SIZE, IMAGE_BASE_URL } from "../config";
//components
import HeroImage from "./HeroImage";
//hook
import { useHomeFetch } from "../hooks/useHomeFetch";
//image
import NoImage from "../images/no_image.jpg";
import Grid from "./Grid";
import Thumb from "./Thumb";
import Spinner from "./Spinner";
import SearchBar from "./SearchBar";
const Home = () => {
const { state, loading, error, setSearchTerm } = useHomeFetch();
console.log(state);
return (
<>
{state.results ? (
<HeroImage
image={`${IMAGE_BASE_URL}${BACKDROP_SIZE}${state.results[0].backdrop_path}`}
title={state.results[0].original_title}
text={state.results[0].overview}
/>
) : null}
<SearchBar setSearchTerm={setSearchTerm} />
<Grid header="Popular Movies">
{state.results.map((movie) => (
<Thumb
key={movie.id}
clickable
image={
movie.poster_path
? IMAGE_BASE_URL + POSTER_SIZE + movie.poster_path
: NoImage
}
movieId={movie.id}
/>
))}
</Grid>
<Spinner />
</>
);
};
export default Home;
useHomeFetch.js
import { useState, useEffect, useRef } from "react";
import reactDom from "react-dom";
//API
import API from "../API";
//initial state
const initialState = {
page: 0,
results: [],
total_pages: 0,
total_results: 0,
};
export const useHomeFetch = () => {
const [searchTerm, setSearchTerm] = useState("");
const [state, setState] = useState();
const [loading, setLoading] = useState(false);
const [error, setError] = useState(false);
const fetchMovies = async (page, searchTerm = "") => {
try {
setError(false);
setLoading(true);
const movies = await API.fetchMovies(searchTerm, page);
setState((prev) => ({
...movies,
results:
page > 1 && prev ? [...prev.results, ...movies.results] : [...movies.results],
}));
} catch (error) {
setError(true);
}
setLoading(false);
};
//initial ans search
useEffect(() => {
// setState(initialState);
fetchMovies(1);
}, []);
return { state, loading, error, setSearchTerm };
};
The first time when you setState the prev is undefined, but you don't have check for that
Try something like that:
results: page > 1 && prev ? [...prev.results, ...movies.results] : [...movies.results]
you are missing the 'movies' after prev. it should be:
page > 1 && prev ? [...prev.movies.results, ...movies.results] : [...movies.results],

React context state updates are always one step behind

I have read questions with similar titles and they have not solved my problem.
I have an API call whose result needs to be shared amongst several components. The parent component makes the call and React's context is used to share it amongst the child components:
MainPage.js:
import React, { useState, useEffect } from "react";
import { useParams } from "react-router-dom";
import { VideoPlayer } from "../components/VideoPlayer";
import VideoContext from "../components/VideoContext";
export default function Watch() {
const [video, setVideo] = useState({});
const { videoHash } = useParams();
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
setIsLoading(true);
getVideo();
}, [videoHash]);
const getVideo = async () => {
if(videoHash) {
const res = await getVideoFromApi();
setIsLoading(false);
// The first time this runs nothing happens, the video can't be played
// The second time it runs (i.e. when the URL/videoHash changes) it updates
// but it shows the video from the first run
setVideo(res.video);
}
};
return (
<>
{isLoading ? (
<div>Loading...</div>
) : (
<VideoContext.Provider value={{ video, setVideo }}>
<VideoPlayer videoHash={videoHash} />
</VideoContext.Provider>
)}
</>
);
}
VideoPlayer.js:
import React, { useState, useEffect, useContext } from "react";
import VideoContext from "./VideoContext";
import styles from "./VideoPlayer.module.css";
export function VideoPlayer({ videoHash }) {
const { video, setVideo } = useContext(VideoContext);
const [forceRender, setforceRender] = useState(true);
useEffect(() => {
// I tried adding this to no effect
setforceRender(!forceRender);
}, [videoHash]);
return (
<video controls className={styles["video-player"]}>
<source src={video.VideoUrl} type="video/mp4" />
Sorry, your browser does not support embedded videos.
</video>
);
}
VideoContext.js:
import { createContext } from "react";
export default createContext({
video: {},
setVideo: () => {}
});
It works when the page loads, but when my Link components change the videoHash property the new video loads (I can see when I console.log() the API call) but it does not update in the video player.
The second time a link is clicked and the videoHash param is changed, the video displays but it's for the previous video.
https://codesandbox.io/s/blazing-lake-k4i8n?file=/src/VideoPlayer.js
Unless I'm missing something, I think the VideoPlayer would be all right just behaving as a functional component without any hooks for state, that could be handled by Watch. When you click on a link to another route that will point to watch, the videoHash will change
VideoPlayer.js
import React from "react";
import { Link } from "react-router-dom";
export function VideoPlayer({ videoHash }) {
// const { video, setVideo } = useContext(VideoContext);
// const [forceRender, setforceRender] = useState(true);
// useEffect(() => {
// // I tried adding this to no effect
// setforceRender(!forceRender);
// }, [videoHash]);
// console.log(video);
// Am I missi
return (
<div>
Am I missing something or could you just use your videoHash: {videoHash},
here?
<Link to="/watch/a">Previous</Link>
<Link to="/watch/c">Next</Link>
</div>
);
}
Watch.js
import React, { useState, useEffect, useCallback } from "react";
import { useParams } from "react-router-dom";
import { VideoPlayer } from "./VideoPlayer";
import VideoContext from "./VideoContext";
export default function Watch() {
const [video, setVideo] = useState({});
const { videoHash } = useParams();
const [isLoading, setIsLoading] = useState(true);
const getVideo = useCallback(async () => {
if (videoHash) {
const res = await getVideoFromApi();
setTimeout(() => {
setIsLoading(false);
setVideo(res);
}, 1000);
}
}, [videoHash]);
useEffect(() => {
setIsLoading(true);
getVideo();
}, [getVideo]);
const getVideoFromApi = async () => {
const videoArray = ["A", "B", "C"];
const randomItem =
videoArray[Math.floor(Math.random() * videoArray.length)];
return Promise.resolve(randomItem);
};
return (
<>
{isLoading ? (
<div>Loading...</div>
) : (
<VideoContext.Provider value={{ video, setVideo }}>
<VideoPlayer videoHash={videoHash} />
</VideoContext.Provider>
)}
</>
);
}
VideoContext.js
import { createContext } from "react";
export default createContext({
video: "",
setVideo: () => {}
});
I added a timeout so you can see the loading part work as well. Let me know if there's something I'm missing about what you need to do.

Testing a button with a method callback in a functional component

I am trying to test a react component using Enzyme. I am not able to test the click on the IconButton component and the function doesn't get called when i simulate a click.
This is how IconButton is defined on an external component.
var IconButton = function (props) {
return (React.createElement(IconButton$1, { color: 'default', onClick: props.onClick, disabled: props.disabled, size: props.size, onMouseDown: props.onMouseDown }, props.children));
};export{Button,IconButton};
This is how I am using it in my app.
import React, {useState, useEffect} from 'react';
import { Drawer } from '#material-ui/core';
import ExpandLessIcon from '#material-ui/icons/ExpandLess';
import ExpandMoreIcon from '#material-ui/icons/ExpandMore';
import { IconButton } from '#mycomponent/button';
export default function Component1 {
const classes = useStyles();
const [open, setOpen] = useState(true);
const handleClick = function (event) {
if (event) {
setOpen(!open);
}
else {
return;
}
};
return (
<Drawer>
<div className="classname1">
<IconButton onClick={(e) => handleClick(e)} className={classes.button, "iconBtn"}>
{open ? <ExpandLessIcon data-test="lessIcon" /> : <ExpandMoreIcon data-test="moreIcon" />}
</IconButton>
</div>
</Drawer>
);
}
Here is my test for simulating the click on the Icon Button. I also tried another way to check that the handleClick was getting called but it still fails.
const wrapper = shallow(<Component1 />);
it('Test the button click', () => {
expect(wrapper.containsMatchingElement(<ExpandMoreIcon />)).toBeTruthy()
const element = wrapper.find(".iconBtn")
const mockEvent = {target: {}};
element.simulate('click', mockEvent)
expect(wrapper.containsMatchingElement(<ExpandLessIcon />)).toBeTruthy()
})
Try changing this line:
const element = wrapper.find("button").at(0);
or you could find it by it's className from debug():
const element = wrapper.find(".MuiButtonBase-root MuiIconButton-root");
Notice that you'd simulate a click on an actual html button in such case.

Add component css to React.js, 'App' is defined but never used no-unused-vars

I've started to learn react by creating a new class App that extends Component, but i found it hard on understanding the concept such as codes below, but I am not sure how do I wrap the codes with extended class component and the methods when it returning the error as 'App' is defined but never used no-unused-vars. Thanks advance for your help and explanation!
App.js
import React, {
useState,
useEffect,
Component,
} from 'react';
import PokemonList from './PokemonList'
import axios from 'axios'
import Pagination from './Pagination';
import './App.css';
function App() {
const [pokemon, setPokemon] = useState([])
const [currentPageUrl, setCurrentPageUrl] = useState("https://pokeapi.co/api/v2/pokemon")
const [nextPageUrl, setNextPageUrl] = useState()
const [prevPageUrl, setPrevPageUrl] = useState()
const [loading, setLoading] = useState(true)
useEffect(() => {
setLoading(true)
let cancel
axios.get(currentPageUrl, {
cancelToken: new axios.CancelToken(c => cancel = c)
}).then(res => {
setLoading(false)
setNextPageUrl(res.data.next)
setPrevPageUrl(res.data.previous)
setPokemon(res.data.results.map(p => p.name))
})
return () => cancel()
}, [currentPageUrl])
function gotoNextPage() {
setCurrentPageUrl(nextPageUrl)
}
function gotoPrevPage() {
setCurrentPageUrl(prevPageUrl)
}
if (loading) return "Loading..."
if (loading) return "Loading..."
return ( <>
<PokemonList pokemon = {
pokemon}/> <
Pagination gotoNextPage = {
nextPageUrl ? gotoNextPage : null
}
gotoPrevPage = {
prevPageUrl ? gotoPrevPage : null
}/> </>
);
}
export default App;
I am trying to add extend the component class and the rendered method with nested elements as below so i can do some styling,
class App extends Component {
constructor() {
super();
this.state = {};
}
render() {
return (
<div className="App">
</div>
);
}
}

Categories

Resources