I am trying to create a react component with imported data from Google API. I can see the code is working in the console.log but when I try to use that code in React render method, I am not getting anything. When I move my function inside the class it comes up as the function not defined. I cannot understand why?
function handleTouchTap() {
console.log('CHIP selected');
authorize();
}
function handleAccounts(response) {
console.log(response.result.username);
var username = response.result.username
console.log(username);
}
function authorize(event) {
var useImmidiate = event ? false : true;
var authData = {
client_id: CLIENT_ID,
scope: SCOPES,
immidiate: useImmidiate
};
gapi.auth.authorize(authData, function (response) {
gapi.client.load('analytics', 'v3').then(function () {
console.log(response);
gapi.client.analytics.management.accounts.list().then(handleAccounts);
});
});
}
class Chips extends React.Component {
render() {
return (
<div style={styles.wrapper}>
<Chip
onTouchTap={handleTouchTap}
style={styles.chip} >
<Avatar icon={<FontIcon className="material-icons">perm_identity</FontIcon>} />
Login
</Chip>
<Chip
style={styles.chip} >
<Avatar icon={<FontIcon className="material-icons">account_circle</FontIcon>} />
{this.username}
</Chip>
</div>
);
}
}
In most cases, when you want to render something that might change, you want to add it to the state. That way when you call setState the component knows it needs to rerender and show the changes.
Here I added the functions as component methods, so that you can call this.setState on the result. Ideally you would probably do this with redux and use actions but this will work as a self contained component.
class Chips extends React.Component {
handleTouchTap = () => {
console.log('CHIP selected');
this.authorize();
}
handleAccounts = (response) => {
var username = response.result.username;
this.setState({
username
});
}
authorize = (event) => {
var useImmidiate = event ? false : true;
var authData = {
client_id: CLIENT_ID,
scope: SCOPES,
immidiate: useImmidiate
};
gapi.auth.authorize(authData, (response) => {
gapi.client.load('analytics', 'v3').then(() => {
console.log(response);
gapi.client.analytics.management.accounts.list()
.then(this.handleAccounts);
});
});
}
render() {
return (
<div style={styles.wrapper}>
<Chip
onTouchTap={this.handleTouchTap}
style={styles.chip}>
<Avatar icon={<FontIcon className="material-icons">perm_identity</FontIcon>} />
Login
</Chip>
<Chip
style={styles.chip} >
<Avatar icon={<FontIcon className="material-icons">account_circle</FontIcon>} />
{this.state.username}
</Chip>
</div>
);
}
}
Related
I'm new to React JS and I'm coding a really simple task manager. So, I have all tasks in state element of MyTodoList class (each task has: id, name, description, completed). Then I draw each task separately with Task constant.
I want to implement changing buttons below every task (if task is completed button should be "Done", if not - "Not done").
I do not understand how I can update "completed" attribute (which is in MyTodoList class in state) from const Task.
Would be grateful for any hint!
Code:
import logo from './logo.svg';
import './App.css';
import React from 'react';
function DoneButton({onClick}) {
return (
<button onClick={onClick}>
Done
</button>
);
}
function NotDoneButton({onClick}) {
return (
<button onClick={onClick}>
Not done
</button>
);
}
const Task = ({id, name, description, completed}) => {
const handleDoneClick = () => {
completed= false //something different should be here
}
const handleNotDoneClick = () => {
completed= true //something different should be here
}
let button;
if (completed) {
button = <DoneButton onClick={handleDoneClick} />
} else {
button = <NotDoneButton onClick={handleNotDoneClick} />
}
return (
<div className='task'>
<h3>{name}</h3>
<div>{description}</div>
<div>{completed}</div>
{button}
</div>
)
}
class MyTodoList extends React.Component {
state = {
tasks: [
{
id: 1,
name: 'Walk the dog',
description: 'Have to walk the dog today',
completed: false,
},
],
}
render () {
return(
<div>
<header><h1>TO-DO</h1></header>
<div>{this.state.tasks.map(task => <Task id={task.id} name={task.name}
description={task.description} completed={task.completed}/>)}
</div>
</div>
)
}
}
const App = () => {
return (
<MyTodoList />
)
}
export default App;
You should never re-assign parameters unless it is the only solution you have, but you should definitely never re-assign parameters which you plan to depend on in the render method.
The proper solution would be this:
import React, { useState } from 'react';
...
const Task = ({ id, name, description, completed }) => {
const [isCompleted, setIsCompleted] = useState(completed);
const handleDoneClick = () => {
setIsCompleted(true);
};
const handleNotDoneClick = () => {
setIsCompleted(false);
};
let button;
if (isCompleted) {
button = <DoneButton onClick={handleDoneClick} />;
} else {
button = <NotDoneButton onClick={handleNotDoneClick} />;
}
return (
<div className="task">
<h3>{name}</h3>
<div>{description}</div>
<div>{isCompleted}</div>
{button}
</div>
);
};
You need to use local state, in which you will set the initial value (completed, or not completed) which you are receiving from props, and then change the state, and not the parameter. Furthermore, continue using the state value of your completed (isCompleted) so React will react to its change.
This is not the final solution though, as this will only keep the local change of the task, and not change the task status in tasks list.
Basically, if you component A holds the tasks and their complete status, you need to create a method in the component A which will modify the respective task by ID, to the correct status. Then you need to pass the respective method to component B which will call the method and pass along the id and complete status (true / false) The method which is assigned in component A will then look through the list of tasks, find the proper task by ID, and assign its new completed value you passed from component B. After that, react does its thing and automatically updates completed prop you passed to component B
Working snippet:
function DoneButton({ onClick }) {
return <button onClick={onClick}>Done</button>;
}
function NotDoneButton({ onClick }) {
return <button onClick={onClick}>Not done</button>;
}
const Task = ({ id, name, description, completed, onTaskClick }) => {
const handleDoneClick = () => {
onTaskClick(id, false);
};
const handleNotDoneClick = () => {
onTaskClick(id, true);
};
let button;
if (completed) {
button = <DoneButton onClick={handleDoneClick} />;
} else {
button = <NotDoneButton onClick={handleNotDoneClick} />;
}
return (
<div className="task">
<h3>{name}</h3>
<div>{description}</div>
<div>{completed}</div>
{button}
</div>
);
};
const MyTodoList = () => {
const [tasks, setTasks] = React.useState([
{
id: 1,
name: 'Walk the dog',
description: 'Have to walk the dog today',
completed: false,
}
]);
const onTaskClick = React.useCallback(
(id, isCompleted) => {
const updatedTasks = [...tasks].map((task) => {
if (task.id === id) {
return {
...task,
completed: isCompleted,
};
}
return task;
});
setTasks(updatedTasks);
},
[tasks]
);
return (
<div>
<header>
<h1>TO-DO</h1>
</header>
<div>
{tasks.map((task) => (
<Task onTaskClick={onTaskClick} id={task.id} name={task.name} description={task.description} completed={task.completed} />
))}
</div>
</div>
);
};
const App = () => <MyTodoList />;
ReactDOM.render(<App />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
Getting undefined data type error while fetching data from JSON
I have searched at many places but didn't get the suitable answer
import SavedData from "./SavedData";
export default class Saved extends React.Component {
constructor() {
super();
this.state = {
loading: true,
datas: [],
};
}
async componentDidMount() {
const url = "https://todo-list-site.herokuapp.com/todo-data";
const response = await fetch(url);
const todoData = response.json().then((res) => {
this.setState({ datas: res });
});
}
render() {
console.log(this.state.datas[0].description); //not able to get data
return (
<div>
{/* {this.state.datas.map((items) => (
<SavedData
key={items.countTodo}
title={items.title}
desc={items.desc}
/>
))} */}
</div>
);
}
}
Someone help me so that I can proceed
Just like Dave Newton has pointed out in the comments, the render is triggered before the request completes. This is normal and you just need to handle it properly.
If you see the console logs of this codesandbox, you can see that initially this.state.datas is just an empty array [] - so any attempt to access this.state.datas[0].description will be undefined. Only after the state is updated when the request completes, the logs show the data retrieved - this is because according to the mount lifecycle of a React Component, the render() is called before the componentDidMount() and also the request being async.
This is very common and it is even recommended by the official React docs to make HTTP calls in componentDidMount(). The docs also has provided an example to handle this issue.
import SavedData from "./SavedData";
export default class Saved extends React.Component {
constructor() {
super();
this.state = {
loading: true, // we initially set this to true
datas: [],
};
}
async componentDidMount() {
const url = "https://todo-list-site.herokuapp.com/todo-data";
const response = await fetch(url);
const todoData = response.json().then((res) => {
this.setState({
datas: res,
loading: false // when the request is complete, we set this to false
});
});
}
render() {
if (this.state.loading) {
// during the first render, loading will be true and we
// can return a loading message or a spinner
return (
<div>Loading...</div>
);
}
// when render is called after the state update, loading will be false
// and this.state.datas will have the fetched data
console.log(this.state.datas[0].description);
return (
<div>
{this.state.datas.map((items) => (
<SavedData
key={items.countTodo}
title={items.title}
desc={items.desc}
/>
))}
</div>
);
}
}
Your datas state is initially an empty array until your componentDidMount fires and sets the state. As a result, your console log will then be undefined until the state is set. In order to combat this you must wait for this.state.datas[0] to be true before accessing the first objects description within the array. The following code seems to work as expected
import React from "react";
export default class Saved extends React.Component {
constructor() {
super();
this.state = {
loading: true,
datas: []
};
}
async componentDidMount() {
const url = "https://todo-list-site.herokuapp.com/todo-data";
const response = await fetch(url);
response.json().then((res) => {
this.setState({ datas: res });
});
}
render() {
console.log(this.state.datas[0] && this.state.datas[0].description);
return (
<div>
{this.state.datas.map((items, i) => (
<div key={i}>
<div> title={items.title}</div>
<div> desc={items.description}</div>
</div>
))}
</div>
);
}
}
I have this code which works perfectly:
componentDidMount() {
fetch('https://services2.arcgis.com/sJvSsHKKEOKRemAr/arcgis/rest/services/Bigfoot%20Locations/FeatureServer/0/query?where=1%3D1&outFields=*&outSR=4326&f=json')
.then((response) => {
return response.json();
})
.then((myJson) => {
this.setState({data: myJson.features[0].attributes.STATE_NAME})
console.log(this.state.data)
});
}
render() {
return (
<div className = ''>
{this.state.data}
</div>
)
}
}
However when I try to make the data set in state more general so that I can render whatever I want like this:
componentDidMount() {
fetch('https://services2.arcgis.com/sJvSsHKKEOKRemAr/arcgis/rest/services/Bigfoot%20Locations/FeatureServer/0/query?where=1%3D1&outFields=*&outSR=4326&f=json')
.then((response) => {
return response.json();
})
.then((myJson) => {
this.setState({data: myJson.features})
console.log(this.state.data)
});
}
render() {
return (
<div className = ''>
{this.state.data[0].attributes.STATE_NAME}
</div>
)
}
}
I get "Cannot read property STATE_NAME of undefined. The only change is that I tried to access the object in the render method instead of ComponentDidMount. What's the issue here?
In your component, the render() function is being called before the data is populated, even though componentDidMount() will run before the first render.
What you need is to store an intermediate loading state in your react state to indicate that the data has not yet arrived.
class RENAME_ME extends Component {
state = {
loaded: false,
data: [],
};
componentDidMount() {
fetch(
"https://services2.arcgis.com/sJvSsHKKEOKRemAr/arcgis/rest/services/Bigfoot%20Locations/FeatureServer/0/query?where=1%3D1&outFields=*&outSR=4326&f=json"
)
.then((response) => {
return response.json();
})
.then((myJson) => {
this.setState({
data: myJson.features[0].attributes.STATE_NAME,
loaded: true,
});
console.log(this.state.data);
});
}
render() {
// Data is still loading, display an intermediate message
if (!this.state.loaded) {
return <p>Loading...</p>;
}
return <div className="">{this.state.data}</div>;
}
}
You shouldn't read from the state until it's present:
render() {
return (
<div className = ''>
{(this.state.data && this.state.data.length) ? this.state.data[0].attributes.STATE_NAME : `still loading, or maybe an error`}
</div>
)
}
Only display the state when it is present so this condition has 2 parts.
First part(this.state.data) is only true when the data is saved in the state so the next part(this.state.data[0].attributes.STATE_NAME) runs after that
render() {
return (
<div className = ''>
{this.state.data && this.state.data[0].attributes.STATE_NAME}
</div>
)
}
}
Your state 'data' is not properly initialized to handle object maybe
are they initialized like this?
this.state = {
data: []
You can render the value whenever it is present by
{this.state.data[0].attributes && this.state.data[0].attributes.STATE_NAME}
Iam using Ant Design for React Js UI. Am using Tree component to show up in the list. I also have 2 button to expand and collapse the Tree list. I use the defaultExpandAll prop to manage this.
On the expand and collapse button click i set a bool to true and false respectively.
Button it doesn't expand on the button click.
If I set True initially to that flag state it works.
Is there any work Around.
I have 2 components. (Expand and collapse button are in parent component)
**Parent Component**
setExpandOrCollapse(value) {
this.setState({ expandOrCollapse: value });
}
<HeaderRow>
<Button onClick={() => this.setExpandOrCollapse(true)}>Expand All</Button>
<Button onClick={() => this.setExpandOrCollapse(false)}>Collapse All</Button>
</HeaderRow>
<Card>
{ItemTree && (ItemTree.length > 0) ? (
<ItemTree
dataSource={ItemTree}
expandOrCollapse={expandOrCollapse}
/>
) : null }
</Card>
**Child Component**
<Tree
draggable={isDraggable}
defaultExpandAll={expandOrCollapse}
>
{loopitemNodes(dataSource)}
</Tree>
dataSource is obtained from Redux api call.
Is there any work around.
The states in Ant design which are prefixed with default only work when they are rendered for the first time (and hence the default).
For working out programmatic expand and collapse, you need to control the expansion of tree using expandedKeys and onExpand props.
import { flattenDeep } from "lodash";
class Demo extends React.Component {
state = {
expandedKeys: []
};
constructor(props) {
super(props);
this.keys = this.getAllKeys(treeData);
}
getAllKeys = data => {
// This function makes an array of keys, this is specific for this example, you would have to adopt for your case. If your list is dynamic, also make sure that you call this function everytime data changes.
const nestedKeys = data.map(node => {
let childKeys = [];
if (node.children) {
childKeys = this.getAllKeys(node.children);
}
return [childKeys, node.key];
});
return flattenDeep(nestedKeys);
};
onExpand = expandedKeys => {
console.log("onExpand", expandedKeys);
// if not set autoExpandParent to false, if children expanded, parent can not collapse.
// or, you can remove all expanded children keys.
this.setState({
expandedKeys
});
};
renderTreeNodes = data =>
data.map(item => {
if (item.children) {
return (
<TreeNode title={item.title} key={item.key} dataRef={item}>
{this.renderTreeNodes(item.children)}
</TreeNode>
);
}
return <TreeNode key={item.key} {...item} />;
});
expandAll = () => {
this.setState({
expandedKeys: this.keys
});
};
collapseAll = () => {
this.setState({
expandedKeys: []
});
};
render() {
return (
<Fragment>
<button onClick={this.expandAll}>Expand All</button>
<button onClick={this.collapseAll}>Collapse All</button>
<Tree onExpand={this.onExpand} expandedKeys={this.state.expandedKeys}>
{this.renderTreeNodes(treeData)}
</Tree>
</Fragment>
);
}
}
Codesandbox
class Demo extends React.Component {
state = {
expandedKeys: ["0-0-0", "0-0-1"],
autoExpandParent: true,
selectedKeys: []
};
onExpand = expandedKeys => {
console.log("onExpand", expandedKeys);
// if not set autoExpandParent to false, if children expanded, parent can not collapse.
// or, you can remove all expanded children keys.
this.setState({
expandedKeys,
autoExpandParent: false
});
};
onSelect = (selectedKeys, info) => {
console.log("onSelect", info);
this.setState({ selectedKeys });
};
renderTreeNodes = data =>
data.map(item => {
if (item.children) {
return (
<TreeNode title={item.title} key={item.key} dataRef={item}>
{this.renderTreeNodes(item.children)}
</TreeNode>
);
}
return <TreeNode key={item.key} {...item} />;
});
onExpandAll = () => {
const expandedKeys = [];
const expandMethod = arr => {
arr.forEach(data => {
expandedKeys.push(data.key);
if (data.children) {
expandMethod(data.children);
}
});
};
expandMethod(treeData);
this.setState({ expandedKeys });
};
onCollapseAll = () => {
this.setState({ expandedKeys: [] });
};
render() {
return (
<Fragment>
<Button onClick={this.onExpandAll} type="primary">
ExpandAll
</Button>
<Button onClick={this.onCollapseAll} type="primary">
CollapseAll
</Button>
<Tree
onExpand={this.onExpand}
expandedKeys={this.state.expandedKeys}
autoExpandParent={this.state.autoExpandParent}
selectedKeys={this.state.selectedKeys}
>
{this.renderTreeNodes(treeData)}
</Tree>
</Fragment>
);
}
}
please refer to the Codesandbox link
Feels like I'm missing something obvious here - but I can't figure out how to access my JSON data. I have a Container component:
class About extends Component {
componentDidMount(){
const APP_URL = 'http://localhost/wordpress/'
const PAGES_URL = `${APP_URL}/wp-json/wp/v2/pages`
this.props.fetchAllPages(PAGES_URL, 'about')
}
render(){
return (
<div>
<Header/>
<div className="bg">
<div className="home-wrapper">
<h1>AAAAABBBBBOOOOUUUUUT</h1>
<Counter/>
<AboutInfo />
</div>
</div>
<Footer/>
</div>
)
}
}
const mapDispatchToProps = (dispatch) => {
return bindActionCreators({ fetchAllPages }, dispatch)
}
export default connect(null, mapDispatchToProps)(About);
And a Smart component:
class AboutInfo extends Component {
render(){
console.log(this.props.page);
console.log(this.props.page.id);
return (
<div>
<h1>This is ID: {this.props.page.id}</h1>
</div>
)
}
}
const mapStateToProps = ({ page }) => {
return { page }
}
export default connect(mapStateToProps)(AboutInfo);
My action:
export const fetchAllPages = (URL, SLUG) => {
var URLEN;
if(!SLUG){
URLEN = URL
} else {
URLEN = URL + "?slug=" + SLUG
}
return (dispatch) => {
dispatch(fetchRequest());
return fetchPosts(URLEN).then(([response, json]) => {
if(response.status === 200){
if(!SLUG) {
dispatch(fetchPagesSuccess(json))
} else {
dispatch(fetchPageBySlugSuccess(json))
}
} else {
dispatch(fetchError())
}
})
}
}
const fetchPageBySlugSuccess = (payload) => {
return {
type: types.FETCH_PAGE_BY_SLUG,
payload
}
}
My reducer:
const page = (state = {}, action) => {
switch (action.type) {
case FETCH_PAGE_BY_SLUG:
console.log(action.paylod)
return action.payload
default:
return state
}
}
This gives me:
When I console.log(this.props.page) in my AboutInfo component, it prints the object, but when I print console.log(this.props.page.id) it gives me undefined. Why can't I print the JSON content? Thanks!
page is an array and hence this.props.page.id is undefined. You might want to access the first element in array in which case you would do
this.props.page[0].id
but you might also need to add a test, since before the response is available you will be trying to access page[0].id and it might break.
You could instead write
this.props.page && this.props.page[0] && this.props.page[0].id
Getting data from the store is async So you must adding loading varibale on your reducer
class AboutInfo extends Component {
render(){
if(this.props.loading) return (<div>loading</div>);
return (
<div>
<h1>This is ID: {this.props.page.id}</h1>
</div>
);
}
}
const mapStateToProps = ({ page, loading }) => {
return { page, loading }
}
on your action try returing
json.page[0]
That is because page is an array and the id is a property of its 1st element.
So use this.props.page[0].id
If the logged object in your screenshot is the this.props.page then you will need and additional .page as that is also a part of the object this.props.page.page[0].id