useEffect after update state - reactjs

My parent component use hook useEffect for get data from API and pass props to child component.
const ParentComoponent = () => {
const [adsData, setAdsData] = useState([]);
useEffect(() => {
api
.get(`MyUrl`, { headers: authHeader() })
.then((res) => {
console.log(res);
setAdsData(res.data.data);
})
.catch((err) => {
console.log(err);
});
}, []);
return <Child adsData={adsData} />;
};
My Child component has handleDeleteClick function for delete request from API
const Child = () => {
const [deletedItem, setDeletedItem] = useState("");
const handleDeleteClick = (e, id) => {
e.preventDefault();
axios
.delete(`MyUrl`, { params: { id: id } })
.then((res) => console.log(res))
.catch((err) => console.log(err));
};
return (
<div>
// array.map Items list
<a
href=""
onClick={(e) => handleDeleteClick(e, ads.id)}
className="tables__link"
>
Delete
</a>
</div>
);
};
Delete request works successfully, but my list not updated.
How update my items list after deleted item?

You would need to pass another function that is called when a delete is executed. Something like:
const ParentComoponent = () => {
const [adsData, setAdsData] = useState([]);
const fetchData = () => {
api
.get(`MyUrl`, { headers: authHeader() })
.then((res) => {
console.log(res);
setAdsData(res.data.data);
})
.catch((err) => {
console.log(err);
});
};
const onDelete = () => {
fetchData();
};
useEffect(() => {
fetchData();
}, []);
return <Child adsData={adsData} onDelete={fetchData} />;
};
const Child = (props) => {
const [deletedItem, setDeletedItem] = useState("");
const handleDeleteClick = (e, id) => {
e.preventDefault();
axios
.delete(`MyUrl`, { params: { id: id } })
.then((res) => {
console.log(res);
props.onDelete();
})
.catch((err) => console.log(err));
};
return (
<div>
// Items list
<a
href=""
onClick={(e) => handleDeleteClick(e, ads.id)}
className="tables__link"
>
Delete
</a>
</div>
);
};

