How to render data from an array created with "reduce()" - javascript

Hook and then map is my possible solution but i need to know how
Well I'm using react and firestore so, the data fetch is saved in a hook called "Asistencias"
well my target it was to get a group of weeks with the same number and collect data by that week, n i get it, but now i would like to render that data, so I need help.
this is the response with the function with reduce
export const Presupuesto = () => {
const auth = getAuth()
const dato =auth.currentUser;
const [Presupuestos, setPresupuesto] = useState([]);
const [Asistencias, setAsistencias] = useState([]);
const [itinerante, setItinerante] = useState([])
const getPresupuestos =async () => {
const q = query(collection(db, "asignaciones"),where("asistencias", "!=", [] ))
await onSnapshot(q, (query)=>{
const data=[]
query.forEach((doc)=>{
data.push(doc.data())
})
setPresupuesto(data)
}) }
useEffect(()=>{
getPresupuestos()
},[])
console.log("hook: ", Asistencias);
const AsistenciasPresupuesto = (props) => {
return props.reduce((past, current)=>{
const foundItem = past.find(it => it.semana === current.semana)
console.log('past:', past);
if (foundItem ){
foundItem.data=foundItem.data
?[...foundItem.data, {'trabajador': current.trabajador,'entrada':current.entrada, 'salida': current.salida}]
:[{ 'trabajador': current.trabajador,'entrada':current.entrada, 'salida': current.salida }]
}else{ past.push( {
'semana': current.semana,
'data': [{
'trabajador': current.trabajador,'entrada':current.entrada, 'salida': current.salida
}]
} ) }
return past;
}, [])}
AsistenciasPresupuesto(Asistencias)
return (
<Card>
<div className='presupuestos'>
{
Presupuestos.map((presupuesto)=>(
<Button variant="danger"
id={presupuesto.obra}
className={presupuesto.obra}
value={presupuesto.presupuesto}
onClick={
(e)=>{
e.preventDefault()
console.log("objeto completo:", presupuesto.asistencias)
setAsistencias(presupuesto.asistencias)
console.log("asistencias:", Asistencias)
}} > {presupuesto.presupuesto} </Button>))
}
</div>
<div>
<Card id="prueba" className='lg'>
{/*
i would like to render here! */}
</Card>
</div>
</Card>
)
}
this is my code
this is the render

Related

React.js: filtering API object with 2 search bars

I am fetching an API data set and filtering that data with a search bar to locate by first or last name. I also have an input field that allows you to add "tags" to the data set that I am mapping through. I am trying to add a second search bar to filter the original data by the unique tags as well, but can not figure out how to incorporate that information into the filter.
export default function Home() {
const [students, setStudents] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [search, setSearch] = useState("");
const [showTests, setShowTests] = useState({});
const [tagSearch, setTagSearch] = useState("");
const [tags, setTags] = useState([]);
useEffect(() => {
const getData = async () => {
try {
const response = await axios.get(
<!-- API -->
);
setStudents(response.data);
setError(null);
} catch (err) {
setError(err.message);
setStudents(null);
} finally {
setLoading(false);
}
};
getData();
}, []);
return (
<div className="home-main">
<Search setSearch={setSearch} />
<TagSearch setTagSearch={setTagSearch} />
{loading && <div>Loading, please wait ...</div>}
{error && (
<div>{`An Error has occurred. - ${error}`}</div>
)}
<div className="students">
<Fragment>
{
students
&&
students.students.filter((val) => {
if(search === '' || tagSearch === '') {
return val
} else if(val.firstName.toLowerCase().includes(search.toLowerCase())
|| val.lastName.toLowerCase().includes(search.toLowerCase())
|| tags.text.toLowerCase().includes(tagSearch.toLowerCase()) ){
return val
}
}).map(({val}) => (
<!-- additional info -->
<div className="tags">
<Tags setTags={setTags} />
</div>
</div>
</div>
))
}
</Fragment>
</div>
</div>
);
}
This is where the "tag" state is coming from...
export default function Tags({setTags}) {
const [inputText, setInputText] = useState('');
const [tiles, setTiles] = useState([]);
const inputTextHandler = (e) => {
setInputText(e.target.value);
};
const submitTagHandler = () => {
setTiles([
...tiles, {text: inputText, id: Math.floor(Math.random() * 1000000)}
]);
setTags([
...tiles, {text: inputText}
])
setInputText('');
};
return (
<div className="tags-main">
<div className="tiles-contain">
{
tiles.map((obj) => (
<Tiles key={obj.id} text={obj.text} id={obj.id} tiles={tiles} setTiles={setTiles} />
))
}
</div>
<input value={inputText} onChange={inputTextHandler} onKeyPress={(e) => {
if(e.key === 'Enter') {
if(inputText !== "") {
submitTagHandler();
} else {
alert("Please enter a tag")
}
};
}} placeholder='Add Tag Here' type="text" />
</div>
);
}
It works without the tag state added to the filter. After adding the tag logic neither search bar works. How can I add the array of tags to the filter dependency to sort by first or last name and tags?
I'm pretty sure you were getting an error "cannot read toLowerCase of undefined"
You probably wanted to do something like this
tags.some(tag => tag.text.toLowerCase() === tagSearch.toLowerCase())
or
tags.map(tag => tag.text.toLowerCase()).includes(tagSearch.toLowerCase())

