How to pass event and id on onClick handler? - javascript

In reality, I have a list of blogs, and each has its own unique ID. I want to get a specific blog's id on onclick and send it through navigate. The issue is that the page reloads when I click on the blog, which stops the request from being sent.
I simply need to know how to accomplish this.
Blogcard.jsx:
import axios from 'axios';
import { useNavigate } from 'react-router-dom';
const BlogCard = ({ blogs }) => {
const navigate = useNavigate();
const singleBlogHandler = async id => {
const singleBlogDetail = await axios.get(
`https://61791a83aa7f3400174047a6.mockapi.io/v1/GetBLogs/${id}`
);
navigate(`blogs/${id}`, { state: singleBlogDetail });
};
return (
<>
{blogs.map(blog => {
return (
<Stack
key={blog.id}
onClick={() => singleBlogHandler(blog.id)}
>
// Blog data is mapping here
</Stack>
);
})}
</>
);
};
export default BlogCard;

Related

Showing data from state variable in ReactJS forms infinite loop

I'm trying to show data from an API call. The structure of the application looks like
MainComponent -> RefreshButton (this will fetch the data)
MainComponent -> ShowData (this will show the data that is being fetched)
MainComponent has a state userData that will store the response that was received from the API. Now the issue is, whenever I'm clicking the button, it is getting into an infinite loop of rendering and calls the API infinite times.
This is what the error shows:
Here is my MainComponent -
import React, { useEffect, useState } from "react";
import RefreshButton from "./RefreshButton";
import ShowData from "./ShowData";
const MainComponent = () => {
const [userData, setUserData] = useState();
useEffect(() => {
console.log(userData);
}, [userData]);
return (
<div>
<p style={{ textAlign: "center" }}>Main Component</p>
<RefreshButton setUserData={setUserData} />
{userData && <ShowData userData={userData} />}
</div>
);
};
export default MainComponent;
Here is my RefreshButton component -
import React from "react";
import axios from "axios";
const RefreshButton = ({ setUserData }) => {
const getData = () => {
axios
.get(`https://jsonplaceholder.typicode.com/todos`)
.then((response) => {
if (response.status === 200) setUserData(response.data);
})
.catch((err) => {
console.log(err);
});
};
return (
<div className="button-container">
<button className="fetch-data-button" onClick={() => getData()}>
Fetch new data
</button>
</div>
);
};
export default RefreshButton;
And here is my ShowData component -
import React from "react";
const ShowData = ({ userData }) => {
console.log("Here", userData);
return (
<>
{userData.map((info, idx) => (
<div className="user-data" key={idx}>
{info}
</div>
))}
</>
);
};
export default ShowData;
PS - I'm new to React and couldn't find a potential solution on this, there are several tutorials on how to fetch data from API calls and show it, but I wanted to know what I'm doing wrong here. Thanks in advance!
You might have misunderstood with the infinite loop error
It's actually a render error as being shown here:
To fix your render error, simply put an actual string variable in the {}
Because the response was an array of this object, so you can't simply render the whole object but need to pick an actual string variable inside:
[{
"userId": 1,
"id": 1,
"title": "delectus aut autem",
"completed": false
}],
Change to something like this:
const ShowData = ({ userData }) => {
console.log("Here", userData);
return (
<>
{userData.map((info, idx) => (
<div className="user-data" key={idx}>
{info.title} // <-- Put a title here.
</div>
))}
</>
);
};
Remove
useEffect(() => {
console.log(userData);
},[userData])
This will reevaluate component whenever user data changes, which Leeds to call showData infinitely

react constantly calling the API when using .map to go through the list

