Cannot read properties of undefined (reading 'categoryName') in react - reactjs

I'm trying to update my category. I want to update only categoryname, categoryDescription and categoryImage inside the category data. For this, I pull the data from the API with the id I wrote in c# on the backend to show the first state of the data to the user, there is no problem there, but when I try to make changes to a data, nothing appears on the screen in the browser and the following errors appear in the console. Actually ı am new in react. How can I fix that problem?
ERRORS
Uncaught TypeError: Cannot read properties of undefined (reading 'categoryName')
The above error occurred in the <UpdateCategory> component:
My CategoryList.js
I send the id of the category I clicked to UpdateCategory.js from here and I do the editing there.
CategoryList.js shows only my categories
import { Button } from "bootstrap";
import React, { useContext } from "react"
import { Link, Router } from "react-router-dom";
import { CategoryContext } from "../Contexts/CategoryContext";
import "../Css/Categories.css"
export default function CategoryList() {
const { Categories } = useContext(CategoryContext)
const truncateOverview = (string, maxLength) => {
if (!string) return null;
if (string.length <= maxLength) return string;
return `${string.substring(0, maxLength)} ...`;
}
return (
<div className="categories">
{Categories.map((category, i) => (
<Link className="category" to={`/ProductList/${category.categoryId}`} key={i}>
<div className="inner-category">
<div className="image-body">
<img src={category.categoryImage} className="image" alt="" />
</div>
<div className="category-body">
<div>
<h5 className="">{category.categoryName}</h5>
<p className="">{truncateOverview(category.categoryDescription, 50)}</p>
<Link to={`/UpdateCategory/${category.categoryId}`}>
<button className ="btn btn-warning" variant="primary" >
EDIT
</button>
</Link>
</div>
</div>
</div>
</Link>
))}
</div>
)
}
My UpdateCategory.js
import React, { useState, useEffect } from 'react';
import { useParams } from 'react-router-dom';
import Button from 'react-bootstrap/Button';
import Col from 'react-bootstrap/Col';
import Form from 'react-bootstrap/Form';
import InputGroup from 'react-bootstrap/InputGroup';
import Row from 'react-bootstrap/Row';
import axios from 'axios';
export default function UpdateCategory() {
const { id } = useParams()
const url = `http://localhost:64082/api/categories/getbyid/${id}`
const [category, setCategory] = useState({})
const fetchData = () => {
axios.get(url)
.then(response => {
setCategory(response.data)
})
.catch(error => {
console.log(error)
})
}
useEffect(() => {
fetchData()
});
const handleInputChange =(e)=>{
setCategory(e.target.category)
}
const handleSubmit = (event) => {
event.preventDefault();
const data = new FormData(event.target);
fetch(`http://localhost:64082/api/categories/update`, {
method: 'POST',
body: data,
})
}
return (
<div>
<form >
<label htmlFor="inputName">Category Name</label>
<input type="text"
className="form-control"
name="categoryName"
value={category.categoryName}
onChange={handleInputChange}
/>
<label htmlFor="inputName">Category Description</label>
<input type="text"
className="form-control"
name="categoryDescription"
value={category.categoryDescription}
onChange={handleInputChange}
/>
<label htmlFor="inputName">Category Image</label>
<input type="text"
className="form-control"
name="categoryImage"
value={category.categoryImage}
onChange={handleInputChange}
/>
<div>
<button onSubmit={handleSubmit} className="btn btn-danger" >EDIT</button>
</div>
</form>
</div>
)
}

The error probably comes about because of the render in UpdateCategory. The code tells it to render three properties of the category object. But initially, category is an empty object. So it will fail there.
There's another point you need to modify though, in the useEffect(). What you have right now will not trigger on the initialisation of the component. Change it to:
useEffect(() => {
fetchData()
}, []);
Since you're not using typescript you'd also probably want some kind of guard on your render to be safe. So for example something like this:
return category.categoryName && category.categoryDescription && category.categoryImage && (
// your render code in here
);
But that's quite long-winded. You could write an if clause before the return () for the render, asking if those properties exist and returning a blank component instead, or some alternate text.
You could also perhaps provide a default version of the category when initialising the hook.
const [category, setCategory] = useState({
categoryName: 'Default',
categoryDescription: 'Default description',
categoryImage: null
});
This would be a simpler effort than the guard block, and you'd still need the useEffect update.

In UpdateCategory.js,edit;
const [category, setCategory] = useState([]) // not useState({})
The default value of this state should be an empty array. Thanks to empty array, It will not be able to return with map until data comes from api, because its length is 0.

