my home component has:
const Home = () => (
<div>
<Head title="Home" />
<Nav />
<div className="container o_block u_blue">
<div className="notification">
This container is <strong>centered</strong> on desktop.
</div>
</div>
</div>
)
export default Home
and I'm trying to add some DOM manipulation into that component:
componentDidMount() {
let burger = document.querySelector('.burger');
let nav = document.querySelector('#'+burger.dataset.target);
burger.addEventListener('click', function(){
burger.classList.toggle('is-active');
nav.classList.toggle('is-active');
});
}
const Home = () => (
<div>
<Head title="Home" />
<Nav />
<div className="container o_block u_blue">
<div className="notification">
This container is <strong>centered</strong> on desktop.
</div>
</div>
</div>
)
export default Home
but unfortunately I am getting a:
SyntaxError: Unexpected token, expected ";" (8:20)
what am I doing wrong and where should I put the method?
Home is a presentational component in your code. Which means a presentational component is like a pure function in Java Script. A Presentational component doesn’t call any React life cycle method and doesn’t modify React state. It only takes props and returns jsx elements. This is also called as stateless component in react.
If you want to play with React life cycle methods then you should go with statefull component.
componentDidMount is one of React life cycle method so it’s not accessible in presentational or functional components in React.
Edit:
If you want to do DOM manipulation before component initial render then do DOM manipulation in componentWillMount() method but please see this method is deprecated in latest React versions.
If you want to do DOM manipulation after first render then do that in componentDidMount() method. This is the one wr you also make axios calls and do setState accordingly. I would recommend you to go with componentDidMount().
import React, { Component} from "react";
export default class Home extends Component {
componentDidMount() {
let burger = document.querySelector('.burger');
let nav = document.querySelector('#'+burger.dataset.target);
burger.addEventListener('click', function(){
burger.classList.toggle('is-active');
nav.classList.toggle('is-active');
});
}
render(){
return(
<div>
<Head title="Home" />
<Nav />
<div className="container o_block u_blue">
<div className="notification">
This container is <strong>centered</strong> on desktop.
</div>
</div>
</div>
)
}
}
Please excuse me if there are any typo error because I am answering in my mobile
You need to transform your component into a class-based component, like this :
export default class Home extends React.Component {
render() {
// Return the JSX code
}
componentDidMount() {
// Your init code
}
}
But I really suggest you to take a look at the official React doc as it's a fairly simple mistake.
Related
I'm new to React and having some difficulty trying to add a new child component to a component that has already been rendered.
I have an App component which initially contains a Main component (main menu).
I also have Popover components which I want to appear on top of Main when they are children of <App> (and hence siblings of <Main>).
These Popover components vary in number. Each <Popover> can contain buttons which launch another <Popover> over the top again. So the structure would be like
<App>
<Main></Main>
<Popover></Popover>
<Popover></Popover>
...
</App>
However, when the page first loads there are no Popover components open, and the<App> is rendered without any. Here is a stripped-down version of my code:
class App extends React.Component{
constructor(props){ super(props) }
render(){
return (
<div>{this.props.children}</div>
)
}
}
class Main extends React.Component{
constructor(props){ super(props) }
render(){
return (
//main menu stuff here
)
}
}
ReactDOM.render(<App><Main /></App>, root);
How can I add new <Popover>s to my <App> when the user clicks something? Before React I would simply do App.appendChild(Popover) kind of thing, but I'm quite lost here.
I should add that the elements the user will click to trigger an initial <Popover> are not contained within <Main>; they are outside of the <App>, as I am trying to slowly transition my existing page to using React. I think this could be part of my problem.
So basically in React, you have multiple ways of doing this, but to be more reliable you need to have data that represents the dynamic components you will render in your DOM. And to do this you need to create a state and a function that can add new information to your state. Then simply by sharing this function with your other components, you can trigger it from wherever you want, and this will update your state which will increase the amount of dynamic components you will render.
Take a look at this example
import { useState } from "react";
export default function App() {
const [popups, setPopups] = useState([]);
const addNewPopup = () => {
setPopups([...popups, { title: "I am a popup" }]);
};
return (
<div className="App">
<ChildComponent onClick={addNewPopup} />
{popups.map((p) => {
return <Popup title={p.title} />;
})}
</div>
);
}
function ChildComponent({ onClick }) {
return (
<div>
<p>I am a child component</p>
<button onClick={onClick}>Add new element</button>
</div>
);
}
function Popup({ title }) {
return <div>I am a popup with title = {title}</div>;
}
I am using React with Node.js.
I have a component ItemList, which fetches some API in the componentDidMount() method, because it allows me to easily render a "loading state".
I need to pass a state to this component, which would change the API's url using a toggle button. This toggle button is an individual component (ToggleButton). These two components are siblings and I am using parent as a way for these components to communicate.
I thought Context is perfect for this kind of job. The issue is, that using React's Context is just re-rendering (calling the function render() of a ItemList and not remounting the component, thus not calling the componentDidMount() method or even constructing the component again).
export const ToggleContext = React.createContext({
switched: false
});
export default class Items extends React.Component
{
constructor(props)
{
super(props)
this.state = {
switched: false
}
this.toggleSwitch = () => {
this.setState(state => ({
switched: !state.switched
}))
}
}
render()
{
console.log(this.state.switched)
return (
<>
<div class="page-header">
<div class="container-fluid">
<h2 class="h5 mb-0">Items</h2>
</div>
</div>
<section class="py-0">
<div class="container-fluid">
<ToggleContext.Provider value={this.state.switched}>
<ToggleButton callFunc={this.toggleSwitch}/>
<ItemList loadWeb={this.state.switched}/>
</ToggleContext.Provider>
</div>
</section>
</>
)
}
}
ItemList component is heavily inspirated by React docs
I am succesfully getting the changed state through ToggleButton in parent component, sending it to ItemList and picking it up as props.loadWeb, I am just not sure if my implementation is wrong or even if what I demand is possible with Context.
Is it possible to reconstruct the whole component using context, should I use refs, sould I fetch the API in the render() method, etc.?
So it appears that you need your componentDidMount called on every toggle of ItemList. But as you noted, it does not remount every time. As such, using componentDidUpdate inside your ItemList is more appropriate to toggle that API link.
Docs: https://reactjs.org/docs/react-component.html#componentdidupdate
I also encourage you to check out this article on using Hooks vs Classes: https://blog.bitsrc.io/6-reasons-to-use-react-hooks-instead-of-classes-7e3ee745fe04
What you are doing seems right, I would say, you could probably simplify (in my opinion) by using function components along with hooks.
import React, {useState} from 'react';
const Items = () => {
const [toggled, toggle] = useState(false);
return (
<div class="container-fluid">
<div class="page-header">
<h2 class="h5 mb-0">Items</h2>
</div>
<section class="py-0">
<ToggleButton callFunc={toggle(!toggled)}/>
<ItemList loadWeb={toggled}/>
</section>
</div>
);
};
I am attempting to pass the changePage class method into a child component called SideBar. When the changePage method is then triggered by an onClick event in the child component I receive the following error:
Uncaught TypeError: this.SetState is not a function
From what I could find in other similar posts I need to bind the changePage method to this. I have done that but I still can't manage to get is to work.
I also saw many suggestions to use ES6 arrow functions for my methods but I get the exact same error message if I do.
I'm still quite new at web development and any help would be appreciated.
Parent Component called Main:
import React from 'react';
import Content from './Content';
import Sidebar from './Sidebar';
export default class Main extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedPage: 'home',
pages: ['home','about','skills','contact'],
};
this.changePage = this.changePage.bind(this);
}
changePage(page) {
console.log(page);
this.SetState({
selectedPage: page,
pages: ['home','about','skills','contact']
});
};
render() {
return (
<div>
<div id="sidebar" className="side-bar">
<Sidebar
changePage={this.changePage}
selectedPage={this.state.selectedPage}
pages={this.state.pages}
/>
</div>
<div id="main" className="main-content">
<Content
selectedPage={this.state.selectedPage}
/>
</div>
</div>
)
}
}
Child Component:
import React from 'react';
export default class Sidebar extends React.Component {
render() {
console.log("content props",this.props);
const buttons = this.props.pages.map(button =>
<span
className='nav-button'
id={button}
key={button}
onClick={() => this.props.changePage(button)}
>
<img src={`./app/images/${button}.svg`} />
</span>
);
return (
<div>
<span>
<img className='headshot' src='./app/images/headshot.jpg' />
</span>
<div className='nav-container'>
{buttons}
</div>
</div>
)
}
}
You have syntax error: this.SetState. Change it to this.setState.
The typo in setState was causing the error, but you may want consider a cleaner way to bind the parent this on the changePage prop. Your way works, but if you changed sidebar Component inclusion to:
<Sidebar
changePage={ (page) => this.changePage(page)}
selectedPage={this.state.selectedPage}
pages={this.state.pages}
/>
That may make it clearer what is going on (changePage prop is a function which takes one parameter and passes that parameter to instance method this.changePage), and removes the binding gymnastics in the constructor.
Good Evening !!
My question title might be off, but here is the problem i'm trying to address. I have a component (Content) which listens for DOM event and call Board (instance which facilitate communication between Containers and their consuming Applications).
import Board from '../Board';
class Content extends React.Component {
constructor(props){
super(props);
}
componentDidMount(){
window.addEventListener("message", this.handleCheck, false);
}
componentWillUnmount(){
window.removeEventListener("message", this.handleCheck, false);
}
handleCheck =(event) => {
const { board } = this.props;
board.call({
size: event.detail.size,
panel: event.detail.panel,
.....
})
}
render(){
return null
}
}
I can consume/call Content component as mentioned below,
import Manager from '../../Manager'
const Example = () => (
<div>
<Manager>
<Content pageType="A4" />
</Manager>
</div>
);
The Manager component utilizes the Board API to manage call requests, and maintains the state of it's children. The component provided as children to Manager should also support Board prop.
In Example component i would like to call <Content pageType="A4" /> instead of wrapping with <Manager> and somehow use the <Manager> within the Content component definition (inside the Content component to leverage Manager). i.e
const Example = () => (
<div>
<Content pageType="A4" />
</div>
);
Pretty sure you are just looking for the basic HOC implemenation...
function withManager(Component) {
class WithManager extends React.Component {
...withManagerStuff
render() {
return <Component/>
}
}
return WithManager;
}
and then where you want to use your components with the shared HOC (ContentWithManager) you can do something like - module.exports = withManager(Content)
This stuff gets complex quickly.
I may be off, as I am slightly confused with what you are trying to do. However, I think you need to pass the wrapped (child) component to the wrapping (parent) component.
Here are two HOC examples of how to do this:
Note: Examples use redux and react-router, but the same pattern should
work without redux or react-router.
Redirect HOC: redirect.hoc.js
Redirect HOC Example: trans.app.container.js
<!-- Redirect HOC Example Code -->
<Route component={RedirectHoc(MainContentContainer)} />
Authorization HOC: authorization.hoc.js
<!-- Authorization HOC Example Code -->
<Route exact path="/beer/add" component={AUTHORIZE_ADMIN(BeerAddComponent)}/>
I'm learning React using JSX and ES6 and I've got a pretty decent handle on how to create components and route to different views using ReactRouter4.
What I still haven't been able to figure out is for example how i can create an Admin page where I input the details of a work for my portfolio and have all the works render on the another page, presumably Portfolio page.
Here's what I've got.
App.js loads the Portfolio.js component
import React from 'react';
import Navigation from './Navigation';
import Title from './Title';
import Portfolio from './Portfolio';
class App extends React.Component {
render() {
return(
<div className="container-fluid">
<div className="container">
<div className="row">
<div className="col-sm-12">
<Navigation />
<Title title="kuality.io"/>
<section className="app">
<Portfolio works={this.props.works} />
</section>
</div>
</div>
</div>
</div>
)
}
}
export default App;
The Portfolio.js component has a constructor to bind a unique method named addWork(), the React methods componentWillMount() and componentWillUnmount() to handle state, and the default render(). One more thing to mention about this component is that it's calling a component called ../base which has all the details to an online DB via Firebase. So if that's relevant as to where it is place, then take that into consideration otherwise don't sweat it.
import React from 'react';
import Work from './Work';
import Admin from './Admin';
import base from '../base';
class Portfolio extends React.Component {
constructor() {
super();
this.addWork = this.addWork.bind(this);
// getInitialState
this.state = {
works: {}
};
}
componentWillMount() {
this.ref = base.syncState(`/works`
, {
context: this,
state: 'works'
})
}
componentWillUnmount() {
base.removeBinding(this.ref);
}
addWork(work) {
// update our state
const works = {...this.state.works};
// add in our new works with a timestamp in seconds since Jan 1st 1970
const timestamp = Date.now();
works[`work-${timestamp}`] = work;
// set state
this.setState({ works });
}
render() {
return(
<div>
<section className="portfolio">
<h3>Portfolio</h3>
<ul className="list-of-work">
{
Object
.keys(this.state.works)
.map(key => <Work key={key} details={this.state.works[key]}/>)
}
</ul>
</section>
</div>
)
}
}
export default Portfolio;
Inside of the Object i'm mapping through the Work component that is just a list item I have made another component for and isn't really relevant in the question.
Finally I have the Admin.js and AddWorkForm.js components. I abstracted the AddWorkForm.js so that I could use it elsewhere if need be, basically the main idea behind React Components, so that's why I chose to do it that way.
import React from 'react';
import Title from './Title';
import AddWorkForm from './AddWorkForm';
class Admin extends React.Component {
constructor() {
super();
this.addWork = this.addWork.bind(this);
// getInitialState
this.state = {
works: {}
};
}
addWork(work) {
// update our state
const works = {...this.state.works};
// add in our new works with a timestamp in seconds since Jan 1st 1970
const timestamp = Date.now();
works[`work-${timestamp}`] = work;
// set state
this.setState({ works });
}
render() {
return(
<div className="container-fluid">
<div className="container">
<div className="row">
<div className="col-sm-12">
<Title title="Admin"/>
<section className="admin">
<AddWorkForm addWork={this.addWork} />
</section>
</div>
</div>
</div>
</div>
)
}
}
export default Admin;
and the AddWorkForm.js component which is basically a form that onSubmit creates and object and resets the form
import React from 'react';
class AddWorkForm extends React.Component {
createWork(event) {
event.preventDefault();
console.log('Creating some work');
const work = {
name: this.name.value,
desc: this.desc.value,
image: this.image.value
}
this.props.addWork(work);
this.workForm.reset();
}
render() {
return(
<form ref={(input) => this.workForm = input} className="work-edit form-group" onSubmit={(e) => this.createWork(e)}>
<input ref={(input) => this.name = input} type="text" className="form-control" placeholder="Work Title"/>
<textarea ref={(input) => this.desc = input} type="text" className="form-control" placeholder="Work Description"></textarea>
<input ref={(input) => this.image = input} type="text" className="form-control" placeholder="Work Image"/>
<button type="submit" className="btn btn-primary">+Add Work</button>
</form>
)
}
}
export default AddWorkForm;
Here is the file that includes where I'm using ReactRouter:
import React from 'react';
import { render } from 'react-dom';
// To render one method from a package user curly brackets, you would have to know what method you wan though
import { BrowserRouter, Match, Miss} from 'react-router';
import './css/normalize.css';
import './css/bootstrap.css';
import './css/style.css';
// import '../js/bootstrap.js';
import App from './components/App';
import WorkItem from './components/WorkItem';
import Capability from './components/Capability';
import Connect from './components/Connect';
import NotFound from './components/NotFound';
import Admin from './components/Admin';
const Root = ()=> {
return(
<BrowserRouter>
<div>
<Match exactly pattern="/" component={App} />
<Match pattern="/work/:workId" component={WorkItem} />
<Match exactly pattern="/capability" component={Capability} />
<Match exactly pattern="/connect" component={Connect} />
<Match exactly pattern="/admin" component={Admin} />
<Miss component={NotFound} />
</div>
</BrowserRouter>
)
}
render (<Root />, document.querySelector('#main'));
So here's what I've tried and failed to accomplish, and it's likely some kind of this.props solution that I haven't been able to define, I need to create the work in Admin.js component, which creates the object and then have it throw that object to Portfolio.js component so it can render it via the Work.js component and it doesn't add the object to the DB.
This works when i put all the components on the same page, which isn't ideal because then anyone accessing my Portfolio could add a work. Sure I could start the process of learning authentication and how to make that component appear or disappear based on user credentials, but I'd much rather also learn the very valuable skill of being able to have my admin page on a separate view all together because I see another application for learning to do so.
Would love to hear others opinions on this and where they may be able to determine I'm failing here.
Btw, I realize I have other components like Nav.js and Title.js but they are not necessary in order to illustrate the example.
Thank you.
You can pass components as props and when using React Router you can have named components.
For data sharing between siblings is better advised to have the data on a parent component although you could use context, but this is not advised and may be unacessible on future versions.
If you need to create something on another component (don't know why) you could pass a function that would render it.