I have this component that loads some data with buttons. I have a function that gets button via ID and updates state/variable to that buttons value, then loads data based on that. However since all the button IDs are the same, it only works for the first button...
I'm not sure how to set unique IDs for each button since they're generated through a map() function, and even if i did know, i'm not sure how my function would then target each button without writing a function for each one...
Edit: I've set unique IDs for buttons now, still need to solve the other problem.
return member.map((player, index) => (
<>
{" "}
<React.Fragment key={index}>
<br />{" "}
<div>
<br />
{player.map((stats) => (
<React.Fragment key={stats.player}>
<div class="flex-child">
<button id={'btn' + index} value={stats.player}>
{stats.player}
</button>
<p>{stats.role}</p>
<p>{stats.win_rate}</p>
<p>{stats.matches}</p>
<p>{stats.total_battles} Total Battles</p>
<p>{stats.score} / Points Earned</p>
</div>
</React.Fragment>
))}
</div>
<br />
</React.Fragment>
</>
));
};
export default SquadMembers;
here is index.js
import Head from "next/head";
import React, { useState } from "react";
import Teams from "../components/Teams";
import styles from "../../styles/Home.module.css";
import SquadMembers from "../components/SquadMembers";
import SquadData from "../components/SquadData";
export default function Home() {
const [teams, setTeams] = useState([]);
const [squads, setSquads] = useState([]);
const [player, setPlayer] = useState("Player Name");
const [squad, setSquad] = useState("default");
const [loading, setLoading] = useState(false);
function clicky() {
if (!document.getElementById("btn")) {
} else {
setPlayer(document.getElementById("btn").value);
loadPeople();
}
}
if (typeof window === "object") {
if (!document.getElementById("btn")) {
} else {
document.getElementById("btn").onclick = function () {
clicky();
};
}
}
function handleChange(e) {
setSquad(e.target.value);
}
const loadPeople = async () => {
setLoading(true);
const req = await fetch(`/api/player/${player}`);
const json = await req.json();
setTeams(json);
setLoading(false);
};
const loadSquad = async () => {
setLoading(true);
const req = await fetch(`/api/squad/${squad}`);
const json = await req.json();
setSquads(json);
setLoading(false);
setTeams([]);
};
return (
<div className={styles.main}>
<main className={styles.main}>
<h1>Silph Team Finder</h1>
<br />
<div>
<select value={squad} onChange={handleChange}>
<option value="default" selected disabled hidden>
Choose here
</option>
<option value="a342">Lots of options, cut them to save space</option>
</select>
<button onClick={() => loadSquad()}>Load</button>
<input
value={player}
id="player"
onChange={(e) => setPlayer(e.target.value)}
/>
<button onClick={() => loadPeople()} id="pbtn">
Load
</button>
{loading && <div className={styles.load}>LOADING</div>}
</div>
<div className={styles.teams}>
<SquadData squadz={squads} />
<Teams teams={teams} />
<div class="player-container">
<SquadMembers squadz={squads} />
</div>
</div>
</main>
</div>
);
}
Would be much easier to have something like:
<button value={stats.player} onClick={() => handleClick(stats.player)}>
{stats.player}
</button>
...
const handleClick = (player) => {
setPlayer(player);
loadPeople();
}
In this way you don't need id for button. Not only but you will avoid warning on same id for multiple elements.
Then I would like to talk about loadPeople(). If I understood correctly in this function you are using player that would be setted by setPlayer.
This is not a good approach. setPlayer is async and you could take an old value of player. Much better pass the last player value directly to loadPeople function. Something like:
const handleClick = (player) => {
setPlayer(player);
loadPeople(player);
}
const loadPeople = async (newPlayer) => {
setLoading(true);
const req = await fetch(`/api/player/${newPlayer}`);
const json = await req.json();
setTeams(json);
setLoading(false);
};
Related
I am working through a tutorial for a course I'm taking. The lab I'm working on walks through creating a to-do app. I'm on step 3, which asks us to create a button that deletes a task. I feel ridiculous, because I know I can figure it out but...well, I haven't yet! I will post the code to see if there are any initial issues, and then update with the methods I've already tried. Any help is greatly appreciated. Thank you!
import React, { useState } from "react";
import "./App.css";
const App = () => {
const [todos, setTodos] = useState([]);
const [todo, setTodo] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
const newTodo = {
id: new Date().getTime(),
text: todo.trim(),
completed: false,
};
if (newTodo.text.length > 0) {
setTodos([...todos].concat(newTodo));
setTodo("");
} else {
alert("Enter Valid Task");
setTodo("");
}
}
const deleteTodo = (id) => {
let updatedTodos = [...todos].filter((todo) => todo.id !== id);
setTodos(updatedTodos);
}
const button = <button onClick={() => deleteTodo(todo.id)}>Delete</button>
return (
<div>
<h1>To-do List</h1>
<form onSubmit={handleSubmit}>
<input
type="text"
onChange={(e) => setTodo(e.target.value)}
placeholder="Add a new task"
value={todo}
/>
<button type="submit">Add Todo</button>
</form>
{todos.map((todo) => <div>ID: {todo.id} Task: {todo.text} {button}</div>)}
</div>
);
};
export default App;
I didn't just copy and paste, so it's possible that I messed something up while typing. I'm expecting the deleteTodo() function to accept a todo.id and filter the list of todos, excluding the one I want to delete. I'm thinking that the issue may be cause by the way I've created the button? Again, I'm not sure why I can't figure it out. TIA.
EDIT: Okay, it works now! Thank you all so much for explaining this. For anyone else that comes across this problem, here's where I mis-stepped:
const button = <button onClick={() => deleteTodo(todo.id)}Delete<button>
#Nicholas Tower's explanation was very clear--creating this outside of .map(...)causes deleteTodo to get the todo state, not the not the todo I want it to delete from the todos array. #Lars Vonk, #0stone0, and #Sudip Shrestha all said this as well. #Sudip Shrestha and #pilchard also helped correct the deleteTodo function. Again, I really appreciate all the help. The code works now. I'll show the updates so people having a similar issue can compare:
import React from "react";
import "./App.css";
const App = () => {
const [todos, setTodos] = React.useState([]);
const [todo, setTodo] = React.useState("");
const handleSubmit = (e) => {
e.preventDefault();
const newTodo = {
id: new Date().getTime(),
text: todo.trim(),
completed: false,
};
if (newTodo.text.length > 0) {
setTodos(todos.concat(newTodo));
setTodo("");
} else {
alert("Enter a valid task");
setTodo("");
}
}
// update the state using setState, rathar than mutating it directly #Sudip Shrestha
const deleteTodo = id => {
setTodos(prevState => {
return prevState.filter(todo => todo.id !== id)
});
};
// line 51: button placed inside .map(), as per many suggestions below.
return (
<>
<h1>Todo List</h1>
<form onSubmit={handleSubmit}>
<input
type="text"
onChange={(e) => setTodo(e.target.value)}
placeholder="Add a new task..."
value={todo}
/>
</form>
{todos.map((todo) =>
<div>
ID: {todo.id} Task: {todo.text}
<button onClick={() => deleteTodo(todo.id)}>Delete</button>
</div>)}
</>
);
};
export default App;
const button = <button onClick={() => deleteTodo(todo.id)}>Delete</button>
You're creating this button element just once, and the todo variable it refers to is the todo state, which is a string (usually an empty string). Since todo is a string, todo.id is undefined, and deleteTodo can't do anything with that.
You need to create separate buttons for each item, so you should move this code down into your .map:
{todos.map((todo) => (
<div>
ID: {todo.id} Task: {todo.text}
<button onClick={() => deleteTodo(todo.id)}>Delete</button>
</div>
))}
Now each item has its own button, with its own onClick function. And in those functions, todo is the item of the array.
The button cannot access which todo it has I think you should put the code from the const button where you are referring to it or by changing it to const button = (todo) => <button onClick={ () => deleteTodo(todo.id); }>Delete</button> and access it by doing {button()}
const button = <button onClick={() => deleteTodo(todo.id)}>Delete</button>
This has the same callBack for each todo, you should move this inside your map so that todo.id refers to the iterator of the map():
{todos.map((todo) => (
<React.Fragment>
<div>ID: {todo.id} Task: {todo.text}</div>
<button onClick={() => deleteTodo(todo.id)}>Delete</button>
</React.Fragment>
))}
Updated Demo:
const { useState } = React;
const App = () => {
const [todos, setTodos] = useState([]);
const [todo, setTodo] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
const newTodo = {
id: new Date().getTime(),
text: todo.trim(),
completed: false,
};
if (newTodo.text.length > 0) {
setTodos([...todos].concat(newTodo));
setTodo("");
} else {
alert("Enter Valid Task");
setTodo("");
}
}
const deleteTodo = (id) => {
let updatedTodos = [...todos].filter((todo) => todo.id !== id);
setTodos(updatedTodos);
}
return (
<div>
<h1>To-do List</h1>
<form onSubmit={handleSubmit}>
<input
type="text"
onChange={(e) => setTodo(e.target.value)}
placeholder="Add a new task"
value={todo}
/>
<button type="submit">Add Todo</button>
</form>
{todos.map((todo) => (
<React.Fragment>
<div>ID: {todo.id} Task: {todo.text}</div>
<button onClick={() => deleteTodo(todo.id)}>Delete</button>
</React.Fragment>
))}
</div>
);
};
ReactDOM.render(<App />, document.getElementById("react"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>
Try this:
const button = (t) => <button onClick={() => deleteTodo(t.id)}>Delete</button>
and then, in the map
{todos.map((todo) => <div>ID: {todo.id} Task: {todo.text} {button(todo)}</div>)}
this way, the "delete todo" button will be bound to the specific todo ID, avoiding being bound to whatever the current value of todo is in the app.
Its better to update the state using setState. Muting the state directly breaks the primary principle of React's data flow (which is made to be unidirectional), making your app very fragile and basically ignoring the whole component lifecycle.
Also You need to change the delete from string to function and pass the id or place the jsx directly inside map function.
import React, { useState } from 'react'
const App = () => {
const [todos, setTodos] = useState([])
const [todo, setTodo] = useState('')
const handleSubmit = e => {
e.preventDefault()
const newTodo = {
id: new Date().getTime(),
text: todo.trim(),
completed: false,
}
if (newTodo.text.length > 0) {
setTodos([...todos].concat(newTodo))
setTodo('')
} else {
alert('Enter Valid Task')
setTodo('')
}
}
/*
* Changed Here
*/
const deleteTodo = id => {
setTodos(prevState => {
return prevState.filter(todo => todo?.id != id)
})
}
const button = id => <button onClick={() =>
deleteTodo(id)}>Delete</button>
return (
<div>
<h1>To-do List</h1>
<form onSubmit={handleSubmit}>
<input
type="text"
onChange={e => setTodo(e.target.value)}
placeholder="Add a new task"
value={todo}
/>
<button type="submit">Add Todo</button>
</form>
{todos.map(todo => (
<div key={todo.id}>
ID: {todo.id} Task: {todo.text} {button(todo.id)}
</div>
))}
</div>
)
}
export default App
Problem in:
const button = <button onClick={() => deleteTodo(todo.id)}>Delete</button>
You can use
const Button = (props) => {
return (
<button
className={`btn ${props.className}`}
title={`${props.title}`}
onClick={props.onClick ? () => props.onClick() : null}
>
{props.children}
</button>
);
};
after that, call it like this
<Button className="delete" title="delete" onClick={()=>deleteTodo(todo.id)}>Delete</Button>
first time asking questions haha, I'm trying to remove a specific JSX element from my array of elements but I am not sure how to identify said element is the one being clicked to get removed so I wanted to know if anyone encountered this issue before and how you would go about solving it.
This is the code for the input Container
const InputComponent = (props) => {
return (
<div className="input-container" onClick={e =>console.log(e)}>
<input className="input-name" type="text" />
<input className="input-value" type="text" />
<button className="remove-button" onClick={props.remove}><img src={minusIcon} alt="remove-icon" /></button>
</div>
);
}
export default InputComponent;
and this is the code for the parent component to manage the removal of said element
const Main = () => {
const [newInput, setInput] = useState([]);
const [currentInput, setCurrentInput] = useState([<InputComponent key={0}/>]);
const [currentIndex, setIndex] = useState(0)
const [currentPlanName, setCurrentPlanName] = useState('Current-Plan');
const [newPlanName, setNewPlanName] = useState('New-Plan');
// const [currentInputValue, setCurrentValue] = useState('')
// const [newInputValue, setNewValue] = useState('')
// Sets Keys for each New Element in Array
const [newKey, setNewKey] = useState(0);
const [currentKey, setCurrentKey] = useState(0)
// Handle Removal of specific array by using key
const handleCurrentRemoval = () => {
let newArray = currentInput.filter(element => element.key !== )
console.log(newArray)
setCurrentInput(newArray)
}
// Initialize Keys for each Array
const currentArrayElement = {
element: <InputComponent key={currentKey} remove={handleCurrentRemoval} />,
index: currentKey
};
const newArrayElement = <InputComponent key={newKey+1}/>
// Adds new Element to array
const handleCurrentClick = () => {
setCurrentInput(prevValues => [...prevValues, currentArrayElement.element])
setCurrentKey(currentKey+1);
console.log(currentArrayElement)
console.log(currentInput)
};
const handleNewClick = () => {
setInput(prevValues => [...prevValues, newArrayElement])
};
// const handleRemoveClick = (value) => {
// currentInput.filter(current => value !=)
// }
return (
<div className="main-container">
<div className="quote-container">
<div className="current-plan">
<h2>{currentPlanName}</h2>
{
currentInput.map((inputs) => {
return inputs
})
}
<div className="button-container">
<button className="add-input" onClick={handleCurrentClick}><img src={addIcon} alt="add"/></button>
</div>
</div>
<div className="new-plan">
<h2>{newPlanName}</h2>
{
newInput.map((inputs) => {
return inputs
})
}
<div className="button-container">
<button className="add-input" onClick={handleNewClick}><img src={addIcon} alt="add"/></button>
</div>
</div>
</div>
</div>
);
}
export default Main;
I do apologize in advance if I posted this incorrectly.
Thank you for your assistance
I think you've made this entirely more complex than it needs to be with all the index value storage in state. It is also anti-pattern in React to store JSX in state, you should store data in state and render the data to JSX.
I suggest storing generated input ids in the arrays instead and mapping these to JSX.
Example:
// id generator
let id = 0;
const getId = () => id++;
export default function App() {
const [newInput, setInput] = useState([]);
const [currentInput, setCurrentInput] = useState([]);
const [currentPlanName, setCurrentPlanName] = useState("Current-Plan");
const [newPlanName, setNewPlanName] = useState("New-Plan");
// Handle Removal of specific element by using id
const handleCurrentRemoval = (removeId) => {
setCurrentInput((ids) => ids.filter((id) => id !== removeId));
};
// Adds new id to array
const handleCurrentClick = () => {
setCurrentInput((ids) => ids.concat(getId()));
};
const handleNewClick = () => {
setInput((ids) => ids.concat(getId()));
};
const handleRemoveClick = (removeId) => {
setInput((ids) => ids.filter((id) => id !== removeId));
};
return (
<div className="main-container">
<div className="quote-container">
<div className="current-plan">
<h2>{currentPlanName}</h2>
{currentInput.map((id) => {
return (
<InputComponent
key={id}
remove={() => handleCurrentRemoval(id)}
/>
);
})}
<div className="button-container">
<button className="add-input" onClick={handleCurrentClick}>
<img src={addIcon} alt="add" />
</button>
</div>
</div>
<div className="new-plan">
<h2>{newPlanName}</h2>
{newInput.map((id) => {
return (
<InputComponent key={id} remove={() => handleRemoveClick(id)} />
);
})}
<div className="button-container">
<button className="add-input" onClick={handleNewClick}>
<img src={addIcon} alt="add" />
</button>
</div>
</div>
</div>
</div>
);
}
I am trying to make a simple react app that pulls info from a MySQL database ("username", "balance", "purchases").
So far, I've used node and react to pull from the database with an HTTP query and to display each element on the website.
I then created the API query for searching the database for all entries that start with what I've typed into the search bar.
The issue I'm running into is how do I change the state of the elements that display the username, etc with the new filtered information from the API query? The search bar and data elements are two separate components so I can't use the use effect hook natively.
I cant use the filter method because the database is huge and I've sent my query limit to 100.
Here's my code so far:
PlayerData.js
import axios from 'axios';
import React,{useState, useEffect} from 'react';
const Player = () => {
const [playerData,setPlayerData]=useState([])
useEffect(()=>{
axios.get("http://localhost:3001/api/get").then((res)=>{
console.log(res.data)
setPlayerData(res.data)
})
.catch(err=>{
console.log(err);
})
},[])
return (
<>
{playerData.map((data,id)=>{
return <div className="Player" key={id}>
<span className="Username"> { data.name } </span>
<span className="Crystals"> { data.balance } </span>
<span className="DateModi"> {Object.keys(JSON.parse(data.items)).length} </span>
</div>
})}
</>
)
};
export default Player;
SearchBar.js
import { useState } from "react";
import axios from 'axios'
const Search = () => {
const [searchTerm, setSearchTerm] = useState("")
axios.get(`http://localhost:3001/api/getSearchName/${searchTerm}`).then((res)=>{
console.log(res.data)
})
return (
<div className="Search">
<input className = "InputField" type="text" placeholder="Enter Username:" onChange={e => {setSearchTerm(e.target.value)}}/>
<span className="SearchButton" onClick={console.log(searchTerm)}>
Search
</span>
</div>
)
};
export default Search;
If I understood the question correctly, you need to set the state of PlayerData to a shared component(App), and pass it to the Player.js component. Then when searching it will be overwritten and update the information in the Player.js
function App() {
const [playerData, setPlayerData] = useState([]);
useEffect(() => {
fetchData();
}, []);
const fetchData = () =>
axios
.get("http://localhost:3001/api/get")
.then((res) => {
setPlayerData(res.data);
})
.catch((err) => {
console.log(err);
});
const handleSearch = (text) => {
const clearText = text.trim();
if (!clearText.length) {
fetchData();
return;
}
axios
.get(`http://localhost:3001/api/getSearchName/${clearText}`)
.then((res) => {
setPlayerData(res.data);
});
};
return (
<div>
<div>
<Search handleSearch={handleSearch} />
</div>
<div>
<Player playerData={playerData} />
</div>
</div>
);
}
Search.js
const Search = ({ handleSearch }) => {
const [searchTerm, setSearchTerm] = useState("");
return (
<div className="Search">
<input
className="InputField"
type="text"
placeholder="Enter Username:"
onChange={(e) => {
setSearchTerm(e.target.value);
}}
/>
<span className="SearchButton" onClick={() => handleSearch(searchTerm)}>
Search
</span>
</div>
);
};
Player.js
const Player = ({ playerData }) => {
return (
<>
{playerData?.length ? (
playerData.map((data, id) => {
return (
<div className="Player" key={id}>
<span className="Username"> {data.name} </span>
<span className="Crystals"> {data.balance} </span>
<span className="DateModi">
{" "}
{Object.keys(JSON.parse(data.items)).length}{" "}
</span>
</div>
);
})
) : (
<div>Loading...</div>
)}
</>
);
};
I am creating a page to update product details on an e-commerce site I am building using NextJS, and I have the image upload section nested inside an accordion on the individual item page. Once images have been uploaded, I would like to clear the upload form and close the accordion. It is closing the accordion I am having trouble with.
ImageUploadAccordion.js:
import React, {useRef} from 'react';
import {Accordion} from 'react-bootstrap'
import ImageUpload from './ImageUpload'
export default function ImageUploadAccordion({ item }) {
const accordionRef = useRef(null);
const toggleAccordion = () => {
accordionRef.current.click();
}
return (
<Accordion ref={accordionRef} defaultActiveKey="0">
<Accordion.Item eventKey="1">
<Accordion.Header>
<span className="btn btn-outline-success">Upload Images</span>
</Accordion.Header>
<Accordion.Body>
<ImageUpload
toggle={toggleAccordion}
item={item}
/>
</Accordion.Body>
</Accordion.Item>
</Accordion>
)
}
ImageUpload.js:
import React, {useState} from 'react';
import { useRouter } from 'next/router'
export default function ImageUpload({ item, toggle }) {
const router = useRouter()
const [images, setImages] = useState([])
const [imageURLS, setImageURLS] = useState([])
const [tags, setTags] = useState([])
const [theInputKey, setTheInputKey] = useState('')
const uploadImageToClient = (event) => {
if (event.target.files && event.target.files[0]) {
setImages((imageList) => [...imageList, {"index": images.length, "data": event.target.files[0]}]);
setImageURLS((urlList) => [
...urlList,
URL.createObjectURL(event.target.files[0])
]);
}
let randomString = Math.random().toString(36);
setTheInputKey(randomString)
};
const uploadTagToClient = (e) => {
if (event.target.value) {
const name = e.target.getAttribute("name")
// const i = event.target.value;
// document.getElementById("image-upload")
setTags((tagList) => [...tagList, {"name": name, "tag": e.target.value}]);
}
};
const removeImage = (name) => {
// debug
alert(`Trying to remove image index ${name}`)
let newImages = []
let newTags = []
setImages(images.filter(image => image.data.name !== name));
setTags(tags.filter(tag => tag.name !== name));
}
const uploadToServer = async (e) => {
const body = new FormData()
images.map((file, index) => {
body.append(`file${index}`, file.data);
});
// Use the filenames as keys then we can retrieve server side once we have the images
tags.map((tag, index) => {
body.append(tag.name, tag.tag)
})
const response = await fetch("/api/file", {
method: "POST",
"Content-Type": "multipart/form-data",
body
})
var message = await response.json();
alert(message['message'])
setImages([])
setTags([])
toggle()
};
const openImageUploadDialogue = () =>{
document.getElementById("image-upload").click()
}
return (
<div className="container">
<input style={{display:'none'}} accept="image/*" id="image-upload" type="file" key={theInputKey || '' } className="btn btn-outline-success-inverse" onChange={uploadImageToClient} />
<button className="btn btn-outline-success-inverse" onClick={openImageUploadDialogue} >
Add Image
</button>
<hr className = "text-pink"/>
<div className="row">
<div className="col d-flex flex-wrap">
{images.map((file, index) => {
return (
<div className="div p-1" key={file.data.name}>
<p className="text-pink">{file.data.name}</p>
<p>Tag</p>
<input type="text" name={file.data.name} id={`${file.data.name}`} onChange={uploadTagToClient} />
<img src={imageURLS[index]} height="200" width="150" />
<div className="btn btn-outline-success-inverse" onClick={ () =>removeImage(file.data.name)}>Remove Image</div>
</div>
);
})}
</div>
<button
className="btn btn-outline-success-inverse"
type="submit"
onClick={uploadToServer}
>
Upload Images
</button>
</div>
</div>
);
}
I tried by creating a reference to the accordion using useRef, and a function which uses this reference to activate the click event, which I passed to the ImageUpload component, according to another answer to a similar question, but it doesn't seem to work and I'm unsure as to why.
Any help always appreciated :-)
I believe you have the wrong target as the ref, update it to target the button that is automatically generated to wrap the header content.
<h2 class="accordion-header"><button type="button" aria-expanded="true" class="accordion-button"><span class="btn btn-outline-success">Upload Images</span></button></h2>
Rudimentary example:
export default function ImageUploadAccordion({ item }) {
const accordionRef = useRef(null);
const toggleAccordion = () => {
accordionRef.current.querySelector('button').click();
}
return (
<Accordion defaultActiveKey="0">
<Accordion.Item eventKey="1">
<Accordion.Header ref={accordionRef}>
<span className="btn btn-outline-success">Upload Images</span>
</Accordion.Header>
<Accordion.Body>
<ImageUpload
toggle={toggleAccordion}
item={item}
/>
</Accordion.Body>
</Accordion.Item>
</Accordion>
)
}
I got this component in React.js which make different kinds of filtering when I click a button, this is my code:
import React, { useContext } from 'react';
import { ModelsContext } from "../context/ModelsContext";
const FilterNav = () => {
const { modelos, guardarModelo } = useContext(ModelsContext);
const filterSegment = e => {
const segment = modelos.filter(modelo => modelo.segment === e.target.name);
guardarModelo(segment);
}
return (
<nav className="filter-container">
<div className="container">
<h3 className="filter-element-title">Filtrar por</h3>
<button type="button" className="filter-element">Todos</button>
<button type="button" className="filter-element" name="Autos" onClick={filterSegment}>Autos</button>
<button type="button" className="filter-element" name="Pickups y Comerciales" onClick={filterSegment}>Pickups y Comerciales</button>
<button type="button" className="filter-element" name="SUVs y Crossovers" onClick={filterSegment}>SUVs y Crossovers</button>
</div>
<p className="filter-element-last">Ordenar por ^</p>
</nav>
);
}
export default FilterNav;
The information I get from the api with useContext in ModelsContext.jsx, here is what I wrote so far:
import React, { createContext, useState, useEffect } from 'react';
export const ModelsContext = createContext();
const ModelsProvider = (props) => {
//State de modelos
const [modelos, guardarModelo] = useState([]);
const consultarAPI = async () => {
const api = await fetch("https://challenge.agenciaego.tech/models");
const modelos = await api.json();
guardarModelo(modelos);
}
//Cargar un modelo
useEffect(() => {
consultarAPI()
}, []);
return (
<ModelsContext.Provider
value={{
modelos,
guardarModelo
}}
>
{props.children}
</ModelsContext.Provider>
)
}
export default ModelsProvider;
My issue is that when I filter the API modelos throught the filterSegment function I don't know how to re-fetch the data from the API, because when I do a new call to the filterSegment function it filters the filtered data. I've tried to add a boolean state, and I was thinking about adding another state with allthedata, but I really lost about implementing it, I'm still very new to React.js.
I've search through stack overflow and google and I cannot get the answer, If you can give me a clue or some sort of guidance it will be appreciated.
Thanks so much!
You can add another state in the ModelsContext:
//State de modelos
const [modelos, guardarModelo] = useState([]);
const [allModelos, guardarAllModelo] = useState([]);
const consultarAPI = async () => {
const api = await fetch("https://challenge.agenciaego.tech/models");
const modelos = await api.json();
guardarAllModelo(modelos);
//uncomment if you want to have initial value for modelos state
//guardarModelo(modelos);
}
// some codes ...
<ModelsContext.Provider
value={{
allModelos,
modelos,
guardarModelo
}}
>
{props.children}
</ModelsContext.Provider>
Then in the FilterNav component:
const {allModelos, modelos, guardarModelo } = useContext(ModelsContext);
const filterSegment = e => {
const segment = allModelos.filter(modelo => modelo.segment === e.target.name);
guardarModelo(segment);
}
But this does not really re-fetch data from your web api. It just re-filters the first fetched data. if you want to re-fetch data from web api you can add consultarAPI in your context provider then call it somewhere.
Thanks code is working
This is my Portfolio gallery code First time load all data when click category then get category dataenter code here
Thanks code is working
This is my Portfolio gallery code First time load all data when click category then get category data`enter code here`
import React, { Component, useEffect, useState } from 'react'`enter code here`;
import Thumnailport_list from './Thumnailport_list';
import Portlightbox from './Portlightbox';
import Functional from './Functional';
import $ from 'jquery';
const Portfolio = () => {
const filterItem = async (categoryitem) => {
const updateitmes = allModelos.filter((curElm) => {
return curElm.categories === categoryitem
})
getporfolioState(updateitmes)
}
const [getporfolio, getporfolioState] = useState([])
const [allModelos, guardarAllModelo] = useState([]);
$(document).ready(function () {
$(".grid-wrap .grid li").unbind().click(function (e) {
console.log(this.className);
var newe = this.className;
$('.' + newe).addClass('current show');
$("#grid-gallery").addClass("slideshow-open");
});
$("#closeport").unbind().click(function (e) {
$("#grid-gallery").removeClass("slideshow-open");
$(".portfolio .grid li").removeClass('current show');
$(".portfolio .slideshow ul > li").removeClass('current show');
});
});
const portadd = () => {
document.body.classList.add('portfolio');
document.body.classList.add('at-top');
document.getElementById('port').classList.add('no-transform');
document.getElementById('port').classList.add('revealator-within');
document.getElementById('port2').classList.add('no-transform');
document.getElementById('port2').classList.add('revealator-within');
document.getElementById('navbar-collapse-toggle').classList.remove('biohidemenu');
}
const getalldata = async () => {
try {
const res = await fetch("/getdata", {
method: 'Get',
headers: {
'Content-Type': 'application/json'
}
})
const data = await res.json()
// console.log("This is our data load")
// console.log(data.portfolio)
getporfolioState(data.portfolio)
guardarAllModelo(data.portfolio)
} catch (error) {
console.log(error)
// history.push("/backoffice/login")
}
}
useEffect(() => {
getalldata()
portadd()
}, []);
return (
<>
<section id="port" class="title-section text-left text-sm-center revealator-slideup revealator-once revealator-delay1">
<h1 >my <span>portfolio</span></h1>
<span class="title-bg">works</span>
</section>
<section id="port2" className="main-content text-center revealator-slideup revealator-once revealator-delay1">
<div class="container">
<button className="btn btn-about " onClick={() => filterItem('mobileapp')}>Mobile</button>
<button className="btn btn-about " onClick={() => filterItem('frontend')}>Frontend</button>
<button className="btn btn-about " onClick={() => filterItem('gdesign')}>Graphics</button>
</div>
<div id="grid-gallery" className="container grid-gallery">
{/* Portfolio Grid Starts */}
<section className="grid-wrap">
<ul className="row grid">
{
getporfolio.map((getdata, index) => {
return (
<>
<Thumnailport_list
key={index}
portID={getdata._id}
imagetag={getdata.imguploadedFile}
figuertext={getdata.projectname}
/>
</>
)
})
}
</ul>
</section>
{/* Portfolio Grid Ends */}
{/* Portfolio Details Starts */}
<section className="slideshow" id="sdfer">
<ul>
{/* Portfolio Item Detail Starts */}
{
getporfolio.map((getdata, index) => {
return (
<>
<Portlightbox
idlight={getdata._id}
imagelight={getdata.imguploadedFile}
langport={getdata.language}
clientport={getdata.client}
projectnameport={getdata.projectname}
previewport={getdata.preview}
/>
</>
)
})
}
</ul>
{/* Portfolio Navigation Starts */}
<nav>
{/*<span className="icon nav-prev prev"><img src="images/left-arrow.png" alt="previous" /></span>
<span className="icon nav-next next"><img src="images/right-arrow.png" alt="next" /></span>*/}
<span className="nav-close" id="closeport"><img src="images/close-button.png" alt="close" /> </span>
</nav>
{/* Portfolio Navigation Ends */}
</section>
</div>
</section>
</>
)
}
export default Portfolio;