I'm trying to understand the state.props to help me pass a value from a child to a parent. I have a header component (child) that when a link is clicked it should pass a value to a method inside the App component (parent). This method then sets the state of a value which is used for other things. I've tried binding the method inside the App.js file but that isn't working. I get the error when I click a link: ×
TypeError: _this.changeSearchState is not a function
Header.js
import React, { Component } from "react";
import { Link } from 'react-router-dom';
class Header extends Component {
changeSearch = (e) => {
e.preventDefault();
var type = this.value;
this.changeSearchState(type);
}
render() {
return (
<header>
<div className="float-right top-bar">
<Link onClick={this.changeSearch} value="users" to="/users">Users</Link>
<Link onClick={this.changeSearch} value="feedback" to="/feedback">Feedback</Link>
...
</div>
</header>
)
}
}
export default Header;
App.js
class App extends Component {
constructor(props) {
super(props)
this.state = {
isOpen: false,
dataType: ''
}
this.changeSearchState = this.changeSearchState.bind(this);
}
changeSearchState = (dataType) => {
this.setState({
dataType: dataType
});
}
...
I think you want to pass changeSearchState to the Header component as a prop.
In render() for App.js:
<Header
changeSearchState={this.changeSearchState}
/>
Then to access it from Header.js:
changeSearch = (e) => {
e.preventDefault();
var type = this.value;
this.props.changeSearchState(type)
}
You can read more about components and props here.
You should update state just in one place. So you should update it in the Parent component and pass it down to the Child. You can access the method passed from parent to child like a normal prop. Something like this:
App.js
class App extends Component {
constructor(props) {
super(props)
this.state = {
isOpen: false,
dataType: ''
}
this.changeSearchState = this.changeSearchState.bind(this);
}
changeSearchState = (dataType) => {
this.setState({
dataType: dataType
});
}
render() {
return (
<Header changeSearchState={this.changeSearchState} /> //this is what you want!!!
)
}
Header.js
import React, { Component } from "react";
import { Link } from 'react-router-dom';
class Header extends Component {
changeSearch = (e) => {
e.preventDefault();
var type = this.value;
this.props.changeSearchState(type); // call it here with this.props!!
}
render() {
return (
<header>
<div className="float-right top-bar">
<Link onClick={this.changeSearch} value="users" to="/users">Users</Link>
<Link onClick={this.changeSearch} value="feedback" to="/feedback">Feedback</Link>
...
</div>
</header>
)
}
}
export default Header;
Related
I am having a question about how to implement a callback function. In my case, I have a React app with this structure: App > Child > Button components
The problem is I do not know how to write a callback function from Button to Child
I would like to update a value in Child (e.g: inputFromButton) after clicking the button in Button Component. The handleClick() is triggered and a value will be sent to the Child component.
Could someone help me to do this?
Here is my code:https://codesandbox.io/s/nifty-stonebraker-0950w8
The App component
import React from 'react';
import Child from './Child';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
data: 'Data from App'
}
}
handleCallback = (childData) => {
this.setState({ data: childData })
}
render() {
const { data } = this.state;
return (
<div>
<Child dataFromApp={data} />
</div>
)
}
}
export default App
The Child component
import React from 'react';
import { renderButton } from './Button';
class Child extends React.Component {
state = {
inputFromApp: "",
inputFromButton: ""
}
componentDidMount() {
this.setState({
inputFromApp: this.props.dataFromApp
})
}
render() {
const renderButtonItem = renderButton(this.props);
const inputFromApp = this.state.inputFromApp
const inputFromButton= this.state.inputFromButton
return (
<div>
<input value={inputFromApp}></input>
<br></br>
<input value={inputFromButton}></input>
<div>{renderButtonItem}</div>
</div>
)
}
}
export default Child
The Button component
import React from 'react';
export const renderButton = (props) => {
const handleClick = () => {
console.log('handleClick() props data from App: ' + props.dataFromApp)
}
return (
<button onClick={handleClick}>Click</button>
)
}
renderButton is a function component and, therefore, needs to be in PascalCase: RenderButton (although it would be better off as Button).
Move handleClick to the Child component.
Then in Button the call to handleClick should be props.handleClick since handleClick will now be a property of the props object passed into the component. We don't need to pass down the data as a prop to the button but can, instead just log the data prop passed into Child.
handleClick = () => {
console.log(`handleClick(): ${props.dataFromApp}`);
}
In Child, instead of calling renderButton, import Button, and then use that in the render passing down the handler in the props. By doing this you're making the component as "dumb" as possible so it can be reused elsewhere in the application.
<Button handleClick={this.handleClick} />
App.js
import React from 'react';
import './App.css'
import Tools from './components/class/Tools'
import Loading from './components/inc/Loading'
export default class App extends React.Component {
componentDidMount() {
Tools.showLoading(); // or new Tools();
}
render() {
return (
<div className="App">
<Loading />
</div>
)
}
}
Loading.js:
import React from 'react'
export default class Loading extends React.Component {
constructor(props) {
super(props)
this.state = {
display: 'none'
}
}
render() {
return (
<div className="loading" style={{display: this.state.display}}>
<span></span>
</div>
)
}
}
Tools.js
export default class Tools extends React.Component {
static showLoading(){ // or non-static
Loading.setState ...
}
}
I want change display state from outside of Loading component.
I use Loading in whole my project and I want create function for handle it.
Example for another use:
function xxx(){
Tools.showLoading(); // or new Tools();
}
Or:
<span onClick={Tools.showLoading(); // or new Tools();}></span>
Actually, I want create only one function to manage and handle display of Loading.
In Tools.js
let loadingStateSetter = null
export function setLoadingStateSetter(setter) {
loadingStateSetter = setter
return () => loadingStateSetter = null
}
export function setLoadingState(value) {
if (loadingStateSetter !== null) loadingStateSetter(value)
}
In Loading.js:
import { setLoadingStateSetter } from './Tools.js'
export default class Loading extends React.Component {
constructor(props) {
super(props)
this.state = {
display: 'none'
}
}
render() {
return (
<div className="loading" style={{display: this.state.display}}>
<span></span>
</div>
)
}
componentDidMount() {
this.removeStateSetter = setLoadStateSetter((value) => {
this.setState((state) => ({
...state,
display: value,
})
})
}
componentWillUnmount() {
this.removeStateSetter()
}
}
Usage:
import { setLoadingState } from './Tools.js'
function xxx(){
setLoadingState('some value')
}
While you can easily expose a setState function externally, it acts just like any other function, its not usually a good idea. You should instead consider rewriting your Loading component to use the property object to tell it if its loading and track the loading state higher up the component tree where it is accessible by things that would want to change its status.
I think you can using redux as store manager global state
https://redux.js.org/
another way pass it through props and handle it at parent component
I'm working on a React SPA and trying to render JSON data with a filter. I have several containers that each have a class value. When clicking on a container, I am trying to pass a value to a setState. When stepping out of the map function, however, I cannot get the new state change to update.
I'm assuming that the render state isn't getting updated for the state.
import React, { Component } from "react";
import ListData from "../data/list.json";
class Levels extends Component {
constructor(props) {
super(props);
this.state = { newFilter: "" };
this.onClick = this.setFilter.bind(this);
}
setFilter = e => {
console.log(e); //This returns the correct class.
this.setState({ newFilter: e }); //This will not update
};
render() {
console.log(this.state.newFilter); //This just returns the initial state of ''
const activeTab = this.props.keyNumber;
let levelFilter = ListData.filter(i => {
return i.level === activeTab;
});
let renderLevel = levelFilter.map((detail, i) => {
let short_desc;
if ((detail.short || []).length === 0) {
short_desc = "";
} else {
short_desc = <p>{detail.short}</p>;
}
return (
<div
className={`kr_sItem ${detail.class}`}
data-value={detail.class}
onClick={() => this.onClick(detail.class)}
value={detail.class}
key={i}
>
<h1>{detail.title}</h1>
{short_desc}
<img src={`${detail.img}`} />
</div>
);
});
console.log(this.state.newFilter);
return (
<div id="kr_app_wrapper">
<div id="lOneWrapper">{renderLevel}</div>
<div id="lTwoWrapper"></div>
</div>
);
}
}
export default Levels;
Here is the code calling in the Component:
import React, {Component} from 'react';
import Levels from './components/levels2';
import {
Route,
NavLink,
HashRouter
} from "react-router-dom";
import LevelTwo from "./levelTwo";
class LevelOne extends Component{
render(){
return(
<div id="lOneWrapper">
<NavLink to='/Level2'><Levels keyNumber={1} /></NavLink>
</div>
)
}
}
export default LevelOne;
Edit: As pointed out in the comments, binding an arrow function is pointless but it wouldn't cause the error here, so there must be something else going on in some other part of your code.
The problem is you are trying to bind an arrow function that doesn't have an implicit this. Either call setFilter directly (no need to bind this with arrow functions) or change your setFilter function to a regular function:
class Levels extends Component{
constructor(props){
super(props);
this.state = {newFilter: ''};
this.onClick = this.setFilter.bind(this);
}
setFilter(e) {
console.log(e); //This returns the correct class.
this.setState({newFilter: e}); //This will not update
}
im having a real hard time learning react and I can't quite understand how to solve this problem. I have Tournaments.js which reads tournament names from an API and displays them as clickable elements with a reference to templates.js in which I would like to simply display the name of the element that was clicked.
Tournaments.js:
import React, { Component } from "react";
const API = 'http://localhost:8080/api/tournaments';
class Tournaments extends Component {
constructor() {
super();
this.state = {
data: [],
}
}
componentDidMount() {
fetch(API)
.then((Response) => Response.json())
.then((findresponse) => {
console.log(findresponse)
this.setState({
data:findresponse,
})
})
}
testfun(e) {
var target = e.target;
console.log(target)
}
render() {
return(
<div class="container">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<div class="jumbotron text-center">
{
this.state.data.map((dynamicData, key) =>
<div>
<a key={dynamicData.id} href={"/#/template"} onClick={this.testfun}>{dynamicData.name}</a>
</div>
)
}
</div>
</div>
</div>
</div>
)
}
}
export default Tournaments;
template.js:
import React, { Component } from "react";
import Tournaments from "./Tournaments";
const API = 'http://localhost:8080/api/tournaments';
class template extends Component {
constructor() {
super();
this.state = {
data: [],
}
}
render() {
return(
<div>
<h1>clicked item</h1>
</div>
)
}
}
export default template;
And my API stores data looking like this:
[{"name":"abc","id":1,"organizer":"kla"},{"name":"fsdfs","id":2,"organizer":"fsdf"}]
I've tried to use onclick to get the value that was clicked but not sure how to display it in template.js.
Thanks for any help
I think you want to put user clicked item from Tournaments.js and display in Template.js
You can try to put Template inside Tournaments
Tournaments.js
import React, { Component } from "react";
import Template from './template';
class Tournaments extends Component {
constructor() {
super();
this.state = {
data: [],
clickedData: []
}
}
testfun(e) {
var target = e.target;
console.log(target);
let newClickedData = [];
newClickedData.push(target);
this.setState({
clickedData: newClickedData
});
}
render() {
return (
<div>
...
...
<Template clickedData={this.state.clickedData} />
</div>
);
}
}
export default Tournaments;
Template.js
import _ from 'lodash';
import React, { Component } from "react";
class template extends Component {
render() {
let clickedData = this.props.clickedData;
let displayClickedData = _.map(clickedData, (item) => {
return <div>item</div>;
});
return(
<div>
{displayClickedData}
</div>
)
}
}
export default template;
I would suggest picking one of the following options:
Wrap both Tournaments and Tournament in some parent component. Then if you click on any specific tournament, you can change its ID in state. If ID is null, you render list of tournaments, if ID is defined, you render your tournament component. That way you would lose the URL effect.
You can pass ID as GET parameter in URL and then read it in your Tournament component, so your code would look similar to this <a href={`/#/template?id=${dynamicData.id}`}>{dynamicData.name}</a>
You can try out React Router which would handle all that for you
I'm building a react application where the header changes based on the routes, but also depending of the state of other components.
I'm looking for a way to control the header from child components.
For example, I would like that when I click on a button in the main page the header would append new components.
Is there a way to achieve this while avoiding multiple if statements in the header ?
Have a variable in your state which contains the content to be appended to the header. React takes care of reloading all the components when there is a change in the state.
Ex - App.jsx
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router';
import { appendHeaderDemo } from 'redux/demo.js'
class ChildComponent1 extends Component {
render() {
return <h3>Child component 1 added to header</h3>
}
};
class ChildComponent2 extends Component {
render() {
return <h3>Child component 2 added to header</h3>
}
};
class App extends Component {
render() {
const { dispatch } = this.props
return (
<div className='container'>
<h1> This is my header {this.props.appendToHeader} </h1>
{ this.props.appendToHeader == 'Button clicked' &&
<ChildComponent1 />
}
{ this.props.appendToHeader == 'Some Other State' &&
<ChildComponent2 />
}
<button type="button" onClick={ () => onButtonClick() }> Change Header Content </button>
<div className='container'>
{this.props.children}
</div>
</div>
);
};
}
function mapStateToProps(state) {
const { appendToHeader } = state
return {
appendToHeader
}
}
export default connect(mapStateToProps, { onButtonClick: appendHeaderDemo })(App)
redux/demo.js -
export const CHANGE_HEADER_CONTENT = 'CHANGE_HEADER_CONTENT'
const initialState = {
appendToHeader: ''
};
// Reducer
export default function appendHeader(state = initialState, action) {
switch (action.type) {
case CHANGE_HEADER_CONTENT:
return Object.assign({}, state, {
appendToHeader: 'Button clicked'
})
default:
return state
}
}
// Actions
export function appendHeaderDemo() {
return {
type: CHANGE_HEADER_CONTENT
}
}
Dispatch function appendHeaderDemo can be called from any child and the corresponding changes will be reflected in the header (if there is a change in the state attribute appendToHeader)