React Component crashes on reload - reactjs

I have a notes-list component that gets the notes data as props from the main component.
Inside the notes-listcomponent, there is a notes-item component which has a dynamic route that loads notesItem-page. So, for every notes-item there is a dynamic url for it's respective notesItem-pagewhich has all the details about the notesItem object. I use Link from react-router-dom
The notes-list component looks like this:
export class NotesList extends Component {
constructor(props) {
super(props);
}
render() {
const { isLoggedIn } = this.props.loggedInContext;
return (
<div className="notes-list">
{this.props.notes.map((notesItem) => (
<Link
to={{
pathname: `${notesItem.slug}`,
id: notesItem._id,
}}
style={{
textDecoration: "none",
color: "#fea82f",
}}
>
<NotesItem notes={notesItem} />
</Link>
))}
</div>
);
}
}
export default loggedInContext(NotesList);
This successfully redirects me to the NotesItem-page with the correct props and inside the notesItem-page I get the receive the id of the object that I had passed as props and make an API call with that particular id in ComponentDidMount() method.
This works perfectly. However, it crashes on reload. It gives the following error:
I am guessing it is because of ComponentDidMount works only once,but I do not seem to find an alternate solution to this problem.
The notesItem-page component looks like this:
export class notesItemPage extends Component {
constructor(props) {
super(props);
this.state = {
notesItem: [],
};
}
componentDidMount() {
fetch(`http://localhost:5000/api/v1/notes/fetch/${this.props.location.id}`)
.then((notesItem) => notesItem.json())
.then((notesItem) =>
this.setState({ notesItem: notesItem.data, isLoaded: true })
);
}
render() {
const { notesItem } = this.state;
return (
<div className="notesItem-details">
<h1> {notesItem.title} Notes</h1>
</div>
);
}
}
export default notesItemPage;
It would be great if anyone could help me with this, thanks!

Issue is here:
<h1> {notesItem.title} Notes</h1>
here the notesItem is coming from an axios call and this data is not available on the time of first component render and this is causing the issue ( app crash ).
So change this:
<h1> {notesItem.title} Notes</h1>
to
<h1> { notesItem && notesItem.title } Notes</h1> // use it when it is available from axios call

Related

Display element based on event fired and props passed in

I am trying, to manipulate another element, by, passing props directly to it, and then have it display itself. If I pass true/false.
Live running code:
https://codesandbox.io/s/keen-dan-rt0kj
I don't know if it's possible to have a system of objects, and based on an event, tell a parent to display a child.
App.js
import React from "react";
import "./styles.css";
import Content from "./components/Content";
export default class App extends React.Component {
state = {
display: false
};
render() {
return (
<div className="App">
<button onClick={() => this.setState({ display: !this.state.display })}>
Display div
</button>
<Content display={this.state.display} />
</div>
);
}
}
./components/Content.js:
import React from "react";
export default class Content extends React.Component {
constructor(props) {
super();
this.state = {
display: props.display
};
}
render() {
const { display } = this.state;
return (
<div
id="mydiv"
className="mydiv"
style={{ display: display ? "block" : "none" }}
>
<h3>A simple div</h3>
</div>
);
}
}
Goal:
I want to based on a state, and based on fired event, display an element that already in store of root.
EDIT: I am aware that, this exists and can be used: import PropTypes from 'prop-types', however, I am not sure this is good practice, since it requires some parent or some other component to implement the props.
JUST Tried:
App:
<Content display={this.state.display} content={"Hello World"} />
Content:
<h3>{this.state.content}</h3>
It seems the passed in text, stored in Content state = {content: props.content} does get displayed, wheres, the boolean value does not work directly. Is there something wrong with sending in a bool ?
try this in your Content Component
export default class Content extends React.Component {
constructor(props) {
super();
this.state = {
};
}
render() {
return (
<>
{this.props.display?(
<div
id="mydiv"
className="mydiv"
>
<h3>A simple div</h3>
</div>
):null}
</>
);
}
}
The reason this may not be working is because you are initiating the state in a way that does not connect the display props after the component is initialized. This means that after the Content component is "constructed", the state of the Content and it's parent are not linked. This is because the constructor() function is only run once to initialize the state.
The best option you have is to not use the internal state of the Content component. Rather than initializing state with the display prop, just use the display prop in your render function.
Trying something like this might work
import React from "react";
export default class Content extends React.Component {
constructor(props) {
super(props);
}
render() {
const { display } = this.props;
return (
<div
id="mydiv"
className="mydiv"
style={{ display: display ? "block" : "none" }}
>
<h3>A simple div</h3>
</div>
);
}
}
Also I would reccommend using state in the root:
import React from "react";
import "./styles.css";
import Content from "./components/Content";
export default class App extends React.Component {
constructor(props) {
super();
state = {
display: false
};
}
render() {
return (
<div className="App">
<button onClick={() => this.setState({ display: !this.state.display })}>
Display div
</button>
<Content display={this.state.display} />
</div>
);
}
}