Related

how to get data from input form in React js by clicking submit button and create a div element that get all data user input. {.map() Multiple states}

I'd like to create div with data getting from user input by clicking btn submit, But I don't know how. I am new in react js.
This is my App.js file:
import './App.css';
import './RegisterApp.css'
import RegisterApp from './Components/RegisterApp';
function App() {
return (
<div className="App">
<RegisterApp />
</div>
);
}
export default App;
and this is my component file RegisterApp.js:
import React, {useState} from 'react'
function RegisterApp() {
const [name, setName] = useState('Khun Neary')
const [position, setPosition] = useState('Designer')
const [list, setList] = useState({name, position})
const formSubmit = (e) => {
e.preventDefault()
setList(...list, name)
setList(...list, position)
console.log(list);
}
return (
<div className='container'>
<form className='form-box' onSubmit={formSubmit}>
<button>Upload Profile</button>
<input
type="text"
placeholder='Name...'
value={name}
onChange={(e) => setName(e.target.value)}
/>
<input
type="text"
placeholder='Position...'
value={position}
onChange={(e) => setPosition(e.target.value)}
/>
<button>Submit</button>
</form>
<div className='register-box'>
<div className='sub-reg-box'>
<div className='img-box'></div>
<div className='detail-box'>
<h2>{name}</h2>
<h4>{position}</h4>
</div>
</div>
</div>
</div>
)
}
export default RegisterApp
enter image description here
I'd like to create div element after I click submit btn and display all the data get from input by user.
add type="submit" to button
<button type="submit">Submit</button>
then update the list state
const formSubmit = (e) => {
setList( {...list, name, position })
}
you won't see the update to the list immediately since setState in asynchronous. But to check that, you can use useEffect
useEffect(() => {
console.log(list)
},[list])
You don't need to "get" the data. You already have it in the variables name and position. You should create an onClick handler for the button that uses these values.
Note that setList() is misnamed. You should use an object here. In fact, you can get rid of list and setList because you already have name, setName, position and setPosition. You don't need both.

react js myfn is not a function when called from a button

I've just started learning about react js and this is my first react js app. I'm using api to fetch the data. so far it works, but now I want to add a search keyword to the function that is acquired from a search bar component.
here's my code:
SearchBar.js
const SearchBar = ({ getUsers }) => {
return (
<div className="is-flex flex-align-items-center mb-3">
<input type="text" id="query" className="input search-input" placeholder="search keyword"/>
<Button className="search-btn ps-3 pe-3"
onClick={() => getUsers(document.querySelector('#query').value)}>
<FontAwesomeIcon icon={faMagnifyingGlass} />
</Button>
</div>
);
};
MasterUser.js
import { useState, useEffect } from "react";
import SearchBar from "./SearchBar";
const MasterUser = () => {
const [users, setUsers] = useState([]);
useEffect(() => {
getUsers();
}, []);
const getUsers = async (query='') => {
console.log('get users', query);
try {
let myurl = 'http://localhost:8080/users';
const response = await fetch(myurl);
const data = await response.json();
setUsers(data);
setIsLoading(false);
} catch (e) {
console.log(e.getMessage());
}
};
return (
<div>
<SearchBar onClick={getUsers}/>
</div>
);
};
when the app loads, the console log says get users <empty string> and it returns all the users as expected, but when I clicked on the search button (magnifyingGlass) it gives an error Uncaught TypeError: getUsers is not a function.
any help is appreciated..
<SearchBar onClick={getUsers}/>
You have named the prop onClick not getUsers. That's why you get that error.
Yeah, accessing dom element value using selectors (e.g. document.querySelector('#query').value) is also not typical react. Read about controlled form elements (save form element value in state).
Make your searchBar component more reactive like so
const SearchBar = ({ getUsers }) => {
const [searchValue,setSearchValue]=useState('');
return (
<div className="is-flex flex-align-items-center mb-3">
<input type="text" id="query" className="input search-input" placeholder="search keyword" value={searchValue} onChange={(e)=>setSearchValue(e.target.value)}/>
<Button className="search-btn ps-3 pe-3"
onClick={() => getUsers(searchValue)}>
<FontAwesomeIcon icon={faMagnifyingGlass} />
</Button>
</div>
);
};

Retrieve the latest data by using API link

