const CompleteDriverInfo = () => {
const [DriverInfo, setDriverInfo] = useState([]);
useEffect(async () => {
await setDriverInfo(await GetFetch('driver'));
}, []);
return (
<div>
<Link to='/adddriver'>
<button className='btn btn-primary'>Add Driver</button>
</Link>
{DriverInfo.map((EachDriver) => (
<EachDriverInfo EachDriver={EachDriver} />
))}
</div>
);
};
const EachDriverInfo = ({ EachDriver }) => {
const DeleteDriver = (e) => {
POST(
'deletedriver',
{
CustomerName: EachDriver.CustomerName,
},
e
);
};
return (
<>
<h1>Name: {EachDriver.CustomerName}</h1>
<h1>Phone Number: {EachDriver.PhoneNumber}</h1>
<h1>Email: {EachDriver.Email}</h1>
<h1>Address: {EachDriver.Address}</h1>
<h1>Country: {EachDriver.Country}</h1>
<button onClick={(e) => DeleteDriver(e)} className='btn btn-primary'>
Delete Driver
</button>
</>
);
};
When I click Delete Driver in my child component, it deletes a driver from the database, but the page doesn't re-render until refresh because of useEffect's empty dependencies.
I was thinking of setting a parent state from the child component and putting that in the dependency array but it feels like an anti-pattern
It is perfectly fine to update parent state from the child component. Just pass a function to the child component, and when child component changes the state, call this function inside the child component so that the parent component can update the app state and the rerender.
Here is a sample codesandbox:
import React, { useState, useEffect } from "react";
import axios from "axios";
const Posts = () => {
const [posts, setPosts] = useState([]);
useEffect(() => {
const getPosts = async () => {
const response = await axios.get(
`https://jsonplaceholder.typicode.com/posts`
);
setPosts(response.data);
};
getPosts();
}, []);
const handleDeletePost = (id) => {
const updatedPosts = posts.filter((p) => p.id !== id);
setPosts(updatedPosts);
};
return (
<div>
{posts.map((post) => (
<PostInfo key={post.id} post={post} deletePost={handleDeletePost} />
))}
</div>
);
};
const PostInfo = ({ post, deletePost }) => {
const DeletePost = async (id) => {
await axios.delete(`https://jsonplaceholder.typicode.com/posts/{id}`);
deletePost(id);
};
return (
<>
<h1>Id: {post.id}</h1>
<h1>Title: {post.title}</h1>
<button onClick={() => DeletePost(post.id)} className="btn btn-primary">
Delete Post
</button>
</>
);
};
export default Posts;
Related
I made a JSON file for the upcoming NFL season. In this component I have a working fetch method that gets my data, and I've named the variable "squads". Now I want to press a button to filter out the selected team's schedule and display it in a modal. I've hard coded my button in this example. My modal component works fine, and I have {props.children} in the modal's body to accept my data.
In the code below you'll see that I'm trying to assign the filtered team to the selectedTeam variable using useState. The error message I'm getting just says my variables are undefined.
import React, { useState, useEffect } from "react";
import Modal from "./Components/Modal";
export default function App() {
const [show, setShow] = useState(false);
const [title, setTitle] = useState("");
const [squads, setSquads] = useState([]);
const [modalTitleBackground, setModalTitleBackground] = useState("");
const [image, setImage] = useState("");
const [selectedTeam, setSelectedTeam] = useState([]);
const url = "../nfl2021.json";
const fetchData = async () => {
try {
const response = await fetch(url);
const data = await response.json();
setSquads(data.teams);
} catch (error) {
console.log(error);
}
};
useEffect(() => {
fetchData();
}, []);
// const filterTeam = (team) => {
// const theTeam = squads.filter((squad) => squad.name === team);
// setModalTitleBackground(theTeam[0].topBG);
// // setTitle(theTeam[0].name);
// setNickname(theTeam[0].nickname);
// setImage(`./images/${theTeam[0].img}`);
// setShow(true);
// };
const filterTeam = (team) => {
setSelectedTeam(squads.filter((squad) => squad.name === team));
console.log(selectedTeam);
setTitle(selectedTeam[0].name);
setModalTitleBackground(selectedTeam[0].topBG);
setImage(`./images/${selectedTeam[0].img}`);
setShow(true);
};
return (
<div className="App">
<button onClick={() => filterTeam("New England Patriots")}>
Show Modal
</button>
<button onClick={() => filterTeam("Green Bay Packers")}>
Show Modal 2
</button>
<button onClick={() => filterTeam("Cincinnati Bengals")}>
Show Modal 3
</button>
<Modal
image={image}
title={title}
backgroundColor={modalTitleBackground}
onClose={() => setShow(false)}
show={show}
>
<p>
This is the modal body using props.children in the modal component.
</p>
<p>The {title} 2021 schedule.</p>
{selectedTeam[0].schedule.map((schedule, index) => {
return (
<p>
Week {index + 1}: The {selectedTeam[0].nickname} play the{" "}
{selectedTeam[0].schedule[index].opponent}.
</p>
);
})}
</Modal>
</div>
);
}
1- In react, the state is set asynchronously. selectedTeam is not set until next render.
2- You can use find instead of filter and get rid of array access.
const [selectedTeam, setSelectedTeam] = useState({schedule: []});
...
const filterTeam = (team) => {
let temp = squads.find((squad) => squad.name === team);
setSelectedTeam(temp);
console.log(temp);
setTitle(temp.name);
setModalTitleBackground(temp.topBG);
setImage(`./images/${temp.img}`);
setShow(true);
};
...
{selectedTeam.schedule.map((match, index) => {
return (
<p>
Week {index + 1}: The {selectedTeam.nickname} play the {match.opponent}.
</p>
);
})}
so i have two components a Child "Board.js" that bring an array from backend and build a Board it looks like this:
const Board = (props) => {
const [data, setData] = useState([]);
const newBoard = async (e) => {
e.preventDefault();
const data = await fetch('http://localhost:8080/viergewinnt/newboard')
.then(response => response.json())
.then(data => {
// console.log(data);
return data
})
const arr =[];
data.map(d => arr.push([...d]))
console.log(arr);
setData(arr);
}
return (
<div className='center'>
<div className='board'>
{ data.map((d) => d.map((v, index) => (<BoardTile key ={index}/>)))}
</div>
</div>
)
and a parent component "NewGame" which has a button, this button when clicked must build that board in child component. The Parent component looks like this:
const NewGame = (props) => {
const onClickHandler = () =>{
return (<Board></Board>)
}
return (
<div className="App">
<div>
<button className='btn' onClick={onClickHandler}>Start new game</button>
</div>
</div>
);
PS. the Board was built successfully when all the code was in one Component.
import Board from './example.js';
import { useState } from 'react';
const NewGame = (props) => {
const [boardIsVisible, setBoardIsVisible] = useState(false);
return (
<div className="App">
<div>
<button
className='btn'
onClick={() => setBoardIsVisible(true)}>
Start new game
</button>
{boardIsVisible && <Board />}
</div>
</div>
);
}
To show my question here is a Demo code.(I'm using React Hooks and Antd.)
My Question is:
when currId state is changed and I click MyButton the state is still '' (which is the initial state). onClick event is an arrow function and in it is showModal with params, if there's no params currId can be seen changed but now with params state isn't changed. May I ask what is the reason of it and how I can get changed currId in showModal?
(operation: click 'Change CurrId' button --> setCurrId('12345') ---> click 'MyButton' ---> console.log(currId))
import React, { useState } from 'react'
import 'antd/dist/antd.css';
import { Button} from 'antd';
const MyComponent= () => {
const [currId, setCurrId] = useState('');
const changeCurrId= async () => {
setCurrSolutionId('12345');
}
const showModal = async (num:any) => {
console.log("☆ currid:");// I cannot get the currId state '12345' but ''
console.log(currId);
console.log("☆ num:");//I can get the num params 5
console.log(num);
};
return (
<>
<Button type="primary" onClick={changeCurrId}>Change CurrId</Button>
<Button type="primary" onClick={() => {showModal(5)}}>MyButton</Button>
</>
);
}
const MyComponent= () => {
const [currId, setCurrId] = useState('');
const changeCurrId= () => {
setCurrId('12345');
}
const showModal = (num:string) => {
console.log("☆ currid:");
console.log(num);
};
const changeCurrentIdAndShowModal = (id : string) => {
setCurrId(id);
showModal(id)
console.log("☆ id:");
console.log(id);
};
return (
<>
<Button type="primary" onClick={() => changeCurrId()}>MyButton</Button>
<Button type="primary" onClick={() => showModal('5')}>MyButton</Button>
<Button type="primary" onClick={() => changeCurrentIdAndShowModal('12345')}>MyButton</Button>
</>
);
}
Is this the intention you want?
I am a beginner with React. I have a project I'm working on with some sample travel tours. I would like to use a "read more/show less" feature for the description of each tour. The read more/show less button is toggling, but it's showing more or less description for all of the tours when clicked, when I want it to just toggle the tour that's clicked. In other words, it's updating the state for ALL tours, rather than just the one that's clicked. Hopefully that makes sense. Please help! Thanks in advance.
import React, { useState, useEffect } from 'react';
import './index.css';
const url = 'https://course-api.com/react-tours-project';
const Tour = () => {
const [tourItem, setTourItem] = useState('');
const removeItem = (id) => {
let newList = tourItems.filter((item) => item.id !== id);
setTourItem(newList);
};
const [fetchingData, setFetchingData] = useState(true);
useEffect(() => {
const abortController = new AbortController();
const fetchUrl = async () => {
try {
const response = await fetch(url, {
signal: abortController.signal,
});
if (fetchingData) {
const data = await response.json();
setTourItem(data);
}
setFetchingData(false);
} catch (e) {
console.log(e);
}
};
fetchUrl();
return () => {
//cleanup!
abortController.abort();
};
});
const tourItems = Object.values(tourItem);
const [readMore, setReadMore] = useState(false);
return (
<>
{tourItems.map((item) => {
return (
<div key={item.id}>
<article className='single-tour'>
<img src={item.image} alt={item.name} />
<footer>
<div className='tour-info'>
<h4>{item.name}</h4>
<h4 className='tour-price'>
${item.price}
</h4>
</div>
{readMore ? (
<p>
{item.info}
<button
onClick={() => setReadMore(false)}
>
Show Less
</button>
</p>
) : (
<p>
{item.info.slice(0, 450) + '...'}
<button
onClick={() => setReadMore(true)}
>
Read More
</button>
</p>
)}
<button
className='delete-btn'
onClick={() => removeItem(item.id)}
>
Not Interested
</button>
</footer>
</article>
</div>
);
})}
</>
);
};
export default Tour;
Good question! It happened because you share the readMore state with all of the tour items. You can fix this by encapsulating the tour items into a component.
It should look something like this;
The component that encapsulates each tour items
import React, {useState} from "react";
import "./index.css";
const SpecificTourItems = ({item, removeItem}) => {
const [readMore, setReadMore] = useState(false);
return (
<div key={item.id}>
<article className="single-tour">
<img src={item.image} alt={item.name} />
<footer>
<div className="tour-info">
<h4>{item.name}</h4>
<h4 className="tour-price">${item.price}</h4>
</div>
{readMore ? (
<p>
{item.info}
<button onClick={() => setReadMore(false)}>Show Less</button>
</p>
) : (
<p>
{item.info.slice(0, 450) + "..."}
<button onClick={() => setReadMore(true)}>Read More</button>
</p>
)}
<button className="delete-btn" onClick={() => removeItem(item.id)}>
Not Interested
</button>
</footer>
</article>
</div>
);
};
export default SpecificTourItems;
the component that fetch & maps all the tour items (your old component :))
import React, {useState, useEffect} from "react";
import SpecificTourItems from "./SpecificTourItems";
const url = "https://course-api.com/react-tours-project";
const Tour = () => {
const [tourItem, setTourItem] = useState("");
const removeItem = (id) => {
let newList = tourItems.filter((item) => item.id !== id);
setTourItem(newList);
};
const [fetchingData, setFetchingData] = useState(true);
useEffect(() => {
const abortController = new AbortController();
const fetchUrl = async () => {
try {
const response = await fetch(url, {
signal: abortController.signal,
});
if (fetchingData) {
const data = await response.json();
setTourItem(data);
}
setFetchingData(false);
} catch (e) {
console.log(e);
}
};
fetchUrl();
return () => {
//cleanup!
abortController.abort();
};
});
const tourItems = Object.values(tourItem);
const [readMore, setReadMore] = useState(false);
return (
<>
{tourItems.map((item, key) => {
return (
<SpecificTourItems item={item} removeItem={removeItem} key={key} />
);
})}
</>
);
};
export default Tour;
I hope it helps, this is my first time answering question in Stack Overflow. Thanks & Good luck!
I have to create component which fetch data with pagination and filters.
Filters are passed by props and if they changed, component should reset data and fetch it from page 0.
I have this:
const PaginationComponent = ({minPrice, maxPrice}) => {
const[page, setPage] = useState(null);
const[items, setItems] = useState([]);
const fetchMore = useCallback(() => {
setPage(prevState => prevState + 1);
}, []);
useEffect(() => {
if (page === null) {
setPage(0);
setItems([]);
} else {
get(page, minPrice, maxPrice)
.then(response => setItems(response));
}
}, [page, minPrice, maxPrice]);
useEffect(() => {
setPage(null);
},[minPrice, maxPrice]);
};
.. and there is a problem, because first useEffect depends on props, because I use them to filtering data and in second one I want to reset component. And as a result after changing props both useEffects run.
I don't have more ideas how to do it correctly.
In general the key here is to move page state up to the parent component and change the page to 0 whenever you change your filters. You can do it either with useState, or with useReducer.
The reason why it works with useState (i.e. there's only one rerender) is because React batches state changes in event handlers, if it didn't, you'd still end up with two API calls. CodeSandbox
const PaginationComponent = ({ page, minPrice, maxPrice, setPage }) => {
const [items, setItems] = useState([]);
useEffect(() => {
get(page, minPrice, maxPrice).then(response => setItems(response));
}, [page, minPrice, maxPrice]);
return (
<div>
{items.map(item => (
<div key={item.id}>
{item.id}, {item.name}, ${item.price}
</div>
))}
<div>Page: {page}</div>
<button onClick={() => setPage(v => v - 1)}>back</button>
<button onClick={() => setPage(v => v + 1)}>next</button>
</div>
);
};
const App = () => {
const [page, setPage] = useState(0);
const [minPrice, setMinPrice] = useState(25);
const [maxPrice, setMaxPrice] = useState(50);
return (
<div>
<div>
<label>Min price:</label>
<input
value={minPrice}
onChange={event => {
const { value } = event.target;
setMinPrice(parseInt(value, 10));
setPage(0);
}}
/>
</div>
<div>
<label>Max price:</label>
<input
value={maxPrice}
onChange={event => {
const { value } = event.target;
setMaxPrice(parseInt(value, 10));
setPage(0);
}}
/>
</div>
<PaginationComponent minPrice={minPrice} maxPrice={maxPrice} page={page} setPage={setPage} />
</div>
);
};
export default App;
The other solution is to use useReducer, which is more transparent, but also, as usual with reducers, a bit heavy on the boilerplate. This example behaves a bit differently, because there is a "set filters" button that makes the change to the state that is passed to the pagination component, a bit more "real life" scenario IMO. CodeSandbox
const PaginationComponent = ({ tableConfig, setPage }) => {
const [items, setItems] = useState([]);
useEffect(() => {
const { page, minPrice, maxPrice } = tableConfig;
get(page, minPrice, maxPrice).then(response => setItems(response));
}, [tableConfig]);
return (
<div>
{items.map(item => (
<div key={item.id}>
{item.id}, {item.name}, ${item.price}
</div>
))}
<div>Page: {tableConfig.page}</div>
<button onClick={() => setPage(v => v - 1)}>back</button>
<button onClick={() => setPage(v => v + 1)}>next</button>
</div>
);
};
const tableStateReducer = (state, action) => {
if (action.type === "setPage") {
return { ...state, page: action.page };
}
if (action.type === "setFilters") {
return { page: 0, minPrice: action.minPrice, maxPrice: action.maxPrice };
}
return state;
};
const App = () => {
const [tableState, dispatch] = useReducer(tableStateReducer, {
page: 0,
minPrice: 25,
maxPrice: 50
});
const [minPrice, setMinPrice] = useState(25);
const [maxPrice, setMaxPrice] = useState(50);
const setPage = useCallback(
page => {
if (typeof page === "function") {
dispatch({ type: "setPage", page: page(tableState.page) });
} else {
dispatch({ type: "setPage", page });
}
},
[tableState]
);
return (
<div>
<div>
<label>Min price:</label>
<input
value={minPrice}
onChange={event => {
const { value } = event.target;
setMinPrice(parseInt(value, 10));
}}
/>
</div>
<div>
<label>Max price:</label>
<input
value={maxPrice}
onChange={event => {
const { value } = event.target;
setMaxPrice(parseInt(value, 10));
}}
/>
</div>
<button
onClick={() => {
dispatch({ type: "setFilters", minPrice, maxPrice });
}}
>
Set filters
</button>
<PaginationComponent tableConfig={tableState} setPage={setPage} />
</div>
);
};
export default App;
You can use following
const fetchData = () => {
get(page, minPrice, maxPrice)
.then(response => setItems(response));
}
// Whenever page updated fetch new data
useEffect(() => {
fetchData();
}, [page]);
// Whenever filter updated reseting page
useEffect(() => {
const prevPage = page;
setPage(0);
if(prevPage === 0 ) {
fetchData();
}
},[minPrice, maxPrice]);