react recreating a component when I don't want to

I'm super new to react, this is probably a terrible question but I'm unable to google the answer correctly.
I have a component (CogSelector) that renders the following
import React from "react"
import PropTypes from "prop-types"
import Collapsible from 'react-collapsible'
import Cog from './cog.js'
const autoBind = require("auto-bind")
import isResultOk from "./is-result-ok.js"
class CogSelector extends React.Component {
constructor(props) {
super(props)
this.state = {
docs: null,
loaded: false,
error: null
}
autoBind(this)
}
static get propTypes() {
return {
selectCog: PropTypes.func
}
}
shouldComponentUpdate(nextProps, nextState){
if (nextState.loaded === this.state.loaded){
return false;
} else {
return true;
}
}
componentDidMount() {
fetch("/api/docs")
.then(isResultOk)
.then(res => res.json())
.then(res => {
this.setState({docs: res.docs, loaded: true})
}, error => {
this.setState({loaded: true, error: JSON.parse(error.message)})
})
}
render() {
const { docs, loaded, error } = this.state
const { selectCog } = this.props
if(!loaded) {
return (
<div>Loading. Please wait...</div>
)
}
if(error) {
console.log(error)
return (
<div>Something broke</div>
)
}
return (
<>
Cogs:
<ul>
{docs.map((cog,index) => {
return (
<li key={index}>
<Cog name={cog.name} documentation={cog.documentation} commands={cog.commands} selectDoc={selectCog} onTriggerOpening={() => selectCog(cog)}></Cog>
</li>
// <li><Collapsible onTriggerOpening={() => selectCog(cog)} onTriggerClosing={() => selectCog(null)} trigger={cog.name}>
// {cog.documentation}
// </Collapsible>
// </li>
)
})}
{/* {docs.map((cog, index) => { */}
{/* return ( */}
{/* <li key={index}><a onClick={() => selectCog(cog)}>{cog.name}</a></li>
)
// })} */}
</ul>
</>
)
}
}
export default CogSelector
the collapsible begins to open on clicking, then it calls the selectCog function which tells it's parent that a cog has been selected, which causes the parent to rerender which causes the following code to run
class DocumentDisplayer extends React.Component{
constructor(props) {
super(props)
this.state = {
cog: null
}
autoBind(this)
}
selectCog(cog) {
this.setState({cog})
}
render(){
const { cog } = this.state
const cogSelector = (
<CogSelector selectCog={this.selectCog}/>
)
if(!cog) {
return cogSelector
}
return (
<>
<div>
{cogSelector}
</div>
<div>
{cog.name} Documentation
</div>
<div
dangerouslySetInnerHTML={{__html: cog.documentation}}>
</div>
</>
)
}
}
export default DocumentDisplayer
hence the cogSelector is rerendered, and it is no longer collapsed. I can then click it again, and it properly opens because selectCog doesn't cause a rerender.
I'm pretty sure this is just some horrible design flaw, but I would like my parent component to rerender without having to rerender the cogSelector. especially because they don't take any state from the parent. Can someone point me to a tutorial or documentation that explains this type of thing?
Assuming that Collapsible is a stateful component that is open by default I guess that the problem is that you use your component as a variable instead of converting it into an actual component ({cogSelector} instead of <CogSelector />).
The problem with this approach is that it inevitably leads to Collapsible 's inner state loss because React has absolutely no way to know that cogSelector from the previous render is the same as cogSelector of the current render (actually React is unaware of cogSelector variable existence, and if this variable is re-declared on each render, React sees its output as a bunch of brand new components on each render).
Solution: convert cogSelector to a proper separated component & use it as <CogSelector />.
I've recently published an article that goes into details of this topic.
UPD:
After you expanded code snippets I noticed that another problem is coming from the fact that you use cogSelector 2 times in your code which yields 2 independent CogSelector components. Each of these 2 is reset when parent state is updated.
I believe, the best thing you can do (and what you implicitly try to do) is to lift the state up and let the parent component have full control over all aspects of the state.
I solved this using contexts. Not sure if this is good practice but it certainly worked
render() {
return (
<DocContext.Provider value={this.state}>{
<>
<div>
<CogSelector />
</div>
{/*here is where we consume the doc which is set by other consumers using updateDoc */}
<DocContext.Consumer>{({ doc }) => (
<>
<div>
Documentation for {doc.name}
</div>
<pre>
{doc.documentation}
</pre>
</>
)}
</DocContext.Consumer>
</>
}
</DocContext.Provider>
)
}
then inside the CogSelector you have something like this
render() {
const { name, commands } = this.props
const cog = this.props
return (
//We want to update the context object by using the updateDoc function of the context any time the documentation changes
<DocContext.Consumer>
{({ updateDoc }) => (
<Collapsible
trigger={name}
onTriggerOpening={() => updateDoc(cog)}
onTriggerClosing={() => updateDoc(defaultDoc)}>
Commands:
<ul>
{commands.map((command, index) => {
return (
<li key={index}>
<Command {...command} />
</li>
)
}
)}
</ul>
</Collapsible>
)}
</DocContext.Consumer>
)
}
in this case it causes doc to be set to what cog was which is a thing that has a name and documentation, which gets displayed. All of this without ever causing the CogSelector to be rerendered.
As per the reconciliation algorithm described here https://reactjs.org/docs/reconciliation.html.
In your parent you have first rendered <CogSelector .../> but later when the state is changed it wants to render <div> <CogSelector .../></div>... which is a completely new tree so react will create a new CogSelector the second time

