How to fetch specific JSON data using an ID (React Hooks) - reactjs

I want to get a specific item from a json using its unique ID but with the function that I have created I do not get any data. This is the function:
export function getPost(id) {
return fetch("http://localhost:3004/edit/"+id)
.then(data => data.json())
}
And this is the page where I want to print the item. The ID comes from another page and it's shown in the url, where I get it thanks to useParams:
interface IPost {
id: number;
title: string;
author: string;
content: string;
}
const Edit: React.FC = () => {
const [post, setPost] = useState<IPost>();
const {id} = useParams();
// Not working
getPost(id)
.then(items => {
setPost(items)
})
return (
<div className="containerHomepage">
<form className="formulari">
<div className="containerBreadCrumb">
<ul className="breadCrumb">
<li>Posts</li>
{/* THIS SHOWS AN ERROR */}
{post.author}
</ul>
</div>
<div className="containerTitleButton">
<input
className=""
type="text"
placeholder='Post title'
name="title"
// onChange={handleInputChange}
></input>
<button
className="button"
type="submit"
>Save</button>
</div>
<div className="containerEdit">
<input
className="editAuthor"
type="text"
placeholder='Author'
name="author"
// onChange={handleInputChange}
></input>
<input
className="editContent"
type="textarea"
placeholder='Content'
name="content"
// onChange={handleInputChange}
></input>
{/* <div className="errorEmpty">{error}</div> */}
</div>
</form>
</div>
);
};
// ========================================
export default Edit;
Throws an error in "{post.author}", and I guess that it's something wrong with my function "getPost".

Since you initialize post to undefined:
const [post, setPost] = useState<IPost>();
trying to access properties of it will throw:
{post.author}
Your TypeScript should have warned you about this - it's good to fix TypeScript warnings before running apps for real to avoid runtime errors. Check that the object exists before trying to access properties on it.
{post?.author}
There's no issue with your getPost function, except for the fact that you should probably only call it once, when the component mounts, not every time it re-renders.
useEffect(() => {
getPost(id).then(setPost);
}, []);
I'd also recommend not ignoring errors - catch them to avoid unhandled rejections.
useEffect(() => {
getPost(id).then(setPost).catch(handleError);
}, []);

Related

Set item in onclick is not logging expected output

I'm writing a simple react code that adds a value to a list onClick of a button. and after adding, I'm logging it in the same block. Currently, my issue is, that the logging is happening with n-1 entered string. i.e. If I enter egg and then add milk, after adding milk, I see egg logged and so on. Here is my code.
function App() {
const [list, setList] = useState([]);
const [gItem, setGItem] = useState("");
const AddItem = (e) => {
e.preventDefault();
setList([...list, gItem]);
console.log(list);
};
return (
<>
<form className="grocery-form">
<h3>grocery bud</h3>
<div className="form-control">
<label htmlFor="name"></label>
<input
type="text"
placeholder="e.g. eggs"
className="grocery"
name="name"
id="name"
onChange={(e) => setGItem(e.target.value)}
/>
<button className="submit-btn" type="submit" onClick={AddItem}>
Submit
</button>
</div>
</form>
<div className="grocery-container">
<List items={list} />
</div>
</>
);
}
I'm unable to understand where I'm going wrong.
setList updates state asynchronously so if you log state after using it the previous value will be displayed, to make it log the current state after this list was changed you can use useEffect hook like this:
useEffect(() => {
console.log(list);
}, [list])

React - how to target value from a form with onClick