i have setup a strapi API, and i am using react to consume that API (with Axios).
here's what the code look like inside App.js
import axios from "axios";
import React, {useEffect, useState} from "react";
import LineCard from "./components/Linecard"
function App() {
// don't mind the URL i will fix them later
const root = "http://localhost:1337"
const URL = 'http://localhost:1337/pick-up-lines'
// this is the "messed up" data from strapi
const [APIdata, setAPIdata] = useState([])
//this is the clean data
const [lines, setLines] = useState([])
// the array that i will be using later to "setLines" state
const linesFromApi = APIdata.map((line, index) => {
const profileImage = root + line.users_permissions_user.profilePicture.formats.thumbnail.url
const userName = line.users_permissions_user.username
const title = line.title
const lineBody = line.line
const rating = line.rating
const categories = line.categories.map((category, index) => category.categoryName)
return {
profileImage,
userName,
title,
lineBody,
rating,
categories
}
})
useEffect(() => {
// calling the API with get method to fetch the data and store it inside APIdata state
axios.get(URL).then((res) => {
setAPIdata(res.data)
})
setLines(linesFromApi)
}, [URL, linesFromApi])
return (
<div>
// mapping through the lines list and rendering a card for each element
{lines.map((line, index) => <LineCard line={line} />)}
</div >
);
}
export default App;
i know for sure that this is causing the problem
return (
<div>
{lines.map((line, index) => <LineCard line={line} />)}
</div >
);
my problem is that react keeps sending GET requests constantly, and i want it to stop after the first time it has the list.
how can i do that!
Try adding a check in your hook so that it restricts the api call if the value is already set.
Something like this
useEffect(() => {
if(lines.length === 0){
axios.get(URL).then((res) => {
setAPIdata(res.data)
})
setLines(linesFromApi)
}
}, [URL, linesFromApi])
You need to add the key property to the element in a map.
<div>
{lines.map((line, index) => <LineCard key={index} line={line} />)}
</div>

How to pass id from one component to another component onclick of an element

I'm trying to pass this is as id as props to another component which is not a child of the component. I was considering using context but i wanted to know if there was another way to it, since I'm quite new to react I'm looking for a more efficient way.
This is the component where the id of the element clicked is being generated. When i logged it the data is correct an no problems was notified. I first tried passing it as props as seen below but since i didn't want it to be seen on that page i didn't pass it to the main return statement neither did i call the method in it, but then it returned undefined in the component where i wanted to make use of it
import React, { useState } from 'react'
import { useHistory } from 'react-router-dom';
import Workspacelist from '../Workspace/Workspacelist';
function BoardList({ boards }) {
const [currentid, setcurrentid] = useState('')
const history = useHistory()
const navigate = (id) => {
setcurrentid(id);
console.log(id)
history.push(`/workspace/${id}`)
return(
<Workspacelist id = {id}/>
)
}
return (
<>
{
boards.map((board) => (
<li key={board.id} className="boardlist" style={styles} onClick={() => navigate(board.id)}>
<h3>{board.title}</h3>
</li>
))}
</>
)
}
export default BoardList
PS: Firebase is being incoporated in this project, i was thinking that might be the reason cause it's my first time using firebase so maybe I'm missing something since all the data is coming from the server
And this is the component i want to pass it to
import React, { useState, useEffect } from 'react'
import Firebase, { db } from '../Firebase/Firebase';
import { Todo } from './List';
function Workspacelist({ id }) {
const [updatedId] = useState(id)
const [show, setshow] = useState(false);
const [Todos, setTodos] = useState([]);//Todolist
const [ToDo, setToDo] = useState('');
useEffect(() => {
const docRef = db.collection("boards").doc(updatedId).get().then(doc => {
if (doc.exists) {
setTodos(doc.data().todo);
console.log("Document data:", doc.data().todo);
} else {
console.log("No such document!");
}
}).catch(function (error) {
console.log("Error getting document:", error);
});
return docRef
})
return (
<div className="workspacelist">
<div className="todo">
<div>
<b>To Do</b>
<b>...</b>
<Todo Todos={Todos} />
<span onClick={() => { setshow(current => !current) }} style={{ display: show ? 'none' : 'block' }}>+ Add a card</span>
</div>
<div className="add" style={{ display: show ? 'block' : 'none' }}>
<textarea placeholder="Enter a title for this card..." value={ToDo} onChange={(e) => { setToDo(e.target.value) }} />
<button className="addcard" onClick={one}>Add Card</button>
<button onClick={() => { setshow(current => !current) }}>X</button>
<button className="more">...</button>
</div>
</div>
</div>
)
}
export default Workspacelist
Thanks in advance i did appreciate the help even if i have to rewrite it just tell me the way you would do it if you were in my shoes
To navigate to another page, you just need history.push(/workspace/${id}).
You don't even need any state here.
import React, { useState } from 'react'
import { useHistory } from 'react-router-dom';
import Workspacelist from '../Workspace/Workspacelist';
function BoardList({ boards }) {
const history = useHistory()
const navigate = (id) => {
history.push(`/workspace/${id}`)
}
return (
<>
{
boards.map((board) => (
<li key={board.id} className="boardlist" style={styles} onClick={() => navigate(board.id)}>
<h3>{board.title}</h3>
</li>
))}
</>
)
}
export default BoardList
To get the id param on the Workspace page, you will need to use the useRouteMatch hook from react-router-dom:
import { useRouteMatch } from 'react-router-dom';
function Workspacelist() {
const {
params: { id },
} = useRouteMatch('/workspace/:id');
console.log(id)
}
Let me know if it solves your problem.
If you use dom version 6, change the following parts that showed in #HichamELBSI answer.
useHistory should change into useNavigate.
useRouteMatch should change into useMatch.
After applying those, the codes should be
import { useNavigate} from 'react-router-dom';
const nav = useNavigate();
const navigate = (id) => {
nav(`/workspace/${id}`)
}
Then other part should be
import { useMatch } from 'react-router-dom';
function Workspacelist() {
const {
params: { id },
} = useMatch('/workspace/:id');
console.log(id)
}