Put your delete function in the parent and pass it to the child. Then after deleting, update your list in the parent.
<ParentComponent>
const [adsData, setAdsData] = useState([]);
const handleDeleteClick = (e, id) => {
e.preventDefault();
axios
.delete(`MyUrl`, {params: {id: id}})
.then(res => {
console.log(res)
//TODO:: Implement list.pop or similar
})
.catch(err => console.log(err));
};
useEffect(() => {
api.get(`MyUrl`, { headers: authHeader() })
.then(res => {
console.log(res);
setAdsData(res.data.data);
})
.catch(err => {
console.log(err);
})
}, []);
return (
<Child
adsData={adsData}
handleClick={handleDeleteClick}
/>
)
</ParentComponent>
return (
<div>
// array.map Items list
<a href="" onClick={(e) =>
handleDeleteClick(e, ads.id)}className="tables__link">Delete</a>
</div>
)```

Related

Change the state when clicking on a button with react

I'm trying to send and see my data status in my console log, when I click on 'Cancel' button, the status will be change by status:cancel, if I click on 'finish' button then the status is status:finish and same idea for the last one with save. Here what I've try to do but the status is not working
export default function App() {
const [data, setData] = useState({
status: ""
});
const [status, setStatus] = useState("");
const saveState = () => {
setStatus("saved");
};
const finishState = () => {
setStatus("finish");
};
const pendingState = () => {
setStatus("pending");
};
useEffect(() => {
axios
.post("")
.then((res) => {
console.log(res);
setInvitations(res.data.invitations[0]);
})
.catch((err) => {
console.log(err);
});
}, []);
function submit(e) {
e.preventDefault();
axios
.post("", {
status: data.status
})
.then((res) => {
console.log(res.data);
});
}
return (
<>
<form onSubmit={(e) => submit(e)}>
<button onClick={saveState}>Save</button>
<button onClick={finishState}> Finish</button>
<button onClick={pendingState}> Cancel</button>
</form>
</>
);
}
you can use simple setsate
export default function App() {
const [data, setData] = useState({
status: "",
});
const [status, setStatus] = useState("");
useEffect(() => {
axios
.post("")
.then((res) => {
console.log(res);
setInvitations(res.data.invitations[0]);
})
.catch((err) => {
console.log(err);
});
}, []);
function submit(e) {
e.preventDefault();
axios
.post("", {
status: data.status,
})
.then((res) => {
console.log(res.data);
});
}
return (
<>
<form onSubmit={(e) => submit(e)}>
<button onClick={() => setStatus({ status: "saved" })}>Save</button>
<button onClick={() => setStatus({ status: "finish" })}> Finish</button>
<button onClick={() => setStatus({ status: "pending" })}>
{" "}
Cancel
</button>
</form>
</>
);
}
You are using setStatus to change the status, but you are using axios.post() on your data.status
You need to either setData in your 3 functions
const saveState = () => {
setData({status:"saved"});
};
const finishState = () => {
setData({status:"finish"});
};
const pendingState = () => {
setData({status:"pending"});
};
or you can change axios.post to:
function submit(e) {
e.preventDefault();
axios
.post("", {
status: status //This is the change
})
.then((res) => {
console.log(res.data);
});
}

props change does not re-render child component

im passing a variable and two functions that changes the state of the variable as props in a child component. when i execute the functions the variable changes its state but the child component does not re-render, knowing that im using the same code in another class that calls the same child component and its working fine.
Here's the functions and the render of the child component.
onRowClickHandle = async (product) => {
BlockTimer.execute(() => {
this.props.onViewProductScreen({ product });
}, 1000);
};
async componentDidMount(){
await this.fetchReadLaterBooks();
}
async fetchReadLaterBooks(){
const user = await AsyncStorage.getItem('username');
const isLoggedIn = await AsyncStorage.getItem('isLoggedIn');
if (isLoggedIn == 1) {
await fetch(Config.backendAPI+`/readlater.php?username=${user}&test=1&select`)
.then((response) => {
return response.json();
})
.then((json) => {
if(json.length != this.state.prodList.length){
json.map((product, index) => {
this.state.prodList.push(product.id)
});
this.setState({
prodList:this.state.prodList,
isLoading:false,
});
}
this.forceUpdate();
})
.catch((error) => alert(error));
}
}
removeReadLater = async (id) => {
const user = await AsyncStorage.getItem('username');
this.setState({
prodList:this.state.prodList.filter((productId) => productId !== id),
});
await fetch(Config.backendAPI+`/readlater.php?username=${user}&idDoc=${id}&delete`)
.then((response) => response.json())
.catch((error) => alert(error));
}
addReadLater = async (id) =>{
try{
const user = await AsyncStorage.getItem('username');
//insertion dans la liste actuelle des readlater.
const joined = this.state.prodList.concat(id);
this.setState({
prodList:joined,
});
//insertion dans la base
await fetch(Config.backendAPI+`/readlater.php?username=${user}&idDoc=${id}&insert`)
.then((response) => response.json())
.catch((er) => alert(er));
}catch(error){
console.log(error);
}
};
renderItem = ({ item }) => {
return (
<ProdList
addReadLater={this.addReadLater}
removeReadLater={this.removeReadLater}
readLaterBooks={this.state.prodList}
item={item}
onRowClickHandle={this.onRowClickHandle}
/>
);
};
render() {
const {
theme: {
colors: { background, text,
dark: isDark },
},
} = this.props;
if(!this.state.isLoading){
return (
<View style={{flex:1 ,backgroundColor:background}}>
<FlatList
data={this.props.products}
renderItem={this.state.prodList ? this.renderItem : null}
/>
</View>
);
}else{
return <LogoSpinner fullStretch />;
}
}
}

Passing parameter onChange using useParams in react-router

I am trying to implement search functionality.
I have the navbar component in searchResult component. In navbar there is a search field.
I am trying to call a 5 function simultaneously which update single state. But in my code results are not getting updated.
Navbar.js
<Link to={`/search/${searchField}`} >
<li className="nav-item">
<form id="search-bar">
<input
type="search"
placeholder="Search"
onChange={(e) => setSearchField(e.target.value)}
/>
</form>
</li>
</Link>
SearchResult.js
const { value } = useParams();
const { searchResults, setSearchResults } = useContext(search);
const findMemberFunction = (value) => {
let dataToSubmit = {
referCode: value,
};
dispatch(findMember(dataToSubmit.referCode))
.then((response) => {
let allSearchResult = searchResults;
response.payload.members.map((item) => {
allSearchResult.members.push(item);
});
setSearchResults(allSearchResult);
})
.catch((error) => {
console.log("err", error);
});
};
const findSearchTagFunction = (value) => {
let dataToSubmit = {
name: value,
};
dispatch(findTags(dataToSubmit.name))
.then((response) => {
let allSearchResult = searchResults;
response.payload.tags.map((item) => {
allSearchResult.tags.push(item);
});
setSearchResults(allSearchResult);
})
.catch((error) => {
console.log("err", error);
});
};
const findGroupFunction = (value) => {
let dataToSubmit = {
name: value,
};
dispatch(findGroup(dataToSubmit.name))
.then((response) => {
let allSearchResult = searchResults;
response?.payload?.groups.map((item) => {
allSearchResult.groups.push(item);
});
setSearchResults(allSearchResult);
})
.catch((error) => {
console.log("err", error);
});
};
const findEventFunction = (value) => {
let dataToSubmit = {
name: value,
};
dispatch(findEvent(dataToSubmit.name))
.then((response) => {
let allSearchResult = searchResults;
response.payload.events.map((item) => {
allSearchResult.events.push(item);
});
setSearchResults(allSearchResult);
})
.catch((error) => {
console.log("err", error);
});
};
const findContentFunction = (value) => {
let dataToSubmit = {
name: value,
};
dispatch(findContent(dataToSubmit.name))
.then((response) => {
let allSearchResult = searchResults;
response.payload.contents.map((item) => {
allSearchResult.contents.push(item);
});
setSearchResults(allSearchResult);
})
.catch((error) => {
console.log("err", error);
});
};
useEffect(() => {
findMemberFunction(value);
findSearchTagFunction(value);
findGroupFunction(value);
findEventFunction(value);
findContentFunction(value);
}, [value]);
This is not working as i am expecting to have parameter onChange.

How to only return last api request (ReactJS)

Alternating between the 2 buttons will display first names or last names, but pressing them together really fast will chain requests and will combine the two. How can I make create a check, and only display the names from the button that was pressed last
export default function App() {
const [name, setName] = useState();
return (
<div className="App">
<button onClick={() => setName("first_name")}>1</button>
<button onClick={() => setName("last_name")}>2</button>
<Users name={name} />
</div>
);
}
export default function Users({ name }) {
const [users, setUsers] = useState([]);
useEffect(() => {
setUsers([]);
axios({
method: "GET",
url: `https://reqres.in/api/users?delay=1`
})
.then((res) => {
const allUsers = res.data.data.map((user) => <p>{user[name]}</p>);
setUsers((prev) => [...prev, ...allUsers]);
})
.catch((e) => {
console.log(e);
});
}, [name]);
return <div className="Users">{users}</div>;
}
Here is a great article by Dan Abramov about the useEffect hook in which he also talks about how to handle race cases- https://overreacted.io/a-complete-guide-to-useeffect/#speaking-of-race-conditions
To solve your issue, create a variable like let didCancel = false at the start of useEffect. Then, you have to return a function from useEffect, which automatically runs at the time when the name changes next time. In that function set didCancel to true. Now, you have to handle fetch response only if didCancel is false. This way, you are discarding all fetch responses received from second-last, third-last, etc. button presses, and handling fetch response only from the last button press.
Here is updated useEffect code:-
useEffect(() => {
let didCancel = false;
setUsers([]);
axios({
method: "GET",
url: `https://reqres.in/api/users?delay=1`
})
.then((res) => {
if (!didCancel) {
const allUsers = res.data.data.map((user) => <p>{user[name]}</p>);
setUsers((prev) => [...prev, ...allUsers]);
}
})
.catch((e) => {
console.log(e);
});
return () => {
didCancel = true;
};
}, [name]);
return <div className="Users">{users}</div>;
}
you have to create a loading state, and the user should not be able to send a new request until the data is received... you can create a hook for this or use SWR:
let me give you an example:
function Users(usersList) {
return (
<ul>
{usersList.map((user, key) => (
<li key={key}>{user}</li>
))}
</ul>
);
}
const useFetchUsers = (name) => {
const [isLoading, setIsLoading] = React.useState(true);
const [error, setError] = React.useState(null);
const [data, setData] = React.useState([]);
React.useEffect(() => {
setIsLoading(true);
setError(null);
fetch('https://blahblahblah.com/api/users')
.then((res) => res.json())
.then((response) => setData(response))
.catch((err) => setError(err))
.finally(() => setIsLoading(false));
}, [name]);
return {
isLoading,
error,
data,
};
};
function App() {
const [name, setName] = React.useState('Tom');
const { isLoading, error, data } = useFetchUsers(name);
const handleSubmitName = (name) => {
if (isLoading) alert('wait!');
else setName(name);
};
if (error) return <>an error occured</>;
if (data)
return (
<>
<button onClick={() => handleSubmitName('first_name')}>1</button>
<button onClick={() => handleSubmitName('last_name')}>2</button>
<Users name={name} />
</>
);
}
hint/note: it's just pseudocode and there are some tools to do data fetching + caching.
The problem is in this line setUsers((prev) => [...prev, ...allUsers]);. You are assuming that prev is [], but when the second request is resolve prev has data, that is why you see the request are combined:
I recommend to change your useEffect block to avoid the problem you are facing:
useEffect(() => {
axios({
method: "GET",
url: `https://reqres.in/api/users?delay=1`
})
.then((res) => {
const allUsers = res.data.data.map((user) => <p>{user[name]}</p>);
setUsers(...allUsers); //--> with the last name's value
})
.catch((e) => {
console.log(e);
});
}, [name]);

