Dynamic header child component in react/redux application - javascript

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)

Related

ReactJS - How to implement a callback function from JSX

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} />

Why is my react component not re rendering when props comes into the component?

I am currently in a project, and I have had to do null checks on every single props that has come in to children components wether through redux or passing it in myself. I feel like that is not normal with react? Isn't a huge plus side of React is automatic re-rendering? If I try to put anything into state, I can't because There has to be a null check in the render before I do anything with the data. Thanks in advance!!
PARENT COMPONENT =
class App extends Component {
componentDidMount(){
//where I load the data
this.loadCardsFromServer();
this.props.GetAllData();
}
render() {
//NEED TO DO A NULL CHECK FROM THIS COMING FROM REDUX
const filteredData = !!this.state.data ? this.state.data.filter(card =>{
return card.name.toUpperCase().includes(this.state.input.toUpperCase())
}) : null;
return (
//MAKES ME DO ANOTHER NULL CHECK
<div>
{!!this.state.data ? filteredData.map(i => <Card person={i} key={i.created} planet={this.props.planets} />) : null}
</div>
))}
CHILD COMPONENT OF CARD
class Card extends Component {
//WHERE I WANT TO PUT THE PROPS
constructor(){
super();
this.state={
edit: false,
name: this.props.person.name,
birthYear: this.props.person.birth_year
}
}
render() {
let world = null;
//ANOTHER NULL CHECK
if(this.props.planet){
this.props.planet.map(i => {
if(i.id === this.props.person.id){
world = i.name
}
})
}
return (
<div>
//THIS IS WHERE I WANT THE VALUE TO BE STATE
{this.state.edit ? <input label="Name" value={this.state.name}/> : <div className='card-name'>{name}</div>}
</div>
You need to update state when data arrive.
You can do it like this:
import React, { Component } from 'react';
import './App.scss';
import Card from './Components/Card/Card.js';
class App extends Component {
constructor(){
super();
this.state = {
loading:true,
cards:[]
};
}
componentDidMount(){
this.loadCardsFromServer();
}
loadCardsFromServer = () => {
let cardsResponseArray = [];
// fetch your cards here, and when you get data:
// cardsResponseArray = filterFunction(response); // make function to filter
cardsResponseArray = [{id:1,name:'aaa'},{id:2,name:'bbb'}];
setTimeout(function () {
this.setState({
loading:false,
cards: cardsResponseArray
});
}.bind(this), 2000)
};
render() {
if(this.state.loading === true){
return(
<h1>loading !!!!!!!!</h1>
);
} else {
return (
<div>
{this.state.cards.map(card => (
<Card key={card.id} card={card}></Card>
))}
</div>
);
}
}
}
export default App;
And then in your Card component:
import React, { Component } from 'react';
class Card extends Component {
constructor(props){
super(props);
this.props = props;
this.state = {
id:this.props.card.id,
name:this.props.card.name
};
}
render() {
return (
<div className={'class'} >
Card Id = {this.state.id}, Card name = {this.state.name}
</div>
);
}
}
export default Card;
For those interested about React state and lifecycle methods go here
Okay, in this case i craft a little helper's for waiting state in my redux store. I fetch data somewhere (app) and i render a connected component waiting for fetched data in store:
const Loader = (props) => {
if (!props.loaded) {
return null;
}
<Card data={props.data}/>
}
const CardLoader = connect (state => {
return {
loaded: state.data !== undefined
data: state.data
}
})(Loader)
<CardLoader />

ReactJS change state of another class from child

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;

React click button to render the page

In the renderList(), I have a delete button that will delete the content once it is clicked. I am not sure where to put the setState so I put it inside on the onClick(). This doesn't work. I would like to know if I am doing this correct or if there is a better way to solve this.
onClick Function
onClick={() => {
this.props.deleteBook(list.book_id);
this.setState({delete: list.book_id});
}}>
React.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { selectUser } from '../actions/index.js';
import { deleteBook } from '../actions/index.js';
import _ from 'lodash';
class myPage extends Component {
constructor(props) {
super(props);
this.state = {
delete: 0
}
}
componentWillMount() {
this.props.selectUser(this.props.params.id);
}
renderList() {
return this.props.list.map((list) => {
return (
<li className='book-list'
key={list.book_id}>
{list.title}
<button
value={this.state.delete}
onChange={this.onClickChange}
onClick={() => {
this.props.deleteBook(list.book_id);
this.setState({delete: list.book_id});
}}>
Delete
</button>
</li>
);
})
}
render() {
const {user} = this.props;
const {list} = this.props;
if(user) {
return(
<div>
<h2>Date Joined: {user.user.joined}</h2>
<h1>My Page</h1>
<h2>Username: {user.user.username}</h2>
<div>My Books:
<h1>
{this.renderList()}
</h1>
</div>
</div>
)
}
}
}
function mapStateToProps(state) {
return {
user: state.user.post,
list: state.list.all
}
}
export default connect(mapStateToProps, { selectUser, deleteBook })(myPage);
Based on your use of mapStateToProps, seems like you are using Redux. Your list of books comes from the Redux store as props which is external to the component.
You do not need this.state.delete in the component. As state is managed by Redux, it seems like the bug is in your Redux code and not React code. Look into the reducers and ensure that you are handling the delete item action correctly.

How to pass down state and handlers in nested React components

I'm having an issue passing down handlers through my React components.
I've tried following the instructions in the Lifting State Up section of the React docs.
The idea is to have a Page with tabbed navigation, and each tab would render the display of some subpage. I have a component Page.js for my overall page, and that's where I'm storing the activeTab state and where I think I should define the function that handles state change. I then pass down the activeTab state and the handler as props to the TabMenu.js component, which in turn passes it down to the TabItem.js components.
The files:
Page.js
import React, { Component } from 'react';
import TabMenu from './TabMenu';
import FooPage from './FooPage';
import BarPage from './BarPage';
class Page extends Component {
constructor(props) {
super(props);
this.state = {
activeTab: 'foo'
};
this.setActiveTab = this.setActiveTab.bind(this);
}
getVisiblePage() {
switch(this.state.activeTab) {
case 'bar':
return (
<FooPage />
);
case 'foo':
default:
return (
<BarPage />
);
}
}
setActiveTab(e, tab) {
this.setState({
activeTab: tab
});
}
render() {
var visiblePage = this.getVisiblePage();
return (
<section>
<TabMenu
activeTab={ this.state.activeTab }
changeTabHandler={ this.setActiveTab }
/>
{ visiblePage }
</section>
);
}
}
export default Page;
TabMenu.js:
import React, { PropTypes } from 'react';
import TabItem from './TabItem';
const TabMenu = ({ activeTab, changeTabHandler }) => {
const tabs = [
{
key: 'foo',
text: 'Foo Page',
},
{
key: 'bar',
text: 'Bar Page'
},
];
const tabItems = tabs.map((item) => (
<TabItem
key={ item.key }
item={ item }
isActive={ item.key === activeTab }
changeTabHandler={ changeTabHandler }
/>
));
return (
<nav id="TabMenu">
<ul className="tab-items">
{ tabItems }
</ul>
</nav>
);
};
TabMenu.displayName = 'TabMenu';
TabMenu.propTypes = {
activeTab: PropTypes.string,
changeTabHandler: PropTypes.func,
};
export default TabMenu;
TabItem.js
import React, { PropTypes } from 'react';
const TabItem = ({ item, changeTabHandler }) => {
return (
<li onClick={ changeTabHandler(item.key) }>
{ item.text }
</li>
);
};
TabItem.displayName = 'TabItem';
TabItem.propTypes = {
item: PropTypes.object,
changeTabHandler: PropTypes.func,
};
export default TabItem;
The end results is that my console overflows with 1000s of copies of the following error:
warning.js:36 Warning: setState(...): Cannot update during an existing state transition (such as within render or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to componentWillMount.
What am I doing wrong?
That infinite loop is because you have something inside the parent component's render function, which invoke setState or trigger some update to another component which affects the state of the origin or parent component which then will call render again.
in your case, it's because in TabItem.js,
<li onClick={ changeTabHandler(item.key) }>
{ item.text }
</li>
actually invoke changeTabHandler immediately which will do setState in Page, and then TabItem will render and call changeTabHandler again
change it to
<li onClick={() => changeTabHandler(item.key) }>
{ item.text }
</li>
Your changeTabHandler() is getting invoked immediately, once for each <li> you are rendering. Change this:
<li onClick={ changeTabHandler(item.key) }>
to this:
<li onClick={ () => changeTabHandler(item.key) }>

Categories