Goal:
Every time when I press the Button 'Test' you always need to fetch fresh data from backend by using API link. Then it should be displayed on the modalform.
Problem:
When I change the text in the input box or delete all text and then closing the modal and then click on the button Test again. The latest changes of what I have done would display. It shouldn't be happening because you always should get the latest data from backend by using API link.
Question:
How should the code always retrieve the data by using api link when you always press on the button 'test'?
Stackblitz:
https://stackblitz.com/edit/react-ts-byxk6x?file=index.tsx
Thank you!
index.tsx
import React, { FC, useState } from 'react';
import { render } from 'react-dom';
import './style.css';
import { TestModalForm } from './TestModalForm';
interface AppProps {}
interface AppState {
name: string;
}
const App: FC<AppProps> = () => {
return (
<div>
<button data-bs-toggle="modal" data-bs-target="#myModal">
Test
</button>
<br />
<TestModalForm />
</div>
);
};
render(<App />, document.getElementById('root'));
TestModalForm.tsx
import React, { useState } from 'react';
export const TestModalForm = () => {
const [inputid, setInputid] = useState('');
const [inputTitle, setInputTitle] = useState('');
React.useEffect(() => {
async function FetchData() {
var data = await fetch(
'https://jsonplaceholder.typicode.com/todos/1'
).then((res) => {
return res.json();
});
setInputid(data.id);
setInputTitle(data.title);
}
FetchData();
}, []);
const handleIdInput = (e: any) => {
setInputid(e.target.value);
};
const handleTitleInput = (e: any) => {
setInputTitle(e.target.value);
};
// Reset Input Field handler
const resetInputField = () => {
setInputid('');
setInputTitle('');
};
return (
<div>
<div
className="modal"
id="myModal"
data-bs-backdrop="static"
data-bs-keyboard="false"
tabIndex={-1}
aria-labelledby="staticBackdropLabel"
aria-hidden="true"
>
<div className="modal-dialog">
<div className="modal-content">
<div className="modal-header">
<h4 className="modal-title">TEST</h4>
<button
type="button"
className="btn-close btn-close-white"
data-bs-dismiss="modal"
></button>
</div>
<div className="modal-body">
<input
type="text"
className="form-control"
placeholder="Id"
value={inputid}
onChange={handleIdInput}
/>
<br />
<input
type="text"
className="form-control"
placeholder="Title"
value={inputTitle}
onChange={handleTitleInput}
/>
<br />
<button className="form-control" onClick={resetInputField}>
Reset
</button>
</div>
</div>
</div>
</div>
</div>
);
};
A simple solution would be to introduce some state to the App component, updated by the test button being clicked, that could be passed to the TestMOdalForm to be used as an useEffect dependency.
It's also anti-pattern to mix async/await with Promise-chains. Pick one or the other.
Example:
const App: FC<AppProps> = () => {
const [id, setId] = useState(0);
return (
<div>
<button
data-bs-toggle="modal"
data-bs-target="#myModal"
onClick={() => setId((c) => c + 1)} // <-- update state upon click
>
Test
</button>
<br />
<TestModalForm id={id} /> // <-- pass state as prop
</div>
);
};
...
const TestModalForm = ({ id }) => { // <-- destructure prop
...
React.useEffect(() => {
async function fetchData() {
try {
const response = await fetch(
'https://jsonplaceholder.typicode.com/todos/1'
);
const data = await response.json();
setInputid(data.id);
setInputTitle(data.title);
} catch(error) {
// handle any fetch rejections or other thrown errors
}
}
fetchData();
}, [id]); // <-- pass prop as dependency
...
return (
...
);
};

Rerender sibling component in React