React useEffect hook infinity loop

I keep encountering an infinity loop when trying to use the useEffect hook to fetch and set data. ive tried 3 variations of the hook and they all produce a loop, how do i stop this from happening?
useEffect(() => {
(async () => {
PostApi.getPostsByUser(auth.user._id, auth.token).then(response => setPosts(response))
})()
})
useEffect(() => {
(async () => {
PostApi.getPostsByUser(auth.user._id, auth.token).then(response => setPosts(response))
})()
},[])
useEffect(() => {
(async () => {
PostApi.getPostsByUser(auth.user._id, auth.token).then(response => setPosts(response))
})()
},[profile.posts])
EDIT: Here is the PostApi.getPostsByUser code
getPostsByUser: (userId, token) => {
return(
axios
.get("/api/posts/by/" + userId, {
headers: {
Accept: "application/json",
"Content-Type": "application/json",
Authorization: "Bearer " + token
}
})
.then(response => {
console.log("Posts by User");
console.log(response.data);
return response.data;
})
.catch(err => console.log(err))
)
}
EDIT: Function component code:
const Posts = () => {
const [{auth}] = useAuth();
const [{profile},, setPosts] = useProfile()
useEffect(() => {
PostApi.getPostsByUser(auth.user._id, auth.token)
.then(response => setPosts(response));
},[]);
console.log(profile)
return(
<div className="User-Post">
<div className="New-Post">
<NewPost />
</div>
<div className="User-Posts-Content">
{
profile.posts ? profile.posts.map((item, key) => {
return <Post post={item} key={key} />
}) : null
}
</div>
</div>
)
}
export default Posts
Change:
const [auth] = useAuth();
const [profile, setPosts] = useState();
const setPosts = posts => { setPosts(state => ({ ...state, profile: {
...state.profile, posts: posts } })) }
getPostsByUser: (userId, token) => {
return(
axios
.get("/api/posts/by/" + userId, {
headers: {
Accept: "application/json",
"Content-Type": "application/json",
Authorization: "Bearer " + token
}
});
}
and
useEffect(() => {
PostApi.getPostsByUser(auth.user._id, auth.token)
.then(response => setPosts(response.data));
},[]);
You can try like this.
useEffect(() => {
const get = async () => {
const response = await PostApi.getPostsByUser(auth.user._id, auth.token);
setPosts(response);
}
get();
},[]);
This works for me ... and the simplest solution too
const [toggle, setToggle] = useState(false);
useEffect(() => {
(async () => {
PostApi.getPostsByUser(auth.user._id, auth.token).then(response => setPosts(response))
})()
},toggle)

Resources