React State updates too late

I try to render a list with products but unfortunely the list updates too late.
The Products List is empty. When I execute it the second time it works.
Is there any solution?
Thank you very much!
export const Products = (props) => {
const [products, setProducts] = useState([]);
const [ListProducts, setListProducts] = useState([]);
const [List, setList] = useState([]);
const loadProducts = async (categorieid) => {
const apiProducts = await axios.get(`${url}/products/${categorieid}`);
setProducts(apiProducts.data.body);
};
const mapData = async () => {
if (products && products.length) {
products.map((product) =>
ListProducts.push(
<ProductCard product={product} key={product.id} />
)
);
setListProducts(ListProducts);
} else {
List.push(
<div className="column">
<span className="title has-text-grey-light">
No products found!
</span>
</div>
);
}
const renderListProducts = () => {
return <div className="container">{ListProducts}</div>;
};
const writeCategory = async (kategorieId) => {
await loadProducts(kategorieId);
await mapData();
await renderListProducts();
};
}
your mistake is in here
const writeCategory = async (kategorieId) => {
await loadProducts(kategorieId);
await mapData();
await renderListProducts();
};
setProducts is called at the end of loadProducts, but it is not necessary will update the state before mapData will be called.
The solution is to use useEffect as the trigger for next function
const writeCategory = async (kategorieId) => {
loadProducts(kategorieId);
};
useEffect(() => { products.length && mapData(), [products.length] }
and so on for the third function renderListProducts
BTW in
products.map((product) =>
ListProducts.push(
<ProductCard product={product} key={product.id} />
)
)
you're modifying ListProducts state outside setState, consider do something like
const temp = products.map((product) => <ProductCard product={product} key={product.id} /> )
setListProducts(temp)
instead

Why doesn't my component re-render when state changes (redux)?

I'm learning redux and i'm making a sort of pokedex app where i fetch 20 pokemons from pokeapi.co at a time. When the page changes a new list of 20 pokemons is fetched. The problem is that while state changes to the new pokemons, they don't actually render.
App.js
const App = () => {
const dispatch = useDispatch();
const offset = useSelector(state => state.offset);
const limit = useSelector(state => state.limit);
useEffect(() => {
//FETCHES 20 OBJECTS THAT CONTAIN AN URL TO AN INDIVIDUAL POKEMON
dispatch(fetchPokemons(limit, offset));
}, [limit, offset, dispatch]);
...
return (
<div style={{ backgroundColor: '#222222' }}>
<Notification />
<AppBarPokemon />
<Switch>
<Route path="/pokemons">
<PokemonsDisplay CapsFirstLetter={CapsFirstLetter}/>
</Route>
...
PokemonsDisplay.js
const PokemonsDisplay = ({ CapsFirstLetter }) => {
const dispatch = useDispatch();
const classes = useStyles();
const pokemons = useSelector(state => state.pokemons);
console.log(pokemons);
const pageSize = 20;
const totalCount = 898;
const handleClick = (p) => {
dispatch(getOnePokemon(p));
};
return (
<div className={classes.root}>
{pokemons && (
<Grid container spacing={3}>
{pokemons.map(p => (
<Grid item xs={3} key={p.name} className={classes.gridItem} component={Link} onClick={() => handleClick(p)} to={`/pokemons/${p.name}`} data-cy={`pokemon-button-${p.name}`}>
<Paper className={classes.paper && classes.color} elevation={10}>
<p className={classes.p}>#{p.id}</p>
<p className={classes.p}>{CapsFirstLetter(p.name)}</p>
{p.sprites.other["dream_world"]["front_default"] !== null ?
<img className={classes.image} alt={`${p.name}'s sprite`} src={p.sprites.other["dream_world"]["front_default"]}/> :
<img className={classes.image} alt={`${p.name}'s sprite`} src={p.sprites.other["official-artwork"]["front_default"]}/>}
</Paper>
</Grid>
))}
</Grid>
)}
<Pagination
totalCount={totalCount}
pageSize={pageSize}
/>
</div>
);
};
pokemonsReducer.js
import getPokemons from '../services/pokemons';
import axios from 'axios';
import { loadPokemonsFromLS, savePokemonsList } from '../utils/localStoragePokemons';
const pokemonsReducer = (state = [], action) => {
console.log('state is:', state)
switch(action.type){
case 'INIT_POKEMONS':
return action.data;
default:
return state;
};
};
export const fetchPokemons = (limit, offset) => {
return async dispatch => {
try {
const pokemons = loadPokemonsFromLS(limit, offset);
dispatch({
type: 'INIT_POKEMONS',
data: pokemons
});
} catch (error) {
const pokemons = await getPokemons.getPokemons(limit, offset);
let pokemonsArray = [];
let pokemonsObject = {};
pokemons.results.forEach(async (r, i) => {
//FETCHES EACH POKEMON URL AND STORES ITS DATA ON pokemons STATE
const pokemonNow = await axios.get(r.url);
pokemonsArray.push(pokemonNow.data);
pokemonsObject[i] = pokemonNow.data
});
savePokemonsList(limit, offset, pokemonsObject);
dispatch({
type: 'INIT_POKEMONS',
data: pokemonsArray
});
}
};
};
export default pokemonsReducer;
I have tried to dispatch({ data: [...pokemons] })
But it doesnt work.
Also i forgot to add. When i go to my component that is routed to '/' and then back to '/pokemons' they render.
Edit: I think i'm getting there.
i changed the reducer function so that it gets called independently from the dispatch, the problem is that now the action doesn't get fired xD.
export const fetchEverything = async (limit, offset) => {
try {
const pokemons = loadPokemonsFromLS(limit, offset);
initPokemons(pokemons);
} catch (error) {
const pokemonsData = await getPokemons.getPokemons(limit, offset);
let pokemons = [];
let pokemonsObject = {};
console.log(pokemonsData)
pokemonsData.results.forEach(async (r, i) => {
//FETCHES EACH POKEMON URL AND STORES ITS DATA ON pokemons STATE
const pokemonNow = await axios.get(r.url);
pokemonsObject[i] = pokemonNow.data;
//console.log([pokemonNow.data][0]);
pokemons.push(pokemonNow.data);
});
console.log(pokemons);
console.log(pokemonsObject);
savePokemonsList(limit, offset, pokemonsObject);
initPokemons(pokemons);
};
};
export const initPokemons = (pokemons) => {
return dispatch => dispatch({ type: 'INIT_POKEMONS', pokemons: pokemons })
};
const pokemonsReducer = (state = [], action) => {
switch(action.type){
case 'INIT_POKEMONS':
console.log(action);
const newState = action.pokemons
return newState;
default:
return state;
};
};
It happens due to redux state mutation, you can resolve this issue using immer as stated in redux documentation as well. https://www.npmjs.com/package/immer

How to use useEffect on button Click?

I've to call useEffect / Fetch the data only when user click on Search Button otherwise not fetch the data..
Code:
const App = () => {
const[datas,setDatas] = useState([])
const [space,setSpace] = useState(null)
const [print, setPrint] = useState(false)
function getData(val){
// console.log(val.target.value)
setSpace(val.target.value);
setPrint(false)
}
// console.log(space)
useEffect(() => {
const fecthPosts = async () => {
let initial_url = `http://localhost:4000/search`
let url = initial_url + "?text=" + space
const res = await fetch(url);
const {result} = await res.json();
setDatas(result);
fecthPosts(); //I've to call this fetchPosts() when Btn is CLicked
},[space]);
return(
<div className="App">
{ //Displaying on search
print?
<>
<h2>{space}</h2>
<div>
{datas.map((field) =>
<p>{field.title}</p>
<p>{field.author}</p>
)}
</div>
</>
:null
}
<input type="text" onChange={getData} />
<button onClick={() => { setSpace(true); fetchPosts() }}>search</button>
</div>
)
}
};
export default App;
It's not working Error:
fetchPosts() is not defined...
I've also tried like this:
function trigger(){
useEffect(() => {
const fecthPosts = async () => {
let initial_url = `http://localhost:4000/search`
let url = initial_url + "?text=" + space
const res = await fetch(url);
const {result} = await res.json();
setDatas(result);
fecthPosts(); //I've to call this fetchPosts() when Btn is CLicked
},[space]);
}
<button onClick={() => { setSpace(true); trigger() }}>search</button>
It's not working Error:
React Hook useEffect has unnecessary dependencies:'space'
/PLZZ help to out...
make a separate function for api call and in your UseEffect function just call that function and on Button click function call the Api Function and it fetch data automatically
Use useCallback not useEffect
useCallback is similar to useEffect but is for when a function needs a callback, like what you're doing here onClick. useEffect is used in response to some prop changing not an action taken by a user.
You have to set your fetchPosts outside of the useEffect.
Then, you can use a new state search to track any click on the button.
const App = () => {
const [datas, setDatas] = useState([]);
const [space, setSpace] = useState(null);
const [print, setPrint] = useState(false);
const [search, setSearch] = useState(false);
const fetchPosts = async () => {
let initial_url = `http://localhost:4000/search`;
let url = initial_url + "?text=" + space;
const res = await fetch(url);
const { result } = await res.json();
setDatas(result);
};
function getData(val) {
setSpace(val.target.value);
setPrint(false);
}
useEffect(() => {
fetchPosts(); // fecthPosts is called each time space changed
}, [search]);
return (
<div className="App">
{
//Displaying on search
print ? (
<>
<h2>{space}</h2>
<div>
{datas.map((field) => (
<>
<p>{field.title}</p>
<p>{field.author}</p>
</>
))}
</div>
</>
) : null
}
<input type="text" onChange={getData} />
<button onClick={() => setSearch(!search)}>search</button>
</div>
);
};
export default App;
I initialized shouldFetchData = false. Once the button is clicked, I changed it's value; shouldFetchData = true. Inside useEffect I called fetchPosts() only when shouldFetchData is true.
import React, { useState } from "react";
const App = () => {
const [datas, setDatas] = useState([])
const [shouldFetchData, setShouldFetchData] = useState(false);
const [space, setSpace] = useState(null)
const [print, setPrint] = useState(false)
function getData(val) {
// console.log(val.target.value)
setSpace(val.target.value);
setPrint(false)
}
// console.log(space)
const fecthPosts = async () => {
let initial_url = `http://localhost:4000/search`
let url = initial_url + "?text=" + space
const res = await fetch(url);
const { result } = await res.json();
setDatas(result);
fecthPosts(); //I've to call this fetchPosts() when Btn is CLicked
}
useEffect(() => {
if(shouldFetchData) {
fecthPosts();
}
}, [space]);
return (
<div className="App">
{
print ?
<>
<h2>{space}</h2>
<div>
{datas.map((field) =>
<>
<p>{field.title}</p>
<p>{field.author}</p>
</>
)}
</div>
</>
: null
}
<input type="text" onChange={getData} />
<button onClick={() => {
setSpace(true);
setShouldFetchData(true);
}}>search</button>
</div>
)
};
export default App;
I found a few syntax errors in your code, so I hope I did what you intended.
If This is not the proper way to do this or if there exists a better way, please let me know. I'd be happy to learnšŸ¤—.

There's a bug with Search and Pagination in React

I'm building my site in React and I have created pagination and search. When I search for something on the site, it only works when after that I go to another page. I think this is due to the fact that Softwares and Pagination are in the same component.
Then I tried lifting-state-up, but I got an error: React Minified Error # 31.
Here's Pagination component:
const Paginator = ({
total, // Total records
startPage = 1,
totalPages = null,
onMovePage = null,
}) => {
...
return (
<>
<section id={styles.paginator}>
<Header/>
...
{range(1, totalPages+1).map(p => (
<PagItem key={p} handleClick={ () => {setCurrentPage(p); onMovePage && onMovePage({currentPage: p})} } title={p} name={p} />
))}
...
</section>
</>
);
};
Here's Softwares component:
const Softwares = ({ search }) => {
const [softwares, setSoftwares] = useState([]);
const [total, setTotal] = useState(null);
const [totalPages, setTotalPages] = useState(null);
const [valid, setValid] = useState(false);
const fetchData = async ({ currentPage }) => {
const SEARCH = search ? `?search=${search}` : '';
const CURRENT_PAGE = currentPage && SEARCH === '' ? `?page=${currentPage}` : '';
const response = await fetch(`http://127.0.0.1:8000/api/software/${CURRENT_PAGE}${SEARCH}`);
const data = await response.json();
setSoftwares(data.results);
setTotal(data.count);
setTotalPages(data.total_pages);
setValid(true);
}
useEffect(() => {
fetchData({ currentPage: 1 });
}, []);
return (
<>
{
valid &&
<section className={styles.softwares}>
<Header header={"new softwares"} />
{softwares.map(s => (
<Article key={s.id} pathname={s.id} title={s.title} image={s.image} pubdate={s.pub_date} icon={s.category.parent.img} categoryID={s.category.id} categoryName={s.category.name} dCount={s.counter} content={s.content} />
))}
<Paginator totalPages={totalPages} total={total} onMovePage={fetchData} />
</section>
}
</>
);
};
SearchForm in Header component:
const Header = ({ handleChange, handleClick }) => {
return (
...
<SearchForm handleChange={handleChange} handleClick={handleClick} />
...
);
};
const SearchForm = ({ style, handleChange, handleClick }) => {
return (
<div style={style}>
<form>
<input
type="text"
onChange={handleChange}
/>
<SearchButton onClick={handleClick} />
<small>ENTER</small>
</form>
</div>
);
};
const SearchButton = ({onClick }) => {
return (
<button type="button" onClick={onClick}>
<FontAwesomeIcon icon={faSearch} />
</button>
);
};
And part of Search in App component:
const App = () => {
...
// Search
const [search, setSearch] = useState('');
const [shouldFetch, setShouldFetch] = useState(false);
const handleChange = (e) => {
setSearch(e.target.value);
}
useEffect(() => {
if (shouldFetch) {
(async () => {
const response = await fetch(`http://127.0.0.1:8000/api/software/?search=${search}`);
const data = await response.json();
setShouldFetch(false);
})()
}
}, [shouldFetch]);
const handleClick = () => setShouldFetch(true);
return (
<div className="App">
<Header handleChange={handleChange} handleClick={handleClick} />
...
<Switch>
<Route path="/" exact render={props => <Softwares {...props} search={search} />} />
</Switch>
{/* Actually I'd like to use Paginator here, but it
throws the error: React Minified Error # 31 */}
...
</div>
);
}
So, how can this be done?
The problem is your useEffect dependencies (or lack thereof).
Here's the relevant section of the code:
const Softwares = ({ search }) => {
const [softwares, setSoftwares] = useState([]);
const [total, setTotal] = useState(null);
const [totalPages, setTotalPages] = useState(null);
const [valid, setValid] = useState(false);
const fetchData = async ({ currentPage }) => {
const SEARCH = search ? `?search=${search}` : '';
const CURRENT_PAGE = currentPage && SEARCH === '' ? `?page=${currentPage}` : '';
const response = await fetch(`http://127.0.0.1:8000/api/software/${CURRENT_PAGE}${SEARCH}`);
const data = await response.json();
setSoftwares(data.results);
setTotal(data.count);
setTotalPages(data.total_pages);
setValid(true);
}
useEffect(() => {
fetchData({ currentPage: 1 });
}, []);
The empty dependency array means that you are running the effect that calls fetchData one time when the component mounts. Clicks in the Pagination component will call the fetchData function directly. Changes to search do not cause fetchData to re-run. The data depends on the search so search should be a dependency.
The fetchData function is fine in this component. The state that I would recommend lifting up is to lift the currentPage up from Pagination into Softwares. The onMovePage callback can just update the currentPage state. That way you can call fetchData only through your effect and run the effect whenever either search or currentPage changes.
const Softwares = ({ search }) => {
const [softwares, setSoftwares] = useState([]);
const [total, setTotal] = useState(null);
const [totalPages, setTotalPages] = useState(null);
const [valid, setValid] = useState(false);
const [currentPage, setCurrentPage] = useState(1);
useEffect(() => {
// defining the function inside of the useEffect
// lets eslint exhaustive dependency checks work their magic
const fetchData = async () => {
const SEARCH = search ? `?search=${search}` : '';
const CURRENT_PAGE = currentPage && SEARCH === '' ? `?page=${currentPage}` : '';
const response = await fetch(`http://127.0.0.1:8000/api/software/${CURRENT_PAGE}${SEARCH}`);
const data = await response.json();
setSoftwares(data.results);
setTotal(data.count);
setTotalPages(data.total_pages);
setValid(true);
}
// need to define and call in separate steps when using async functions
fetchData();
}, [currentPage, search]);
return (
...
<Paginator page={currentPage} totalPages={totalPages} total={total} onMovePage={setCurrentPage} />
...
);
};

Categories

Resources