As you can tell from the code, I'm trying to make a custom hook that would sort the rendered cards.
When the cards first render, they show up in the default order from the API, but they get sorted only after forcing a re-render, usually by pressing Ctrl+S to save so Vite registers a change.
I'm new to custom hooks so I don't really know what I'm doing wrong, probably a problem with the dispatches inside the useEffect? How can I get the cards to sort properly?
This is the custom hook:
import { useEffect, useState } from 'react';
// Redux
import { getAllBosses, SortBosses } from '../redux/slices/Bosses';
import { useDispatch, useSelector } from 'react-redux';
export default function useFetch(value) {
const dispatch = useDispatch();
const { list: bosses } = useSelector(state => state.bosses);
const [type, setType] = useState([]);
useEffect(() => {
value === 'default'
? dispatch(getAllBosses())
: dispatch(SortBosses(value));
setType(bosses);
}, [value]);
return type;
}
And this is the component:
import { useEffect, useState } from 'react';
// Redux
// import { getAllBosses } from '../../redux/slices/Bosses';
// import { useDispatch, useSelector } from 'react-redux';
// components
import { BossCard, Loading, Pagination, Error } from '#components';
import { useFetch } from '../../hooks';
import { Cards } from '#components/styles/Cards.style';
function BossesList() {
// const dispatch = useDispatch();
const bosses = useFetch('ztoa');
const [currentPage, setCurrentPage] = useState(1);
const bossPerPage = 8;
const indexOfLastBoss = currentPage * bossPerPage;
const indexOfFirstBoss = indexOfLastBoss - bossPerPage;
const currentBosses = bosses.slice(indexOfFirstBoss, indexOfLastBoss);
const pages = pageNumber => {
setCurrentPage(pageNumber);
};
// useEffect(() => {
// dispatch(getAllBosses());
// }, [dispatch]);
useEffect(() => {
setCurrentPage(1);
}, [bosses]);
return (
<>
<Cards>
{currentBosses.length !== 0 ? (
currentBosses.map((boss, index) =>
boss.error ? (
<Error key={index} error={'No boss with that name.'} />
) : (
<BossCard
key={boss.id}
id={boss.id}
name={boss.name}
image={boss.image}
/>
)
)
) : (
<div>
<Loading />
</div>
)}
</Cards>
<Pagination
bossPerPage={bossPerPage}
bosses={bosses.length}
currentBosses={currentBosses}
pages={pages}
currentPage={currentPage}
/>
</>
);
}
export default BossesList;
Related
I have two function components with useState in two different files in my project. I want to display the url on my FaceRecognition component if I set fetchSuccess to true.
const ImageLinkForm = () => {
const [url, setUrl] = useState("");
const [fetchSuccess, setFetchSuccess] = useState(false);
const onInputChange = (event) => {
// I get the url and fetchSuccess is true
};
return (
<div>
// I return a form that allowed me to make the fetch call
</div>
);
};
export default ImageLinkForm;
const FaceRecognition = () => {
return (
<div>
{/* if fetchSuccess */}
<img src=url />
</div>
);
};
export default FaceRecognition;
This really depends on how these components are hierarchically related but one easy-ish option is to use the context API
// context/image.js
import { createContext, useState } from "react";
export const ImageContext = createContext({ fetchSuccess: false });
export const ImageContextProvider = ({ children }) => {
const [fetchSuccess, setFetchSuccess] = useState(false);
const setSuccessful = () => {
setFetchSuccess(true);
};
return (
<ImageContext.Provider value={{ fetchSuccess, setSuccessful }}>
{children}
</ImageContext.Provider>
);
};
Your components can then use the context to read the value...
import { useContext } from "react";
import { ImageContext } from "path/to/context/image";
const FaceRecognition = () => {
const { fetchSuccess } = useContext(ImageContext);
return <div>{fetchSuccess && <img src="url" />}</div>;
};
and write the value...
import { useContext, useState } from "react";
import { ImageContext } from "path/to/context/image";
const ImageLinkForm = () => {
const [url, setUrl] = useState("");
const { setSuccessful } = useContext(ImageContext);
const onInputChange = (event) => {
// I get the url and fetchSuccess is true
setSuccessful();
};
return (
<div>{/* I return a form that allowed me to make the fetch call */}</div>
);
};
The only thing you need to do is wrap both these components somewhere in the hierarchy with the provider
import { ImageContextProvider } from "path/to/context/image";
const SomeParent = () => (
<ImageContextProvider>
<ImageLinkForm />
<FaceRecognition />
</ImageContextProvider>
);
I am displaying a foto in the front using Leigh Halliday's Image Previews in React with FileReader from https://www.youtube.com/watch?v=BPUgM1Ig4Po and everything is super BUT:
1.I want to get information from the image is displaying, exactly the base64 info, and have it then globally in my reactjs app.
2.for that reason I made a Context, i configured it ok BUT:
when I am doing dispatch inside a useEffect I want the image rendering and the info store in my variable globally
but I have one thing or another
if my image renders ok in my front, I can not obtain the value of my dispatch and viceversa
this is the code of my component:
import React, { useContext, useEffect, useRef, useState } from 'react'
import { AuthContext } from '../../auth/AuthContext'
import { types } from '../../types/types'
export const ButtonLoadFoto = () => {
const { dispatchFoto } = useContext(AuthContext)
const [image, setImage] = useState('')
const [preview, setPreview] = useState('')
const [status, setStatus] = useState(false)
useEffect(() => {
if (image) {
const reader = new FileReader()
reader.onloadend = () => {
setPreview(reader.result)
}
reader.readAsDataURL(image)
setStatus(true)
} else {
setPreview('')
}
}, [image])
// useEffect(() => {
// if (status) {
// dispatchFoto({
// type: types.foto,
// payload: {
// foto: preview.split(',')[1]
// }
// })
// }
// return () => setStatus(false)
// }, [preview])
const fileInputRef = useRef()
const handleRef = (e) => {
e.preventDefault()
fileInputRef.current.click()
}
const handleFile = (e) => {
const file = e.target.files[0]
if (file && file.type.substr(0, 5) === 'image') {
setImage(file)
}
}
return (
<div className='load-input '>
{
preview
?
(<img src={preview} alt='' onClick={() => setImage('')} />)
:
(<button
className='alert alert-danger'
onClick={handleRef}>
foto
</button>
)
}
< input
type='file'
style={{ display: 'none' }}
ref={fileInputRef}
accept='image/*'
onChange={handleFile}
/>
</div>
)
}
in the code above if you put away the comments we will have the information we want but the foto won t display at all
thanks all for your time , I really appreciate!
EDIT
this is the main component
import React, { useEffect, useReducer } from 'react'
import { AuthContext } from './auth/AuthContext'
import { fotoReducer } from './components/formScreen/fotoReducer'
import { AppRouter } from './routers/AppRouter'
const initImage = () => {
return { foto: '' }
}
export const CMI = () => {
const [foto, dispatchFoto] = useReducer(fotoReducer, {}, initImage)
return (
<div>
<AuthContext.Provider value={{
foto,
dispatchFoto
}}>
<AppRouter />
</AuthContext.Provider>
</div>
)
}
this is the componenent I use
import React, { useContext} from 'react'
import { ButtonLoadFoto } from '../components/formScreen/ButtonLoadFoto'
import { AuthContext } from '../auth/AuthContext'
export const FormScreen = () => {
const { foto } = useContext(AuthContext)
}
return (
<div>
<ButtonLoadFoto/>
</div>
)
as I said : if a render the image I can not have the information and viceversa...
when I use dispatch I don t know I it brokes the image render
thanks in advance
My React App is crashing when fetching searchResults from API, I have checked the API urls wise search queries and it works perfectly however when i try to send input via React and display results it crashes and even freezes my PC. I dont understand whats going on here. I have fetched results from the API in React without search query and it works. So the API works when used via Curl and React app can fetch and display all the data but unable to display specific data. Below is my code:
function Search() {
const [data, setData] = React.useState([]);
const [searchTerm, setSearchTerm] = React.useState("");
const handleChange = e => {
setSearchTerm(e.target.value);
};
React.useEffect(() => {
if (searchTerm) {
getData(searchTerm);
}
});
const getData = (searchTerm) => {
axios.get("http://localhost:8000/SearchPost/?search="+searchTerm)
.then(res => (setData(res.data)))
}
return (
<div className="App">
<input
type="text"
placeholder="Search"
value={searchTerm}
onChange={handleChange}
/>
<ul>
{data.map(item => (
<li>{item.co_N}</li>
))}
</ul>
</div>
);
}
export default Search;
One solution is to "debounce" setting searchTerm to minimize the request to the API:
we're going to use lodash package particularly it's debounce method (doc here), and useCallback from Hooks API (doc here) :
import React, { useState, useCallback, useRef } from "react";
import _ from "lodash";
import axios from "axios";
import TextField from "#material-ui/core/TextField";
const SearchInputComponent = ({ label }) => {
const [value, setValue] = useState("");
const [data, setData] = useState([]);
const inputRef = useRef(null);
const debounceLoadData = useCallback(
_.debounce((value) => {
getData(value);
}, 500), // you can set a higher value if you want
[]
);
const getData = (name) => {
axios.get(`https://restcountries.eu/rest/v2/name/${name}`).then((res) => {
console.log(res);
setData(res.data);
});
};
const handleSearchFieldChange = (event) => {
const { value } = event.target;
setValue(value);
debounceLoadData(value);
};
return (
<>
<TextField
inputRef={inputRef}
id="searchField"
value={value}
label={"search"}
onChange={handleSearchFieldChange}
/>
{data &&
<ul>
{data.map(country=> (
<li key={country.alpha3Code}>{country.name}</li>
))
}
</ul>
}
</>
);
};
export default SearchInputComponent;
with this code the front end will wait 500 ms before fetching api with the search input value.
here a sandBox example.
Possible Feature: Make search field generic
If in the future you will need a search component you can make it generic with Context:
first create a context file named for example SearchInputContext.js and add:
SearchInputContext.js
import React, {
createContext,
useState
} from 'react';
export const SearchInputContext = createContext({});
export const SearchInputContextProvider = ({ children }) => {
const [value, setValue] = useState('');
return (
<SearchInputContext.Provider
value={{ searchValue: value, setSearchValue: setValue }}
>
{children}
</SearchInputContext.Provider>
);
};
Next create a generic searchField component named for example SearchInput.js and add in it :
SearchInput.js
import React, {
useState,
useCallback,
useRef,
useContext
} from 'react';
import _ from 'lodash';
import TextField from "#material-ui/core/TextField";
import { SearchInputContext } from './SearchInputContext';
const SearchInputComponent = () => {
const [value, setValue] = useState('');
const { setSearchValue } = useContext(SearchInputContext);
const inputRef = useRef(null);
const debounceLoadData = useCallback(
_.debounce((value) => {
setSearchValue(value);
}, 500),
[]
);
const handleSearchFieldChange = (event) => {
const { value } = event.target;
setValue(value);
debounceLoadData(value);
};
return (
<>
<TextField
inputRef={inputRef}
id="searchField"
value={value}
label={"search"}
onChange={handleSearchFieldChange}
/>
</>
);
};
export default SearchInputComponent;
After in your App.js (or other component page where you want a searchField) add your ContextProvider like this:
App.js
import {ListPage} from "./searchPage";
import {SearchInputContextProvider} from './SearchInputContext';
import "./styles.css";
export default function App() {
return (
<SearchInputContextProvider>
<ListPage/>
</SearchInputContextProvider>
);
}
And finally add your searchComponent where you need a search feature like in the ListPage component :
SearchPage.js:
import React, { useState,useContext, useEffect } from "react";
import axios from "axios";
import SearchInputComponent from './SearchInput';
import {SearchInputContext} from './SearchInputContext'
export const ListPage = () => {
const [data, setData] = useState([]);
const { searchValue } = useContext(SearchInputContext);
useEffect(() => {
if(searchValue){
const getData = (name) => {
axios.get(`https://restcountries.eu/rest/v2/name/${name}`).then((res) => {
console.log(res);
setData(res.data);
});
};
return getData(searchValue)
}
}, [ searchValue]);
return (
<>
<SearchInputComponent />
{data &&
<ul>
{data.map(country=> (
<li key={country.alpha3Code}>{country.name}</li>
))
}
</ul>
}
</>
);
};
here a sandbox link of this example
I have trouble with react context.
Especially with function getProductCategory which I use in the second component.
My React context provider looks like :
import React, { createContext, useState, useEffect } from "react";
import data from "./data";
export const ProductContext = createContext();
const ProductContextProvider = ({ children }) => {
const [productsCategory, setProductCategory] = useState();
const [products, setProducts] = useState();
useEffect(() => {
setProducts(data);
});
function getProductCategory(category) {
const productFromCategory = data.filter(
(product) => product.type === category
);
setProductCategory(productFromCategory); //this line is causing the problem
console.log(productFromCategory);
return productsCategory;
}
const getProduct = (product) => {
const currentProduct = products
? products.filter((item) => item.slug === product)[0]
: undefined;
return currentProduct;
};
return (
<ProductContext.Provider
value={{ getProduct, getProductCategory }}
>
{children}
</ProductContext.Provider>
);
};
export default ProductContextProvider;
I want to get access to my context from another component Mats which is a page.
import React, { useContext, useEffect } from "react";
import { ProductContext } from "../../context";
/accesoriesComponent/CategoryTitle/CategoryTitle";
import Filters from "../../components/accesoriesComponent/Filters/Filters";
import ProductWrapper from "../../components/accesoriesComponent/ProductWrapper/ProductWrapper";
export default function Mats(props) {
const { getProductCategory } = useContext(ProductContext);
const mats = getProductCategory("mats");
return (
<div>
<div style={{ display: "flex" }}>
<Filters />
{mats && <ProductWrapper products={mats} />}
</div>
</div>
);
}
When I want to visit page Mats, I get error "Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside component WillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.".
I marked the line of code in the first code, that courses the problem .
I don't know how to handle it.
Any suggestions ?
Based on your code:-
instead of return-ing productsCategory from your getProductCategory function. Send your productsCategory as ProductContext.Provider values props
Btw, your useEffect should have [] or empty array as dependency
ProductContextProvider.js:-
import React, { createContext, useState, useEffect } from "react";
import data from "./data";
export const ProductContext = createContext();
const ProductContextProvider = ({ children }) => {
const [productsCategory, setProductCategory] = useState();
const [products, setProducts] = useState();
useEffect(() => {
setProducts(data);
}, []); // making sure it will only run once when rendered
function getProductCategory(category) {
const productFromCategory = data.filter(
(product) => product.type === category
);
setProductCategory(productFromCategory); // you already set the data in here
console.log(productFromCategory);
// return productsCategory; // no need to return this
}
const getProduct = (product) => {
const currentProduct = products
? products.filter((item) => item.slug === product)[0]
: undefined;
return currentProduct;
};
return (
<ProductContext.Provider
value={{ productsCategory, getProduct, getProductCategory }}
>
{children}
</ProductContext.Provider>
);
};
export default ProductContextProvider;
So in Mats.js, just get display your updated ``
export default function Mats(props) {
const { productsCategory, getProductCategory } = useContext(ProductContext);
// Shouldn't do this. Cause it will keep rerender
// const mats = getProductCategory("mats");
// default state of category
const [category, setCategory] = useState('mats')
// use useffect to send the updated products by category
useEffect(() => {
(() => {
if(category) {
getProductCategory(category);
}
})()
}, [category]) // will initiated every time category change
return (
<div>
<div style={{ display: "flex" }}>
<Filters />
{productsCategory && <ProductWrapper products={productsCategory} />}
</div>
</div>
);
}
I have a question.
I have a page inside my application that dispatches an action and waits for the result (using useDispatch and useSelector hooks)
I wanted to know what is the right way to display an empty div until I get the results back from the dispatch?
This is what I wrote so far:
import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { fetchRecommendedPosts } from "../redux/actions/postsActions";
import Post from "../components/sharedComponents/Post";
const RecommendedUserPostsPage = () => {
const dispatch = useDispatch();
const { token } = useSelector((state) => state.auth);
useEffect(() => {
dispatch(fetchRecommendedPosts({ token }));
}, []);
const fetchedPosts = useSelector((state) => state.posts.recommendedPosts);
// this is the place where I wait for the response from the dispatch
const posts =
fetchedPosts.length > 0 ? (
fetchedPosts.map((post) => <Post postData={post} />)
) : (
<div />
);
return <div style={{ margin: "5em" }}>{posts}</div>;
};
export default RecommendedUserPostsPage;
I always like to add the data, error, loading and fetched properties in the Redux state for API data. With these properties, you know exactly what the state is.
These properties needs to be set in your reducers according the API response.
import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { fetchRecommendedPosts } from "../redux/actions/postsActions";
import Post from "../components/sharedComponents/Post";
const RecommendedUserPostsPage = () => {
const dispatch = useDispatch();
const { token } = useSelector((state) => state.auth);
const { data, loading, fetched, error } = useSelector((state) => state.posts.recommendedPosts);
useEffect(() => {
dispatch(fetchRecommendedPosts({ token }));
}, [token]);
return (
<div style={{ margin: "5em" }}>
{fetched && data.map((post) => <Post postData={post} />)}
{error && <p>Oh no!</p>}
{loading && <p>Please stay seated...</p>}
</div>
);
};
export default RecommendedUserPostsPage;
However, more recent there are more people talking about "Avoiding the boolean trap". I haven't used this yet, but I do like the idea.
Instead of having multiple boolean properties which need to be decoded in your container, you only add these properties: data, error and state.
The data property contains your API response data.
The error property will contain the error/problem occurred during the request.
The state property contains an enum (string/number) which represent the current state (obviously) of the request/response.
In your container, you can test the state property without having to combine more boolean properties.
import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { fetchRecommendedPosts } from "../redux/actions/postsActions";
import Post from "../components/sharedComponents/Post";
// this should be in a separate file `./src/enums/RequestState.js`
const RequestState = {
INITIAL: 'initial',
FETCHED: 'fetched',
LOADING: 'loading',
FAILURE: 'failure',
};
const RecommendedUserPostsPage = () => {
const dispatch = useDispatch();
const { token } = useSelector((state) => state.auth);
const { data, state } = useSelector((state) => state.posts.recommendedPosts);
useEffect(() => {
dispatch(fetchRecommendedPosts({ token }));
}, [token]);
return (
<div style={{ margin: "5em" }}>
{state === RequestState.FETCHED && data.map((post) => <Post postData={post} />)}
{state === RequestState.FAILURE && <p>Oh no!</p>}
{state === RequestState.LOADING && <p>Please stay seated...</p>}
</div>
);
};
export default RecommendedUserPostsPage;