How to use React component's custom hook with "map"

I'm trying to make a Checkbox component.
Here is my Checkbox.tsx.
import React from "react";
import * as S from "./style";
const Checkbox: React.FC<S.ICheckboxProps> = ({ checked, setChecked }) => {
return <S.StyledCheckbox checked={checked} onClick={setChecked} />;
};
and this is my useCheckbox.tsx,
import { useState } from "react";
export const useCheckbox = (initialState: boolean) => {
const [checked, _setChecked] = useState<boolean>(initialState);
const setCheckedToggle = () => _setChecked((prev) => !prev);
const setCheckedTrue = () => _setChecked(true);
const setCheckedFalse = () => _setChecked(false);
return { checked, setCheckedToggle, setCheckedTrue, setCheckedFalse };
};
export default Checkbox;
It works good. I can use this like
import Layout from "components/Layout";
import { useCheckbox } from "hooks/useCheckbox";
import Checkbox from "components/Checkbox";
const Home = () => {
const { checked, setCheckedToggle } = useCheckbox(false);
return (
<Layout>
<Checkbox checked={checked} setChecked={setCheckedToggle} />
</Layout>
);
};
export default Home;
But I have trouble in the List component.
List has a Checkbox component, and I have to use this List with data.
const Home = ({data}) => {
return (
<Layout>
{data.map((d) => <List />)}
</Layout>
);
};
In this case, is there a way to determine if the list is selected?
If the List has useCheckbox, the Home component doesn't know the checked state.
Should I use useCheckbox in the Home component for data.length times? I think this is not good.
Thanks for reading, and Happy new year.
If you want the checkbox state to exist at the level of Home then you'll need state in the Home component that can handle multiple items, either as an array or object.
Then where you map over data you can pass down checked and setChecked as props to List, with all the logic defined in Home using the item index (or preferably an ID if you have one) in relation to your Home state.
Here's an example of a hook you could use in Home
import { useState } from "react";
export const useCheckboxes = () => {
const [checkedIds, setCheckedIds] = useState([]);
const addToChecked = (id) => setCheckedIds((prev) => [...prev, id]);
const removeFromChecked = (id) =>
setCheckedIds((prev) => prev.filter((existingId) => existingId !== id));
const isChecked = (id) => !!checkedIds.find(id);
const toggleChecked = (id) =>
isChecked(id) ? removeFromChecked(id) : addToChecked(id);
return { isChecked, toggleChecked };
};
And you would use it like this
const Home = ({ data }) => {
const { isChecked, toggleChecked } = useCheckboxes();
return (
<Layout>
{data.map((d) => (
<List
key={d.id}
checked={isChecked(d.id)}
toggleChecked={() => toggleChecked(d.id)}
/>
))}
</Layout>
);
};