New to react and currently working on a project with a backend.
Everything functions correctly apart from targeting the value of user selection.
basically whenever a user enters a number the setId is saved properly to the const with no problems while using the onChange method.
this method would render my page every change on text.
I am trying to save the Id only when the user clicks the button. however,
event.target.value does not work with onClick.
I tried using event.currentTarget.value and this does not seem to work.
Code:
<form onSubmit={handleSubmit}>
<label>Company ID</label>
<input value={id} onChange={(e) => setId(e.target.value)} type="number" />
{/* <button value={id} type="button" onClick={(e) => setId(e.currentTarget.value)}>Search</button> */}
</form>
Handle Submit:
const handleSubmit = (e) => {
e.preventDefault();
console.log(id)
}
is there a way of doing this with onclick? since I wouldn't like my component to render on every typo and only once a user has clicked the button.
Componenet:
interface GetOneCompanyProps {
company: CompanyModel;
}
interface RouteParam {
id: any;
}
interface CompanyById extends RouteComponentProps<RouteParam> {
}
function GetOneCompany(): JSX.Element {
const [id, setId] = useState('4');
const [company, setCompany] = useState<any>('');
const handleSubmit = (e) => {
e.preventDefault();
console.log(id)
}
async function send() {
try {
const response = await axios.get<CompanyModel>(globals.adminUrls.getOneCompany + id)
store.dispatch(oneCompanyAction(response.data));
console.log(response);
const company = response.data;
setCompany(company)
} catch (err) {
notify.error(err);
}
}
useEffect(() => {
send();
}, [id]);
return (
<div className="getOneCompany">
<h1>hi </h1>
<form onSubmit={handleSubmit}>
<label>Company ID</label>
<input value={id} onChange={(e) => setId(e.target.value)} type="number" />
{/* <button value={id} type="button" onClick={(e) => setId(e.currentTarget.value)}>Search</button> */}
</form>
<div className="top">
</div>
<br/>
Company: {id}
<br/>
Client Type: {company.clientType}
<br/>
Company Name: {company.name}
<br/>
Email Adress: {company.email}
<br/>
</div>
);
}
export default GetOneCompany;
Hope I am clear on this.
Thanks.
You can turn your input from being a controlled input to an uncontrolled input, and make use of the useRef hook. Basically, remove most of your attributes from the input element, and grab the current value of the input form on click of the button. From there, you can do whatever you want with the input value.
const inputRef = useRef()
...other code
<form onSubmit={handleSubmit}>
<label>Company ID</label>
<input type="number" ref={inputRef} />
<button value={id} type="button" onClick={() => console.log(inputRef.current.value)}>Search</button>
</form>
...other code
I'm afraid to say that here onChange is mandatory as we also are interested in the value which we set by setId. onClick can't be used as we can't set the value in the input.
Hope I'm clear.
Thankyou!

React UseState hook for updating data - CRUD

