How to render form after submission in react? - reactjs

Given the following form, I need whenever the form is submitted, the new post to be listed/rendered without having to refresh the page.
const PostCreate = () => {
const [title, setTitle] = useState('');
const onSubmit = async (event) => {
event.preventDefault();
await axios.post(`http://${posts_host}/posts/create`, {title}).catch(error => {
console.log(error)
})
setTitle('');
};
return (<div>
<form onSubmit={onSubmit}>
<div className="form-group">
<label>Title</label>
<input value={title} onChange={event => setTitle(event.target.value)}
className="form-control "/>
</div>
<button className="btn btn-primary">Submit</button>
</form>
</div>)
}
export default PostCreate;
I tried adding this.forceUpdate() and this.setState(this.state), neither works, and I still have to refresh the page for the new post to show.
Here's how the posts are rendered:
const PostList = () => {
const [posts, setPosts] = useState({});
const fetchPosts = async () => {
await axios.get(`http://${queries_host}/posts`).then(response => {
setPosts(response.data);
}).catch(error => {
console.log(error)
});
};
useEffect(() => {
fetchPosts();
}, []);
const renderedPosts = Object.values(posts).map(post => {
return <div className="card"
style={{width: '30%', marginBottom: '20px'}}
key={post.id}>
<div className="card-body">
<h3>{post.title}</h3>
<CommentList comments={post.comments}></CommentList>
<CommentCreate postId={post.id}></CommentCreate>
</div>
</div>
});
return <div>
{renderedPosts}
</div>;
}
export default PostList;
This is what App.js looks like
const App = () => {
return <div>
<h1>Create Post</h1>
<PostCreate></PostCreate>
<hr/>
<h1>Posts</h1>
<PostList></PostList>
</div>;
};
export default App;
and is eventually rendered using:
ReactDOM.render(
<App></App>,
document.getElementById('root')
)

In your PostList, useEffect called once when you first load your component, so when you create new post, it will not be re-rendered
You should bring your fetchPost logic to your App component, and add function props onPostCreated to PostCreate component, trigger it after you finish creating your new post
The code should be:
const App = () => {
const [posts, setPosts] = useState({});
const fetchPosts = async () => {
await axios.get(`http://${queries_host}/posts`).then(response => {
setPosts(response.data);
}).catch(error => {
console.log(error)
});
};
useEffect(() => {
fetchPosts();
}, []);
return <div>
<h1>Create Post</h1>
<PostCreate onCreatePost={() => fetchPost()}></PostCreate>
<hr/>
<h1>Posts</h1>
<PostList posts={posts}></PostList>
</div>;
};
export default App;
const PostList = ({ posts }) => {
const renderedPosts = Object.values(posts).map(post => {
return <div className="card"
style={{width: '30%', marginBottom: '20px'}}
key={post.id}>
<div className="card-body">
<h3>{post.title}</h3>
<CommentList comments={post.comments}></CommentList>
<CommentCreate postId={post.id}></CommentCreate>
</div>
</div>
});
return <div>
{renderedPosts}
</div>;
}
export default PostList;
const PostCreate = ({ onCreatePost }) => {
const [title, setTitle] = useState('');
const onSubmit = async (event) => {
event.preventDefault();
await axios.post(`http://${posts_host}/posts/create`, {title}).catch(error => {
console.log(error)
})
onCreatePost && onCreatePost();
setTitle('');
};
return (<div>
<form onSubmit={onSubmit}>
<div className="form-group">
<label>Title</label>
<input value={title} onChange={event => setTitle(event.target.value)}
className="form-control "/>
</div>
<button className="btn btn-primary">Submit</button>
</form>
</div>)
}
export default PostCreate;

I think the problem you are having is not in the code you have displayed. The component is indeed rerendering after you change its state and also when you forceUpdate() it. I assume the posts you are trying to display are taken from the same API that you post to. Even if this component is being rerendered, your GET request which gives the data to the component who renders it is not called again so the data doesn't update. You need to refetch it. This can be done by many different ways (useEffect(), callbacks, reactQuery refetch) depending on the rest of your code. I would need the component that renders the data and the API call to help you further.
Another thing that you didn't ask but is good practice. In your PostCreate component you don't need to manage the state of fields that are in the form, because it already does it for you. Just give a name to your inputs and use the form data. I've given an example below.
import { useState } from "react";
const PostCreate = () => {
const onSubmit = async (event) => {
event.preventDefault();
console.log(event.target.elements.title.value);
};
return (
<div>
<form onSubmit={onSubmit}>
<div className="form-group">
<label>Title</label>
<input name="title" className="form-control" />
</div>
<button className="btn btn-primary">Submit</button>
</form>
</div>
);
};
export default PostCreate;

