Using state and props between React components - reactjs

This is my project for business card app.
I have a problem with using state and props between components.
Component tree looks like this.
Editor <- CardEditForm <- ImageFileInput
The url state is in the Editor component. There is a function to update state and give it as props to child components. When I upload an image on ImageFileInput component, url data goes up until the editor component and then using setUrl to url be updated. And then I gave url to CardEditorForm component.
The problem is this, In cardEditorForm, when it comes to using url props, I can't get the updated url. Only gets the initial state. I really need to get an updated url. I also tried to use setTimeout() to get the updated url. But it doesn't work either. What can I do?..
It's my first time to ask a question on stack overflow. Thank you for helping the newb.
Here is the code.
editor.jsx
const Editor = ({ cards, deleteCard, createOrUpdateCard }) => {
const [url, setUrl] = useState('');
const updateUrl = (src) => {
setUrl(src);
};
return (
<section className={styles.editor}>
<h1 className={styles.title}>Card Maker</h1>
{Object.keys(cards).map((key) => (
<CardEditForm
key={key}
card={cards[key]}
onDelete={deleteCard}
onUpdate={createOrUpdateCard}
updateUrl={updateUrl}
url={url}
/>
))}
<CardAddForm onAdd={createOrUpdateCard} updateUrl={updateUrl} url={url} />
</section>
);
};
card_edit_form.jsx
const CardEditForm = ({ card, onDelete, onUpdate, updateUrl, url }) => {
// ...
const changeUrl = () => {
setTimeout(() => {
const newCard = {
...card,
fileURL: url,
};
onUpdate(newCard);
}, 4000);
};
return (
<form className={styles.form}>
// ...
<div className={styles.fileInput}>
<ImageFileInput updateCard={changeUrl} updateUrl={updateUrl} />
</div>
// ...
</form>
);
};
export default CardEditForm;
image_file_input.jsx
const ImageFileInput = ({ updateUrl, updateCard }) => {
const [image, setImage] = useState('');
const upload = new Upload();
const onUpload = (e) => {
e.preventDefault();
upload.uploadImage(image).then((data) => updateUrl(data));
updateCard(e);
};
return (
<div>
<input type="file" onChange={(e) => setImage(e.target.files[0])} />
<button name="fileURL" onClick={onUpload}>
image
</button>
</div>
);
};

Related

how to show a new todo-item without refreshing the page?

I tried a lots of things , and this problem does not seem to go away , can someone help me with this ??
this is my app component :
function App() {
const [todo, setTodo] = useState([]);
async function getTodo() {
try {
const todo = await axios.get("http://localhost:5000/api/todos");
// console.log(todo.data)
setTodo(todo.data);
} catch (error) {
console.log("something is wrong");
}
}
useEffect(() => {
// Update the document title using the browser API
getTodo();
}, []);
return (
<div className="App">
<h1>My Todo List</h1>
<h2>My Todo List</h2>
<Task Todor={todo} />
<Write />
</div>
);
}
export default App;
and this is my todos component :
function Todos({ Todor }) {
return (
<div className="Todos">
{Todor.map(T => <Todo post={T} />)}
</div>
);
}
export default Todos;
and this is my todo component :
function Todo({ post }) {
return (
<div className="Todo">
<h2>{post.title}</h2>
</div>
);
}
export default Todo ;
and this my add component :
export default function Write() {
const [inputText, setInputText] = useState({
title: ""
});
function handleChange(e) {
setInputText({
...inputText,
[e.target.name]: e.target.value,
});
}
const [status, setStatus] = useState(false);
async function addItem(e) {
e.preventDefault();
const res = await axios.post("http://localhost:5000/api/todos", inputText);
setInputText(inputText)
console.log("response:", res)
setStatus(true);
setInputText("");
}
return (
<div className="container">
<div className="form">
<input onChange={handleChange} type="text" name="title" />
<button onClick={addItem}>
<span>Add</span>
</button>
</div>
</div>
);
}
the new items dont show until I refresh the page , how to do that without refreshing ?
because obviously that defeats the purpose of React !!
useEffect(() => {
// Update the document title using the browser API
getTodo();
}, []);
The code inside useEffect with empty dependencies array [] only runs on the first render, to run it on every render you should remove the empty array dependencies.
useEffect(() => {
// Update the document title using the browser API
getTodo();
});
Note: It is not a best practice because your component will invoke getTodo() every time rerendered. In your case, you can use a state variable to control where to re-run the getTodo funtion e.g:
const [isAddedSuccess, setIsAddedSuccess] = useState(false)
Everytime you add new item successfully, just setIsAddedSuccess(true) and your useEffect should look like below:
useEffect(() => {
// Update the document title using the browser API
if (isAddedSuccess) getTodo();
}, [isAddedSuccess]);

