Rerender sibling component in React - reactjs

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

Related

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

Simple weather app breaking at weather.main.temp variable

I'm building a really basic weather app with React hooks, codesandbox.io and OpenWeatherAPI.
It's breaking at const temp = forecast.main.temp; and returning "TypeError: Cannot read properties of undefined (reading 'temp')" It actually does run when I initially write it but breaks if I add another variable or have to edit the temp variable in any way.
import React, { useState } from "react";
import "../styles.css";
import CityCard from "./cityCard";
export default function GetWeather() {
const [city, setCity] = useState('');
const [forecast, setForecast] = useState([]);
const getCity = async (e) => {
e.preventDefault();
//API variables
const url = `https://api.openweathermap.org/data/2.5/weather?q=${city}&units=imperial&appid=${APIkey}`;
//fetch response
try {
const res = await fetch(url);
const data = await res.json();
setForecast(data);
} catch (err) {
console.error(err);
}
};
return (
//basic input form to search by city
<div>
<h1 className="title">Weather App</h1>
<CityCard forecast={forecast} />
<form className="form" onSubmit={getCity}>
<label className="label" htmlFor="city">
<p>Search by City</p>
</label>
<input
className="input"
type="text"
name="query place"
placeholder="i.e. Seattle"
value={city} onChange={(e) => setCity(e.target.value)}
>
</input>
<button
className="button"
type="submit"
>Search</button>
</form>
</div>
);
}
CityCard
import React from "react";
import "../../src/styles.css";
export default function CityCard({ forecast }) {
//pass props to cards
const cityName = forecast.name;
const temp = forecast.main.temp;
return (
<div className="card-container">
<p className="card-title">{cityName}</p>
<p>Temp: {temp} </p>
</div>
);
}
Issue
The initial forecast state is declared as an array:
const [forecast, setForecast] = useState([]);
and passed to CityCard on forecast prop:
<CityCard forecast={forecast} />
then accessed as if it were an object:
function CityCard({ forecast }) {
//pass props to cards
const cityName = forecast.name; // OK, undefined
const temp = forecast.main.temp; // Not OK, can't access temp of undefined
return (
<div className="card-container">
<p className="card-title">{cityName}</p>
<p>Temp: {temp}</p>
</div>
);
}
Solution
Use Optional Chaining operator to protect against null/undefined property accesses.
function CityCard({ forecast }) {
//pass props to cards
const cityName = forecast?.name;
const temp = forecast?.main?.temp;
return (
<div className="card-container">
<p className="card-title">{cityName}</p>
<p>Temp: {temp}</p>
</div>
);
}
Or wait to render CityCard when there's a valid forecast data to display.
const [forecast, setForecast] = useState();
...
{forecast?.name && forecast?.main?.temp && <CityCard forecast={forecast} />}
OpenWeather JSON API response

Why it is rendering first and then updating the state later in react?

