The Link component is loaded into the Foot component (seen at the bottom).
What I want to happen is for the Content component to load the initial state, and change the state as the user clicks the appropriate button.
I can't seem to change the state unless it's within the same component, but having that state load into a ul is pretty weird.
Link Component
import React, { Component } from 'react';
import Home from './Home';
import Characters from './Characters';
import Plot from './Plot';
import Scenes from './Scenes';
import Notes from './Notes';
class Link extends Component {
constructor() {
super();
this.state = {page: <Home />}
}
loadPage = (page) => {
this.setState({page: page})
}
render() {
const links = [
{
title: 'Home',
page: <Home />
},
{
title: 'Characters',
page: <Characters />
},
{
title: 'Plot',
page: <Plot />
},
{
title: 'Scenes',
page: <Scenes />
},
{
title: 'Notes',
page: <Notes />
}
]
const link = links.map((item, index) => {
return (
<li key={index}>
<button onClick={() => this.loadPage(item.page)}>
{item.title}
</button>
</li>
)
})
return (
<ul className="Link">
{link}
</ul>
);
}
}
export default Link;
Content Component
import React, { Component } from 'react';
import Link from './Link';
class Content extends Component {
render() {
return (
<div className="Content">
{/*this.state.page goes here*/}
</div>
);
}
}
export default Content;
Foot Component (not that relevant, but the general links will go here
import React, { Component } from 'react';
import Link from './Link'
class Foot extends Component {
render() {
return (
<footer className="Foot">
<Link />
</footer>
);
}
}
export default Foot;
If you are using latest react version you can use React context to pass the data.
const {Provider, Consumer} = React.createContext(defaultValue);
Use the provider in your Link component.
<Provider value={/* some value */}>
Use the consumer in your content component.
<Consumer>
{value => /* render something based on the context value */}
</Consumer>
For more details take a look at official documentation.
https://reactjs.org/docs/context.html#reactcreatecontext
You will need to use Props to push the links clicked up to the parent.
So for the following example I am assuming that the Content is going to be the parent Component.
import React, { Component } from 'react';
import Home from './Home';
import Characters from './Characters';
import Plot from './Plot';
import Scenes from './Scenes';
import Notes from './Notes';
class Link extends Component {
// this.props.menuClicked(item.page)
// hits the menuClicked props of Foot Component
render() {
return (
<ul className="Link">
{links.map( (item, index) => {
return (
<li key={ index }>
<a href="#" onClick={ () => this.props.menuClicked(item.page) }>
{ item.title }
</a>
</li>
)
})}
</ul>
);
}
}
export default Link;
And Foot component is the parent of Link Component.
import React, { Component } from 'react';
import Link from './Link'
class Foot extends Component {
constructor() {
super();
this.loadPage = this.loadPage.bind(this)
}
loadPage = (page) => {
// notice this also pushes the page clicked up to the parent component which is
// the Content Component
this.props.changePage(page);
}
render() {
return (
<footer className="Foot">
<Link menuClicked={ () => this.loadPage(page) } />
</footer>
);
}
}
export default Foot;
And the main parent (Grandparent of Link Component) is going to be the Content Component.
// Content
import React, { Component } from 'react';
import Footer from './Footer';
import Home from './Home';
import Characters from './Characters';
import Plot from './Plot';
import Scenes from './Scenes';
import Notes from './Notes';
import Link from './Link';
class Content extends Component {
constructor() {
super()
// default page
this.state = { page: 'Home' }
this.changePage = this.changePage.bind(this);
}
// called everytime the Link is clicked
changePage(newPage) {
this.setState({
page: newPage
})
}
render() {
const links = [
{
title: 'Home',
page: <Home />
},
{
title: 'Characters',
page: <Characters />
},
{
title: 'Plot',
page: <Plot />
},
{
title: 'Scenes',
page: <Scenes />
},
{
title: 'Notes',
page: <Notes />
}
];
let contentToLoad = null,
pageToLoad = this.state.page;
links.forEach( (item) => {
if (item.title == pageToLoad) {
contentToLoad = item.page;
}
});
return (
<div className="Content">
{ contentToLoad }
<Footer changePage={ this.changePage } />
</div>
);
}
}
export default Content;
Based on the link that is clicked in the Link Component, the corresponding content will be loaded in the Content Component. As you can see the Foot Component is just passing what the Link Component is pushing up to the Content Component, you can either do away with the Foot Component altogether or use what #ShubamGupta has proposed, that is using Context APIs.
With ContextAPIs you can have a Provider and a Consumer, and if you follow the link that he has sent, you will be able to pass content directly from the Link Component to the Content Component.
Related
I am trying to understand how to pass props to components in react. I am new to react and I have been struggling to understand the fundamentals of state.
I would like a button to display an overlay and populate the "content" of that overlay with some arbitrary text for now.
I am trying to use a prop called doWeDisplay as variable to hold a css value of "none" or "absolute" (to hide and show the component) and a prop called "content" for the content of the overlay.
Here is my code, could someone please point me in the right direction. I need that eureka moment for it to click in place and my head is all over the place trying to get this.
app.js
import React from 'react';
import Overlay from './components/overlay';
import Header from './components/header';
import Body from './components/body';
import Footer from './components/footer';
import './App.css';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
};
}
render() {
console.log("App props", this.props)
return (
<div className="App">
<Overlay />
<Header />
<Body content={ this.props.appContent } />
<Footer />
</div>
);
}
}
export default App;
body.js
import React from 'react';
import './body.css';
import Overlay from './overlay'
class Body extends React.Component {
constructor(props) {
super(props);
this.state = {
doWeDisplay : "absolute",
content : "Go on"
};
}
render() {
function handleClick(e) {
console.log("Click")
Overlay.setState((state, props) => ({
doWeDisplay : "absolute",
content : "Go on"
}))
}
console.log("Body props ", this.props);
return (
<div className="App-Body">
<p>Here is the body of the page.</p>
<button onClick={ handleClick }>Click me</button>
</div>
);
}
}
export default Body
overlay.js
import React from 'react';
import './overlay.css';
class Overlay extends React.Component {
constructor(props) {
super(props);
this.state = {
doWeDisplay : props.doWeDisplay,
content : props.doWeDisplay
};
}
render() {
console.log("Overlay props " , this.props)
return (
<div className="App-Overlay" style={{ display: this.state.doWeDisplay }}>
{ this.state.content }
</div>
);
}
}
export default Overlay
When you have sibling components that can change other's state, you will need to handle that state in the parent component (in your case it is App).
So the handleClick() function should be in the parent component, and the state itself of the variables doWeDisplay and content should be in App as well.
Then, you can pass that function to Body as a prop, so you could trigger it on click within the Body component. I called that prop clickFunc in my example.
Finally, the last thing you will need is to pass the current state to Overlay as props, so I passed the doWeDisplay and content as different props, that gets the value from App's state.
It should look something like this:
app.js
import React from 'react';
import Overlay from './components/overlay';
import Header from './components/header';
import Body from './components/body';
import Footer from './components/footer';
import './App.css';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
doWeDisplay: "absolute",
content: "Go on"
};
}
handleClick = () => {
console.log("Click");
this.setState({
doWeDisplay: "absolute",
content: "Go on CHANGED!"
});
};
render() {
console.log("App props", this.props);
return (
<div className="App">
<Overlay
doWeDisplay={this.state.doWeDisplay}
content={this.state.content}
/>
<Body clickFunc={this.handleClick} content={this.props.appContent} />
</div>
);
}
}
export default App;
body.js
import React from 'react';
import './body.css';
import Overlay from './overlay'
class Body extends React.Component {
render() {
console.log("Body props ", this.props);
return (
<div className="App-Body">
<p>Here is the body of the page.</p>
<button onClick={() => this.props.clickFunc()}>Click me</button>
</div>
);
}
}
export default Body
overlay.js
import React from 'react';
import './overlay.css';
class Overlay extends React.Component {
render() {
console.log("Overlay props ", this.props);
return (
<div className="App-Overlay" style={{ display: this.props.doWeDisplay }}>
{this.props.content}
</div>
);
}
}
export default Overlay
Here's codesandbox
I am trying to concat the data entered in text field passing data from another stateless component, using props. Not sure why it is not working.
I have created two components
app.js 2. title.js
Data entered in input field needs to concat the string every time and display dynamically using props.
App.js
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import Home from './Home';
import Title from './Title';
class App extends Component {
constructor(props)
{
super(props);
this.state =
{
text : ' ',
collection: []
}
this.eventHandler = this.eventHandler.bind(this);
this.eventSubmit = this.eventSubmit.bind(this);
}
eventHandler(event)
{
this.setState(
{text:event.target.value}
)
}
eventSubmit(event)
{
this.setState(
{collection:this.state.collection.concat(this.state.text)}
)
}
render() {
return (
<div className="App">
<input type="text" onChange ={this.eventHandler} />
<p> {this.state.text} </p>
<input type="submit" onClick={this.eventSubmit} />
<title collection={this.state.collection} />
</div>
);
}
}
export default App;
Title.js
import React from 'react';
const title = (props) =>
{
return (
<div>
<h1> {this.props.collection.toString()} </h1>
<h1> hello </h1>
</div>
);
}
export default title;
setState is async and when you use this.state inside it, it might not re-render. Use function inside setState instead:
eventSubmit(event) {
this.setState((prevState, props) => ({
collection: prevState.collection.concat(prevState.text)
}));
}
See 3. setState() is async: https://codeburst.io/how-to-not-react-common-anti-patterns-and-gotchas-in-react-40141fe0dcd
Mutations are bad in general and can lead to side effects use spread operator(...) to copy prevState array instead.
eventSubmit(event) {
this.setState((prevState) => ({
collection: [...prevState.collection, prevState.text]
}));
}
That's how you append data in array and update the state
Instead of stateless component I have created class component and it worked. Can someone explain me why it didn't worked with stateless why it worked now.
App.js
<code>
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import Home from './Home';
import Title from './Title';
import Collection from './Collection';
class App extends React.Component {
constructor(props)
{
super(props);
this.state =
{
text : ' ',
collection: []
}
this.eventHandler = this.eventHandler.bind(this);
this.eventSubmit = this.eventSubmit.bind(this);
}
eventHandler(event)
{
this.setState(
{text:event.target.value}
)
}
eventSubmit(event)
{
this.setState(
{collection:this.state.collection.concat(this.state.text)}
)
}
render() {
return (
<div className="App">
<h1> ramesh </h1>
<input type="text" onChange ={this.eventHandler} />
<p> {this.state.text} </p>
<input type="submit" onClick={this.eventSubmit} />
<title name ={this.state.collection} />
<Collection name={this.state.collection} />
</div>
);
}
}
export default App;
</code>
Collection.js
<code>
import React, {Component} from 'react';
class Collection extends React.Component
{
render()
{
return(
<div>
<h1> {this.props.name.toString()} </h1>
</div>
);
}
}
export default Collection;
</code>
i want to change state different js but i can not , i have a sidebar.js with react-burger-menu
i want to call and change toggleMenu state in header.js
When I click the menu link, i want to toggle react-burger-menu but different js. this is not working.
sidebar.js
import React from "react";
import PropTypes from "prop-types";
import { reveal as Menu } from "react-burger-menu";
import * as FontAwesome from "react-icons/lib/fa";
export default class SidebarMenu extends React.Component {
constructor (props) {
super(props)
this.state = {
menuOpen: false
}
}
handleStateChange (state) {
this.setState({menuOpen: state.isOpen})
}
closeMenu () {
this.setState({menuOpen: false})
}
toggleMenu () {
this.setState({menuOpen: !this.state.menuOpen})
}
render () {
return (
<div>
<Menu
isOpen={this.state.menuOpen}
onStateChange={(state) => this.handleStateChange(state)}
>
// menu content
</Menu>
</div>
</div>
)
}
}
header.js have link for react-burger-menu
import React from 'react';
import PropTypes from 'prop-types';
import SidebarMenu from "../SidebarMenu";
export default class Header_Video extends React.Component {
render() {
return (
<Container>
<Row>
<Col md={5} sm={12} xs={12} className="text-left mobile-right">
<div className="bar__module">
<a onClick={this.toggleMenu}>Menu</a>
</div>
</Col>
</Row>
</Container>
);
}
}
thanks for help
note: i have a app.js all files import. I want to run toggleMenu in header.js
app.js
const TemplateWrapper = ({ children }) => (
<div id="outer-container">
<SidebarMenu />
<main id="page-wrap" className="page-wrap">
<HeaderVideo /> {children()}
<Footer />
</main>
</div>
);
menuOpen should be in a parent state of both components.
Example:
class App extends React.Component {
constructor (props) {
super(props)
this.state = {
menuOpen: false
}
}
closeMenu = () => {
this.setState({menuOpen: false})
}
toggleMenu = () => {
this.setState({menuOpen: !this.state.menuOpen})
}
render() {
return (
<div>
<SidebarMenu isMenuOpen={this.state.menuOpen} toggleMenu={this.toggleMenu} />
<Header toggleMenu={this.toggleMenu} />
</div>
)
}
}
You may have further errors beyond just this, but the glaring error to me is that toggleMenu() is not bound to the constructor.
https://reactjs.org/docs/handling-events.html
try:
import React from "react";
import PropTypes from "prop-types";
import { reveal as Menu } from "react-burger-menu";
import * as FontAwesome from "react-icons/lib/fa";
export default class SidebarMenu extends React.Component {
constructor (props) {
super(props)
this.state = {
menuOpen: false
}
this.toggleMenu = this.toggleMenu.bind(this);
// the above statement binds the function to the object constructor
}
handleStateChange (state) {
this.setState({menuOpen: state.isOpen})
}
closeMenu () {
this.setState({menuOpen: false})
}
toggleMenu () {
this.setState({menuOpen: !this.state.menuOpen})
}
render () {
return (
<div>
<Menu
isOpen={this.state.menuOpen}
onStateChange={(state) => this.handleStateChange(state)}
>
// menu content
</Menu>
</div>
</div>
)
}
}
You'll also want to use an HTML5 button tag instead of a link tag, the correct HTML semantic structure provides a bunch of underlying features and greatly improves accessibility out of the box.
Also, remove the arrow function and pass a reference to the function, not the returned value. This is so react doesn't call the function immediately but stores the function reference to execute upon the click event.
<button onClick={this.toggleMenu}>Menu</button>
// instead of
<a onClick={() => this.toggleMenu()}>Menu</a>
Hope this helps!
I'm building a search engine with React.js, where I can look for GIPHY gifs using their API. Everytime I type a word(any word), it always loads the same gifs and when I erase and write another word, the gifs don't update.
index.js:
import React from 'react'; //react library
import ReactDOM from 'react-dom'; //react DOM - to manipulate elements
import './index.css';
import SearchBar from './components/Search';
import GifList from './components/SelectedList';
class Root extends React.Component { //Component that will serve as the parent for the rest of the application.
constructor() {
super();
this.state = {
gifs: []
}
this.handleTermChange = this.handleTermChange.bind(this)
}
handleTermChange(term) {
console.log(term);
let url = 'http://api.giphy.com/v1/gifs/search?q=${term.replace(/\s/g, '+')}&api_key=aOfWv08Of7UqS6nBOzsO36NDvwYzO6io';
fetch(url).
then(response => response.json()).then((gifs) => {
console.log(gifs);
this.setState({
gifs: gifs
});
});
};
render() {
return (
<div>
<SearchBar onTermChange={this.handleTermChange} />
<GifList gifs={this.state.gifs} />
</div>
);
}
}
ReactDOM.render( <Root />, document.getElementById('root'));
search.js
import React, { PropTypes } from 'react'
import './Search.css'
class SearchBar extends React.Component {
onInputChange(term) {
this.props.onTermChange(term);
}
render() {
return (
<div className="search">
<input placeholder="Enter text to search for gifs!" onChange={event => this.onInputChange(event.target.value)} />
</div>
);
}
}
export default SearchBar;
Giflist:
import React from 'react';
import GifItem from './SelectedListItem';
const GifList = (props) => {
console.log(props.gifs);
const gifItems = props.gifs && props.gifs.data && props.gifs.data.map((image) => {
return <GifItem key={image.id} gif={image} />
});
return (
<div className="gif-list">{gifItems}</div>
);
};
export default GifList;
GifItem:
import React from 'react';
const GifItem = (image) => {
return (
<div className="gif-item">
<img src={image.gif.images.downsized.url} />
</div>
)
};
export default GifItem;
I can't seem to find where is the issue here. Is it because of this.handleTermChange = this.handleTermChange.bind(this) and there is no "update" state after?
Any help is welcome :) Thanks!
Its because, you are not putting the term value entered by user in the url, all the time you hit the api with static value term, here:
'http://api.giphy.com/v1/gifs/search?q=${term.replace(/\s/g, '+')}&api_key=aOfWv08Of7UqS6nBOzsO36NDvwYzO6io';
Replace ' by ' (tick), like this:
let url = `http://api.giphy.com/v1/gifs/search?q=${term.replace(/\s/g, '+')}&api_key=aOfWv08Of7UqS6nBOzsO36NDvwYzO6io`;
Check MDN Doc for more details about Template Literals.
I need advice on how to pass a Reactjs prop from a component to my app when it's loaded. I'm able to pass the value when my props are in app.js but how do I handle the prop when loading from a separate component?
Here's what's working so far in app.js:
import React, { PropTypes, Component } from 'react';
import { Grid, Nav, Navbar, NavItem, Jumbotron } from 'react-bootstrap';
import { Link } from 'react-router';
import { LinkContainer, IndexLinkContainer } from 'react-router-bootstrap';
function HeaderTitle(props) {
return <h1>{props.name}</h1>;
}
const headerTitle = <HeaderTitle name="About Page" />;
class App extends React.Component {
constructor(props) {
super(props);
}
render() { ...
<p>{headerTitle.props.name}</p>
Parent to Child
If you want to pass all the props through:
render() {
return (
<div>
<HeaderTitle {...this.props} />
</div>
);
}
Or if just some:
render() {
return (
<div>
<HeaderTitle name={this.props.name} />
</div>
);
}
Child to Parent
If you mean the other way round, you need to use a callback
// app
render() {
return (
<div>
<HeaderTitle onGetName={(name) => this.setState({childName: name})} />
<p>{this.state.childName}</p>
</div>
);
}
// headertitle
componentDidMount() {
// maybe some async call or something
axios
.get('/api/say-my-name')
.then(response => this.props.onGetName(response.data);
}
So I guess you need some thing like importing components from one file to another. So you have options to import and export components from another file since React components are basically Objects. In index.js if you have something like
const headerTitle = <HeaderTitle name="About Page" />;
export default headerTitle;
and in app.js
import React, { PropTypes, Component } from 'react';
import { Grid, Nav, Navbar, NavItem, Jumbotron } from 'react-bootstrap';
import { Link } from 'react-router';
import { LinkContainer, IndexLinkContainer } from 'react-router-bootstrap';
import HeaderTitle from '/// put the file relative location to app.js"
function HeaderTitle(props) {
return <h1>{props.name}</h1>;
}
class App extends React.Component {
constructor(props) {
super(props);
}
render() { ...
<p>{headerTitle.props.name}</p>
}