useReducer passed with useContext but a child component displays empty state.map - javascript

First, I would thank you for the support.
As new to the ReactJS world, I am trying to complete a concept example of a product store with some filters as checkbox. The idea is that you select a filter, you get displayed the products that have the selected proprety.
Everything works, except that when you refresh the page you get the filters column and a blank column where products are supposed to appear, even if the console.log(state) give back the correct array of objects.
As you click a checkbox (the filters) it render correctly and the products appear.
The GITHUB LINK for the complete code.
Here the component CardProduct that does not display at refresh.
import React, { useContext, useEffect } from 'react'
import { AppContext } from '../../App'
import { Hearty } from '../Hearty'
import Star from '../Star'
import boiler from '../../images/boiler.png'
import Confronta from '../Confronta'
const CardProduct = ({ count, setCount }) => {
const [state, dispatch] = useContext(AppContext)
console.log('State in CardProduct:', state)
function returnCardProduct () {
return (
state.map((item, i) => {
const { brand, descrizione, prezzo, note, stelle } = item
return (
<div className="row">
<div className="colcard">
<div key={ i } className="card"
style={ { width: 'auto', height: 'auto' } }>
<Hearty/>
<img className="card-img-top" src={ boiler } alt="boiler"/>
<div className="card-body">
<p className="card-title"> { brand.toUpperCase() }</p>
<h6 className="card-text">{ descrizione }</h6>
<Star stelle={ stelle }/>
<h4> { prezzo } </h4>
<h5> { note } </h5>
<Confronta count={ count } setCount={ setCount }/>
</div>
</div>
</div>
</div>
)
}))
}
return (
<div className="container">
{ returnCardProduct() }
</div>
)
}
export default CardProduct
Here the Filters component
import { useContext, useEffect, useState } from 'react'
import { AppContext } from '../App'
const Filters = () => {
const [stock, setStock] = useState([])
const [state,dispatch] = useContext(AppContext)
function fetchInitialStock () {
async function fetchStock () {
let result1 = await fetch('http://localhost:9000/stock').
then(result1 => result1.json()).
then(data => setStock(data))
}
fetchStock()
return stock
}
useEffect (()=>fetchInitialStock(),[])
console.log( 'initStock' ,stock)
return (
<>
<div className="container">
<div className="row">
<div className="categories">
<p>CATEGORIE</p>
<h6>Riscaldamento</h6>
<h6>Casa e acqua</h6>
<h6>Casa</h6>
<h6>Acqua</h6>
</div>
<div className="scegli">
<p>SCEGLI PER</p>
<h6><span><input type="checkbox"
name="DISPONIBILI"
onChange={(e)=> {
e.target.checked ? dispatch({ type: 'DISPONIBILI' }) : dispatch({ type:'PREV' })
} }/>
</span> Disponibili ({stock.map((item) => item.disponibili )}) </h6>
<h6><span><input type="checkbox"
name="PROMO"
onChange={(e)=> e.target.checked ? dispatch({ type: 'PROMO' }) : dispatch({ type: 'PREV' }) }
/> </span>In Promozione ({ stock.map((item) => item.inSconto) }) </h6><br/>
</div>
<div className="marche">
<p>MARCHE</p>
<h6><span><input type="checkbox" name="ariston" onChange={(e)=>{
e.target.checked
? dispatch({ type: 'ARISTON' })
: dispatch({ type: 'PREV' })
}}
/> </span> Ariston ({stock.map((item)=>item.hasOwnProperty('brand')? item.brand.ariston: null)})</h6>
<h6><span><input type="checkbox" name="baxi" onChange={(e)=>{
e.target.checked
? dispatch({ type: 'BAXI' })
: dispatch({ type: 'PREV' })
}}/> </span>Baxi ({stock.map((item)=>item.hasOwnProperty('brand')? item.brand.baxi : null)})</h6><br/>
</div>
<div className="tipologia">
<p>TIPOLOGIA</p>
<h6><span><input type="checkbox" name="condensazione" onChange={(e)=>{
e.target.checked
? dispatch({ type: 'CONDENSAZIONE' })
: dispatch({ type: 'PREV' })
}}/> </span> A Condensazione ({stock.map((item)=>item.hasOwnProperty('tipologia')? item.tipologia.condensazione: null)}) </h6>
<h6><span><input type="checkbox" name="cameraAperta" onChange={(e)=>{
e.target.checked
? dispatch({ type: 'APERTA' })
: dispatch({ type: 'PREV' })
}}/> </span>Camera Aperta ({ stock.map((item)=>item.hasOwnProperty('tipologia')? item.tipologia.cameraAperta: null) }) </h6>
<h6><span><input type="checkbox" name="cameraStagna" onChange={(e)=>{
e.target.checked
? dispatch({ type: 'STAGNA' })
: dispatch({ type: 'PREV' })
}}/> </span>Camera Stagna ({ stock.map((item)=>item.hasOwnProperty('tipologia')? item.tipologia.cameraStagna: null) })</h6><br/>
</div>
</div>
</div>
</>
)
}
export default Filters
..and FINALLY the App()
import CardProduct from './components/CardProduct'
import { createContext, useReducer, useState, useEffect } from 'react'
import Filters from './components/Filters'
import Footer from './components/Footer/Footer'
export const AppContext = createContext()
function App () {
const [count, setCount] = useState(0)
function setInit (data, array) {
data.map((item) => array.push(item))
return array
}
/*Function for setting BOILERS from fetch*/
function fetchInitialBoiler () {
let initB = []
async function fetchBoilers () {
let response = await fetch('http://localhost:9000/boilers').
then(response => response.json()).
then(data => setInit(data, initB))
}
fetchBoilers()
return initB
}
const initBoilers = fetchInitialBoiler()
const [prev, setPrev] = useState([])
const [state, dispatch] = useReducer(reducer, initBoilers)
/* Define the reducer function*/
function reducer (state, action) {
let current
switch (action.type) {
case 'DISPONIBILI':
current = []
current = state.filter((item) => item.disponibile ? item : null)
setPrev(current)
return current
case 'PROMO':
current = []
current = state.filter((item) => item.inSconto ? item : null)
setPrev(current)
return current
case 'ARISTON':
current = []
current = state.filter(
(item) => ((item.hasOwnProperty('brand')) &&
(item.brand === 'Ariston'))
? item
: null)
setPrev(current)
return current
case 'BAXI':
current = []
current = state.filter(
(item) => (item.hasOwnProperty('brand')) && (item.brand === 'Baxi')
? item
: null)
setPrev(current)
return current
case 'CONDENSAZIONE':
current = []
current = state.filter((item) => (item.hasOwnProperty('tipologia')) &&
(item.tipologia === 'condensazione')
? item
: null)
setPrev(current)
return current
case 'APERTA':
current = []
current = state.filter((item) => (item.hasOwnProperty('tipologia')) &&
(item.tipologia === 'camera-aperta')
? item
: null)
setPrev(current)
return current
case 'STAGNA':
current = []
current = state.filter((item) => (item.hasOwnProperty('tipologia')) &&
(item.tipologia === 'camera-stagna')
? item
: null)
setPrev(current)
return current
case 'PREV':
current = []
/*console.log('PREV', prev)*/
return prev
default:
return state
}
}
return (
<>
<AppContext.Provider value={ [state, dispatch] }>
<main>
<div className="container">
<div className="container">
<>
<div className="col align-self-start">
<Filters/>
</div>
<div className="col-9">
<CardProduct count={ count } setCount={ setCount }/>
</div>
</>
</div>
</div>
<>
<div>
<Footer className="footer" count={ count }/>
</div>
</>
</main>
</AppContext.Provider>
</>
)
}
export default App
--- THANK YOU ---

