Passing state-based data between pages in React - reactjs

I am trying to create a multiple page form in React, and I have the basic wireframe set up. I am trying to export a user's Name from one page to the next, but the user will change depending on who has logged in. I've been in google purgatory for a while trying figure out how to grab a specific state-based value out of a component to be available on another page. In my code below, I'm exporting the whole component to render on the App.js page. However, I'd also like to grab just the {userName} to render within another component.
import React, { Component } from 'react'
class Intro extends Component {
state = { userName: ''}
handleChange = (event) => this.setState({ userName: event.target.value })
render() {
const { userName } = this.state
return (
<div id='intro'>
<form>
<FieldGroup
id='nameArea'
value={this.state.value}
onChange={this.handleChange}
/>
<input id='submit' type='submit' value='Submit' /> .
</form>
</div>
)
}
}
export default Intro

To put it simply, you can't. This is where tools like redux come into play. Here's an example using React's new context API:
const UserContext = React.createContext('');
class Intro extends Component {
handleChange = (event) => {
this.props.updateUserName(event.target.value);
}
render() {
const { userName } = this.props
return (
<div id='intro'>
<form>
<input
id='nameArea'
value={userName}
onChange={this.handleChange}
/>
<input id='submit' type='submit' value='Submit' /> .
</form>
</div>
)
}
}
// only doing this to shield end-users from the
// implementation detail of "context"
const UserConsumer = UserContext.Consumer
class App extends React.Component {
state = { userName: '' }
render() {
return (
<UserContext.Provider value={this.state.userName}>
<React.Fragment>
<Intro userName={this.state.userName} updateUserName={(userName) => this.setState({userName})} />
<UserConsumer>
{user => <div>Username: {JSON.stringify(user)}</div>}
</UserConsumer>
</React.Fragment>
</UserContext.Provider>
)
}
}
See my updated codesandbox here.

Most of the time, when you need data on another component, the solution is store the date higher in your component.

As the others already said, the most easy way is trying to pass your state via props from your higher order component to the childs.
Another approach would be to use Redux for your state management. This gives you one global state store accessible from any component.
Third you can try to use the react context api.

Related

React.js Controlled Input in child component

