I'm a beginner in React and I'm facing a problem that I can't solve.
I get an object from an API call.
In my console log, it appears well. I am able to access first-level properties (like ID for example) but if I want to access ACF values for example I get the error:
TypeError: can't access property "date_project", this.state.projet.acf is undefined
I guess I don't do correctly to get the data from the ACF object but I don't understand how to do otherwise.
Here's my code:
[import React, { Component } from 'react';
import '../App.scss';
import {Row, Container, Col} from 'react-bootstrap';
import 'bootstrap/dist/css/bootstrap.min.css';
import {Config} from '../config';
import { withRouter } from "react-router";
class Projet extends Component {
constructor(props) {
super(props);
this.state = {
projet: \[\]
};
}
async componentDidMount() {
const id = parseInt(this.props.match.params.id);
const response = await fetch(Config.apiUrl + `wp-json/wp/v2/projet/${id}`);
const json = await response.json();
this.setState({ projet: json });
}
renderProjet() {
console.log('projet', this.state.projet)
return (
<p>{this.state.projet.acf.date_projet}</p>
)
}
render() {
return (
<Container fluid className="App-home">
<Row className="align-items-left">
<div>
{ this.renderProjet()}
</div>
</Row>
</Container>
)
}
}
export default (Projet);][1]
It's because of asynchronous call. When its trying to access acf at that time it is not available. Either create loading variable to track it or do not render component if data is not available:
render() {
if(!this.state.projet?.acf) return null; //Do not render if data is not available
return (
<Container fluid className="App-home">
<Row className="align-items-left">
<div>
{ this.renderProjet()}
</div>
</Row>
</Container>
)
}
Related
i have a random list of 10 dogs and fetching it from api https://dog.ceo/api/breeds/image/random/10 and with every refresh i get another 10 dogs list so my question is how do i make favorite list out of it and show it on another page. I dont want to use Localstorage or redux or context api.
DogHome.js
import "bootstrap/dist/css/bootstrap.min.css";
import "../../Pages/Pages.css";
import DogList from "../Dog/DogList";
import React, { Component } from "react";
class DogHome extends Component {
constructor(props) {
super(props);
this.state = {
dogs: [],
};
}
async componentDidMount() {
try {
const url = "https://dog.ceo/api/breeds/image/random/10";
const response = await fetch(url);
const data = await response.json();
this.setState({ dogs: data.message });
} catch (e) {
console.error(e);
}
}
render() {
return (
<div className="home">
<DogList dogs={this.state.dogs} />
</div>
);
}
}
export default DogHome;
DogList.js
import React from "react";
import Dog from "./Dog";
import "../../Pages/Pages.css";
const DogList = (props) => {
const dogsArray = props.dogs.map((dogURL, index) => {
return <Dog key={index} url={dogURL} />;
});
return (
<>
<div className="doglist">{dogsArray}</div>
</>
);
};
export default DogList;
and lastly here i have a favorite button in every dog image which i want to make favorite on click.
Dog.js
import React from "react";
import { Button } from "react-bootstrap";
import "../../Pages/Pages.css";
const Dog = (props) => {
return (
<div id="child">
<img
style={{ width: 300, height: 300 }}
src={props.url}
alt="ten dogs list"
/>
<Button >Favorite</Button>
</div>
);
};
export default Dog;
You say don't want to use those APIs but you need a method to store favorites. How are you going to decide which are the favorites? How else are you planning to store the values of favorite dogs? You can use a database or a flat file. But you might as well take advantage of some of the React state management if that's your goal.
React Newbie
I am coding in React. I am taking an object of JSON data from a GET request to an api, and trying to pass it as a prop in a component. Then I am mapping over it to make a list of "trail" objects.
I am getting this error in the console:
"Warning: Failed prop type: Invalid prop trail of type array supplied to TrailItem, expected object."
Here's the code for my app level component:
import React, { Component } from "react";
import "./App.css";
import Navbar from "./components/layout/Navbar";
import Trails from "./components/trails/Trails";
import axios from "axios";
class App extends Component {
state = {
trails: {},
};
async componentDidMount() {
const res = await axios.get(
`https://www.hikingproject.com/data/get-trails?lat=35.0844&lon=-106.6504&maxDistance=10&key=${process.env.REACT_APP_HIKING_PROJECT_KEY}`
);
console.log(res.data);
this.setState({ trails: res.data });
}
render() {
return (
<div className='App'>
<Navbar />
<div>
<Trails trails={this.state.trails} />
</div>
</div>
);
}
}
export default App;
As far as I can tell, there is no problem with the data. A console.log(res.data); returns an object, so I know the api request is working.
Here's the code for my "Trails" component:
import React, { Component } from "react";
import TrailItem from "./TrailItem";
class Trails extends Component {
render() {
return (
<div style={trailStyle}>
{Object.keys(this.props.trails).map((key) => (
<TrailItem key={key} trail={this.props.trails[key]} />
))}
</div>
);
}
}
const trailStyle = {
display: "grid",
gridTemplateColumns: "repeat(3, 1fr)",
gridGap: "1rem",
};
export default Trails;
I feel like maybe I'm not using the correct syntax to step into the object, and then further into the "trails" array, but I'm stumped. Thank you for you help!
EDIT
Here is the "TrailItem" code:
import React from "react";
import PropTypes from "prop-types";
const TrailItem = ({ trail: { name, location, imgSmall } }) => {
return (
<div className='card text-center'>
<img src={imgSmall} alt='trail' style={{ width: "25%" }} />
<h3>{name}</h3>
<p>{location}</p>
</div>
);
};
TrailItem.propTypes = {
trail: PropTypes.array.isRequired,
};
export default TrailItem;
I followed the advice of one of the comments and changed the PropType to array, and that fixed one of the warnings. But I still can't get a list of <TrailItem />.
Inside you App render method put this at the start:
if (!this.state.trails.length) {
return <div>Loading...</div>;
}
You can give trails a default or initial value of an empty array so the map function can be invoked. By using an empty array the component will map over an the empty array and return an empty array to render.
Default Value:
const Trails = ({ trails }) => {
console.log(trails.trails);
return (
<div style={trailStyle}>
{trails.map(trail => <TrailItem key={trail.id} trail={trail} />)}
</div>
);
};
TrailItem.propTypes = {
trail: PropTypes.array.isRequired,
};
TrailItem.defaultValue = {
trail: [],
};
Initial Value:
const Trails = ({ trails = [] }) => {
console.log(trails.trails);
return (
<div style={trailStyle}>
{trails.map(trail => <TrailItem key={trail.id} trail={trail} />)}
</div>
);
};
TrailItem.propTypes = {
trail: PropTypes.array.isRequired,
};
Note: This won't fix passing a prop of the incorrect type, but the prop validation react does will. It sounds like you got that bit sorted out though.
I solved it! Granted, I may not have been clear in my original question, but I figured out why I couldn't get access to the data object from the API.
I needed to step into the object one more time upon receiving the response in my App component:
this.setState({ trails: res.data.trails });
Once I did that, in my Trails component I needed Object.key() to make turn the "trails" prop into an array so I could .map() over it.
And finally, the "tricky" part was that I needed to use each "key" as the index for each "trail" prop I was trying to pass to <TrailItem />:
{Object.keys(trails).map((key) => (
<TrailItem key={key} trail={trails[key]} />
))}
New to React - I am trying to use multiple contexts within my App component, I tried following the official guide on multiple contexts.
Here is my current code:
App.js
import React from "react";
import { render } from "react-dom";
import Login from "./Login";
import AuthContext from "./AuthContext";
import LayoutContext from "./LayoutContext";
import LoadingScreen from "./LoadingScreen";
class App extends React.Component {
render() {
const { auth, layout } = this.props;
return (
<LayoutContext.Provider value={layout}>
<LoadingScreen />
<AuthContext.Provider value={auth}>
<AuthContext.Consumer>
{auth => (auth.logged_in ? console.log("logged in") : <Login />)}
</AuthContext.Consumer>
</AuthContext.Provider>
</LayoutContext.Provider>
);
}
}
render(<App />, document.getElementById("root"));
Login.js
import React from "react";
class Login extends React.Component {
render() {
return (
<div></div>
);
}
}
export default Login;
AuthContext.js
import React from "react";
const AuthContext = React.createContext({
logged_in: false
});
export default AuthContext;
LayoutContext.js
import React from "react";
const LayoutContext = React.createContext({
show_loading: false
});
export default LayoutContext;
LoadingScreen.js
import React from "react";
import LayoutContext from "./LayoutContext";
class LoadingScreen extends React.Component {
render() {
return (
<LayoutContext.Consumer>
{layout =>
layout.show_loading ? (
<div id="loading">
<div id="loading-center">
<div className="sk-chasing-dots">
<div className="sk-child sk-dot1"></div>
<div className="sk-child sk-dot2"></div>
</div>
</div>
</div>
) : null
}
</LayoutContext.Consumer>
);
}
}
export default LoadingScreen;
Following the example, I never really understood how this.props (in App.js) could hold my different contexts.
Both auth and layout show up as undefined, this.props is empty, which will in turn cause my app to throw errors such as Cannot read property 'show_loading' of undefined
I immediately liked the example provided in the React documentation, but I can't get this to work.
I've made a small snippet to show you how you could structure your context providers and consumers.
My App component in this case is the root of the app. It has all the providers, along with the value for each one of them. I am not changing this value, but I could if I wanted to.
This then has a single child component, MyOutsideComponent, containing all the chained consumers. There are better ways to do this, I just wanted to show you, one by one, how chaining consumers work. In practice you can neatly reduce this using a few techniques.
This MyOutsideComponent has the actual component, MyComponent, which takes all the context elements and just puts their value on the page. Nothing fancy, the point was to show how the values get passed.
let FirstContext = React.createContext('first');
let SecondContext = React.createContext('second');
let ThirdContext = React.createContext('third');
let FourthContext = React.createContext('fourth');
let MyComponent = (props) => {
return (<span >{Object.values(props).join(" ")}</span>);
};
let App = (props) => {
return (
<FirstContext.Provider value="this is">
<SecondContext.Provider value="how you">
<ThirdContext.Provider value="pass context">
<FourthContext.Provider value="around">
<MyOutsideComponent />
</FourthContext.Provider>
</ThirdContext.Provider>
</SecondContext.Provider>
</FirstContext.Provider>
);
};
let MyOutsideComponent = () => {
return ( < FirstContext.Consumer >
{first =>
(< SecondContext.Consumer >
{second =>
(< ThirdContext.Consumer >
{third =>
(<FourthContext.Consumer >
{fourth =>
(<MyComponent first={first} second={second} third={third} fourth={fourth} />)
}
</FourthContext.Consumer>)
}
</ThirdContext.Consumer>)
}
</SecondContext.Consumer>)
}
</FirstContext.Consumer>);
}
ReactDOM.render(<App />, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app"></div>
Now, for the actual explanation. createContext gives you two actual components: a Provider and Consumer. This Provider, as you found out, has the value. The Consumer takes as child a single function taking one argument, which is your context's value.
This is where the docs are a bit unclear, and a bit which I hope I can help a bit. This does not get passed automatically in props unless the Provider is the direct parent of the component. You have to do it yourself. So, in the example above, I chained four consumers and then lined them all up in the props of my component.
You've asked about class-based components, this is how it ends up looking like:
let FirstContext = React.createContext('first');
let SecondContext = React.createContext('second');
let ThirdContext = React.createContext('third');
let FourthContext = React.createContext('fourth');
class MyComponent extends React.Component {
render() {
return ( < span > {Object.values(this.props).join(" ")} < /span>);
}
}
class App extends React.Component {
render() {
return (
<FirstContext.Provider value = "this is" >
<SecondContext.Provider value = "how you" >
<ThirdContext.Provider value = "pass context" >
<FourthContext.Provider value = "around" >
<MyOutsideComponent / >
</FourthContext.Provider>
</ThirdContext.Provider >
</SecondContext.Provider>
</FirstContext.Provider >
);
}
}
class MyOutsideComponent extends React.Component {
render() {
return (
<FirstContext.Consumer >
{ first =>
(< SecondContext.Consumer >
{ second =>
( < ThirdContext.Consumer >
{ third =>
( < FourthContext.Consumer >
{ fourth =>
( < MyComponent first = {first} second={second} third={third} fourth={fourth} />)
}
</FourthContext.Consumer>)
}
</ThirdContext.Consumer>)
}
</SecondContext.Consumer>)
}
</FirstContext.Consumer>
);
}
}
ReactDOM.render( < App / > , document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app" />
Using Next.js / React.js I have build a simple app using the setup as shown in the official Next.js tutorial. This means that I am using a <Layout /> component which is rendered around the different pages. I show my code below.
Now, using render props I try to set some state of the <Layout /> component from a child component. When doing this I get the following error:
TypeError: setLoginToken is not a function
Can someone explain why this is happening, and show me how to get this to work? I get this error when clicking one of the two buttons on
My code:
Layout component (components/Layout.js)
import React from "react";
class Layout extends React.Component {
state = {
loginToken: "abc123"
};
setLoginToken = newToken => {
this.setState({ loginToken: newToken });
};
render() {
const { render } = this.props;
const { loginToken, setLoginToken } = this.state;
return (
<React.Fragment>{render({ loginToken, setLoginToken })}</React.Fragment>
);
}
}
export default Layout;
index page (pages/index.js)
import React from "react";
import Layout from "../components/Layout";
class Index extends React.Component {
render() {
return (
<Layout
render={({ loginToken, setLoginToken }) => (
<div>
<p>Login token: {loginToken}</p>
<button onClick={() => setLoginToken("asdfasdf")}>Log in</button>
<button onClick={() => setLoginToken("")}>Logout</button>
</div>
)}
/>
);
}
}
export default Index;
I have also tried the following, with the same result:
(setLoginToken) => setLoginToken("asdfasdf")
(setLoginToken) => setLoginToken("")
The issue come from that you're destructuring setLoginToken from this.state, instead of this.
You can one shot this with
const { state: { loginToken }, setLoginToken } = this
So I'm trying to pass a field from a MongoDB into a React Component as such:
import React from 'react';
import ReactDOM from 'react-dom';
import { Meteor } from 'meteor/meteor';
import { Tracker } from 'meteor/tracker';
import { Players } from './../imports/api/players';
import TitleBar from './../imports/UI/TitleBar';
import AddPlayer from './../imports/UI/AddPlayer';
import Player from './../imports/UI/Player';
const renderPlayers = (playersList) => {
return playersList.map((player) => {
return <Player key={player._id} />;
});
};
But the component isn't able to read the passed in player._id value.
import React from 'react';
import { Players } from './../api/players';
export default class Player extends React.Component {
render() {
return (
<p key={this.props.player._id}>
{this.props.player.name} has {this.props.player.score} point(s).
<button onClick={() => {Players.update(this.props.player._id, { $inc: { score: 1 } });}}>+1</button>
<button onClick={() => {Players.update(this.props.player._id, { $inc: { score: -1 } });}}>-1</button>
<button onClick={() => Players.remove(this.props.player._id)}>x</button>
</p>
);
}
}
Functionality wise - the code works as intended - just not as a component here because player._id isn't able to be passed in. How do I go about passing the player._id field in properly?
Error: TypeError: Cannot read property '_id' of undefined
I thought it might be because there was no data entries in the DB - but I made sure to make a few beforehand and test that, didn't work unfortunately.
In order to use _id or fields of the player object in the child component, you should pass a player object as props to child component from the Parent component. Check the modified code below
const renderPlayers = (playersList) => {
return playersList.map((player) => {
return <Player key={player._id} player={player} />;
});
}
then your child component will work fine.