How to set initial state in React to display nothing before fetching data on demand?

I'd like to display data fetched from API. The API url is based on user input. Once a user inputs and clicks submit, it is supposed to display the fetched data. But before user click submit, it should display nothing.
The problem I have is, when rendering the a user's data in lists in html, the map function doesn't exist on property{}, before user input anything. What should i set as the initial state?
The transformedData works fine. console.log(transformedData) prints
I'd like to display every title as a list. Example user input can be 0x147412d494731cbb91dbb5d7019464a536de04dc
import { useState } from "react";
// example user: 0x147412d494731cbb91dbb5d7019464a536de04dc
function App() {
const [data, setData] = useState({});
const [enteredWallet, setEnteredWallet] = useState("");
const [owner, setOwner] = useState("");
const walletChangeHandler = (event) => {
setEnteredWallet(event.target.value);
};
const submittedHandler = (event) => {
event.preventDefault();
setOwner(enteredWallet);
fetchNFTHandler();
};
function fetchNFTHandler() {
fetch(
`https://api.opensea.io/api/v1/assets?owner=${owner}&order_direction=desc&offset=0&limit=10`
)
.then((res) => {
return res.json();
})
.then((data) => {
const transformedData = data.assets.map((element, index) => {
return {
title: element.name,
id: index,
};
});
setData(transformedData);
console.log(transformedData);
});
}
return (
<div className="App">
<header className="App-header">
<h3>Show me assets in this wallet</h3>
<form onSubmit={submittedHandler}>
<input
placeholder="wallet address"
value={enteredWallet}
onChange={walletChangeHandler}
/>
<button>Submit</button>
</form>
<div>
console.log(data)
{/* {data.map((element) => (<li key={element.id}>
{element.title}</li>))}
*/}.
{/* here is the problem. before user input, this map function doesn't work */}
</div>
</header>
</div>
);
}
export default App;

node.current is not a function

I am trying to create a function close modal when click outside but I am keep getting this error:
TypeError: node.current is not a function
Here is my following code in MemberCard.js:
const [modalStatus, setModalStatus] = useState(false);
const node = useRef(null);
const openModal = () => {
setModalStatus(!modalStatus);
};
const handleClick = (e) => {
if (node.current(e.target)) {
return;
}
// outside click
setModalStatus(false);
};
useEffect(() => {
document.addEventListener("mousedown", handleClick);
return () => {
document.removeEventListener("mousedown", handleClick);
};
}, []);
return (
<div className="member-card">
<div className="member-edit" onClick={openModal}>
<Symlink />
</div>
{modalStatus && (
<TeamStatusModal
active={modalStatus}
ref={node}
tab={tab}
member={member}
/>
)}
...
}
Here is my modal that I open after click:
const TeamStatusModal = (props) => {
const { active, tab, member, ref } = props;
console.log(ref);
return (
<div
className={`team-status-modal-container ${active ? "ACTIVE_CLASS" : ""}`}
>
<button className="status">
<ProfileIcon /> <span>View Profile</span>
</button>
<hr />
<button className="status">
<MessageIcon /> <span>Message Me</span>
</button>
</div>
);
};
How can I implement this feature?
In react, there are some good libraries that can help you with modals, one of them is called react-modal, you can give it a check.
If you want to implement a modal by yourself, we can follow some steps.
First we need to define a context, because the modal state needs to be accesed by more than one component or page in your app.
In the context, you could store the modal in a isModalOpen state, and add functions to manipulate it, such as openModal and closeModal. It really depends on the amount of features you want to add to this implementation.
Finally, you make the context globally accessible wrapping your app around a provider.
an example implementation
const ModalContext = createContext({})
export const ModalContextProvider = ({children}) => {
const [isModalOpen, setIsModalOpen] = useState(false)
const toggleModalState = () => {
setIsModalOpen(state => !state)
}
return <ModalContext.Provider value={{isModalOpen, toggleModalState}}>{children}<ModalContext.Provider>
}
export const useModal = () => {
return useContext(ModalContext)
}
Now the modal will be available globally

