How to pass state data to another webpage through react router link? - reactjs

I am building an issue tracker app that is based on react/django. I am trying to pass projects, a state variable that is an array of project objects, to manageusers so that I can have access to the name of the project in manageusers. The reason behind this is so that I can use that project name to find the project associated with it, and assign users to the project. I am using a state object to pass projects to a link, but when I try to load the manageusers webpage it is stating that state is undefined. Keep in mind the projects data from the django backend is coming in fine, I am just trying to get that data onto manageusers.
Project
project.js
import { useState, useEffect } from "react";
import { Grid, TextField, Button, Typography } from "#material-ui/core";
import {
BrowserRouter as Router,
Link,
Route,
Switch,
} from 'react-router-dom';
const project = () => {
const [name, setName] = useState();
const [description, setDescription] = useState();
const [projects, setProjects] = useState([]);
const post = () => {
const requestOptions = {
method: "POST",
headers: { "Content-Type": "application/json"},
body: JSON.stringify({
name: name,
description: description,
}),
};
fetch("/api/newProject",requestOptions)
.then((response) => response.json())
.then((data) =>{
})
}
useEffect(() => {
fetch("/api/manageprojects")
.then((response) => response.json())
.then((data) =>{
setProjects(data)
})
}, [])
return (
<div>
<body>
<form action="#" method="POST">
<TextField onChange={(e) => setName(e.target.value)}> </TextField>
<br>
</br>
<TextField onChange={(e) => setDescription(e.target.value)}> </TextField>
<br>
</br>
<Button onClick={() => post()}> Create New Project </Button>
</form>
</body>
<div>
{projects && projects.map(project => (
<Grid item xs={3}>
<Typography component="h5" variant="h5">
<h5> {project.name} </h5>
<h5> {project.description} </h5>
</Typography>
</Grid>
))}
</div>
<Link to={{
pathname: '/manageusers',
state: {
projects: projects
}
}}>Manage Users
</Link>
</div>
);
}
export default project;
Manage Users
a portion of manageusers.js
const Name = (props) => {
const {projects} = props.location.state;
console.log(projects.name);
}
return (
<div>
<Select
value={selectedValue}
options={roles}
onChange={handleChange}
isOptionDisabled={option => option.isDisabled}
/>
<div class="ex1">
{role && role.map(roles => (
<Grid item xs={3}>
<Typography component="h5" variant="h5">
<h5> {roles.username} </h5>
</Typography>
</Grid>
))}
</div>
<Name/>
</div>

Instead of using React Router link,
what you need is a global store, such as Redux or Mobx. That way you can update the state with relevant data and retrieve them on the new page.
If you need a workaround, you could use history.pushState()
Basically you can push to your location and with it a state.
And on the next page, which is MyComp in this case, you could use history.state to retrieve the pushed data.

Related

Cannot read properties of undefined (reading 'categoryName') in react

I'm trying to update my category. I want to update only categoryname, categoryDescription and categoryImage inside the category data. For this, I pull the data from the API with the id I wrote in c# on the backend to show the first state of the data to the user, there is no problem there, but when I try to make changes to a data, nothing appears on the screen in the browser and the following errors appear in the console. Actually ı am new in react. How can I fix that problem?
ERRORS
Uncaught TypeError: Cannot read properties of undefined (reading 'categoryName')
The above error occurred in the <UpdateCategory> component:
My CategoryList.js
I send the id of the category I clicked to UpdateCategory.js from here and I do the editing there.
CategoryList.js shows only my categories
import { Button } from "bootstrap";
import React, { useContext } from "react"
import { Link, Router } from "react-router-dom";
import { CategoryContext } from "../Contexts/CategoryContext";
import "../Css/Categories.css"
export default function CategoryList() {
const { Categories } = useContext(CategoryContext)
const truncateOverview = (string, maxLength) => {
if (!string) return null;
if (string.length <= maxLength) return string;
return `${string.substring(0, maxLength)} ...`;
}
return (
<div className="categories">
{Categories.map((category, i) => (
<Link className="category" to={`/ProductList/${category.categoryId}`} key={i}>
<div className="inner-category">
<div className="image-body">
<img src={category.categoryImage} className="image" alt="" />
</div>
<div className="category-body">
<div>
<h5 className="">{category.categoryName}</h5>
<p className="">{truncateOverview(category.categoryDescription, 50)}</p>
<Link to={`/UpdateCategory/${category.categoryId}`}>
<button className ="btn btn-warning" variant="primary" >
EDIT
</button>
</Link>
</div>
</div>
</div>
</Link>
))}
</div>
)
}
My UpdateCategory.js
import React, { useState, useEffect } from 'react';
import { useParams } from 'react-router-dom';
import Button from 'react-bootstrap/Button';
import Col from 'react-bootstrap/Col';
import Form from 'react-bootstrap/Form';
import InputGroup from 'react-bootstrap/InputGroup';
import Row from 'react-bootstrap/Row';
import axios from 'axios';
export default function UpdateCategory() {
const { id } = useParams()
const url = `http://localhost:64082/api/categories/getbyid/${id}`
const [category, setCategory] = useState({})
const fetchData = () => {
axios.get(url)
.then(response => {
setCategory(response.data)
})
.catch(error => {
console.log(error)
})
}
useEffect(() => {
fetchData()
});
const handleInputChange =(e)=>{
setCategory(e.target.category)
}
const handleSubmit = (event) => {
event.preventDefault();
const data = new FormData(event.target);
fetch(`http://localhost:64082/api/categories/update`, {
method: 'POST',
body: data,
})
}
return (
<div>
<form >
<label htmlFor="inputName">Category Name</label>
<input type="text"
className="form-control"
name="categoryName"
value={category.categoryName}
onChange={handleInputChange}
/>
<label htmlFor="inputName">Category Description</label>
<input type="text"
className="form-control"
name="categoryDescription"
value={category.categoryDescription}
onChange={handleInputChange}
/>
<label htmlFor="inputName">Category Image</label>
<input type="text"
className="form-control"
name="categoryImage"
value={category.categoryImage}
onChange={handleInputChange}
/>
<div>
<button onSubmit={handleSubmit} className="btn btn-danger" >EDIT</button>
</div>
</form>
</div>
)
}
The error probably comes about because of the render in UpdateCategory. The code tells it to render three properties of the category object. But initially, category is an empty object. So it will fail there.
There's another point you need to modify though, in the useEffect(). What you have right now will not trigger on the initialisation of the component. Change it to:
useEffect(() => {
fetchData()
}, []);
Since you're not using typescript you'd also probably want some kind of guard on your render to be safe. So for example something like this:
return category.categoryName && category.categoryDescription && category.categoryImage && (
// your render code in here
);
But that's quite long-winded. You could write an if clause before the return () for the render, asking if those properties exist and returning a blank component instead, or some alternate text.
You could also perhaps provide a default version of the category when initialising the hook.
const [category, setCategory] = useState({
categoryName: 'Default',
categoryDescription: 'Default description',
categoryImage: null
});
This would be a simpler effort than the guard block, and you'd still need the useEffect update.
In UpdateCategory.js,edit;
const [category, setCategory] = useState([]) // not useState({})
The default value of this state should be an empty array. Thanks to empty array, It will not be able to return with map until data comes from api, because its length is 0.

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

Issues loading comments in Realtime

I have a social media app that I would like to function like facebook, where you comment and the comment is loaded in real-time with out having to refresh your browser to display the comment. I am able to send data from React to backend server and I am able to get that data with a axios http request, but I have to refresh the browser to see the comment displayed. I am also see the comment display more then once. I am not getting any errors but the comment is not unique to the post, as it is an array that loads the posts. Did I make a mistake in my code?
Here is the front end code.
import React, { useState, useEffect } from "react";
import Container from "react-bootstrap/Container";
import Card from "react-bootstrap/Card";
import Button from "react-bootstrap/Button";
import axios from "axios";
import "./css/sharewall.css";
const ComponentName = () => {
const [posts, setPosts] = useState([]);
const [comment, setComment] = useState("");
const [id, setId] = useState("");
const loadData = async () => {
try {
let res = await axios.get(`http://localhost:5000/getall`);
setPosts(res.data);
} catch (error) {
console.log(error);
}
};
function makeRequest(e) {
e.preventDefault();
axios({
method: "POST",
url: "http://localhost:5000/postinput",
data: {
comment: comment,
},
}).then((res) => {
setComment(res.data.comment);
console.log(res.data);
});
}
const loadComment = async () => {
try {
let res = await axios.post("http://localhost:5000/postinput");
setComment(res.data.comment._id);
console.log(res.data.comment._id)
} catch (error) {
console.log(error);
}
};
useEffect(() => {
loadData();
}, []);
return (
<div className="compoentclass">
<Container className="mt-5 ml-auto mr-auto">
<div className="text-center">
{posts.map((post, index) => (
<div>
<Card className="">
<Card.Img alt="" src={post.url} />
<Card.ImgOverlay className="overlay">
<Card.Title className="text-center mt-5">
<Card.Text className="cardStyle text-light">
{post.body}
</Card.Text>
</Card.Title>
</Card.ImgOverlay>
</Card>
{posts.map((post, index) => (
<div><Card.Text>{post.comment}</Card.Text></div>
))}
<textarea
className="comment text-center mt-3 mb-3"
onChange={e => setComment(e.target.value)}
value={comment}
name={"comment"}
type={"text"}
/>
<div className="d-flex justify-content-start mt-n3 mb-4">
<Button
className="shareButton"
variant="secondary"
onClick={makeRequest}
onChange={loadComment}
>
Comment
</Button>
</div>
</div>
))}
</div>
</Container>
</div>
);
};
export default ComponentName;
Here is the render from the comments, the comments double or tripple.
In order for other users (the user that posts a comment should be easily able to see the comment immediately) to see the comments real-time, you must implement some sort of "listener" to the server/database to listen for new comments. Otherwise, how should my browser know that YOU posted a comment just now? Check out socket.io, it is quite easy to implement.
I've added some additions to your code, see comments.
First, it seems you can use useEffect to rerender your comments every time you will click the "comment" button. To handle updates you can create a new state as I did.
Probably you are having troubles with multi comments because your posts array contains more than one element inside. Inside render it maps through all posts array and displays every element.
Also, would be better if you will recreate your code inside codesandbox.io or similar.
import React, { useState, useEffect } from "react";
import Container from "react-bootstrap/Container";
import Card from "react-bootstrap/Card";
import Button from "react-bootstrap/Button";
import axios from "axios";
import "./css/sharewall.css";
const ComponentName = () => {
const [posts, setPosts] = useState([]);
const [comment, setComment] = useState("");
const [id, setId] = useState("");
//state for resending load request
const [isCommentFetched, setCommentFetched] = useState(false);
const loadData = async () => {
try {
let res = await axios.get(`http://localhost:5000/getall`);
setPosts(res.data);
} catch (error) {
console.log(error);
}
};
function makeRequest(e) {
e.preventDefault();
axios({
method: "POST",
url: "http://localhost:5000/postinput",
data: {
comment: comment,
},
}).then((res) => {
setComment(res.data.comment);
setCommentFetched(true)
console.log(res.data);
})
//don't forget to catch errors
.catch((err)=>{
console.log(err)
})
}
const loadComment = async () => {
try {
let res = await axios.post("http://localhost:5000/postinput");
setComment(res.data.comment._id);
console.log(res.data.comment._id);
} catch (error) {
console.log(error);
}
};
useEffect(() => {
loadData();
}, []);
//hook fires whenever your isCommentFetched state updating.
useEffect(() => {
// if isCommentFetched true, it will send request for get new posts, and will update your comments in render.
if(isCommentFetched){
loadData();
}
}, [isCommentFetched]);
return (
<div className="compoentclass">
<Container className="mt-5 ml-auto mr-auto">
<div className="text-center">
{posts.map((post, index) => (
<div>
<Card className="">
<Card.Img alt="" src={post.url} />
<Card.ImgOverlay className="overlay">
<Card.Title className="text-center mt-5">
<Card.Text className="cardStyle text-light">
{post.body}
</Card.Text>
</Card.Title>
</Card.ImgOverlay>
</Card>
{posts.map((post, index) => (
<div>
<Card.Text>{post.comment}</Card.Text>
</div>
))}
<textarea
className="comment text-center mt-3 mb-3"
onChange={(e) => setComment(e.target.value)}
value={comment}
name={"comment"}
type={"text"}
/>
<div className="d-flex justify-content-start mt-n3 mb-4">
<Button
className="shareButton"
variant="secondary"
onClick={makeRequest}
onChange={loadComment}
>
Comment
</Button>
</div>
</div>
))}
</div>
</Container>
</div>
);
};
export default ComponentName;

How to pass objects and states between two components in React

I want to move the value of an object received by an API from one component to another.
I would like to use this "countryInfo.todayCases" inside SubCard component in App.js file as prop. For example where it says in App.js <SubCard title="Cases" cases={countryInfo.todayCases} total={2000} /> but I couldn't access this info from Header.js. I have googled everywhere and couldn't find something similar to my case. Your help is much appreciated
App.js
import Header from "./components/Header";
import SubCard from "./components/SubCard";
function App() {
return (
<div className="app">
<div className="app__left">
<Header />
<div className="app__stats">
<SubCard title="Cases" cases={_cases} total={2000} />
<SubCard title="Recovered" cases={4000} total={2000} />
<SubCard title="Deaths" cases={4000} total={2000} />
</div>
<Map />
</div>
<div className="app__right_bar">
<SideBar />
{/* Graph */}
</div>
</div>
);
}
export default App;
Header.js
function Header({ _cases, _recovered, _deaths }) {
const [countries, setCountries] = useState([]);
const [country, setCountry] = useState("worldwide");
const [countryInfo, setCountryInfo] = useState({});
const _cases = countryInfo.todayCases;
const _recovered = countryInfo.todayRecovered;
const _deaths = countryInfo.todayDeaths;
useEffect(() => {
// async -> send a request, wait for it and do something
const getCountriesData = async () => {
await fetch(`${COUNTRIES_URL}`)
.then((response) => response.json())
.then((data) => {
const countries = data.map((country) => ({
name: country.country, //country name ex United Kingdom
value: country.countryInfo.iso2, // country code ex: UK
}));
setCountries(countries);
});
};
getCountriesData();
}, []);
const onCountryChange = async (event) => {
const countryCode = event.target.value;
setCountry(countryCode);
const url =
countryCode === "worldwide"
? WORLDWIDE_URL
: `${COUNTRIES_URL}/${countryCode}`;
await fetch(url)
.then((response) => response.json())
.then((data) => {
setCountry(countryCode);
setCountryInfo(data);
});
};
console.log("Country info here >>>", countryInfo);
return (
<div className="app__header">
<h1>Covid 19 tracker</h1>
<FormControl className="app__dropdown">
<Select variant="outlined" onChange={onCountryChange} value={country}>
<MenuItem value="worldwide">Worldwide</MenuItem>
{countries.map((country) => (
<MenuItem value={country.value}>{country.name}</MenuItem>
))}
</Select>
</FormControl>
</div>
);
}
export default Header;
SubCard.js
function SubCard({ title, cases, total }) {
return (
<div>
<Card className="sub_card">
<CardContent>
{/*Title */}
<Typography color="primary">{title}</Typography>
{/*Number of Cases */}
<h2 className="card_cases">{cases}</h2>
{/*Total */}
<Typography className="card_total" color="textSecondary">
{total} Total
</Typography>
</CardContent>
</Card>
</div>
);
}
export default SubCard;
It appears that App calls Header and Subcard
/-> Header
App ->
\-> SubCard
In order for props to pass through to each component there are three options:
Move the shared data to the Parent
If you move the shared data to the Parent (App), then you can share that data with both its children as props.
Parent > Child > GrandChild
Change the components so that the data flows down through props from the Parent > Child > GrandChild. Then the order of the components would be
App -> Header -> SubCard
React Context
You could use React Context to create a global variable to share between the components.
With any of these three choices, you need to rebalance how the code is laid out between the components.

How to upload an image in React JS?

<div className="mb-1">
Image <span className="font-css top">*</span>
<div className="">
<input type="file" id="file-input" name="ImageStyle"/>
</div>
</div>
This is the snippet i provided that i was using to pick the file from the device in react js,
Using this i can select the file and that filename is also shown as well
What i want is now to store this file on S3 or anywhere and get its URL from there and POST it to my server using fetch api call.
import React, { useState } from "react";
const UploadAndDisplayImage = () => {
const [selectedImage, setSelectedImage] = useState(null);
return (
<div>
<h1>Upload and Display Image usign React Hook's</h1>
{selectedImage && (
<div>
<img
alt="not found"
width={"250px"}
src={URL.createObjectURL(selectedImage)}
/>
<br />
<button onClick={() => setSelectedImage(null)}>Remove</button>
</div>
)}
<br />
<br />
<input
type="file"
name="myImage"
onChange={(event) => {
console.log(event.target.files[0]);
setSelectedImage(event.target.files[0]);
}}
/>
</div>
);
};
export default UploadAndDisplayImage;
Upload the image from your file and display it on your page in react,
you can also get the image object in the state when we select the image
to display on the webpage you have to convert the image object to object using URL.createObjectURL(fileObject)
import React, { Component } from "react";
class DisplayImage extends Component {
constructor(props) {
super(props);
this.state = {
image: null
};
// if we are using arrow function binding is not required
// this.onImageChange = this.onImageChange.bind(this);
}
onImageChange = event => {
if (event.target.files && event.target.files[0]) {
let img = event.target.files[0];
this.setState({
image: URL.createObjectURL(img)
});
}
};
render() {
return (
<div>
<div>
<div>
<img src={this.state.image} />
<h1>Select Image</h1>
<input type="file" name="myImage" onChange={this.onImageChange} />
</div>
</div>
</div>
);
}
}
export default DisplayImage;
If you want to upload image and post it to an API. Then you install react-image-uploader. It saves the image to your local port and also in your database by raising a POST request.
This code let you upload image to the server,the backend code is written in nestjs,and display the image which will be uploaded.I have used the formdata.
import React, { useEffect, useState } from "react";
function Product() {
const { REACT_APP_REST } = process.env;
const [file, setFile] = useState([]);
const handleFile = event => {
setFile(
URL.createObjectURL(event.target.files[0])
);
const formData = new FormData();
formData.append("fileupload", event.target.files[0]);
fetch(REACT_APP_REST + "/product/upload", {
method: 'POST',
body: formData,
dataType: "jsonp"
})
};
return (
<>
<Container fluid>
<Col md="4">
<Card className="card-user">
<img src={file} />
<Card.Body>
<Form.Group>
<label>IMAGE</label>
<Form.Control
type="file"
required="required"
onChange={handleFile}
></Form.Control>
</Form.Group>
</Card.Body>
<hr></hr>
</Card>
</Col>
</Container>
</>
);
}
export default Product;
using react-uploady you can do this very easily:
import React from "react";
import Uploady from "#rpldy/uploady";
import UploadButton from "#rpldy/upload-button";
import UploadPreview from "#rpldy/upload-preview";
const filterBySize = (file) => {
//filter out images larger than 5MB
return file.size <= 5242880;
};
const App = () => (
<Uploady
destination={{ url: "my-server.com/upload" }}
fileFilter={filterBySize}
accept="image/*"
>
<UploadButton />
<UploadPreview />
</Uploady>
);
Failed to execute 'createObjectURL' on 'URL': Overload resolution failed.
For some reason I coudn't use URL.createObjectURL(image) as
const [image, setImage] = useState(null);
const [imgURL, setImgURL] = useState();
<img src={URL.createObjectURL(image)}/>
So I save the Url in the state for instant display on the button click method. This worked!
setImgURL(URL.createObjectURL(image));
Unfortunately, I was still getting the same error when I use useEffect.
useEffect(() => {
setImgURL(URL.createObjectURL(image));
}, [image]);

Resources