I am trying to have a controlled input set up in a child component (the Search component). I wanted to keep the input state in the main App component so that I can access it in my apiCall method. I am getting the following error:
Warning: You provided a value prop to a form field without an onChange handler. This will render a read-only field. If the field should be mutable use defaultValue. Otherwise, set either onChange or readOnly.
However, I did add an onChange handler. I'm assuming the problem is that the onChange handler function is in the parent component and React doesn't like this. I did try moving the input to the main App component and worked fine (logged input to console).
Am I going about this wrong? And is there a way to set it up so that I can access the input from the Search component in the App component? I was hoping to keep most of my code/functions/state in the main App component.
Here is the App component
import React, { Component } from "react";
import './App.css';
import Header from './Components/Header'
import Search from './Components/Search'
import MainInfo from './Components/MainInfo'
import Details from './Components/Details'
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
weather: null,
main: '',
wind: '',
loading: null,
cityInput: 'Houston',
city: 'City Name',
date: new Date()
};
this.apiCall = this.apiCall.bind(this);
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
this.setState({
cityInput: event.target.value
})
console.log(this.state.cityInput)
}
// Fetch data from OpenWeatherAPI
apiCall() {
this.setState({
loading: true
})
const currentWeather = fetch(
`https://api.openweathermap.org/data/2.5/weather?q=${this.state.cityInput}&appid={apiKey}&units=imperial`
).then((res) => res.json());
const futureWeather = fetch(
`https://api.openweathermap.org/data/2.5/forecast?q=houston&appid={apiKey}&units=imperial`
).then((res) => res.json());
const allData = Promise.all([currentWeather, futureWeather]);
// attach then() handler to the allData Promise
allData.then((res) => {
this.setState({
weather: res[0].weather,
main: res[0].main,
wind: res[0].wind,
city: res[0].name
})
});
}
componentDidMount() {
this.apiCall();
}
render() {
return (
<div className="container-fluid bg-primary vh-100 vw-100 d-flex flex-column align-items-center justify-content-around p-3">
<Header />
<Search cityInput={this.state.cityInput} />
<MainInfo main={this.state.main} date={this.state.date} city={this.state.city} weather={this.state.weather} />
<Details main={this.state.main} wind={this.state.wind} />
</div>
);
}
}
export default App;
Here is Search component
import React, { Component } from "react";
class Search extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="row">
<div className="col-12">
<div className="d-flex">
<input className="form-control shadow-none mx-1" placeholder="Enter a city..." value={this.props.cityInput} onChange={this.handleChange}></input>
<button className="btn btn-light shadow-none mx-1" onClick={this.apiCall}>Test</button></div>
</div>
</div>
);
}
}
export default Search;
The Search component is indeed unaware of the implementation of the onChange function you have made in your App. If you really want to use a function from the parent (App) component in the child (Search), you'll need to add it as a property, as such:
<Search cityInput={this.state.cityInput} onChange={this.onChange} />
Then, you need to set it in the Child component's constructor:
constructor(props) {
super(props);
this.onChange = props.onChange;
}
I also suggest you'll have a look at React's functional approach with hooks https://reactjs.org/docs/hooks-intro.html, which makes all this a whole lot less fiddly, in my opinion. But it might take a bit to get used to.
u can pass functions like ur handler over the prop to childrens and update so from a child to the mother of the children, in the children u give the value the prop u supply from mother
<Select dataFeld="broker" id="yourid" value={this.state.brokerSel} ownonChange={(e) => this.setState({statename: e})

Squaring the value of an input box in React

I am new to learning React and doing a little test project each day. Today, I am trying to create an input box that when I click a Submit button, it alerts the square of a number. Nice and simple. But, I am trying to do this without using State. Just trying to understand how. Here is my code but something is missing. I think I am close!
Any ideas?
import { render } from '#testing-library/react';
import React from 'react';
class App extends React.Component {
sayHi = props => {
alert(this.props.mySentProps);
};
squareTheNumber = () => {
alert('this is the squared number'+ );
};
render() {
return (
<div>
<div onClick={this.sayHi}>
<h1>Hello World</h1>
</div>
<div>
<input type="text" placeholder={'Enter a number to square'} />
</div>
<div>
<button onClick={this.squareTheNumber}>Submit me</button>
</div>
</div>
);
}
}
export default App;
Try this:
import React from "react";
import "./styles.css";
class App extends React.Component {
sayHi = (props) => {
alert(this.props.mySentProps);
};
squareTheNumber = (event) => {
event.preventDefault();
// Should be the same as input's "name" or "id" property
// Docs: https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/elements
const { number } = event.target.elements;
alert(`this is the squared number: ${number.value ** 2}`);
};
render() {
return (
<div>
<div onClick={this.sayHi}>
<h1>Hello World</h1>
</div>
<div>
<form onSubmit={this.squareTheNumber}>
<input
name="number"
type="text"
placeholder="Enter a number to square"
/>
<button type="submit">Submit me</button>
</form>
</div>
<div></div>
</div>
);
}
}
export default App;
P.S.: render from #testing-library/react is used for testing purposes only. See docs here. Class components have their own field with the same name.
As said, there is no clean way to do it without state or any extensions. The best way is to use state and make things clean. But another way you can do it is to use JQuery.
For example:
You can assign the <input> an id, say myId. Then you do this:
var content = $('#myId').content;
And then you can change the content in the p by assigning it a new value.
But using JQuery kinds of defeats the purpose of React, so I would recommend using state.
You can use refs to access mounted elements directly.
https://reactjs.org/docs/refs-and-the-dom.html

Having Difficulty sending state data on form to different component

I understand that this question has been asked many different times. I've looked through many different ones and I'm still having a difficult time understanding it.
I've read many articles online and I know that I need to pass my state to props and I'm having a difficult time doing so. I managed to pass a simple string of test, however I cannot pass my state to props as it simply returns nothing and I'm not sure why if the values on the form get updated in the onchange method.
I also want to avoid using redux as a alternative as I'm trying to learn the basic way first
What I'm trying to do is very simple, user fills out a box that contains ordernumber on the form. They hit submit, redirects to another page where I'll have access to the ordernum they submitted on the input box for the order number.
Here is my code:
simple input form page
/*eslint-disable no-unused-vars*/
import React from 'react';
import ReactDOM from 'react-dom';
class Reloform extends React.Component {
constructor(props){
super(props);
this.state = {
orderNum: "",
errorMsg: ""
}
}
onChange(e){
this.setState({
[e.target.name]: e.target.value
})
}
onSubmit(e){
if(this.state.orderNum === '') {
this.setState({
errorMsg: 'Please enter your order number.'
});
} else {
this.setState({
errorMsg: ''
});
// Submission successful
window.location = '/relotoForm';
}
e.preventDefault();
}
render() {
console.log(this.props)
return (
<div className="container">
// I get message prop back but not orderNum?
<h1>{this.props.message}</h1>
<h1>{this.props.orderNum}</h1>
<div className="reloContainer">
<form
method="POST"
id="reloForm"
onSubmit={e => this.onSubmit(e)}
autoComplete="off"
>
<h1>T/O Form</h1>
{this.state.errorMsg !== '' ? <p style={{color:'#E2231A'}}>Please enter an order number.</p> : ''}
<label>Order #</label>
<input
type="text"
name="orderNum"
className="form-control"
value={this.state.orderNum}
onChange={e => this.onChange(e)}
/>
<button type="submit" className="btn btn primary" id="reloButton" onClick={this.props.updateData}>Submit</button>
</form>
</div>
</div>
)
}
}
export default Reloform;
/* form action page */
/*eslint-disable no-unused-vars*/
import React from 'react';
import ReactDOM from 'react-dom';
import Reloform from './reloForm';
class Relotoform extends React.Component {
render() {
return (
<div>
//The string that I returned works but the state that I try to have access on this page does not. I am not sure why. My assumption is because maybe this.state is referring to the state of this component and not the form component on my form page?
<Reloform message="Works" orderNum={this.state.orderNum} />
</div>
)
}
}
export default Relotoform;
State is local to the component you are accessing. If you want to pass a state value to another component, this is where you use props. So when you are trying to render Reloform in your Relotoform component, when you do orderNum={this.state.orderNum}, you won't get any value as there is no state defined for Relotoform in which there's a variable called orderNum.
You need to update the orderNum through your state in the Reloform. You get message displayed because you are passing the value "Works" as a prop in Relotoform. Then you are accessing it correctly as a prop in Reloform. Adapt a similar arhcitecture for orderNum.

React component re renders to it's initial state automatically

I am making a simple react test application.
What is the application: It shows names of projects and has an option to add a new project. Each project has title and category.
This is how it looks
Problem: When I try to add a new project by entering the title and then clicking on submit button, the new project name appears in the projects for a fraction of seconds and then disappears. The project list gets back to the initial state which is three projects (which are shown below)
This is the code
App.js
import React, { Component } from 'react';
import './App.css';
import Projects from "./Components/Projects"
import AddProjects from './Components/AddProject'
class App extends Component {
constructor(){
super();
this.state = {
projects:[]
}
}
componentWillMount(){
this.setState({projects:[
{
title: "Trigger",
category: "Web App"
},
{
title: "Trigger",
category: "Web App"
},
{
title: "Trigger",
category: "Web App"
}
]})
}
handleAddProject(project){
let projects = this.state.projects;
projects.push(project)
this.setState({projects:projects})
console.log(this.state.projects)
}
render() {
return (
<div className="App">
My Project
<AddProjects addProject={this.handleAddProject.bind(this)}/>
<Projects projects={this.state.projects}/>
</div>
);
}
}
export default App;
Projects.js
import React, { Component } from 'react';
import Projectitem from './ProjectItem'
class Projects extends Component {
render() {
let projectItems
if(this.props.projects){
projectItems = this.props.projects.map(project =>{
return (
<Projectitem project={project}/>
);
})
}
else{
console.log("hello")
}
return (
<div>
This is a list of objects
{projectItems}
</div>
);
}
}
export default Projects
ProjectItem.js
import React, { Component } from 'react';
class ProjectItem extends Component {
render() {
return (
<li>
{this.props.project.title}:{this.props.project.category}
</li>
);
}
}
export default ProjectItem
AddProject.js
import React, { Component } from 'react';
var categories = ["Web dev", "Mobile dev", "websiite"]
class AddProject extends Component {
constructor(){
super();
this.state = {
newProject:{}
}
}
handleSubmit(){
this.setState({newProject:{
title:this.refs.title.value,
category:this.refs.category.value
}}, function () {
this.props.addProject(this.state.newProject)
})
}
render() {
var categoryOptions = categories.map(category=>{
return <option key={category} value={category}>{category}</option>
})
return (
<div>
Add Project <br/>
<form onSubmit={this.handleSubmit.bind(this)}>
<div>
<label>title</label><br/>
<input type="text" ref="title"/>
</div>
<div>
<label>Category</label><br/>
<select ref="category">
{categoryOptions}
</select>
</div>
<input type="submit" value="Submit"/>
</form>
</div>
);
}
}
export default AddProject
Add event.preventDefault() to your handleSubmit() method
handleSubmit(event) {
event.preventDefault();
alert('A name was submitted: ' + this.state.value);
}
Without it your page will refresh (as it does by default when a form is submitted)
You are editting the state directly, that's a big no-no in React.
Change your handler function to something like this:
handleAddProject(project){
this.setState((prevState) => {
return { projects: [...prevState.projects, project] }
})
}
The setState function can be called using a 'callback', that receives the state previous to its call, and lets you modify it. The important part is not mutating the state directly (as your projects.push(project) line is doing).
Other option, that's more like what you are already trying to do, is copying the current state before mutating it:
handleAddProject(project){
let projects = this.state.projects.slice(); // notice the slice here will return
// a copy of 'projects', and then
// you modify it
projects.push(project)
this.setState({projects:projects})
console.log(this.state.projects)
}
Also, keep in mind, setState is called asynchronously, so the console.log(this.state.projects) call may show the old state yet, as setState may not have been called yet.
You are using a Form tag which is for submit information to a web server if you use the input type="submit" it will always reload the page and initial everithig as the first time loaded. If you do as #Galupuf it will work
because the event.preventDefault(); will not allow the browser to reload and it will not initial everyting as first time loaded.
My answer is if you are not sending any data to any sever or working with a server at all change the form tag for div the input type="submit" for<button onClick={()=>this.handleSubmit()}>Send</button>
<div>
<div>
<label>title</label><br/>
<input type="text" ref="title"/>
</div>
<div>
<label>Category</label><br/>
<select ref="category">
{categoryOptions}
</select>
</div>
<button onClick={()=>this.handleSubmit()}>Submit</button>
</div>
other suggestion work the modified value of the state like #cfraser suggest like that you will have immutable data

Accessing refs in a stateful component doesn't work in React?

I'm currently trying to refactor the simple-todos tutorial for meteor using presentational and container components, but ran into a problem trying to access the refs of an input in a functional stateless component. I found out that to access refs, you have to wrap the component in a stateful component, which I did with the input.
// import React ...
import Input from './Input.jsx';
const AppWrapper = (props) => {
// ... more lines of code
<form className="new-task" onSubmit={props.handleSubmit}>
<Input />
</form>
}
import React, { Component } from 'react';
This Input should be stateful because it uses class syntax, at least I think.
export default class Input extends Component {
render() {
return (
<input
type="text"
ref="textInput"
placeholder="Type here to add more todos"
/>
)
}
}
I use refs to search for the input's value in the encompassing AppContainer.
import AppWrapper from '../ui/AppWrapper.js';
handleSubmit = (event) => {
event.preventDefault();
// find the text field via the React ref
console.log(ReactDOM.findDOMNode(this.refs.textInput));
const text = ReactDOM.findDOMNode(this.refs.textInput).value.trim();
...
}
The result of the console.log is null, so is my Input component not stateful? Do I need to set a constructor that sets a value for this.state to make this component stateful, or should I just give up on using functional stateless components when I need to use refs?
or should I just give up on using functional stateless components when I need to use refs?
Yes. If components need to keep references to the elements they render, they are stateful.
Refs can be set with a "callback" function like so:
export default class Input extends Component {
render() {
// the ref is now accessable as this.textInput
alert(this.textInput.value)
return (
<input
type="text"
ref={node => this.textInput = node}
placeholder="Type here to add more todos"
/>
)
}
}
You have to use stateful components when using refs. In your handleSubmit event, you're calling 'this.refs' when the field is in a separate component.
To use refs, you add a ref to where you render AppWrapper, and AppWrapper itself must be stateful.
Here's an example:
AppWrapper - This is your form
class AppWrapper extends React.Component {
render() {
return (
<form
ref={f => this._form = f}
onSubmit={this.props.handleSubmit}>
<Input
name="textInput"
placeholder="Type here to add more todos" />
</form>
);
}
};
Input - This is a reusable textbox component
const Input = (props) => (
<input
type="text"
name={props.name}
className="textbox"
placeholder={props.placeholder}
/>
);
App - This is the container component
class App extends React.Component {
constructor() {
super();
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(event) {
event.preventDefault();
const text = this._wrapperComponent._form.textInput.value;
console.log(text);
}
render() {
return (
<AppWrapper
handleSubmit={this.handleSubmit}
ref={r => this._wrapperComponent = r}
/>
);
}
}
http://codepen.io/jzmmm/pen/BzAqbk?editors=0011
As you can see, the Input component is stateless, and AppWrapper is stateful. You can now avoid using ReactDOM.findDOMNode, and directly access textInput. The input must have a name attribute to be referenced.
You could simplify this by moving the <form> tag into the App component. This will eliminate one ref.

Resources