Passing data between two components in React.js - reactjs

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.

Related

How Do I give dynamic colors to the each list here

import React, { useState, useEffect } from "react";
import "./style.css";
const getLocalItem = () => {
let list = localStorage.getItem("lists");
console.log(list);
if (list) {
return JSON.parse(list);
} else {
return [];
}
};
function App() {
const [text, setText] = useState("");
const [task, setTask] = useState(getLocalItem());
const changeText = (e) => {
setText(e.target.value);
};
const submitHandler = (e) => {
console.log("submited");
e.preventDefault();
setTask([...task, text]);
setText("");
};
const removeTask = (a) => {
const finalData = task.filter((curEle, index) => {
return index !== a;
});
setTask(finalData);
};
useEffect(() => {
localStorage.setItem("lists", JSON.stringify(task));
}, [task]);
return (
<>
<form onSubmit={submitHandler} className='form'>
<div className="action" >
<div >
<input
className="input"
type="text"
value={text}
onChange={changeText}
placeholder='add task...'
/>
</div>
<button type="submit" className="button" >
Add todo
</button>
</div>
<div className="listsData">
{task.map((value, index) => {
return (
<>
<div key={index}>
{value}
</div>
</>
);
})}
</div>
</form>
</>
);
}
export default App;
On adding each item I want a different color for each list. Currently, I am fetching list data from localstorage while fetching also it should remain same. which is working but the dynamic colors is what I need for each list. Any ideas or dynamic logics??
Let me know if u need more details regarding my code if u doont understand something

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

How to render form after submission in react?

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;

How to pass const useState("") between functions

I would like to pull the const ChatLog out of the main function Chats and insert it as a component or outside of the Chats function for now. The Problem is that the ChatLog needs the useState variables [msg, sendMsg] (..) that are called in the Chats function. How could I do this anyway? Am new to react.
function Chats() {
const [msg, sendMsg] = useState("");
const [msgs1, sendMsgAll] = useState([]);
useEffect(() => {
onValue(ref(database), (snapshot) => {
sendMsgAll([]);
const data = snapshot.val();
if (data !== null) {
Object.values(data).map((msg) => {
sendMsgAll((oldArray) => [...oldArray, msg]);
});
}
});
}, [])
const ChatLog = () => {
return (
<div>
{msgs1.map((msg) => (
<div className="chat-log">
<p align = {checkSide(msg.usr)}>
<h2>{msg.msg}</h2>
<h4>User: {msg.usr}</h4>
<h4>Time: {convertUnix(msg.time)}</h4>
<button>update</button>
<button>delete</button>
</p>
</div>
))}
</div>
)
}
return (
<div className="ChatView">
<p><ChatLog /></p>
<p>{ChatInput()}</p>
</div>
)
};
You can add props to ChatLog component. Check this...
const ChatLog = (props) => {
const {msg1} = props
return (
<div>
{msgs1.map((msg) => (
<div className="chat-log">
<p align = {checkSide(msg.usr)}>
<h2>{msg.msg}</h2>
<h4>User: {msg.usr}</h4>
<h4>Time: {convertUnix(msg.time)}</h4>
<button>update</button>
<button>delete</button>
</p>
</div>
))}
</div>
)
}
However,you need add props to component when you use it. Something like this...
return (
<div className="ChatView">
<p><ChatLog msg1={msg1} /></p>
<p>{ChatInput()}</p>
</div>
)
One more thing, when you declaring a component, it has to be declared of another component. Like this
//this is ChatLog component
const ChatLog = (props)=>{
return <div/>
}
//this is Chats component
const Chats = ()=>{
return (
<div>
<ChatLog {...props}/>
</div>
)
}

How do I edit form data in a React function component?

I'm trying to set a form field value with useState.
The settings.values.apiKey variable has a value, but the textarea element is empty. What's wrong with my useState?
I tried to change value={apiKey} to value={settings.values.apiKey} and then the value is displayed, but then I can't change the value of the field. When I try to enter something, it always shows the original value.
App.js
const App = () => {
const [apiKey, setApiKey] = useState(settings.values.apiKey)
useEffect(() => {
const getSettings = async () => {
const settingsFromServer = await fetchSettings()
setSettings(settingsFromServer)
}
getSettings()
}, [])
const fetchSettings = async () => {
const res = await fetch('http://127.0.0.1/react-server/get.php')
return await res.json()
}
const saveSettings = async (settings) => {
}
return (
<div className="container">
<Header />
<Settings
settings={settings}
saveSettings={saveSettings}
/>
<Footer />
</div>
);
}
export default App;
Settings.js:
import { useState } from 'react';
const Settings = ({ settings, saveSettings }) => {
const [apiKey, setApiKey] = useState(settings.values.apiKey)
const onSubmit = (e) => {
e.preventDefault()
saveSettings({ apiKey})
}
return (
<div>
<form className='add-form' onSubmit={onSubmit}>
<div className='form-control'>
<label>Api key</label>
<textarea
value={apiKey}
onChange={(e) => setApiKey(e.target.value)}
/>
</div>
<input type='submit' value='Save settings' className='mt15' />
</form>
</div>
)
}
export default Settings
It looks like by mistake you have used apiKey in App.js file as your state variable. It should be replaced by settings.
const [settings, setSettings] = React.useState();
The above code would make value={apiKey} work properly for textarea in Settings.js file.
And, then onChange will also start working properly.
UPDATE
In addition to the above mentioned error, in case settings props is undefined in Settings.js, this might cause your code to break at useState. So, instead put a check for settings values in useEffect and then set the value. The code would look like this or you can check the codesandbox link here for working demo.
Settings.js
import { useEffect, useState } from "react";
const Settings = ({ settings, saveSettings }) => {
const [apiKey, setApiKey] = useState();
useEffect(() => {
if (settings?.values?.apiKey) {
setApiKey(settings.values.apiKey);
}
}, [settings]);
const onSubmit = (e) => {
e.preventDefault();
saveSettings({ apiKey });
};
return (
<div>
<form className="add-form" onSubmit={onSubmit}>
<div className="form-control">
<label>Api key</label>
<textarea
value={apiKey}
onChange={(e) => setApiKey(e.target.value)}
/>
</div>
<input type="submit" value="Save settings" className="mt15" />
</form>
</div>
);
};
export default Settings;
App.js
const [settings, setSettings] = useState()
const saveSettings = async (settings) => {
setSettings(settings);
}

Resources