I created a custom hook that uses useEffect to update a redux store when certain values in the store change:
import { useRef, useEffect } from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { index } from '../actions'
import { createSelector } from 'reselect'
import { omit } from 'lodash'
const selectPerformanceSearch = createSelector(
state => omit(state.eventSearch, ['results', 'isFetching']),
items => items
)
export default function useEventSearch() {
const state = useSelector(selectEventSearch)
const dispatch = useDispatch()
const initialFetch = useRef(true)
const { tags, near, filters } = state
const { lng, lat, location } = near
useEffect(() => {
if (!initialFetch.current) {
const data = { tags, lng, lat, filters }
dispatch(index('/event/search', 'EVENT_SEARCH', data))
}
}, [tags, lng, lat, filters, dispatch])
useEffect(() => {
if (initialFetch.current && location) {
initialFetch.current = false
const data = { lng, lat }
dispatch(index('/event/closest', 'EVENT_SEARCH', data))
}
}, [lng, lat, location, dispatch])
}
Then I have a React component that uses the hook like so:
import useEventSearch from '../../hooks/usePerformanceSearch'
const SearchBar = props => {
// ...code
usePerformanceSearch()
/// ...other code
return ( ... )
}
It all works as I would expect but is it ok to just call and use a hook the way I did?
Related
I'm managing a context for the classes of my school so that i don't have to fetch on every page, for this i write this custom component and wrap my entire app in it.I whenever i fetch the classes i store them in the redux store so that i can use it again and again.
And before fetch i check the store if the classes exist there i don't hit the API instead get the classes from there. in my dependency array i put navigate, dispatch and Classes because react was complaining about that. but this cause infinite loop
here is my code
// ** React Imports
import { createContext, useEffect, useState } from 'react';
import { useDispatch, connect } from 'react-redux';
// ** Config
import { toast } from 'react-hot-toast';
// ** custom imports
import { useNavigate } from 'react-router-dom';
import { CLASSES_API_HANDLER } from 'src/redux/actions/classes/classes';
// ** Defaults
const defaultProvider = {
classes: [],
setClasses: () => null,
loading: true,
setLoading: () => null,
};
export const ClassesContext = createContext(defaultProvider);
const ClassesProvider = ({ children, CLASSES }) => {
const dispatch = useDispatch();
const navigate=useNavigate()
const [classes, setClasses] = useState(defaultProvider.classes);
const [loading, setLoading] = useState(defaultProvider.loading);
useEffect(() => {
const getClasses = async () => {
if (CLASSES.length > 0) {
setClasses(CLASSES);
setLoading(false);
return;
}
try {
const Data = await dispatch(CLASSES_API_HANDLER());
setClasses(Data.result);
setLoading(false);
} catch (ex) {
navigate("/error")
console.log(ex);
}
}
getClasses()
}, [CLASSES,navigate,dispatch]);
const values = {
classes:classes,
setClasses:setClasses,
loading:loading,
setLoading:setLoading,
};
return (
<ClassesContext.Provider value={values}>
{children}
</ClassesContext.Provider>
);
};
function mapStateToProps(state) {
return { CLASSES: state.CLASSES };
}
export default connect(mapStateToProps)(ClassesProvider);
I'm trying to fix the infinite loop
What's the way to pass a useRef to a custom hook? I'm creating a custom hook to trigger an animation when the user reaches a section with react-intersection-observer and if the user passes more than one the animation doesn't happen again.
If i pass the ref like this:
useAnimation parameters: const useAnimation = (customRef) and the setView method from react-intersection-observer like this: const { ref: customRef, inView: elementIsVisible } = useInView(); i get Parsing error: Identifier 'customRef' has already been declared. If i delete the ref: it happens the same thing.
If i console.log the customRef parameter i get current: undefined
Here is the full code:
useAnimation:
import React, { useEffect, useState, useRef, useContext } from "react";
import { DisableAnimationContext } from "../context/disableAnimationContext";
import { useInView } from "react-intersection-observer";
const useAnimation = (customRef) => {
const { setdisableAnimation } = useContext(DisableAnimationContext);
const { ref: customRef, inView: elementIsVisible } = useInView();
const [animationHappened, setAnimationHappened] = useState(false);
const [animationCounter, setAnimationCounter] = useState({
counter: 0,
});
useEffect(() => {
if (elementIsVisible) {
setdisableAnimation(true);
var currentState = animationCounter.counter;
setAnimationCounter({
...animationCounter,
counter: (currentState += 1),
});
}
if (animationCounter.counter > 0) {
setAnimationHappened(true);
}
}, [elementIsVisible]);
return { elementIsVisible, animationHappened };
};
export default useAnimation;
Here is how i call the custom hook in a component:
import React, { useRef, useContext } from "react";
import useAnimation from "../../../hooks/useAnimation";
const aboutRef = useRef();
console.log(aboutRef);
const { elementIsVisible, animationHappened } = useAnimation(aboutRef);
I have this problem, can anyone help me?
TypeError: customers.map is not a function.
I've always used it that way and I've never had any problems.
Its about data integration.
Basically is that, please anyone can help me?
import React, { useState, useEffect } from "react";
import { List, Card } from "antd";
import { data } from "../../../mocks/customers";
import { DeleteCustomerButton } from "#components/atoms/DeleteCustomerButton";
import { CustomersEditButton } from "#components/atoms/CustomersEditButton";
import { useContext } from "../../../contexts/context";
const { Meta } = Card;
const CustomersCardList: React.FC = () => {
const customers: any = useContext();
return (
<div>
{customers.map((customer, key) => { })}</div>)
}
//context.tsx
import * as React from 'react';
import axios from 'axios';
export const AccountContext = React.createContext({});
export const useContext = () => React.useContext(AccountContext);
interface AccounterContextProviderProps {
value: any
};
export const AccounterContextProvider: React.FC<AccounterContextProviderProps> = ({ children, value }) => {
const [customers, setCustomers] = React.useState<any>([]);
React.useEffect(() => {
const getCustomers = async () => {
const result = await axios.get("http://localhost:3333/customers");
setCustomers(result.data);
}
getCustomers();
}, []);
console.log(customers);
return (
<AccountContext.Provider value={{ ...value, customers }}>
{children}
</AccountContext.Provider>
)
};
Any can be anything not only array, so it will not have a map method. Use const customers:any[] = useContext() instead
I have an existential question about react context.
Let's say I have a component that uses useEffect to make call my back-end and get some data. then useState set it inside the context, it works fine, but what if I use useEffect and call my back-end inside the context and then just read data in the component. what you think which one is more "accurate"
for example
old method
context.Js
import React, { useState, useEffect } from "react";
const defaultValue = {someState};
export const SomeContext = React.createContext();
export const SomeProvider = ({ someId }) => {
const [state, setState] = useState(defaultValue);
setData = (data) => { do some logic then setState(data)}
const value = {
...state,
setData
};
return <SomeContext.Provider value={value}>{children}</SomeContext.Provider>;
};
index.js
import React, { useState, useEffect } from "react";
import { SomeContext, SomeProvider } from "context/SomeContext";
import { getById } from "services";
const Layout = ({ someId }) => {
const { data, setData } = useContext(GroupContext);
useEffect(()=>{
//getById call my back-end to get some data
getById(someId).then(setData)
}, [])
return <Component />
}
export default props => {
const providerProps = {
someId,
};
return (
<GroupProvider {...providerProps}>
<Layout activeTab={activeTab} {...props} />
</GroupProvider>
);
};
new method
context.Js
import React, { useState, useEffect } from "react";
import { getById } from "services";
const defaultValue = {someState};
export const SomeContext = React.createContext();
export const SomeProvider = ({ someId }) => {
const [state, setState] = useState(defaultValue);
useEffect(()=>{
//getById call my back-end to get some data
getById(someId).then(setState)
}, [])
const value = {
...state,
};
return <SomeContext.Provider value={value}>{children}</SomeContext.Provider>;
};
index.js
import { SomeContext, SomeProvider } from "context/SomeContext";
const Layout = ({ }) => {
const { data } = useContext(GroupContext);
return <Component />
}
export default props => {
const providerProps = {
someId,
};
return (
<GroupProvider {...providerProps}>
<Layout activeTab={activeTab} {...props} />
</GroupProvider>
);
};
something like this
import React from "react"
import { useSelector } from 'react-redux'
import { useLocation } from "react-router"
const BreadCrumb = () => {
const location = useLocation()
const currentPageTitle = useSelector(state => {
// match current location to get the currrent page title from redux store and return it
})
return (
<h2>Home / { currentPageTitle}</h2>
)
}
export default BreadCrumb
This code works fine in the initial render and I do get the intended result { currentPageTitle } but the UI doesn't seem to re-render and stays the same despite route change. Although, if I console.log( location ) before the return statement, it logs successfully on route change. What is the issue here?
I would suggest that you use useEffect hook:
import React, { useEffect, useState } from "react"
import { useSelector } from 'react-redux'
import { useLocation } from "react-router"
const BreadCrumb = () => {
const location = useLocation()
const [currentLocation, setCurrentLocation] = useState('')
const currentPageTitle = useSelector(state => {
console.log(currentLocation);
// Do something with currentLocation
})
useEffect(() => {
if (location) {
setCurrentLocation(location.pathname)
} else {
setCurrentLocation('')
}
}, [location])
return (
<h2>Home / { currentPageTitle}</h2>
)
}
export default BreadCrumb