I am new to React. I am stuck on this problem for days now.
I have got a parent component which wraps two sibling components, "FileUpload" and "Documents"
The "FileUpload" is for uploading a file and "Documents" is for displaying all the uploaded files.
I want the "Documents" rerender after a new file is uploaded via "FileUpload", so that it shows the new file in the UI.
What would be the best approach to achieve this ?
Below is the code I have written so far for the sibling components:
FileUpload:
import React, { useState } from "react";
import Axios from "axios";
const FileUpload = (props) => {
const [files, setFiles] = useState([]);
const onInputChange = (e) => {
setFiles(e.target.files);
};
const handleSubmit = async (e) => {
e.preventDefault();
const data = new FormData();
for (let i = 0; i < files.length; i++) {
// console.log(files);
data.append("file", files[i]);
}
data.append("parentDbId", props.parentDbId);
data.append("parentObject", props.parentObject);
//console.log(data);
try {
await Axios.post("http://localhost:5000/upload", data);
} catch (err) {
console.error(err.message);
}
};
return (
<form
// action="http://localhost:5000/upload"
// method="POST"
//encType="multipart/form-data"
onSubmit={handleSubmit}
>
<div className="row mb-3">
<div className="col-lg-4">
<label htmlFor="formFileMultiple" className="form-label mb-0">
Add files
</label>
<input
className="form-control"
type="file"
id="formFileMultiple"
name="file"
multiple
onChange={onInputChange}
/>
</div>
<div className="col-lg-4 mt-0 gx-0">
<button type="submit" className="btn btn-primary mt-4">
Upload
</button>
</div>
</div>
</form>
);
};
export default FileUpload;
====================================================================
Documents:
import React, { useState, useEffect } from "react";
import axios from "axios";
const Documents = (props) => {
const parentDbId = props.parentDbId;
const [documents, setDocuments] = useState([]);
//mount
useEffect(() => {
console.log("first use effect");
loadDocuments();
}, []);
const loadDocuments = async () => {
const result = await axios.get(
`http://localhost:5000/documents/${parentDbId}`
);
setDocuments(result.data);
};
return (
<>
<div className="row">
{documents.map((document, index) => (
<div className="col-lg-3" key={index}>
<a href={document.filePath}>{document.fileName}</a>
</div>
))}
</div>
</>
);
};
export default Documents;
Thanks,
Jimmy
Simple, just have the parent control document state and pass the state and callback down to the children as a prop. Now the siblings are referencing the same state and will be re-rendered when props (ie document state) changes. The parent can also handle the data fetching and uploading.
it will look like this:
const Parent = () => {
const [documents, setDocuments] = useState([]);
...do data fetching here
const handleSubmit = useCallback(async () => {}, []); // You might want to reset document state here?
return (
<div>
<Docs documents={documents} />
<Upload onUpload={setDocuments} onSubmit={handleSubmit} />
</div>
);
}
I wonder if you should actually have two documents components, one for displaying the files being uploaded, and one for displaying the already uploaded files. You would embed one within the Upload component and the other would fetch documents from the api every time onUpload completes

Is there a way to handle input hidden value in onSubmit?

I'm creating a todo app and I'm thinking of sending the id of the todo I want to delete using the post method to delete it. I'm currently using <form action="localhost" method="delete">, but I'd like to do something a little more complicated, so I'm trying to pass the id to the OnSubmit handler. is there a way to use the id in OnSubmit? Is there any way to use id in OnSubmit?
Implemented code
import React, {useState, useEffect} from "react";
import axios from "axios";
interface Todo {
id: number,
text: string,
}
const ShowContent = () => {
const [todoes, setTodoes] = useState<Todo[]>([]);
useEffect(() => {
axios.get<Todo[]>("http://localhost:8888")
.then(res => {
setTodoes(res.data)
})
.catch(_ => alert("useeffect error"))
}, [])
return (
<div>
<h1>Todo App</h1>
{todoes.map(todo =>
<div key={todo.id}>
<div>id: {todo.id}, text: {todo.text}</div>
<form action="localhost:8888" method="post">
<input type="hidden" name="id" value={todo.id}></input>
<button>Delete</button>
</form>
</div>
)}
<div>
<form action="localhost:8888" method="post">
<input type="text"></input>
<button>Submit</button>
</form>
</div>
</div>
);
}
What I want to do
import React, {useState, useEffect} from "react";
import axios from "axios";
interface Todo {
id: number,
text: string,
}
const ShowContent = () => {
const [todoes, setTodoes] = useState<Todo[]>([]);
useEffect(() => {
axios.get<Todo[]>("http://localhost:8888")
.then(res => {
setTodoes(res.data)
})
.catch(_ => alert("useeffect error"))
}, [])
const handleOnSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
// I want to get the id here.
}
return (
<div>
<h1>Todo App</h1>
{todoes.map(todo =>
<div key={todo.id}>
<div>id: {todo.id}, text: {todo.text}</div>
<form onSubmit={handleOnSubmit}>
<input type="hidden" name="id" value={todo.id}></input>
<button>submit</button>
</form>
</div>
)}
</div>
);
}
export default ShowContent;
It is possible to get the id from your hidden input through the event using e.target either with FormData or document.querySelector, but there are Typsecript issues with this.
You already have access to the todo in the loop where you create the form, so my recommendation is that you create a function which already knows the id.
You can change your handler to a curried function like this:
const handleOnSubmit = (id: number) => (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
console.log(`submitted todo ${id}`);
}
And use it like this:
<form onSubmit={handleOnSubmit(todo.id)}>

Resources