I want to fetch the data when the button is clicked but the Newsitem component is running first and then updating the value of data_grabber. That means it is displaying the defalut values rather than the data that I fetched from the newsapi. After displaying the newsitem component with default values, data_grabber is updating the fetched data.
What can be the solution?
App.js
function App() {
const [input_data, setInput_data] = useState("");
const [btn_data, setBtn_data] = useState("");
const [data_grabber, setData_grabber] = useState([]);
return (
<>
<Navbar
input_data={input_data}
setInput_data={setInput_data}
setBtn_data={setBtn_data}
btn_data={btn_data}
data_grabber={data_grabber}
setData_grabber={setData_grabber}
/>
{data_grabber? data_grabber.map((news_data)=>{
return(
<NewsItem news_data={news_data}/>
)
}):<div>No data available</div>}
</>
);
}
export default App;
Navbar.js
import { useEffect } from "react";
export default function Navbar(props) {
const onClicker = async (e) => {
e.preventDefault();
props.setBtn_data(props.input_data);
};
useEffect(() => {
const fetcher = async () => {
const link = `https://newsapi.org/v2/everything?q=${props.btn_data}&apiKey=API_KEY`;
const raw_data = await fetch(link);
const data = await raw_data.json();
console.log(data);
props.setData_grabber(data.articles)
};
fetcher();
}, [props.btn_data]);
return (
<div>
<form className="d-flex">
<input
onChange={(e) => props.setInput_data(e.target.value)}
value={props.input_data}
className="form-control me-2"
type="search"
placeholder="Search"
aria-label="Search"
/>
<button
className="btn btn-outline-success"
type="submit"
onClick={onClicker}
>
Search
</button>
</form>
</div>
NewsItem.js
import React, { Component } from "react";
export default class NewsItem extends Component {
render() {
const {title, description, url, urlToImage} = this.props.data
const defaultImage = `https://blogger.googleusercontent.com/img/a/AVvXsEh20SgNNsDlKyWWmB7XgB5SfFY10M6CqJAq93HwGtssTn2cWz6w9zHPjXf91WwoWr27QeaC4HsGv2NxPOXUdvk6xodUojnw8rUuAkEMY3Qb4ucoVpN3nSyF8JW_xVDWa2aSMEWH387hPsfouSJyClLNburIcDbXIeJamuTHwiSvw4hdNnqeeICcvg1wrQ=w1200-h630-p-k-no-nu`
return (
<div>
<div className="card">
<img src={urlToImage?urlToImage:defaultImage} className="card-img-top" alt="..." />
<div className="card-body">
<h5 className="card-title">{title?title:'No title available'}</h5>
<p className="card-text">
{description?description.slice(0, 50):"no description available"}...
</p>
<a href={url} target="_blank" rel="noreferrer"className="btn btn-primary">
read more
</a>
</div>
</div>
</div>
);
}
}
One fix could be to
make a variable of the updated state:
in the UseEffect ,
add :
const updated = data.articles
props.setData_grabber(updated)
Check whether data_grabber array is empty or not and then do the rendering inside App component as follows.
{
data_grabber.length > 0 ? (
data_grabber.map((news_data) => {
return <NewsItem news_data={news_data} />;
})
) : (
<div>No data available</div>
);
}
{ data_grabber !== undefined && data_grabber.length > 0 ? data_grabber.map((news_data)=>{
return(
<NewsItem news_data={news_data}/>
)
}):<div>No data available</div>}
Check data_grabber is undefined or empty.
Then, fix NewsItem props.data like this.
export default class NewsItem extends Component {
render() {
const {title, description, url, urlToImage} = this.props.news_data
also fix here in useEffect
useEffect(() => {
const fetcher = async () => {
const link = `https://newsapi.org/v2/everything?q=${props.btn_data}&apiKey=c990aa0235da4635997afd1f7459860c`;
const raw_data = await fetch(link);
const data = await raw_data.json();
console.log(data);
if(data.articles){
props.setData_grabber(data.articles)
}
};
fetcher();

Creating a history page with React Hooks

I am trying to create a history page with react hooks that keeps track of the users most recent searches they don't have to be persistent through refreshes only from this session.
my search component looks like this this is a simple app that does not need a UI just a simple navigation on the search page it will show the results and on the history page I would like to be able to show the previous searches from this session
I am trying to keep track of the debouncedTerm so I can display it in a new component
import React, { useState, useEffect } from 'react';
import axios from 'axios';
const Search = () => {
const history = [];
const [term, setTerm] = useState('');
const [debouncedTerm, setDebouncedTerm] = useState(term);
const [results, setResults] = useState([]);
useEffect(() => {
const timerId = setTimeout(() => {
setDebouncedTerm(term);
}, 1000);
return () => {
clearTimeout(timerId);
};
}, [term]);
useEffect(() => {
const search = async () => {
const { data } = await axios.get('http://hn.algolia.com/api/v1/search?', {
params: {
query: debouncedTerm,
},
});
setResults(data.hits);
};
if (debouncedTerm) {
search();
}
}, [debouncedTerm]);
const renderedResults = results.map((result) => {
return (
<div key={result.objectID} className="item">
<div className="right floated content">
<a className="ui button" href={result.url}>
Go
</a>
</div>
<div className="content">
<div className="header">{result.title}</div>
</div>
</div>
);
});
return (
<div>
<div className="ui form">
<div className="field">
<label>Hacker News Search:</label>
<input
value={term}
onChange={(e) => setTerm(e.target.value)}
className="input"
/>
</div>
</div>
<div className="ui celled list">{renderedResults}</div>
</div>
);
};
export default Search;
Your code looks like it's going in the right direction but you have a constant const history = []; and you must keep in mind that this will not work, because you will have a new constant re-declared in every render. You must use React setState like so:
const [history, setHistory] = useState([]);
You can read more about it in the React documentation.
edit:
In order to add new elements to the existing history you have to append it like this:
setHistory(oldHistory => [...oldHistory, newHistoryElement]);

Resources