I've been beating my head against the wall and cross referencing code I've read from others and questions that have been asked. I'm at the point where I know I'm just not looking in the right direction, however I'm not sure where to turn.
The app I'm writing was originally written by me in 2019, and I did not know of React h
Hooks as I learned components from a coding bootcamp. TL;DR: I'm rusty.
The issue:
I'm trying to use AXIOS to use a PUT call into my MongoDB, and the way I learned was by using refs. Refs in the way I learned is now deprecated, and I just want to get this working so I can move on to another project and start using Hooks instead.
When I use my button created to save the change, I get an error in the browser console, however it refreshes too fast for me to catch the exact error. The second I get to look at the wall of text, it looks similar to a bad promise, but I'm not entirely sure. Either way, the data does not get updated.
My code:
import React, { Component } from 'react'
import axios from 'axios'
export default class EditSeed extends Component {
constructor(props){
super(props)
this.state = {
_id: '',
seed: '',
created_at: '',
__v: ''
}
this.changeHandler = this.changeHandler.bind(this)
this.myRef = React.createRef
this.focusTextInput = this.focusTextInput.bind(this);
}
focusTextInput() {
// Explicitly focus the text input using the raw DOM API
// Note: we're accessing "current" to get the DOM node
this.textInput.current.focus();
}
componentDidMount(){
this.fetchSeed()
}
fetchSeed = async () => {
try {
const res = await axios.get(`/api/grapevine/${this.props.match.params.id}`)
this.setState({
_id: res.data._id ,
seed: res.data.seed,
created_at: res.data.created_at,
__v: res.data.__v
})
}
catch (err) {
this.setState({ error: err.message })
console.log(err)
}
}
editSeed = async (newSeed) => {
try {
const res = await axios.request({
method: 'put',
url: `/api/grapevine/${this.state._id}`,
data: newSeed
})
res.this.props.history.push(`/`)
}
catch (err) {
this.setState({ error: err.message })
console.log(err)
}
}
onSubmit = (e) => {
const newSeed = {
seed: this.myRef.current.seed.value,
created_at: this.myRef.current.created_at.value
}
this.editSeed(newSeed)
e.preventDefault()
}
changeHandler = (e) => {
const target = e.target
const value = target.value
const name = target.name
this.setState({
[name]: value
})
}
render(){
return(
<div>
<h1>Edit Seed</h1>
<form onSubmit={this.onSubmit.bind(this)}>
<label>
Edit Message:
<input type="text" name="seed" ref={this.myRef} value={this.state.seed} onChange={this.changeHandler} />
</label>
<input type="submit" value="Save" />
</form>
</div>
)
}
}
My fetch function works as intended, it's mainly just the edit that doesn't want to work. I have tried changing
<input type="text" name="seed" ref={this.myRef} value={this.state.seed} onChange={this.changeHandler} />
to
<input type="text" name="seed" ref={this.myRef.current} value={this.state.seed} onChange={this.changeHandler} />
Obviously I am doing something wrong, and I have referenced and read the React docs on this about 10 times and I'm really just hitting a wall.
Any insight would be massively appreciated.
Firstly, React.createRef is a function, so it needs to be invoked.
this.myRef = React.createRef();
Then you simply attach the ref to the element.
<input
ref={this.myRef}
type="text"
name="seed"
value={this.state.seed}
onChange={this.changeHandler}
/>
Then whenever you need to reference the input's ref you access the current value of it.
this.myRef.current
Update
I don't think the React ref is providing much for you here since you duplicate the seed and created_at into local state. You update the seed state via the onChange handler. Why not just forego the ref and access the state in the submit handler. I believe this will resolve your "cannot access value of undefined" error since the input element target object likely doesn't have seed or created_at attributes to access a value of.
onSubmit = (e) => {
e.preventDefault();
const newSeed = {
seed: this.state.seed,
created_at: this.state.created_at // is this supposed to be a current datetime?
}
this.editSeed(newSeed);
}
changeHandler = (e) => {
const { name, value } = e.target;
this.setState({
[name]: value
});
}
You did nearly got it right, just a few minor changes will lead you to the right way:
First: You need to invoke React.createRef function:
this.myRef = React.createRef -> this.myRef = React.createRef()
Second: You need to call e.preventDefault() from the begining of your handler so that the whole page will not get refeshed imediately. Something like this:
onSubmit = (e) => {
e.preventDefault();
const newSeed = {
seed: this.myRef.current.seed.value,
created_at: this.myRef.current.created_at.value
}
this.editSeed(newSeed)
}
From then, I think you will easily get to the right path.
You can use useRef hook in functional components like this:
import React, {useRef} from 'react';
function MyComponenet () {
myRef = useRef(null) // <---- initialize your ref with null
return (
<div ref=myRef >
Hello World
</div>
)
}
now you create a reference and initiate it with a null value, then passed it to the div element. from now you can manipulate your ref like get its value or assign values, click, and other actions.
with TypeScript
myRef = useRef<HTMLDivElement>(null)
if you are passing myRef to a div element, so your ref type will be HTMLDivElement
Related
When I submit an object/issue I am calling a function of the parent component, which has been passed to the child component as props.
After sending my data I want to clear my input fields. The Problem is, that I am clearing my input fields immediately after calling addNewIssue(). I actually want to wait until the POST-Call has resolved.
If I only had one component I could just clear the input fields after e.g. setissueList(newIssueList), since state and rest-call would be in the same component.
How can I notify the child component that the POST-Call has resolved and only then it should clear the inputs?
I tried to google this problem, but I couldn't find anything. Most likely I am not using the right words. I assume it has to do something with promises and the solution might be simple but I am not an expert regarding promises or async.
// parent component
const addNewIssue = (issue) => {
axios.post(url + '/forms/issue', issue, headers).then((res) => {
let newIssueList = [...issueList, res.data];
setissueList(newIssueList);
});
}
<NewIssue addNewIssue={addNewIssue} />
// child component
const const handleSubmit = (e) => {
e.preventDefault();
addNewIssue(inputs);
setInputs({
summary: "",
description: ""
});
}
Use your API request along with a callback
// parent component
const addNewIssue = (issue,cb) => {
axios.post(url + '/forms/issue', issue, headers).then((res) => {
let newIssueList = [...issueList, res.data];
setissueList(newIssueList);
cb()
});
}
<NewIssue addNewIssue={addNewIssue} />
// child component
const const handleSubmit = (e) => {
e.preventDefault();
const cb=()=>{setInputs({
summary: "",
description: ""
});}
addNewIssue(inputs,cb);
}
I have a search page, something/search?q=foo, whenever im at this page I want to put foo in my form's value tag. (Not for search purposes, I have a fully functioning search bar, I just want to show the client the last thing he searched for).
I've gotten the search term with: (window.location.href.indexOf("?q") != -1) ? window.location.href.substring(window.location.href.indexOf("?q") + 3) : '', this works, although when putting it into the forms value tag, react blocks immediately, it doesn't let me write anything into the input field. I think this is because it updates to this string super fast and you don't see it happening.
How would I achieve this, to update my form's value one time and thats it?
Here is my simplified code:
<input type="search" name="q" id="q" value={(window.location.href.indexOf("?q") != -1) ? window.location.href.substring(window.location.href.indexOf("?q") + 3) : ''} <--- This is where I want to put the search string
What i've tried so far is this:
this.state = {
value:''
}
...
handleTitle = (s) => {
this.setState({value:s})
}
...
<input ... value={this.state.value} onChange={this.HandleTitle((window.location.href.indexOf("?q") != -1) ? window.location.href.substring(window.location.href.indexOf("?q") + 3) : '')}
this results in infinite state updates
I would suggest you get the value of the search-param when the component mounts, and store it in the component's local state. Then read/update the value from state. Something like:
const [search, setSearch] = useState("");
useEffect(() => {
setSearch(new URLSearchParams(new URL(window.location.href).search).get('q'));
}, []);
return (
<intput type="text" value={search} onChange={e => setSearch(e.target.value)} />
);
I've not tested it, but you get the gist of it.
Anyway if you want to access the q natively.
Working example
https://8zwht.csb.app/?q=test
import React from "react";
import "./styles.css";
class App extends React.Component {
state = {
value: ""
};
componentDidMount() {
const search = new URL(window.location).searchParams;
const term = search.get("q");
if (term)
this.setState({
value: term
});
}
handleChange = (e) => {
this.setState({
value: e.target.value
});
};
render() {
return (
<input
type="text"
onChange={this.handleChange}
value={this.state.value}
/>
);
}
}
export default App;
It would be easier to provide a more concrete answer if you share the code or gist
I'm working on coding a Dapp with the react boxe from truffle. For that application I need to use a function (just two getters) from my smart contract in another component than App.js. This function is the getUser, which needs to be in form from Login.js.
App.js:
class App extends Component {
state = {
web3: null,
contract: undefined,
account: null,
user: {
id: null,
name: '',
password: ''
}
};
componentDidMount = async () => {
try {
const web3 = await getWeb3();
const accounts = await web3.eth.getAccounts();
const networkId = await web3.eth.net.getId();
const deployedNetwork = Login.networks[networkId];
const instance = new web3.eth.Contract(
Login.abi,
deployedNetwork && deployedNetwork.address,
);
this.setState({ web3, accounts, contract: instance });
} catch (error) {
alert(
`Failed to load web3, accounts, or contract.
Check console for details.`,
);
console.error(error);
}
};
getUser = async (event) => {
const userID = this.state.id;
const userName = await this.state.contract.methods
.getUsername(userID).call();
const userPassword = await this.state.contract.methods
.getPassword(userID).call();
console.log(userName + ' - ' +userPassword);
};
render() {
if (!this.state.web3) {
return <div>Loading Web3, accounts, and contract...</div>;
}
return (
<div className="App">
</div>
);
}
}
export default App;
Login.js:
class App extends Component{
handleInputChange = (event) => {
let input = event.target;
let name = event.target.name;
let value = input.value;
this.setState({
[name]: value
})
}
render() {
return (
<div className='Login'>
<form>
<label>
<span>Barcode:</span>
<input name="id" type="number" required
onChange={this.handleInputChange} />
</label>
<button type="submit" value="submit">Get user data</button>
</form>
</div>
);
}
}
export default Login;
I first tried to export the getUser function:
App.js:
export const getUser = async (event) => {
const userID = this.state.id;
const userName = await this.state.contract.methods
.getUsername(userID).call();
const userPassword = await this.state.contract.methods
.getPassword(userID).call();
console.log(userName + ' - ' +userPassword);
};
Login.js:
import {getUser} from ...
But I got the following error:
Unhandled Rejection (TypeError): Cannot read property 'state' of undefined
I then tried to add inside the Login, the same componentDidMount
and state found inside App.js (with the imports), however I got
the following error:
TypeError: Cannot read property 'methods' of null
I no longer have any idea of how to do it. So I would like to ask some help please.
I thank in advance anyone who will take the time to help me.
You just need to pass the value between components. Let's say onClick you will move from login to app component and you will pass id & name.
onClick=(id,name)=>{
history.push({
pathname: '/secondpage', //app component url
state: { id: id, name: name }
})
}
history could be imported from react-router
Then in app component you can access this value in, this.props.location.state
If you want to send value through component, in login component declare app component like this,
<App
id={id}
name={name}
/>
Then in app component you can get the value in this.props
Global State Management
You are looking for a way to manage state at a global level. There are lots of solutions for this problem that exists and are widely used.
Solution 1: Props or "prop drilling"
The idea is to pass the state to every component that needs it. You should only consider doing this if you are passing down props that are not global level or is needed by only a few component trees.
An example of passing down props is in the official docs (https://reactjs.org/docs/context.html)
Solution 2: React Context API
There is already a built in feature inside React that is widely used called Context API that solves this. Example of usage is also in the docs. https://reactjs.org/docs/context.html
Context provides a way to pass data through the component tree without having to
pass props down manually at every level.
TLDR; If you need to your state to be at a global level where lots of component trees need access to it, this is probably the solution.
I am trying to create a search feature using react hooks but it keeps returning the error:
Cannot read Property of Undefined
on the updateSearch function whenever I type in the input field.
const [search, setSearch] = React.useState('');
const [searchResults, setSearchResults] = React.useState([]);
const [state, setState] = React.useState({
open: false,
name: '',
users:[]
});
useEffect(() => {
getAllUsers();
}, []);
const getAllUsers = () => {
fetch('/userinformation/', {
method: 'GET',
headers: {'Content-Type':'application/json'}
})
.then(function(response) {
return response.json()
}).then(function(body) {
console.log(body);
setState({...state, users: body });
})
}
const updateSearch = (event) => {
setSearch(event.target.value)
}
React.useEffect(() => {
const results = state.users.filter(user =>
user.toLowerCase().includes(search)
);
setSearchResults(results);
}, [search]);
return (
<input type="text" value={search} onChange={(e) => updateSearch(e.target.value)}/>
)
Whenever I type in the search bar I get the following error:
How can i fix this?
You can either get to the value of passed event by changing
<input type="text" value={search} onChange={(event) => updateSearch(event}/>
or you can keep the input element as it is and change the update updateSearch callback to
const updateSearch = (event) => { setSearch(event) }
Secondly, you are applying includes to a single item of an array which is specific to array methods, you need to make following changes as well to make it work:
React.useEffect(() => {
const results = state.users.filter( user => user.firstName.toLowerCase() === search );
setSearchResults(results);
}, [search])
in your input you're already passing valueonChange={(e) => updateSearch(e.target.value) and in updateSearch you're trying to accessing it. Change it like this, if you want to access event in updateSearch method and get value from it.
<input type="text" value={search} onChange={(e) => updateSearch(e}/>
I would teach you a secret that has worked very well for me over the years. When javascript gives you such error as cannot read property ***whateverValue*** value of undefined it means you are trying to read the property of an object that does not exist. In this case, the object you're trying to read from is undefined, hence it cannot have any key: value pair.
Back to your question: TypeError: Cannot read property value of undefined
Using cmd+f to check for all places where value is used shows me everywhere you used value on event.target.value
Stay with me (I know this is boring, but it would help later).
You have an event handler named updateSearch.
All you need here now is to change your input tag to this:
<input type="text" value={search} onChange={updateSearch}/>
Don't worry, React would handle the rest, it automatically parses the event to eventHandler which can then be accessed on such eventHandler.
Also, I think you might want to refactor this component.
Something like import React, {useState, useEffect} from React
you won't have to call React.useEffect or React.useState in other parts of the project. just useEffect or useState.
Goodluck :+1
You have already passed the value of the input into the updateSearch method.
This is how you should fix it:
const updateSearch = (value) => {
setSearch(value);
};
And as for the second issue you have raised on your useEffect hook, you will have to call toLowerCase() on one of your properties (either firstName or lastName), depending on what you need to search for.
React.useEffect(() => {
const results = state.users.filter(user =>
user.firstName.toLowerCase().includes(search)
);
setSearchResults(results);
}, [search]);
In my code, I did not use constructor (). I've always seen people use the constructor in class components, but even though I'm not using it in that code, it's working perfectly. In my code, putting the state outside the constructor, is it a good idea or would it be better to use the constructor with the state set inside it? Can it give some sort of error in the future, or worsen my system's performance doing so? What is more advisable to do in this case?
import React, { Component, Fragment } from 'react'
import {Redirect} from 'react-router-dom'
import { connect } from 'react-redux'
import ActionCreator from '../redux/actionCreators'
import Button from '../elements/Button'
const statsgenre = {
'Ação': 'Action',
'Comédia': 'Comedy',
'Drama': 'Drama'
}
const statsuser = {
'Assistido' : 'Watched',
'Assistindo': 'Watching',
'Assistir': 'Watch'
}
class ScreensEditSeries extends Component{
state = {
id: '',
name: '',
status: '',
genre: '',
notes: ''
}
componentDidMount = () => {
const serie = {...this.props.match.params}
this.props.load(serie)
this.props.reset()
}
static getDerivedStateFromProps(newProps, prevState){
let serie = {}
if (prevState.name === '' || prevState.name === undefined){
if (newProps.series.serie.name !== prevState.name){
serie.name = newProps.series.serie.name
}
if (newProps.series.serie.genre !== prevState.genre){
serie.genre = newProps.series.serie.genre
}
if (newProps.series.serie.status !== prevState.status){
serie.status = newProps.series.serie.status
}
if (newProps.series.serie.notes !== prevState.notes){
serie.notes = newProps.series.serie.notes
}
return serie
}
}
saveSeries = () => {
const {name, status, genre, notes} = this.state
const id = this.props.match.params.id
const newSerie = {
id,
name,
status,
genre,
notes
}
this.props.save(newSerie)
}
handleChange = field => event => {
this.setState({[field] : event.target.value})
}
render(){
return (
<Fragment>
<div className="container">
<div>
{this.props.series.saved && <Redirect to={`/series/${this.props.match.params.genre}`}/>}
<h1 className='text-white'>Edit Série</h1>
{!this.props.series.isLoadding && <Button>
Name: <input type="text" value={this.state.name} onChange={this.handleChange('name')} className="form-control" /><br />
Status: {<span> </span>}
<select value={this.state.status} onChange={this.handleChange('status')}>
{Object.keys(statsuser)
.map( key => <option key={key}>{statsuser[key]}</option>)}
</select><br/><br/>
Genre: {<span> </span>}
<select value={this.state.genre} onChange={this.handleChange('genre')}>
{Object.keys(statsgenre)
.map(key => <option key={key}>{statsgenre[key]}</option>)}
</select><br/><br/>
Notes: <textarea type='text' value={this.state.notes} onChange={this.handleChange('notes')} className="form-control"></textarea><br />
<button className="button button2" type="button" onClick={this.saveSeries}>Save</button>
</Button>}
{this.props.series.isLoadding && <p className='text-info'>Loading...</p>}
</div>
</div>
</Fragment>
)
}
}
const mapStateToProps = state => {
return {
series: state.series
}
}
const mapDispatchToProps = dispatch => {
return {
load : serie => dispatch(ActionCreator.getSerieRequest(serie)),
save: newSerie => dispatch(ActionCreator.updateSerieRequest(newSerie)),
reset : () => dispatch(ActionCreator.seriesReset()),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ScreensEditSeries)
In general, you should only use a constructor if you need logic when the class is first created, or if your setup depends on the props passed in. Since everything in your initial state is hardcoded not using a constructor is fine in this case.
There is no problem in using class components without a constructor. Usually you need it in case you have to do some work to prepare the state, process some props or other setup some instance variables as soon as the component is instantiated.
It's ok :)
Here, instead, there is a very interesting post from Dan Abramov about why, if you need to use the constructor, is needed to call super(props):
https://overreacted.io/why-do-we-write-super-props/
Not super related to the question, but asking about constructor, I thought it could be useful to you.
There's no difference. The reason you see most people doing it inside of the constructor is because doing state = {} directly on the class is new syntax that hasn't been widely adopted yet (it often still requires a Babel or similar transformation). See proposal-class-fields for more information on it. One thing to note is that if you need to access any props to initialize the state, you have to do that in the constructor.