Related

How can I send the data to the parent component by click on the button in React?

My question is how can I send the input value to the parent component by clicking on the button? Because now if I type something in the input it shanges the value instantly, I want it to do after I click on the button.
Currently I am using that method:
const FormInput = ({setIpAddress}) => {
return (
<div className="formInput">
<form className="form_container" onSubmit={e => {e.preventDefault();}}>
<input type="text" id="input" onChange={(e) => setIpAddress(e.target.value)} required={true} placeholder="Search for any IP address or domain"/>
<button type="submit" className="input_btn">
<img src={arrow} alt="arrow"/>
</button>
</form>
</div>
);
};
export default FormInput
You can pass an onClick callback function to the child component. When this function is called it will trigger a rerender in the child.
Example:
Parent:
const handleClick = (value) => {
//set the state here
}
<ChildComponent onClick={handleClick} />
Child:
<button type="submit" className="input_btn" onClick={(value) => props.onClick?.(value)}>
In your case you need to get rid of the onChange in your input tag:
parents:
function App() {
const [ipAddress, setIpAddress] = useState("");
const url = `${BASE_URL}apiKey=${process.env.REACT_APP_API_KEY}&ipAddress=${ipAddress}`;
useEffect(() => {
try {
const getData = async () => {
axios.get(url).then((respone) => {
setIpAddress(respone.data.ip);
});
};
getData();
} catch (error) {
console.trace(error);
}
}, [url]);
const handleClick = (event) => {
setIpAddress(event.target.value)
}
return (
<div className="App">
<SearchSection onClick={handleClick} />
</div>
);
}
const SearchSection = ({onClick}) => {
return (
<div className="search_container">
<h1 className="search_heading">IP Address Tracker</h1>
<FormInput onClick={onClick}/>
</div>
);
};
Child
const FormInput = ({onClick}) => {
return (
<div className="formInput">
<form className="form_container" onSubmit={e => {e.preventDefault();}}>
<input type="text" id="input" required={true} placeholder="Search for any IP address or domain"/>
<button type="submit" className="input_btn" onClick={(e) => onClick(e}>
<img src={arrow} alt="arrow"/>
</button>
</form>
</div>
);
};
Thank you for your answer, but I don't really get it, bcs my parent component has no paramter, sorry I am new in react.
This is my parent component where I am fetching the data and I want to update the ipAdress when I click on the button which is in the FormInput component. So the SearchSection is the parent of the FormInput.
function App() {
const [ipAddress, setIpAddress] = useState("");
const url = `${BASE_URL}apiKey=${process.env.REACT_APP_API_KEY}&ipAddress=${ipAddress}`;
useEffect(() => {
const getData = async () => {
axios.get(url).then((respone) => {
setIpAddress(respone.data.ip)
...
getData();
}, [url]);
return (
<div className="App">
<SearchSection setIpAddress={setIpAddress} />
</div>
);
}
I hope it's enough :)
const SearchSection = ({setIpAddress}) => {
return (
<div className="search_container">
<h1 className="search_heading">IP Address Tracker</h1>
<FormInput setIpAddress={setIpAddress}/>
</div>
);
};
function App() {
const [ipAddress, setIpAddress] = useState("");
const url = `${BASE_URL}apiKey=${process.env.REACT_APP_API_KEY}&ipAddress=${ipAddress}`;
useEffect(() => {
try {
const getData = async () => {
axios.get(url).then((respone) => {
setIpAddress(respone.data.ip);
});
};
getData();
} catch (error) {
console.trace(error);
}
}, [url]);
return (
<div className="App">
<SearchSection setIpAddress={setIpAddress} />
</div>
);
}

Passing data between two components in React.js

Currently learning React and building a side project where i can render rss-feeds in my browser window. It works in a single component.
Original working component
function App (){
const [rssUrl, setRssUrl] = useState('');
const [items, setItems] = useState([]);
const getRss = async (e) => {
e.preventDefault();
const urlRegex =
/(http|ftp|https):\/\/[\w-]+(\.[\w-]+)+([\w.,#?^=%&:\/~+#-]*[\w#?^=%&\/~+#-])?/;
if (!urlRegex.test(rssUrl)) {
return;
}
const res = await fetch(`https://api.allorigins.win/get?url=${rssUrl}`);
const { contents } = await res.json();
const feed = new window.DOMParser().parseFromString(contents, 'text/xml');
const items = feed.querySelectorAll('item');
const feedItems = [...items].map((el) => ({
link: el.querySelector('link').innerHTML,
title: el.querySelector('title').innerHTML,
author: el.querySelector('author').innerHTML,
}));
setItems(feedItems);
};
}
return (
<div className="App">
<form onSubmit={getRss}>
<div>
<h1>Next Pod For Chrome</h1>
<label> rss url</label>
<br />
<input onChange={(e) => setRssUrl(e.target.value)} value={rssUrl} />
</div>
<input type="submit" />
</form>
{items.map((item) => {
return (
<div>
<h1>{item.title}</h1>
<p>{item.author}</p>
<a href={item.link}>{item.link}</a>
</div>
);
})}
</div>
);
}
export default App;
At the moment I try to separate the functionality into two components. How can I pass a link from one component to another one where I want to trigger a function handled by the first component?
Any tips are much appreciated. Thanks.
Current state of component to search for rss-feed
function Search() {
const [rssUrl, setRssUrl] = useState('');
const formatRss = async (e) => {
e.preventDefault();
const urlRegex =
/(http|ftp|https):\/\/[\w-]+(\.[\w-]+)+([\w.,#?^=%&:\/~+#-]*[\w#?^=%&\/~+#-])?/;
if (!urlRegex.test(rssUrl)) {
return;
}
console.log(rssUrl);
};
return (
<div className="App">
<form onSubmit={formatRss}>
<div>
<h1>Next Pod For Chrome</h1>
<label>rss url</label>
<br />
<input onChange={(e) => setRssUrl(e.target.value)} value={rssUrl} />
</div>
<input type="Submit" />
</form>
</div>
);
}
export default Search;
Current stage of component to parse and render
function List(props) {
const [items, setItems] = useState([]);
const formatRss = async (e) => {
e.preventDefault();
console.log(rssUrl);
const res = await fetch(`https://api.allorigins.win/get?url=${rssUrl}`);
const { contents } = await res.json();
const feed = new window.DOMParser().parseFromString(contents, 'text/xml');
const items = feed.querySelectorAll('item');
const feedItems = [...items].map((el) => ({
link: el.querySelector('link').innerHTML,
title: el.querySelector('title').innerHTML,
author: el.querySelector('author').innerHTML,
}));
setItems(feedItems);
};
return (
<div className="App">
{items.map((item) => {
return (
<div>
<h1>{item.title}</h1>
<p>{item.author}</p>
<a href={item.link}>{item.link}</a>
</div>
);
})}
</div>
);
}
export default List;
You can declare the state on both's parent, for example: App.js
And use prop to pass the variable to the component
like this:
export default function App() {
const [rssUrl, setRssUrl] = useState("");
return (
<div className="App">
<Search rssUrl={rssUrl} setRssUrl={setRssUrl} />
<List rssUrl={rssUrl} />
</div>
);
}
Below is the live example for you:
https://codesandbox.io/s/cocky-tharp-7d5uu8?file=/src/App.js
There are many platforms where you can put the demo project which make it easier for people to answer your question.

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 (
...
);
};

How to pass data from one component to another component/parent in React

I am new to programming and started learning React a few weeks ago. I am trying to create a weather app. I created a file called Weather.js where I the fetch api data that will be displayed. One of the inputs for the api link is lat/log. I decided to create another file called Button.js, where a user will enter their lat/long and submit it. Once submitted, that lat/long will get placed on the api link (in Weather.js), to fetch that person's weather forecast.
I am able to console.log the button data in Button.js.
How do I pass that data to Weather.js?
I think I'm supposed to use props and/or a callback function, but I am at a loss on how to do it properly. Nothing has worked so far.
Thank you for your help.
function Weather() {
const [loading, setLoading] = React.useState(false)
const [maxTemp, setMaxTemp] = React.useState([])
React.useEffect(() => {
setLoading(true)
fetch("https://api.openweathermap.org/data/2.5/onecall?lat=34.1030&lon=-118.4105&units=imperial&exclude=current,minutely,hourly,alerts&appid={api}")
.then(res => res.json())
.then((data) => {
setLoading(false)
setMaxTemp(data.daily[0].temp.max)
})
}, [])
if(loading === true){
return <div>Loading...</div>
} else return(
<div>
High: {Math.round(maxTemp)} <br />
</div>
)
}
ReactDOM.render(<Weather />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
function Button(props) {
const [lat, setLat] = React.useState([])
const handleSubmit = (event) => {
console.log(lat)
event.preventDefault();
}
return(
<form onSubmit={handleSubmit}>
<input type="text" value={lat} onChange={e => setLat(e.target.value)} />
<input type="submit" value="Submit" />
</form>
)
}
ReactDOM.render(<Button />, document.getElementById("root"));
<div id="root"></div><script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
so first you have to import the Button into your Weather file, then you have to pass a (setState) function into that button from the parent (weather.js) and then call that inside the button with passing the data:
and you don't have to pass the Button.js into React.DOM, we do that only 1 time for the most parent component.
Weather.js:
import Button from './button' // pass your correct paths
function Weather() {
const [loading, setLoading] = React.useState(false)
const [maxTemp, setMaxTemp] = React.useState([])
const [coords, setCoords] = React.useState(null)
React.useEffect(() => {
if(!coords) return. // checking if no coords to skip calling API
setLoading(true)
fetch(`https://api.openweathermap.org/data/2.5/onecall?lat=${coords.lat}&lon=${coords.long}&units=imperial&exclude=current,minutely,hourly,alerts&appid=${api}`)
.then(res => res.json())
.then((data) => {
setLoading(false)
setMaxTemp(data.daily[0].temp.max)
})
}, [coords]) // watching for coords change from button passed data
if(loading === true){
return <div>Loading...</div>
} else return(
<div>
High: {Math.round(maxTemp)} <br />
<Button setCoords={setCoords} /> // here we pass the setState function into the button to get the data back once its called from inside
</div>
)
}
ReactDOM.render(<Weather />, document.getElementById("root"));
Button.js:
function Button(props) {
const [lat, setLat] = useState("");
const [long, setLong] = useState("");
const handleSubmit = (event) => {
console.log(lat)
event.preventDefault();
props.setCoords({ lat, long }); // here we call the function that is passed from parent and give it the data
}
return(
<form onSubmit={handleSubmit}>
<input
placeholder="Lat"
type="number"
value={lat}
onChange={(e) => setLat(e.target.value)}
/>
<input
placeholder="Long"
type="number"
value={long}
onChange={(e) => setLong(e.target.value)}
/>
<input type="submit" value="Submit" />
</form>
)
}
You don't need to use two components. Simply add more state to your weather component.
You also don't need to use a form. Just create two input fields and a button.
Extract your effect code into a separate function. This way you can fire up API call on page load and on button click.
function Weather() {
const [loading, setLoading] = React.useState(false);
const [maxTemp, setMaxTemp] = React.useState([]);
const [lat, setLat] = React.useState(34.103);
const [lo, setLo] = React.useState(-118.4105);
const apiCall = () => {
setLoading(true);
fetch(
`https://api.openweathermap.org/data/2.5/onecall?lat=${lat}&lon=${lo}&units=imperial&exclude=current,minutely,hourly,alerts&appid={api}`
)
.then((res) => res.json())
.then((data) => {
setLoading(false);
setMaxTemp(data.daily[0].temp.max);
});
};
React.useEffect(() => {
apiCall();
}, []);
return (
<React.Fragment>
<input
placeholder="Latitude"
type="range"
step="0.5"
max="90"
min="-90"
value={lat}
onChange={(e) => setLat(e.target.value)}
/>
<span>{lat}</span>
<br/>
<input
placeholder="Longitude"
type="range"
step="0.5"
max="180"
min="-180"
value={lo}
onChange={(e) => setLo(e.target.value)}
/>
<span>{lo}</span>
<br/>
<button onClick={apiCall}>Submit</button>
{loading ? (
<div>Loading...</div>
) : (
<div>
High: {Math.round(maxTemp)} <br />
</div>
)}
</React.Fragment>
);
}
ReactDOM.render(<Weather />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>

Resources