Your initB is an array declartion which doesn't re-render a React component. Replace it with useState to see the updated data after the API call.
const [init, setInit] = useState([]);
Secondly,
const initBoilers = fetchInitialBoiler()
you should invoke the fetchInitialBoiler() after the document is mounted, with the current approach chances are the API is invoked even before the intial mount.
Invoke it in an [useEffect][1] block.
useEffect(()=>{
fetchInitialBoiler() //
// simply invoke it, since you're not returning anything from the function, there's no need to save the response.
}, [])
Your function should set the state in the then/catch block, so that the component tree re-renders in an error case.
async function fetchBoilers () {
let response = await fetch('http://localhost:9000/boilers').
then(response => response.json())
.then(data => setInit(response))
.catch(error => setError(error); // state
}
More on Fetch API: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API

So in the end the problem was to pass the fetch as initialState to useReducer. Searching online I found that is passed throught a dispatch action inside useEffect hook. In this case the useReducer hook is declared with initialState '[]' inside the component. Here the code:
const CardProduct = ({ count, setCount }) => {
const [state, dispatch] = useReducer(reducer, [])
async function initfetch () {
let response = await fetch('http://localhost:9000/boilers').
then(response => response.json()).
then(data => dispatch({
type : 'INITIALIZE',
data: data
}))}
useEffect(() => {
initfetch()
}, [])

Related

Reload table data when select menu item is changed

I have this table with select menu:
export interface IActivePairsProps extends StateProps, DispatchProps, RouteComponentProps<{ url: string }> {}
export const ActivePairs = (props: IActivePairsProps) => {
const [paginationState, setPaginationState] = useState(
overridePaginationStateWithQueryParams(getSortState(props.location, ITEMS_PER_PAGE, 'id'), props.location.search)
);
const [exchangeId, setExchangeId] = useState('');
const getAllEntities = () => {
props.getEntities(paginationState.activePage - 1, paginationState.itemsPerPage, `${paginationState.sort},${paginationState.order}`);
props.getExchangesList();
};
const sortEntities = () => {
getAllEntities();
const endURL = `?page=${paginationState.activePage}&sort=${paginationState.sort},${paginationState.order}&exchangeId=${exchangeId}`;
if (props.location.search !== endURL) {
props.history.push(`${props.location.pathname}${endURL}`);
}
};
useEffect(() => {
sortEntities();
}, [paginationState.activePage, paginationState.order, paginationState.sort]);
useEffect(() => {
const params = new URLSearchParams(props.location.search);
const page = params.get('page');
const sort = params.get('sort');
if (page && sort) {
const sortSplit = sort.split(',');
setPaginationState({
...paginationState,
activePage: +page,
sort: sortSplit[0],
order: sortSplit[1],
});
}
const exchangeId = params.get('exchangeId');
}, [props.location.search]);
const sort = p => () => {
setPaginationState({
...paginationState,
order: paginationState.order === 'asc' ? 'desc' : 'asc',
sort: p,
});
};
const handlePagination = currentPage =>
setPaginationState({
...paginationState,
activePage: currentPage,
});
const handleSyncList = () => {
sortEntities();
};
const { activePairsList, exchangesList, match, loading, totalItems } = props;
return (
<div>
<div className="table-responsive">
{activePairsList && activePairsList.length > 0 ? (
<Table responsive>
<thead>
<tr>
.....
<select onChange={e => setExchangeId(e.target.value)}>
{exchangesList
? exchangesList.map(otherEntity => (
<option value={otherEntity.exchangeId} key={otherEntity.exchangeId}>
{otherEntity.exchangeLongName} - {otherEntity.exchangeId}
</option>
))
: null}
</select>
.........
</Table>
) : (
!loading && <div className="alert alert-warning">No Active Pairs found</div>
)}
</div>
{props.totalItems ? (
<div className={activePairsList && activePairsList.length > 0 ? '' : 'd-none'}>
<Row className="justify-content-center">
<JhiItemCount page={paginationState.activePage} total={totalItems} itemsPerPage={paginationState.itemsPerPage} />
</Row>
<Row className="justify-content-center">
<JhiPagination
activePage={paginationState.activePage}
onSelect={handlePagination}
maxButtons={5}
itemsPerPage={paginationState.itemsPerPage}
totalItems={props.totalItems}
/>
</Row>
</div>
) : (
''
)}
</div>
);
};
const mapStateToProps = ({ activePairs, exchangesList }: IRootState) => ({
activePairsList: activePairs.entities,
exchangesList: exchangesList.entities,
loading: activePairs.loading,
totalItems: activePairs.totalItems,
});
const mapDispatchToProps = {
getEntities,
getExchangesList,
};
type StateProps = ReturnType<typeof mapStateToProps>;
type DispatchProps = typeof mapDispatchToProps;
export default connect(mapStateToProps, mapDispatchToProps)(ActivePairs);
How I can reload the table data when I change the select menu item? I would like to reload the data from the table data with the new selected exchageId param.
useEffect(fn, deps);
As we can see in the React documentation, the way we use the effect hook looks like this:
,fn is the effectful function, and deps is an array of values it depends on. Every time the component renders, React checks if all the values in the deps array are still the same. If any of them has changed since the last render, fn is run again.,All right, so far all the examples exhibit the same behavior. The effect simply doesn't run again if the dependency value doesn't change.
So you only need to give the useEffect hook exchageId as deps and the component's loading function as fn, then UseEffect will rerenders your component.

Prevent re-render using React.memo and React.useCallback

For learning purpose,
I am trying prevent re-render on <InputWithLable /> component whenever i Dismiss a search result (see deploy in Full code)
I have use React.memo but it still re-render. So I think maybe its props is the culprit. I use React.useCallback to handleSearch prop, but it doesn't work.
Full code
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
import React from 'react';
const API_ENDPOINT = 'https://hn.algolia.com/api/v1/search?query=';
const useSemiPersistentState = (key, initialState) => {
const [value, setValue] = React.useState(
localStorage.getItem(key) || initialState
);
React.useEffect(() => {
localStorage.setItem(key, value);
}, [value, key]);
return [value, setValue];
};
function storiesReducer(prevState, action) {
switch (action.type) {
case "SET":
return { ...prevState, data: action.data, isLoading: false, isError: false };
case "REMOVE":
return {
...prevState,
data: prevState.data.filter(
story => action.data.objectID !== story.objectID
)
}
case "ERROR":
return { ...prevState, isLoading: false, isError: true };
default:
throw new Error();
}
}
const App = () => {
const [searchTerm, setSearchTerm] = useSemiPersistentState(
'search',
'Google'
);
const [stories, dispatchStories] = React.useReducer(storiesReducer, { data: [], isLoading: true, isError: false });
const [url, setUrl] = React.useState("");
const handleFetchStories = React.useCallback(() => {
fetch(url)
.then((response) => response.json())
.then((result) => {
console.log(result);
dispatchStories({ type: "SET", data: result.hits })
})
.catch(err => dispatchStories({ type: "ERROR", data: err }))
}, [url])
React.useEffect(() => {
handleFetchStories();
}, [handleFetchStories])
const handleRemoveStory = React.useCallback(
(item) => {
dispatchStories({ type: "REMOVE", data: item });
},
[], // chi render 1 lan vi props khong thay doi
)
const handleSearch = React.useCallback(
(e) => {
setSearchTerm(e.target.value);
},
[],
)
// Chuc nang filter la cua server (vd: database)
// const searchedStories = stories.data ? stories.data.filter(story =>
// story.title.toLowerCase().includes(searchTerm.toLowerCase())
// ) : null; // nghich cai nay!
console.log('App render');
return (
<div>
<h1>My Hacker Stories</h1>
<InputWithLabel
id="search"
value={searchTerm}
isFocused
onInputChange={handleSearch}
>
<strong>Search:</strong>
</InputWithLabel>
<button onClick={() => setUrl(API_ENDPOINT + searchTerm)}>Search!</button>
<hr />
{stories.isError && <h4>ERROR!</h4>}
{stories.isLoading ? <i>Loading...</i>
: <List list={stories.data} onRemoveItem={handleRemoveStory} />}
</div>
);
};
const InputWithLabel = React.memo(
({
id,
value,
type = 'text',
onInputChange,
isFocused,
children,
}) => {
const inputRef = React.useRef();
React.useEffect(() => {
if (isFocused) {
inputRef.current.focus();
}
}, [isFocused]);
console.log('Search render')
return (
<>
<label htmlFor={id}>{children}</label>
<input
ref={inputRef}
id={id}
type={type}
value={value}
onChange={onInputChange}
/>
</>
);
}
);
// Prevent default React render mechanism: Parent rerender -> Child rerender
const List = React.memo(
({ list, onRemoveItem }) =>
console.log('List render') || list.map(item => (
<Item
key={item.objectID}
item={item}
onRemoveItem={onRemoveItem}
/>
))
);
const Item = ({ item, onRemoveItem }) => (
<div>
<span>
<a href={item.url}>{item.title}</a>
</span>
<span>{item.author}</span>
<span>{item.num_comments}</span>
<span>{item.points}</span>
<span>
<button type="button" onClick={() => onRemoveItem(item)}>
Dismiss
</button>
</span>
</div>
);
export default App;
You should not be looking at how many times a component's render function gets called; React is free to call it as many times as it likes (and indeed, in strict mode, it calls them twice to help you not make mistakes).
But to answer your question (with the actual code that uses children):
<InputWithLabel>
<strong>Search:</strong>
</InputWithLabel>
compiles down to
React.createElement(InputWithLabel, null,
React.createElement("strong", null, "Search:"))
the identity of the children prop (the <strong /> element) changes for each render of the parent component since React.createElement() returns new objects for each invocation. Since that identity changes, React.memo does nothing.
If you wanted to (but please don't), you could do
const child = React.useMemo(() => <strong>Search:</strong>);
// ...
<InputWithLabel>{child}</InputWithLabel>
but doing that for all of your markup leads to nigh-unreadable code.

change button text and state by click and show list in react hooks

I have one button in front each list item when I click on the watched button, I want the text of the button to be changed to not watched(or when click on not watched it change to watched) and to be included in the watched list. At the top, I have three buttons, watched and not watched , which one I clicked on. Show me the list of the movies that I changed, their state and for third button(with text of all )it shows the whole list.I think that my problem is handleWatchedBtn function . this is picture of project maybe it is simple my explanation! thank you for your help.
import React, { useEffect, useState } from "react";
const App = () => {
const [Movies, setMovie] = useState([]);
const [Loading, setLoading] = useState(true);
const [Keyword, setKeyword] = useState("");
const [OverSeven, setOverSeven] = useState(false);
const [filterByWatch, setfilterByWatch] = useState("ALL");
useEffect(() => {
fetch("http://my-json-server.typicode.com/bemaxima/fake-api/movies")
.then((response) => response.json())
.then((response) => {
setMovie(
response.map((item) => ({
id: item.id,
name: item.name,
rate: item.rate,
watched: false,
}))
);
setLoading(false);
});
}, []);
function handleWatchedBtn(id) {
setMovie(() =>
Movies.map((movie) => {
if (movie.id === id) {
return { movie, watched: !movie.watched };
}
return movie;
})
);
}
function handleWatchedChange(filter) {
setfilterByWatch({ filterByWatch: filter });
}
function handleKeywordChange(e) {
setKeyword(e.target.value);
}
function handleOverSevenChange(e) {
setOverSeven(e.target.checked);
}
function filterItems() {
return Movies.filter((item) =>
item.name.toLowerCase().includes(Keyword.toLowerCase())
)
.filter((item) => (OverSeven ? item.rate > 7 : true))
.filter((item) =>
filterByWatch === "ALL"
? true
: item.watched === (filterByWatch === "WATCHED")
);
}
if (Loading) {
return "Please wait...";
}
return (
<div>
<div>
<div>
Keyword
<input type="text" value={Keyword} onChange={handleKeywordChange} />
</div>
<div>
<button onClick={() => handleWatchedChange("ALL")}>all</button>
<button onClick={() => handleWatchedChange("WATCHED")}>watch</button>
<button onClick={() => handleWatchedChange("NOT_WATCHED")}>
not watch
</button>
</div>
<div>
Only over 7.0
<input
type="checkbox"
checked={OverSeven}
onChange={handleOverSevenChange}
/>
</div>
<div>
<ul>
{filterItems().map((movie) => (
<li data-id={movie.id}>
{`${movie.name} ${movie.rate}`}{" "}
<button onClick={() => handleWatchedBtn(movie.id)}>
{movie.watched ? "Watched" : " Not watched"}
</button>
</li>
))}
</ul>
</div>
</div>
</div>
);
};
export default App;
You could introduce a new array state which stores all filtered movies and is updated every time the movies or the filter are updated.
You can then pass its reference to the map function that generates the list.
Also, notice that I've added the spread operator (...) in the handleWatchedBtn function and adjusted the handleWatchedChange method to update the state to a string and not an object.
Try to change your code like this:
import React, { useEffect, useState } from "react";
const App = () => {
const [Movies, setMovie] = useState([]);
const [filteredMovies, setFilteredMovies] = useState([]);
const [Loading, setLoading] = useState(true);
const [Keyword, setKeyword] = useState("");
const [OverSeven, setOverSeven] = useState(false);
const [filterByWatch, setfilterByWatch] = useState("ALL");
useEffect(() => {
fetch("http://my-json-server.typicode.com/bemaxima/fake-api/movies")
.then((response) => response.json())
.then((response) => {
const newMovies = response.map((item) => ({
id: item.id,
name: item.name,
rate: item.rate,
watched: false,
}));
setMovie(newMovies);
setLoading(false);
});
}, []);
useEffect(() => {
// Update filtered movies when the data or the filter changes
const newFilteredMovies = Movies.filter((item) =>
item.name.toLowerCase().includes(Keyword.toLowerCase())
)
.filter((item) => (OverSeven ? item.rate > 7 : true))
.filter((item) =>
filterByWatch === "ALL"
? true
: item.watched === (filterByWatch === "WATCHED")
);
setFilteredMovies(newFilteredMovies);
}, [Movies, filterByWatch]);
function handleWatchedBtn(id) {
setMovie(() =>
Movies.map((movie) => {
if (movie.id === id) {
// Add the spread operator here
return { ...movie, watched: !movie.watched };
}
return movie;
})
);
}
function handleWatchedChange(filter) {
// Change this line
setfilterByWatch(filter);
}
function handleKeywordChange(e) {
setKeyword(e.target.value);
}
function handleOverSevenChange(e) {
setOverSeven(e.target.checked);
}
if (Loading) {
return "Please wait...";
}
return (
<div>
<div>
<div>
Keyword
<input type="text" value={Keyword} onChange={handleKeywordChange} />
</div>
<div>
<button onClick={() => handleWatchedChange("ALL")}>all</button>
<button onClick={() => handleWatchedChange("WATCHED")}>watch</button>
<button onClick={() => handleWatchedChange("NOT_WATCHED")}>
not watch
</button>
</div>
<div>
Only over 7.0
<input
type="checkbox"
checked={OverSeven}
onChange={handleOverSevenChange}
/>
</div>
<div>
<ul>
{filteredMovies.map((movie) => (
<li data-id={movie.id}>
{`${movie.name} ${movie.rate}`}{" "}
<button onClick={() => handleWatchedBtn(movie.id)}>
{movie.watched ? "Watched" : " Not watched"}
</button>
</li>
))}
</ul>
</div>
</div>
</div>
);
};
export default App;

Why useEffect runs every time when component re-render?

In my Home component(I call it Home Page!) I am using Cards.JS component which has posts attribute as shown in following code.
const Home = () => {
const dispatch = useDispatch()
const isLoading = useSelector(state => state.isLoading)
const currentPage = useSelector((state) => state.idFor.currentPageHome)
const homePosts = useSelector((state) => state.posts)
useEffect(() => {
dispatch(setIsLoading(true))
dispatch(getAllPosts(currentPage))
}, [dispatch, currentPage])
return (
isLoading ? (
<Loader type="ThreeDots" color="#000000" height={500} width={80} />
) : (
<Cards posts={homePosts} setCurrentPage={setCurrentPageHome} currentPage={currentPage} pageName={"LATEST"} />
)
)
}
And Cards.Js is as following
const Cards = ({ posts, setCurrentPage, currentPage, pageName }) => {
console.log('Cards.JS called', posts);
const dispatch = useDispatch()
useEffect(() => {
dispatch(setIsLoading(false))
})
const handleNextPage = () => {
dispatch(setIsLoading(true))
dispatch(setCurrentPage(currentPage + 1))
}
const handlePreviousPage = () => {
dispatch(setIsLoading(true))
dispatch(setCurrentPage(currentPage - 1))
}
return (
<div className="container">
<h4 className="page-heading">{pageName}</h4>
<div className="card-container">
{
posts.map(post => <Card key={post._id} post={post} />)
}
</div>
<div className="page-div">
{currentPage !== 1 ? <span className="previous-page" onClick={handlePreviousPage}><</span>
: null}
<span className="next-page" onClick={handleNextPage}>></span>
</div>
</div>
)
}
My Problem:
When i come back to home page useEffect is called everytime and request same data to back-end which are already avaliable in Redux store.
Thanks in Advance :)
useEffect will run every time the component rerenders.
However, useEffect also takes a second parameter: an array of variables to monitor. And it will only run the callback if any variable changes in that array.
If you pass an empty array, it will only run once initially, and never again no matter how many times your component rerenders.
useEffect(() => {
dispatch(setIsLoading(false))
}, [])

Is there a way to re-render data after submit-axios post request? ReactJS

So I've recently started on learning React, where I've created a little project for me. Now on backend everythings works etc.
By now everything was going good, but now I've got stuck.
Now about the page: I've got page, where u can see details about single article, and get info about loan for price of article. I've made it on backend that default value of it is 60 months, and if u want different period, u submit other months value eg. 120. So on backend when I hit route http://localhost:8080/api/article/id i get response of data and loan is calculated with 60 months. Now if in body i send eg. {"months": 6} i get different data in response which is expected and working fine.
Now where I've hit a wall: on front end I have no idea how to update data when form is submited. Here you can see my from:
And idea is when u enter eg. 6 to lower part of page is changed:
These last two right clomuns should be changed.
Now I've tried to send with id months to the actions and then refresh page when disptach is triggered but no success - and I know that after refresh months are reseted to default value.
Now these values come from that localhost route and I'm fetching it with a axios call, and displaying content
Here is my Article.js component:
import React, { useEffect, useState, useRef } from 'react';
import { useDispatch } from 'react-redux';
import { Link } from 'react-router-dom';
import Form from 'react-validation/build/form';
import Input from 'react-validation/build/input';
import CheckButton from 'react-validation/build/button';
import ArticleService from '../services/article.service';
import { getArticle } from '../actions/articles';
const Article = (props) => {
const form = useRef();
const checkBtn = useRef();
const [content, setContent] = useState([]);
const [dataArr, setDataArr] = useState([]);
const [months, setMonths] = useState([]);
const [loading, setLoading] = useState(false);
const dispatch = useDispatch();
const onChangeMonths = (e) => {
const months = e.target.value;
setMonths(months);
};
const handleMonths = (e) => {
e.preventDefault();
setLoading(true);
if (checkBtn.current.context._errors.length === 0) {
const id = props.match.params.id;
dispatch(getArticle(id, months))
.then(() => {})
.catch(() => {
setLoading(false);
});
} else {
setLoading(false);
}
};
useEffect(() => {
const fetchPosts = async () => {
const id = props.match.params.id;
const res = await ArticleService.article(id);
setContent(res.data);
const data = res.data.kredit;
const dataArr = [];
dataArr.push({
name: 'kreditNKS-rataNKS',
price: data.kreditNKS.map((item) => {
return item;
}),
rate: data.rataNKS.map((item) => {
return item;
}),
nks: data.stopaNKS.map((item) => {
return item;
}),
banka: {
eks: data.stopaEKS.map((item) => {
return item;
}),
bankname: data.ime.map((item) => {
return item;
}),
type: data.tip.map((item) => {
return item;
}),
},
});
setDataArr(dataArr);
};
fetchPosts();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const a = dataArr;
return (
<div>
<p className='text-dark'>
<Link to='/dashboard'>
<i className='fas fa-arrow-left'></i> Nazad
</Link>
</p>
<div className='container p-3 my-3 bg-dark text-white'>
<strong>Artikal id:{content.id}</strong>
<br></br>
<br></br>
<div className='row'>
<div className='col-sm'>
Opis:
<br></br>
{content.descr}
</div>
<div className='col-sm'>
Cijena
<br></br>
{content.price}
</div>
<div className='col-sm'>
Cijena po metru kvadratnom:
<br></br>
{content.ppm2}/m2
</div>
</div>
</div>
<div className='container'>
<h3>KREDITI ZA CIJENU {content.price}</h3>
<Form onSubmit={handleMonths} ref={form}>
<div className='form-group'>
<label>Vrijeme otplate u mjesecima:</label>
<Input
type='text'
className='form-control w-25'
name='months'
value={months}
onChange={onChangeMonths}
/>
<button
className='btn btn-primary btn-block w-25'
disabled={loading}
>
{loading && (
<span className='spinner-border spinner-border-sm'></span>
)}
<span>Click</span>
</button>
<CheckButton style={{ display: 'none' }} ref={checkBtn} />
<small>
Ako se ne unese vrijeme otplate kredita, kredit se izračunava za
60 mjeseci
</small>
</div>
</Form>
</div>
<div className='container-fluid'>
<br></br>
<h4>Lista kredita</h4>
<div className='row'>
<div className='col-sm'>
<h4>Informacije o banci</h4>
{a &&
a.map((item) =>
item.banka.bankname.map((its, index) => (
<div className='card card-body flex-fill'>
<h2>{its}</h2>
<h6>EKS: {item.banka.eks[index]}%</h6>
<h6>Tip: {item.banka.type[index]} K.S</h6>
</div>
))
)}
</div>
<div className='col-sm'>
<h4>NKS</h4>
{a &&
a.map((item) =>
item.nks.map((s) => (
<div className='card card-body flex-fill'>
<h2>{s}</h2>
</div>
))
)}
</div>
<div className='col-sm'>
<h4>Ukupna cijena kredita</h4>
{a &&
a.map((item) =>
item.price.map((it2) => (
<div className='card card-body flex-fill'>
<h2>{it2} KM</h2>
</div>
))
)}
</div>
<div className='col-sm'>
<h4>Rata</h4>
{a &&
a.map((item) =>
item.rate.map((it2) => (
<div className='card card-body flex-fill'>
<h2>{it2} KM/mj</h2>
</div>
))
)}
</div>
</div>
</div>
</div>
);
};
export default Article;
actions/article.js
import { SET_MESSAGE, RATE_UPDATE, UPDATE_FAIL } from './types';
import ArticleService from '../services/article.service';
export const getArticle = (id, months) => (dispatch) => {
return ArticleService.article(id, months).then(
(response) => {
dispatch({
type: RATE_UPDATE,
});
dispatch({
type: SET_MESSAGE,
payload: response.data.message,
});
return Promise.resolve();
},
(error) => {
const message =
(error.response &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString();
dispatch({
type: UPDATE_FAIL,
});
dispatch({
type: SET_MESSAGE,
payload: message,
});
return Promise.reject();
}
);
};
services/article.service.js
import axios from 'axios';
//const API_URL = 'https://stanbackapp.azurewebsites.net/api/articles/';
const API_URL = 'http://localhost:8080/api/articles/';
//const dAPI_URL = 'https://stanbackapp.azurewebsites.net/api/article/';
const dAPI_URL = 'http://localhost:8080/api/article/';
const articles = () => {
return axios.get(API_URL);
};
const article = (id, months) => {
return axios.post(dAPI_URL + `${id}`, {
months,
});
};
export default {
articles,
article,
};
I want to frontend behave just like backend: If i don't send anything in the form display data for 60 months.(that is what is doing now), but if i type in 10 and hit click a want to page re-render these two last columns(because if you send months only them are changed) and display that new data.
Also I've tried this in dispatch:
dispatch(getArticle(id, months))
.then((response) => console.log(response))
But console.log is undefiend
Any tips? Thanks!
I think your issue may be that you are mixing state. The first time you fetch data (via useEffect), you are fetching directly via axios. However, when you fetch data in the handleMonths, you are passing the action over to Redux, which operates very differently. The other issue is that the component never retrieves the data from Redux, so any updates are essentially ignored.
My suggestion would be to use the same method for retrieving data the first time as you do subsequent times:
const handleMonths = (e) => {
...
if (checkBtn.current.context._errors.length === 0) {
const id = props.match.params.id;
try {
const res = await ArticleService.article(id, months);
... // Handle the response appropriately.
setContent(res.data);
const data = res.data.kredit;
const dataArr = [];
dataArr.push({
name: 'kreditNKS-rataNKS',
price: data.kreditNKS,
rate: data.rataNKS,
nks: data.stopaNKS,
banka: {
eks: data.stopaEKS,
bankname: data.ime,
type: data.tip,
},
});
setDataArr(dataArr);
} catch (e) {
setLoading(false);
}
} else {
setLoading(false);
}
};
This provides 2 benefits:
You can extract the method for handling the response data to make it the same for both cases.
You remove the need for an external state handler (i.e. Redux) which you may not need.

Categories