ReactJS - How to re-render functional component after POST data to database

I am building a blogging application. This application has frontend built with ReactJS and backend built with Python (Django Framework). The frontend and backend are connected with Django REST API Framework.
This post is on ReactJS Frontend.
I am using Functional Component. There is a "BlogList" Component where all blogs fetched from API endpoints will be displayed. I created a "BlogForm" Component where I have to enter 2 fields: "Title" and "Content" to create blog post. I imported the "BlogForm" Component inside "BlogList" Component to show it on the same page.
Please see this image to get a good understanding
When I enter "Title" and "Content" inside "BlogForm" Component and click on "Submit" button, data is saved in database using API call. But the added data is not showing in the "BlogList" Component at that time. I have to refresh the page to see the new blog post in the blog list.
Can anyone in the community help me to solve the problem that how can I instantly view the added blog post when I click on "Submit" button?
For your kind reference, the code is as below.
BlogList.js
import BlogForm from './BlogForm'
const BlogList = (url) => {
const [blogs, setBlogs] = useState([])
useEffect(() => {
async function fetchBlogData() {
const url = requests.blogList
const request = await axios.get(url)
setBlogs(request.data)
return request
}
fetchBlogData()
}, [url])
return (
<Wrapper>
<BlogWrapper className="blog">
// Blog Form Component Added
<BlogForm />
<div className="blog__header">
<h1 className="blog__header--title">
Information
</h1>
</div>
<hr className="blog__divider"/>
<div className="blog__list">
<ul>
<li className="blog__item">
{ blogs.map( (blog) => (
<div className="blog__item--container" key={ blog.id }>
<h1 className="blog__item--title">
<a className="blog__item--title blog__item--title--link" href={`/blog/${ blog.id }`}>
{ blog.title }
</a>
</h1>
<small className="blog__item--date">{ blog.date_published }</small>
<p className="blog__item--content">{ blog.content }</p>
</div>
) ) }
</li>
</ul>
</div>
</BlogWrapper>
</Wrapper>
)
}
export default BlogList
BlogForm.js
const BlogForm = () => {
const [inputValues, setInputValues] = useState({
title : '',
content : ''
})
const handleSubmit = async (event) => {
event.preventDefault()
setInputValues({ ...inputValues })
const { title, content } = inputValues
const blogPost = { title, content }
const url = requests.blogCreate
const response = await axios.post(url, blogPost)
return response
}
const handleChange = (name) => (event) => {
setInputValues({ ...inputValues, [name] : event.target.value })
}
return (
<div>
<form onSubmit={ handleSubmit }>
<input type="text" name="title" placeholder="Title" required value={ inputValues.title } onChange={ handleChange('title') }/>
<input type="text" name="content" placeholder="Content" required value={ inputValues.content } onChange={ handleChange('content') }/>
<button type="submit">Submit</button>
</form>
</div>
)
}
export default BlogForm
UPDATED
After the answer from DrewReese, I updated my code and I am getting error when I try to add a new Blog Post. It is showing undefined
BlogList has the state you want to be updated from the child component BlogForm. You can pass a callback from parent to child for BlogForm to call with the updated posts.
BlogList
const BlogList = (props) => {
const [blogs, setBlogs] = useState([]);
useEffect(() => {
async function fetchBlogData() {
const url = requests.blogList;
const request = await axios.get(url);
setBlogs(request.data);
}
fetchBlogData();
}, [props]);
// (1) Define a callback to update state
const addNewPost = post => setBlogs(posts => [...posts, post]);
return (
<Wrapper>
<BlogWrapper className="blog">
// Blog Form Component Added
<BlogForm addNewPost={addNewPost} /> // <-- (2) pass callback
...
</BlogWrapper>
</Wrapper>
);
}
BlogForm
const BlogForm = ({ addNewPost }) => { // <-- (3) destructure callback
const [inputValues, setInputValues] = useState({
title : '',
content : ''
})
const handleSubmit = async (event) => {
event.preventDefault()
setInputValues({ ...inputValues });
const { title, content } = inputValues;
const blogPost = { title, content };
const url = requests.blogCreate;
const response = await axios.post(url, blogPost);
addNewPost(response.data); // <-- (4) call callback with new post data
}
...
return (
...
);
}

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