Im just wondering if anyone could point out where im going wrong with my code.
Im relativly new to react so began with a simple todo list.
I then edited this to allow for various other forms such as menu, profile etc.
Below is the code attached for the menu section.
My back end works if I use postmaster which leads me to believe its my front end, and specifically my useState.
I can call the data and view it within my modal and it appears, however, I cant seem to edit the specific data within the form field and/or post it to my database.
Any help would be greatly appreciated.
Ive attached my code below.
import React, { Fragment, useState } from "react";
const EditMenu = ({ menu }) => {
//editText function
const [inputs, setInputs] = useState(menu.item_title, menu.item_price, menu.item_description, menu.item_category);
const { title, category, price, description } = inputs;
const onChange = e =>
setInputs({ ...inputs, [e.target.name]: e.target.value });
const editMenuItem = async (item_id) => {
try {
const body = { title, category, price, description };
const res = await fetch(`http://localhost:5000/menu/${item_id}`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body)
});
window.location = "/admin";
} catch (error) {
console.error(error.message);
}
};
return (
<Fragment>
<button type="button" className="btn btn-warning" data-toggle="modal" data-target={`#id${menu.item_id}`}>Edit</button>
{/*id = "id21"*/}
<div className="modal" id={`id${menu.item_id}`} onClick={() => setInputs(menu.item_title, menu.item_price, menu.item_description, menu.item_category)}>
<div className="modal-dialog">
<div className="modal-content">
<div className="modal-header">
<h4 className="modal-title">Edit Menu</h4>
<button className="close btn-danger" data-dismiss="modal" onClick={() => setInputs(menu.item_title, menu.item_price, menu.item_description, menu.item_category)}>×</button>
</div>
<div className="modal-body">
<input type="text" name="title" placeholder="Title" className="form-control my-3" value={menu.item_title} onChange={e => onChange(e)} />
<input type="tel" name="price" placeholder="Price" className="form-control my-3" value={menu.item_price} onChange={e => onChange(e)} />
<input type="text" name="description" placeholder="Description" className="form-control my-3" value={menu.item_description} onChange={e => onChange(e)} />
<input type="text" name="category" placeholder="Category" className="form-control my-3" value={menu.item_category} onChange={e => onChange(e)} />
</div>
<div className="modal-footer">
<button type="button" className="btn btn-warning" data-dismiss="modal" onClick={() => editMenuItem(menu.item_id)}>Edit</button>
<button type="button" className="btn btn-danger" data-dismiss="modal" onClick={() => setInputs(menu.item_title, menu.item_price, menu.item_description, menu.item_category)}>Close</button>
</div>
</div>
</div>
</div>
</Fragment>
);
};
Update,
Ive tried various suggested fixes using the below answers so far.
Both of these fixes allow the form fields to be editable, and the information within the form fields changes and thus within the state also however it is not sent to the database. Upon refresh of the page, the old information is pulled from the database.
Ive discovered that if I removed all of the form fields but one, it successfully updates AND sends to the database.
Title OR Description OR Price OR Category.
Checking the network tab within the browser whilst updating shows that for more than one input field, the put request fails and no information/payload is sent to the body within the request tab.
As a result, the database returns a NOT NULL error.
Based off Oliviers answer below, that setInput is only recognises one parameter, I can only imagine that this is what is breaking when there is more than one form field/input added. I unfortunatly dont know enough react to know if this is the case or not.
I see a problem in your state initialization => const [inputs, setInputs] = useState(menu.item_title, menu.item_price, menu.item_description, menu.item_category); is not correct, useState take a single parameter, here you must build an object representing the inputs.
Here is a solution using a function to initialize the inputs state, to prevent computing the object each time the component is re-rendered
function buildInputs(menu) {
return {
title: menu.item_title,
category: menu.item_category,
price: menu.item_price,
description: menu.item_description
};
}
const EditMenu = ({ menu }) => {
//editText function
const [inputs, setInputs] = useState(() => buildInputs(menu));
const { title, category, price, description } = inputs;
// Needed if you want the inputs to be updtated when the menu property is updated
useEffect(() => setInputs(buildInputs(menu)), [menu]);
const onChange = e => setInputs({ ...inputs, [e.target.name]: e.target.value });
...
You must also change the input value to reflect the state variable :
<input type="text" name="title" placeholder="Title"
className="form-control my-3" value={title} onChange={onChange} />
You should set your state like this:
const [inputs, setInputs] = useState({
title: menu.item_title,
price: menu.item_price,
category: menu.item_category,
description: menu.item_description
});
also you need to change value attributes to be variables rather than setting them to the menu values, for example:
//code
<input name="title" value={inputs.title} onChange={onChange}/>
cause values inside inputs are changeable by your onChange method, on the other hand, values inside menu object will remain with the same values.
I eventually figured out the issue.
By splitting my setInput useState into seperate individual useStates, I was able to get it to work.
So my origional code of...
const EditMenu = ({ menu }) => {
const [inputs, setInputs] = useState(menu.item_title, menu.item_price, menu.item_description, menu.item_category);
const { title, category, price, description } = inputs;
changed to this.
const EditMenu = ({ menu }) => {
const [item_title, setTitle] = useState(menu.item_title);
const [item_price, setPrice] = useState(menu.item_price);
const [item_description, setDescription] = useState(menu.item_description);
and the onChange function and form input...
const onChange = e =>
setInputs({ ...inputs, [e.target.name]: e.target.value });
<input... onChange={e => onChange(e)} />
changed to this...
value={item_title} onChange={e => setTitle(e.target.value)} />
value={item_price} onChange={e => setPrice(e.target.value)} />
value={item_description} onChange={e => setDescription(e.target.value)} />
In the end, Oliviers reasoing was correct even if the soloution didnt work for me. That my setInput only allowed for one parameter. Splitting it up allowed me to pass the remaining parameters.
Thank you everyone for the help, hopefully this might help someone else some day too!

Trying to get a innerhtml functionality to work in react todo list

I am aiming for new tasks to show as user clicks "add task", simple I know, but still learning react.
My goal was to use a ternary operator until its no longer null, and then map through the array each time a user clicks add task.
Issue:
I believe the renderTasks array isn't set by the time it tries to map over it, I get an error...
renderTasks.map is not a function
Is there a way I could utilize the useEffect for what I am trying to do, or any better ideas that could help? Thanks
Here's the code snippet of App.js
function App() {
const [tasks, setTasks] = useState([]);
const [renderTasks, setRenderTasks] = useState(null);
const handleAddTask = () => {
setRenderTasks(tasks);
};
const handleOnChange = (e) => {
setTasks({
...tasks,
[e.target.name]: e.target.value,
});
};
return (
<>
<div className="overview">
<label className="my-todos">My Todos</label>
<div className="input-div">
<div className="input-container">
<label className="title-desc">Title</label>
<input
name="title"
onChange={handleOnChange}
className="input-values"
type="text"
></input>
</div>
<div className="input-container">
<label className="title-desc">Description</label>
<input
name="description"
onChange={handleOnChange}
className="input-values"
type="text"
></input>
</div>
<button onClick={handleAddTask} className="add-task">
Add Task
</button>
</div>
{renderTasks !== null ? (
<ul>
{renderTasks.map((x) => {
return <li>{x.title - x.description}</li>;
})}
</ul>
) : null}
</div>
</>
);
}
export default App;
There were few issues in your implementation like how you destructing tasks, trying to access an object as an array and abusing the useState. You don't need useEffect or two useState to do the trick.
import React from "react";
import React, { useState } from 'react';
import "./style.css";
function App() {
const [tasks, setTasks] = useState([]);
const task = {};
const handleOnChange = (e) => {
task[e.target.name] = e.target.value;
};
const onClickHandler = (e)=>{
(task.title) && setTasks( [...tasks, task]);
}
return (
<>
<div className="overview">
<label className="my-todos">My Todos</label>
<div className="input-div">
<div className="input-container">
<label className="title-desc">Title</label>
<input
name="title"
onChange={handleOnChange}
className="input-values"
type="text"
></input>
</div>
<div className="input-container">
<label className="title-desc">Description</label>
<input
name="description"
onChange={handleOnChange}
className="input-values"
type="text"
></input>
</div>
<button onClick={onClickHandler} className="add-task">
Add Task
</button>
</div>
<ul>
{tasks.map((x) => {return <li>{x.title} - {x.description}</li> })}
</ul>
</div>
</>
);
}
export default App;
Even though you are initialising tasks to be an array, in handleOnChange you are setting it to an Object like this -
setTasks({
...tasks,
[e.target.name]: e.target.value,
});
This same tasks object you are trying to set for renderTasks in handleAddTask. So renderTasks is assigned to an Object and not an array and only arrays have map function and hence you are facing the issue renderTasks.map is not a function error
Try doing
Object.keys(renderTasks).map((x) => {
return <li>{x.title - x.description}</li>;
})

How should I use React to display global error messages?

I have an App component at the top level, and then within that component, I have one called ErrorMessages.
I put my App component's state into a context, and then elsewhere in my app, if an error occurs, I set errors on the App component's context.
The App component will then pass the errors to ErrorMessages via prop, and then the error messages will display.
The problem is, I can't seem to clear out the error messages.
What I've tried so far is, using the cleanup mechanism of useEffect to clear the errorMessages.
Say I have a component called UserRegistration, the user doesn't enter an e-mail address, and I set an error "email address required", this displays just fine.
Now, when UserRegistration is unmounted, I want to clear the error messages out of the App context.
How might I achieve this?
This is my app component:
class App extends React.Component {
// For errors in the format of:
// [{message: "first_name missing..."}, {message: "Email address in use..."}]
setErrorMessages = (errors) => {
this.setState({errors}) }
// Other messages not in the format above. Can set "alert-warning" or
// "alert-success" type messages.
setStatusMessage = (statusType, statusMessage) => {
this.setState({statusType, statusMessage}) }
clearAllMessages = () => {
this.setState({errors: [], statusType: null, statusMessage: null}) }
setAuthToken = () => {}
setLoaded = (val) => { this.setState({loaded: val}) }
state = {
user: {},
loaded: false,
errors: [],
statusType: null,
statusMessage: null,
setErrorMessages: this.setErrorMessages,
clearAllMessages: this.clearAllMessages,
setStatusMessage: this.setStatusMessage,
setLoading: this.setLoading,
setLoaded: this.setLoaded,
setAuthToken: this.setAuthToken,
}
render() {
return (
<div className="App container">
<React.StrictMode>
<AppContext.Provider value={this.state}>
<BrowserRouter>
{this.state.statusMessage ?
<div className={"alert alert-" + this.state.statusType}>{this.state.statusMessage}</div> : null}
<ErrorMessages list={this.state.errors} />
{!this.state.loaded ? <LoaderWidget /> : null }
<IssueBrowser />
</BrowserRouter>
</AppContext.Provider>
</React.StrictMode>
</div>
);
}
}
If I use the cleanup mechanism of useEffect in a lower level component, I get the "maximum depth exceeded" error, and the page hangs.
Here's the component I'm calling useEffect in:
const UserRegistration = (props) => {
const appContext = useContext(AppContext)
const history = useHistory();
const registerUser = (event) => {
event.preventDefault()
appContext.setLoaded(false)
let payload = {
first_name: event.target.first_name.value,
last_name: event.target.last_name.value,
email_address: event.target.email_address.value,
password: event.target.password.value }
Users.create(payload)
.then(response => {
appContext.setStatusMessage("success", "User successfully registered.")
appContext.setLoaded(true)
history.push('/')
})
.catch(error => {
if (error.response.data.messages) { appContext.setErrorMessages(error.response.data.messages) }
appContext.setLoaded(true)
})
}
useEffect(() => {
return function cleanup() {
appContext.clearAllMessages()
// this just seems to hang, the app goes into an infinite loop at this point and freezes.
}
})
return (
<form onSubmit={registerUser}>
<div className="form-group">
<label htmlFor="first_name">First Name:</label>
<input name="first_name" type="text" className="form-control" />
</div>
<div className="form-group">
<label htmlFor="last_name">Last Name:</label>
<input name="last_name" type="text" className="form-control" />
</div>
<div className="form-group">
<label htmlFor="email_address">Email Address:</label>
<input name="email_address" type="text" className="form-control" />
</div>
<div className="form-group">
<label htmlFor="password">Password:</label>
<input name="password" type="password" className="form-control" />
</div>
<div className="form-group">
<label htmlFor="password">Confirm Password:</label>
<input name="password" type="password" className="form-control" />
</div>
<div className="form-group">
<input className="btn btn-primary form-control" type="submit" value="Register" />
</div>
</form>
)
}
Use events! Send a cleanAllErrors when the user closes the event.
In the same way listen for errors in the ErrorHandlingComponent and display errors.
If you want to make errors disapear after a timeout that is fine. For certain errors that is ok. Same here send a message. And if you want user interaction on some important error, send the message in the onClose of the error label!
Cheers
The reason why you're getting maximum depth exceeded is because your useEffect hook inside the UserRegistration component runs every time new props are received or you get new data through hooks.
This happens when your cleanup function gets triggered because it causes an update to your context and therefore causing an infinite re-render.
You should pass an empty dependency array so that the useEffect hook will behave similarly to a componentWillUnmount lifecycle method in a class component:
const UserRegistration = (props) => {
// ...
useEffect(() => {
return function cleanup() {
appContext.clearAllMessages();
}
// b/c of the empty dependency array
// this hook will only run once when
// the component renders for the first time, and
// it will run the cleanup function once,
// when it unmounts
}, []);
}

Resources