Display pagination Wordpress rest api with ReactJS

I have problem with displaying pagination. I'm using Wordpress REST API to fetch my posts
Here is my code:
import React, { Component } from "react";
import "./App.css";
class App extends React.Component {
constructor() {
super();
this.state = {
items: [],
totalPages: '',
nextPage: '',
};
this._loadData = this._loadData.bind(this);
}
componentDidMount() {
const url = 'http://localhost/wp-json/wp/v2/';
this._loadData(url);
}
_loadData(url) {
request.get(url).then((response) => {
this.setState({
items: response.body.items.data,
totalPages: response.body.items.last_page,
nextPage: response.body.items.next_page_url
});
});
}
render() {
let items = _.map(this.state.items, (item) => {
return (
<div key={item.id}>
<div className="content">
<span>
{item.type}
</span>
</div>
</div>
)
});
return (
<div>
{items}
</div>
<div>
<a href="#0" onClick={this._loadData(this.state.nextPage)}/>Next
</div>
}
}
export default App;
I need help beacuse I can not figure out where the problem is. I would appreciate some tutorial or something like that.
Your WordPress's REST API Endpoint isn't correct.
When you fetching data from http://localhost/wp-json/wp/v2/ using GET method, it will returns only the namespaces and routes.
If you would like to pull posts or pages, should use path like this.
http://localhost/wp-json/wp/v2/{post_type}
Replace {post_type} with the post type in plural word.
For example you would like to get latest posts you should request to
http://localhost/wp-json/wp/v2/posts
Also you can preview this url in your browser.

Trying to dynamically select components in React

I'm super new to React and I have two components I want to toggle between based on a user click. I've went about it by creating a 'currentView' state and using that to set/update what the user should be looking at, but when I try to get the components to display it throws tag errors. Here's my code:
class App extends Component {
constructor(props){
super(props);
this.state={
currentView: "Welcome",
};
}
goTrack() {
this.setState({currentView: "Tracker"});
console.log(this.state.currentView);
}
goReview() {
this.setState({currentView: "Review"});
console.log(this.state.currentView);
}
render() {
return (
<div className="App">
<aside>
<nav>
<ul>
<li onClick={()=>this.goTrack()}> Tracker </li>
<li onClick={()=>this.goReview()}> Review </li>
</ul>
</nav>
</aside>
<main>
<this.state.currentView/>
</main>
</div>
);
}
}
export default App;
My question is how should I go about dynamically selecting components to display without re-rendering the entire DOM?
One way to solve this is to use the current state to match a key in an object containing the component you want in a certain state.
constructor() {
super()
this.state = {
current: 'welcome',
}
}
render() {
const myComponents = {
welcome: <Welcome />,
tracker: <Tracker />,
}
const CurrentComponent = myComponents[this.state.current]
return (
<CurrentComponent />
)
}
And when you change the state current with the value 'tracker', Tracker component will be rendered instead of Welcome component.
I am guessing Tracker and Review are two components you want to toggle based on value in this.state.currentView
In short this <this.state.currentView/> expects a component/html element.
one way to do what you want to do would be to do this instead.
<main>
{ this.state.currentView == 'Review' && (
<Review />
)}
{ this.state.currentView == 'Tracker' && (
<Tracker />
)}
</main>
Option 1: Conditions
render() {
const { currentView } = this.state;
// I omit the other stuff to focus on your question
return (
<div>
{currentView === 'Welcome' && <Welcome />}
{currentView === 'Tracker' && <Tracker />}
{currentView === 'Review' && <Review />}
</div>
);
}
Option 2: Dynamic component
import Welcome from './Welcome';
import Review from './Review';
class App extends Component {
constructor(props) {
super(props);
this.state = {
current: Welcome,
};
}
// ... stuff ...
goTrack() {
this.setState(prevState => { ...prevState, current: Review });
}
// ... other stuff ...
render() {
# I rename it because React expects a component's name
# with a capital name.
const { current: Current } = this.state;
# As above, I put only relevant rendering
return <div><Current /></div>;
}
}
Option 3: Guess
And actually, looking at what you are trying to do, I'd suggest you have a look at react-router-dom.

Pass props to React Router's Link Component

I'm passing location props to React Router's Link. It successfully navigates me away, but when I click "back" to go back to the previous page, I get an error:
TypeError Cannot Read property "statusUser" Undefined
It looks like the props are getting messed up when I navigate back.
Sidebar.js
Class Sidebar extends Commponent{
render(){
return(
<Link to={
{
pathname: `/user`,
state:{
statusUser: 'abc',
}
}
}><Button>User Menu</Button>
}
)
}
User.jsx
class user extends Component {
render(){
return(
<UserTable status={this.props.location.state.statusUser}/>
)
}
}
UserTable.jsx
class UserTable extends Component{
render(){
return(
<Link to={
{
pathname: `/user/detail/${this.props.status}`,
state:{
statusUser: this.props.status,
}
}
}><Button>Detail</Button>
)
}
}
UserDetail.jsx
class UserDetail extends Component{
render(){
return(
<p>{this.props.location.state.statusUser}</p>
<Link to={
{
pathname: `/user`,
}
}><Button>Back</Button>
)
}
}
I think the problem is in User.js. How do I make the props fixed like using setState or stuff like that inside User.js file?
Just to be able read the props property, and after reading it go back to the previous page by clicking "back" button.
I'm sorry I just really really new to React so any help would be really appreciate. Thank you.
I believe you're getting that error when you click the Back button in UserDetail.jsx. You aren't passing a state object to User.jsx.
The User.jsx component tries to access this.props.location.state.statusUser, but state doesn't exist, and so it says it can't read the property statusUser on undefined (state is undefined).
Here's what you're missing, with a few typos fixed:
// UserDetail.jsx
class UserDetail extends Component {
render() {
return (
<div>
<p>{this.props.location.state.statusUser}</p>
<Link
to={{
pathname: `/user`
// (You aren't passing `state`)
}}
>
<Button>Back</Button>
</Link> // <-- You weren't closing Link
</div>
);
}
}
// User.jsx
class User extends Component { // <-- Capital 'U'
render() {
return (
<UserTable
status={this.props.location.state.statusUser} // <-- You require `state`
/>
);
}
}
As for how to fix it, the easiest way would be to pass a state object with your "Back" button in UserDetail.jsx.
You can also set defaultProps in React. I'm not sure if that's applicable to you, but it lets you provide a fallback for such things:
Edit: Include example of defaultProps and propTypes.
// User.jsx
class User extends Component {
render() {
return (
<UserTable
status={this.props.location.state.statusUser}
/>
);
}
}
// Maybe something you put in a different file
const defaultLocation = {
state: {
statusUser: '', // <-- Your default statusUser
},
};
User.defaultProps = {
location: defaultLocation,
};
// import PropTypes from 'prop-types';
User.propTypes = {
location: PropTypes.shape({
state: PropTypes.shape({
statusUser: PropTypes.string.isRequired,
})
})
};
I added PropTypes in the above example. It's a way to set checks for the data you're requiring in your components.
Typically, we don't put props into defaultProps if it isRequired, though, because it will never come to that. If you're unsure what default value to give statusUser, I'd recommend starting with making it required in propTypes, and then you can refactor in the future if the need arises.
There were some bugs with your other code samples, too. I've formatted them below, and fixed the bugs. I'll point out what I fixed in comments:
Sidebar.js
class Sidebar extends Component { // <-- Lowercase 'c'; "Component"
render() {
return (
<Link
to={{
pathname: `/user`,
state: {
statusUser: "abc"
}
}}
>
<Button>User Menu</Button>
</Link> // <-- You weren't closing Link
);
}
}
UserTable.jsx
class UserTable extends Component {
render() {
return (
<Link
to={{
pathname: `/user/detail/${this.props.status}`,
state: {
statusUser: this.props.status
}
}}
>
<Button>Detail</Button>
</Link> // <-- You weren't closing Link
);
}
}

Resources