I would like to use useEffect()to instead of componentWillMount(), but I found the hook can not use in class components, so I change the code as Function component, but it will get more error for the whole component, all code with this.xxx are getting an error, how could I edit below code to make it work? Please help me. Below code is working fine with componentWillMount().
import React, { Component } from 'react';
import './index.less';
import { formateDate } from '../../utils/dateUtils';
import memoryUtils from '../../utils/memoryUtils';
import { reqWeather } from '../../api/index';
import { withRouter } from 'react-router-dom';
import menuList from '../../config/menuConfig';
class Header extends Component {
state = {
currentTime: formateDate(Date.now()),
dayPictureUrl: '',
weather: '',
};
getTime = () => {
setInterval(() => {
const currentTime = formateDate(Date.now());
this.setState({ currentTime });
}, 1000);
};
getWeather = async () => {
const { dayPictureUrl, weather } = await reqWeather('Auckland');
this.setState({ dayPictureUrl, weather });
};
getTitle = (props) => {
const path = this.props.location.pathname;
let title;
menuList.forEach(item => {
if (item.key === path) {
title = item.title;
} else if (item.children) {
const cItem = item.children.find(cItem => cItem.key === path);
if (cItem) {
title = cItem.title;
}
}
});
return title;
};
componentDidMount() {
this.getTime();
this.getWeather();
}
render() {
const { currentTime, dayPictureUrl, weather } = this.state;
const username = memoryUtils.user.username;
const title = this.getTitle();
return (
<div className="header">
<div className="header-top">
<span>Welcome, {username}</span>
<a href>logout</a>
</div>
<div className="header-bottom">
<div className="header-bottom-left">{title}</div>
<div className="header-bottom-right">
<span>{currentTime}</span>
<img src={dayPictureUrl} alt="weather" />
<span>{weather}</span>
</div>
</div>
</div>
);
}
}
export default withRouter(Header)
I've converted from react classes to react hooks I hope it help, I haven't tested because I don't have the external files that you have but I hope it helps otherwise just comment on this solution ;)
import React, { useState, useEffect } from 'react';
import './index.less';
import { formateDate } from '../../utils/dateUtils';
import memoryUtils from '../../utils/memoryUtils';
import { reqWeather } from '../../api/index';
import { withRouter, useLocation } from 'react-router-dom';
import menuList from '../../config/menuConfig';
function Header(){
const [currentTime, setCurrentTime] = useState(formateDate(Date.now()))
const [dayPictureUrl, setDayPictureUrl] = useState('')
const [weather, setWeather] = useState('')
const location = useLocation();
const path = location.pathname;
useEffect(() => {
getTime();
getWeather();
},[]);
const getTime = () => {
setInterval(() => {
const currentTime = formateDate(Date.now());
setCurrentTime(currentTime)
}, 1000);
};
const getWeather = async () => {
const { dayPictureUrl, weather } = await reqWeather('Auckland');
setDayPictureUrl(dayPictureUrl)
setWeather(weather)
};
const getTitle = (props) => {
let title;
menuList.forEach(item => {
if (item.key === path) {
title = item.title;
} else if (item.children) {
const cItem = item.children.find(cItem => cItem.key === path);
if (cItem) {
title = cItem.title;
}
}
});
return title;
};
const username = memoryUtils.user.username;
const title = getTitle();
return (<div className="header">
<div className="header-top">
<span>Welcome, {username}</span>
<a href>logout</a>
</div>
<div className="header-bottom">
<div className="header-bottom-left">{title}</div>
<div className="header-bottom-right">
<span>{currentTime}</span>
<img src={dayPictureUrl} alt="weather" />
<span>{weather}</span>
</div>
</div>
</div> )
}
export default Header
Here's my go at converting the function to using hooks.
One of the best things about hooks is that they can all be called as many times as you like, which allows us to separate the concerns of a component into logical blocks.
useEffect shouldn't be considered a direct replacement for componentDidMount as it works differently. The closest would actually be useLayoutEffect because of the timing of it matches componentDidMount and componentdDidUpdate. More detail on the difference between the two: useEffect vs useLayoutEffect. Although you should in general use useEffect primarily.
Getting used to hooks requires a bit of a shift in how you think of components, but in my opinion, it's worth the effort to switch!
import React, {useEffect, useMemo, useState} from 'react';
import './index.less';
import { formateDate } from '../../utils/dateUtils';
import memoryUtils from '../../utils/memoryUtils';
import { reqWeather } from '../../api/index';
import { useLocation } from 'react-router-dom';
import menuList from '../../config/menuConfig';
export default function Header (props){
const [currentTime, setCurrentTime] = useState(formateDate(Date.now()));
useEffect(()=>{
const intervalId = setInterval(()=>{
setCurrentTime(formateDate(Date.now()));
},1000)
// Make sure to cleanup your effects!
return ()=>{clearInterval(intervalId)}
},[])
const [dayPictureUrl, setDayPictureUrl] = useState('');
const [weather, setWeather] = useState('');
useEffect(() => {
const getWeather = async () => {
const { dayPictureUrl, weather } = await reqWeather('auckland');
setDayPictureUrl(dayPictureUrl);
setWeather(weather);
};
// Assuming that we want to have the weather dynamically based on a passed in prop (i.e. props.city), or a state.
getWeather();
}, []);
// useLocation gets the location via a hook from react router dom
const location = useLocation();
const title = useMemo(()=>{
// useMemo as this can be an expensive calculation depending on the length of menuList
// menuList is always a constant value, so it won't change
const path = location.pathname;
let title;
menuList.forEach(item => {
if (item.key === path) {
title = item.title;
} else if (item.children) {
const cItem = item.children.find(cItem => cItem.key === path);
if (cItem) {
title = cItem.title;
}
}
});
return title;
},[location.pathname])
const username = memoryUtils.user.username;
return (
<div className="header">
<div className="header-top">
<span>Welcome, {username}</span>
<a href>logout</a>
</div>
<div className="header-bottom">
<div className="header-bottom-left">{title}</div>
<div className="header-bottom-right">
<span>{currentTime}</span>
<img src={dayPictureUrl} alt="weather" />
<span>{weather}</span>
</div>
</div>
</div>
);
}
Related
where is the error and what should need to change can you explain. After the cart.map props can not read the property name and others. it just read it as an array . when i do console.log(props.product) then in console tab show all added product but when i want to read the name,price and others it cannot it just read only quantity.
i added picture of the console tab please check it.
now what is the problem of that code
please help me out
**Review.js
**
import React, { useEffect, useState } from 'react';
import { getDatabaseCart } from '../../utilities/databaseManager';
import fakeData from '../../fakeData';
import ReviewItem from '../ReviewItem/ReviewItem';
import Cart from '../Cart/Cart';
const Review = () => { const [cart, setCart] = useState([]);
useEffect(()=>{
//cart
const savedCart = getDatabaseCart();
const productKeys = Object.keys(savedCart);
const cartProducts = productKeys.map( key => {
const product = fakeData.filter( pd => pd.key === key);
product.quantity = savedCart[key];
return product;
});
setCart(cartProducts);
}, []);
return (
<div className="twin-container">
<div className="product-container">
{
cart.map(pc => <ReviewItem
key={pc.key}
product={pc}></ReviewItem>)
}
</div>
</div>
);
};
export default Review;
reviewItem.js
import React from 'react';
const ReviewItem = (props) => {
const {name,price,quantity} = props.product;
const reviewItemStyle={
borderBottom:'1px solid lightgray',
marginBottom:'5px',
paddingBottom:'5px',
marginLeft:'200px'
};
console.log(name);
return (
<div style={reviewItemStyle} className="review-item">
<h4 className="product-name">Name:{name}</h4>
<p>Quantity: {quantity}</p>
<p><small>$ {price}</small></p>
<br/>
</div>
);
};
export default ReviewItem;
I'm using the Intersection Observer API in React to add some animations. I am adding as Intersection Entries some elements.
The problem is that I have the app in multiple languages, and due to the implementation that the tool I am using to translate has, I need to wrap all my components into React.Suspense to wait for languages to load.
When useEffect queries for the elements, they aren't still in the DOM, and therefore they are not assigned as entries.
This is my custom hook:
hooks/useObserver.js
import { useState } from "react";
import { useEffect, useRef } from "react";
export function useObserver(config = {}) {
const [elements, setElements] = useState([]);
const [entries, setEntries] = useState([]);
const observer = useRef(
new IntersectionObserver(observedEntries => {
setEntries(observedEntries);
}, config)
);
useEffect(() => {
const { current: currentObserver } = observer;
currentObserver.disconnect();
if (elements.length > 0) {
elements.forEach(el => currentObserver.observe(el));
}
return () => {
if (currentObserver) {
currentObserver.disconnect();
}
};
}, [elements]);
return { observer: observer.current, setElements, entries };
}
and this is my main component:
App.jsx
import Header from "./components/Header";
import Hero from "./components/Hero";
import Footer from "./components/Footer";
import { Loader } from "./components/shared/Loader";
import { useObserver } from "./hooks/useObserver";
import { useEffect, Suspense } from "react";
function App() {
const { entries, setElements } = useObserver({});
useEffect(() => {
const sections = document.querySelectorAll("section.animated-section");
setElements(sections);
};
}, [setElements]);
useEffect(() => {
entries.forEach(entry => {
entry.target.classList.toggle("section-visible", entry.isIntersecting);
});
}, [entries]);
return (
<Suspense fallback={<Loader />}>
<Header />
<Hero />
<Footer />
</Suspense>
);
}
export default App;
I tried to set a timeout to wait some seconds and then add the elements as entries, and it works correctly:
useEffect(() => {
const observeElements = () => {
const sections = document.querySelectorAll("section.animated-section");
setElements(sections);
};
const observeElementsTimeout = setTimeout(observeElements, 3000);
return () => clearTimeout(observeElementsTimeout)
}, [setElements]);
I want to know if:
There is a way to know when React.Suspense is ready
There is a better approach to solve my problem
Thanks in advance!!
I'm learning React and am having trouble with a value defined in a custom context provider. I access the value in a component under the provider with a custom hook but it's reported as being undefined. I've gone through the questions on SO and have verified my syntax with the lesson in my book but can't find the problem.
This is my custom provider and custom hook:
import React, { createContext, useState, useEffect, useContext } from 'react';
const ApiContext = createContext();
export const useApi = () => useContext(ApiContext);
export const ApiProvider = ({ children }) => {
const [loading, setLoading] = useState(false);
const [error, setError] = useState();
const [baseImageUrl, setBaseImageUrl] = useState();
const apiKey = 'api_key=SECRET';
const baseUrl = 'https://api.themoviedb.org/3';
const objToParams = (obj) => {
let params = '';
if(obj) {
const keys = Object.keys(obj);
for(let key of keys) {
params += `&${key}=${encodeURIComponent(obj[key])}`;
}
}
return params;
}
const api = {
get: async (path, params) => {
const resp = await fetch(baseUrl + path + '?' + apiKey + objToParams(params));
return await resp.json();
}
}
useEffect( () => {
try {
setLoading(true);
const config = api.get('/configuration');
console.log(config);
config.images && setBaseImageUrl(config.images.secure_base_url);
}
catch (error) {
console.error(error);
setError(error);
}
finally {
setLoading(false);
}
}, []);
if( loading ) {
return <p>Loading...</p>;
}
if( error ) {
return <pre>{JSON.stringify(error, null, 2)}</pre>;
}
return (
<ApiContext.Provider value={{ api, baseImageUrl }}>
{ children }
</ApiContext.Provider>
);
}
and this is the component where I access the value through the custom hook:
import React, { useState } from 'react';
import { ApiProvider, useApi } from './components/context/ApiProvider';
import Header from './components/Header';
import Main from './components/Main';
import Footer from './components/Footer';
import './App.css';
const App = () => {
const [searching, setSearching] = useState(false);
const [searchResults, setSearchResults] = useState([])
const [searchError, setSearchError] = useState();
const {api} = useApi();
const onSearch = (query) => {
try {
setSearching(true);
setSearchResults(api.get('/search/multi', {query: encodeURIComponent(query)} ));
console.log(searchResults);
}
catch (error) {
console.error(error);
setSearchError(error);
}
finally {
setSearching(false);
}
}
return (
<ApiProvider>
<div className="main-layout">
<Header onSearch={ onSearch }/>
<Main
searching={ searching }
searchError={ searchError }
searchResults={ searchResults }
/>
<Footer />
</div>
</ApiProvider>
);
}
export default App;
You can't consume the context in the component where you apply it.
<ComponentA>
<Context.Provider value={"somethong"} >
<ComponentB/>
</Context.Provider>
</ComponentA>
In the above example, only ComponentB can consume the value. ComponentA can't.
If you wan't to consume the value in your App component, it has to be the child (or grandchild ...) of the ContextProvider.
<Context.Provider value={"somethong"} >
<App/>
</Context.Provider>
If I understand your code correctly than you are trying to consume the context in your App, while also returning the provider for the same context.
I'm creating React context but it returns a promise. In the file playlistcontext.js I've the following code:
import React, { useEffect } from 'react';
import YouTube from '../services/youtube';
const playlistsData = YouTube.getPlaylists();
// console.log(playlistsData);
const PlaylistsDataContext = React.createContext(playlistsData);
const PlaylistsDataProvider = (props) => {
const [playlists, setPlaylists] = React.useState(playlistsData);
useEffect(() =>{
const playlistsData = YouTube.getPlaylists();
console.log(playlistsData);
setPlaylists(playlistsData);
},[])
return <PlaylistsDataContext.Provider value={[playlists, setPlaylists]}>{props.children}</PlaylistsDataContext.Provider>;
}
export {PlaylistsDataContext, PlaylistsDataProvider};
In the file youtube.js, that I use it like a service, I'have the code below. In this function a console.log(result.data) return me the correct data.
import axios from 'axios';
import { YOUTUBE_API } from '../config/config';
function Youtube() {
const handleError = (resp) => {
let message = '';
switch (+resp.status) {
case 401:
message = resp.data.error;
break;
default:
message = 'general error';
}
return message;
}
const getPlaylists = async () => {
try {
const result = await axios.get(YOUTUBE_API + '');
return result.data;
} catch(e) {
return Promise.reject(handleError(e.response));
}
}
return {
getPlaylists
}
}
const ytMethod = Youtube();
export default ytMethod;
then, I have a containers "tutorialcontainer.js" in which I've wrapped a component:
import React, {useState} from 'react';
import { PlaylistsDataProvider } from '../containers/playlistscontext';
import Tutorials from '../components/tutorials';
const TutorialsContainer = (props) => {
return (
<PlaylistsDataProvider>
<Tutorials />
</PlaylistsDataProvider>
);
}
export default TutorialsContainer;
In the last file tutorials.js I have the component. In this file the console.log(playlist) returns me a promise.
import React, {useState, useEffect} from 'react';
import SectionBoxPlaylist from '../components/html_elements/card_playlist';
import Header from '../components/header';
import { PlaylistsDataContext } from '../containers/playlistscontext';
const Tutorials = (props) => {
const [playlists, setPlaylists] = React.useContext(PlaylistsDataContext);
return (
<div className="app-container">
<Header />
<div className="section section-one text-center">
<div className="section-content">
<div className="section-box-items">
{
Object.keys(playlists).map((item) => {
return <SectionBoxPlaylist key={item} id={item} info={playlists[item]} />
})
}
</div>
</div>
</div>
</div>
);
}
export default Tutorials;
Can you help and explain me why?
Thank you!
setPlaylists is called immediately after YouTube.getPlaylists().
useEffect(() => {
const playlistsData = YouTube.getPlaylists();
console.log(playlistsData); // playlistsData is not fetched
setPlaylists(playlistsData);
},[])
You should be able to use .then():
YouTube.getPlaylists().then(response => {
console.log(response);
setPlaylists(response);
});
You can also create async function inside useEffect():
useEffect(() => {
const getYTPlaylist = async () => {
const playlistsData = await YouTube.getPlaylists();
console.log(playlistsData);
setPlaylists(playlistsData);
}
getYTPlaylist();
},[])
What is wrong with this case. I want to display a random name and change it for every 2 seconds but after few seconds is changing continuously and look like the names are overwriting even when I clean the setName?
import React, {useState} from "react";
import "./styles.css";
export default function App() {
const [name, setName] = useState();
const arrayName = ['Tom','Alice','Matt','Chris'];
const nameChange = () => {
const rand = Math.floor(Math.random()*arrayName.length);
setName(arrayName[rand])
}
setInterval(()=>{
setName('');
nameChange();
console.log(name);
}, 2000)
return (
<div className="App">
<h1>Hello {name}</h1>
</div>
);
}
It's creating a new interval every time your component renders, which causes it to render again and you end up with an infinite loop.
Try this:
import React, {useState, useEffect, useCallback} from "react";
import "./styles.css";
const arrayName = ['Tom','Alice','Matt','Chris'];
export default function App() {
const [name, setName] = useState();
const nameChange = useCallback(() => {
const rand = Math.floor(Math.random()*arrayName.length);
setName(arrayName[rand])
}, []);
useEffect(() => {
const interval = setInterval(() => {
setName('');
nameChange();
}, 2000)
return () => clearInterval(interval)
}, [nameChange]);
return (
<div className="App">
<h1>Hello {name}</h1>
</div>
);
}
The issue is that you never do clearInterval. Whenever the component calls render, a new interval will issue.
Wrap setInterval in useEffect, which gets called when a component renders. The return of useEffectis a function that dictates what happens on component unmounting phase. See more here
useEffect(){
const tmp = setInterval(()=>{
setName('');
nameChange();
console.log(name);
}, 2000)
return () => { clearInterval(tmp); };
}
The issue is that every time your component is rendered, you are creating a new interval.
The solution is to wrap the setInterval call in useEffect, and then return a function to useEffect to clear the interval.
import React, { useState, useCallback, useEffect } from 'react';
import './styles.css';
const arrayName = ['Tom', 'Alice', 'Matt', 'Chris'];
export default function App() {
const [name, setName] = useState();
const nameChange = useCallback(() => {
const rand = Math.floor(Math.random() * arrayName.length);
setName(arrayName[rand]);
}, [setName]);
useEffect(() => {
const intervalId = setInterval(() => {
setName('');
nameChange();
}, 2000);
return () => clearInterval(intervalId);
}, [nameChange]);
return (
<div className="App">
<h1>Hello {name}</h1>
</div>
);
}