How to Re-render Component Only Once after the data is changed?

I am new to React JS. I am making CRUD Operation in React. Everything is fine but when I delete the item from the list I have to refresh the browser tho update the List. How can I solve this?
import React, { useState, useEffect } from 'react'
import axios from 'axios';
import { Segment, Item, Container, Card, Icon, Button } from 'semantic-ui-react';
import { IEmployee } from '../../src/Model/activity'
import { Link, RouteComponentProps } from 'react-router-dom';
interface DetailParams {
id: string;
}
const EmployeeList : React.FC<RouteComponentProps<DetailParams>> = ({ match, history }) => {
const [employees, setEmployees] = useState<IEmployee[]>([])
useEffect(() => {
axios.get('https://localhost:44353/Employee/GetEmployeeList')
.then((response) => {
setEmployees(response.data)
})
}, [])
const deleteEmployee =(id: string) => {
axios.get(`https://localhost:44353/Employee/DeleteEmployee/${id}`)
.then((response) => {
history.push('/employeeList')
})
}
return (
<Container style={{ marginTop: '7em' }}>
<Segment>
{
employees.map(employee => (
<Card key={employee.id}>
{/* <Image src='/images/avatar/large/daniel.jpg' wrapped ui={false} /> */}
<Card.Content>
<Card.Header>{employee.firstName}</Card.Header>
<Card.Meta>{employee.address}</Card.Meta>
<Card.Description>
{employee.organization}
</Card.Description>
</Card.Content>
<Card.Content>
<Button
onClick={() => deleteEmployee(employee.id)}
floated="right"
content="Delete"
color="red" />
<Button
as={Link} to={`/edit/${employee.id}`}
floated="right"
content="View"
color="blue" />
</Card.Content>
</Card>
))
}
</Segment>
</Container>
)
}
export default EmployeeList
The above code is of EmployeeList Component which is routed by ** /employeeList ** . Here is the UI of the code
when I delete the item from the list I need to reload the browser to update the List. I tried using employee dependent in useEffect
useEffect(() => {
axios.get('https://localhost:44353/Employee/GetEmployeeList')
.then((response) => {
setEmployees(response.data)
})
}, [employees])
this worked fine but the API method is executing infinitely. How do I solve this?
Two things can be done
if your delete api returns the updated data you can just call setEmployess and set the updated value .
or you can filter the deleted value from the state employees
const deleteEmployee =(id: string) => {
//add this in axios call success
let updatedEmployee = [...employees];
updatedEmployee.filter(eachEmployee=>eachEmployee.id !== id);
setEmployees(updatedEmployee);
}
Instead of refreshing the page you should just make another request after the delete request to get an updated employees list.
const deleteEmployee = async (id: string) => {
// Delete employee
await axios.get(`https://localhost:44353/Employee/DeleteEmployee/${id}`)
// Get a fresh list
const employees = (await axios.get('https://localhost:44353/Employee/GetEmployeeList')).data
setEmployees(employees)
// Navigate
history.push('/employeeList')
}

Categories

Resources