I'm trying to fetch records from backend graphql service and render them with Array.map function. Unfortunately before they're loaded I get error because they are undefined. I tried to set default props on component but it didin't work. Do i have to check if everything is loaded or is there specific way to inject default values into those props. My code looks like that right now
import React from 'react';
import { graphql } from 'react-apollo';
import { fetchTasks } from '../../../graphql/tasks';
import { Dashboard } from '../components/Dashboard';
const propTypes = {
data: React.PropTypes.shape({
tasks: React.PropTypes.array
})
};
const defaultProps = {
data: {
tasks: []
}
};
class DashboardContainer extends React.Component {
render() {
const titles = this.props.data.tasks.map(task => task.title);
return(
<Dashboard
titles={titles}
/>
);
}
}
DashboardContainer.propTypes = propTypes;
DashboardContainer.defaultProps = defaultProps;
export default graphql(fetchTasks)(DashboardContainer);
Yes you have to check if the query has finished to load. You could go through this nice tutorial, where you build a pokemon app. The link points to the part where they show a basic query and how you check if it is loaded.
In your case it could look like this:
import React from 'react';
import { graphql } from 'react-apollo';
import { fetchTasks } from '../../../graphql/tasks';
import { Dashboard } from '../components/Dashboard';
const propTypes = {
data: React.PropTypes.shape({
tasks: React.PropTypes.array
})
};
const defaultProps = {
data: {
tasks: []
}
};
class DashboardContainer extends React.Component {
render() {
if (this.props.data.loading) {
return <div > Loading < /div>;
}
const titles = this.props.data.tasks.map(task => task.title);
return ( <
Dashboard titles = {
titles
}
/>
);
}
}
DashboardContainer.propTypes = propTypes;
DashboardContainer.defaultProps = defaultProps;
export default graphql(fetchTasks)(DashboardContainer);
Related
I'm trying to map / convert object from props in React. Data is coming from rest-api library and to component via mapStateToProps.
But when i try to use Object.keys its empty
console.log('storagesTree', this.props.storagesTree);
console.log('data', this.props.storagesTree.data);
let items = Object.keys(this.props.storagesTree.data);
console.log('length', items.length);
Below console report screen
UPDATE
this is my raw data from api:
{"#context":"\/admin\/api\/user_storages","#type":"UserStoragesList","user":"5eb2a2ae37f018528939f883","groups":{},"storages":{"first":{"create":true,"read":true,"update":true,"delete":true},"second":{"create":true,"read":true,"update":true,"delete":true}}}
file with fetching data from rest via redux-api library
import "isomorphic-fetch";
import reduxApi, {transformers} from "redux-api";
import adapterFetch from "redux-api/lib/adapters/fetch";
export default reduxApi({
storagesTree: {data: {}},
userInfo: {
url: `/admin/api/user_storages_test`,
postfetch: [
function({data, getState}) {
let {storagesTree} = getState();
for(const storage in data.storages) {
if(data.storages[storage].read === true) {
storagesTree.data[storage] = {};
}
}
}
]
}
}).use("rootUrl", "http://localhost:84").use("fetch", adapterFetch(fetch));
and my component, where i trying to use Object.keys
import React, { Component } from 'react'
import { render } from 'react-dom'
import {connect} from 'react-redux';
class StoragesTree extends Component {
render() {
console.log(JSON.stringify(this.props.storagesTree.data, null, 3))
console.log('storagesTree', this.props.storagesTree);
console.log('data', this.props.storagesTree.data);
var items = Object.keys(this.props.storagesTree.data);
console.log('lengths', items.length);
return (
<div>
<div>StoragesTree</div>
</div>
)
}
}
const mapStateToProps = state => {
const { userInfo, storagesTree } = state
return {
userInfo,
storagesTree,
}
}
export default connect(mapStateToProps)(StoragesTree)
index.jsx
import React from 'react'
import { render } from 'react-dom'
import Main from './Main'
window.DatoCmsPlugin.init((plugin) => {
plugin.startAutoResizer()
const container = document.createElement('div')
document.body.appendChild(container)
render(<Main plugin={plugin} />, container)
})
Main.jsx
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import connectToDatoCms from './connectToDatoCms';
import './style.sass';
#connectToDatoCms(plugin => ({
developmentMode: plugin.parameters.global.developmentMode,
fieldValue: plugin.getFieldValue(plugin.fieldPath),
}))
export default class Main extends Component {
static propTypes = {
fieldValue: PropTypes.bool.isRequired,
}
render() {
const { fieldValue } = this.props;
return (
<div className="container">
{JSON.stringify(fieldValue)}
</div>
);
}
}
connectToDatoCms.jsx
import React, { Component } from 'react'
export default mapPluginToProps => BaseComponent => (
class ConnectToDatoCms extends Component {
constructor (props) {
super(props)
this.state = mapPluginToProps(props.plugin)
}
componentDidMount () {
const { plugin } = this.props
this.unsubscribe = plugin.addFieldChangeListener(plugin.fieldPath, () => {
this.setState(mapPluginToProps(plugin))
})
}
componentWillUnmount () {
this.unsubscribe()
}
render () {
return <BaseComponent {...this.props} {...this.state} />
}
}
)
I used this command to generate a starter code for datoCMS plugin, npx -p yo -p generator-datocms-plugin -c 'yo datocms-plugin'.
What is # in #connectToDatoCms, Main.jsx.
#connectToDatoCms uses the decorator pattern.
Your webpack config in your code is setup to process decorators, probably using babel-plugin-transform-decorators
Decorators are similar to HOCs
Decorators are just a wrapper around a function. They are used to enhance the functionality of the function without modifying the underlying function.
With the current HOC syntax pattern the above could have been used as
import connectToDatoCms from './connectToDatoCms';
import './style.sass';
class Main extends Component {
static propTypes = {
fieldValue: PropTypes.bool.isRequired,
}
render() {
const { fieldValue } = this.props;
return (
<div className="container">
{JSON.stringify(fieldValue)}
</div>
);
}
}
const mapPluginToProps = plugin => ({
developmentMode: plugin.parameters.global.developmentMode,
fieldValue: plugin.getFieldValue(plugin.fieldPath),
})
export default connectToDatoCms(mapPluginToProps)(Main);
I would like to use some data I received from firestore to build a quiz. Unfortunately I can console.log the array, but if I use .length it is undefined.
Is this problem caused by some lifecycle or asnynchronous issue?
Thanks in advance!
import React, { Component } from 'react';
import { connect } from 'react-redux';
class LernenContainer extends Component {
constructor(props) {
super(props);
this.state = {
data: []
}
}
render() {
return (
<div className="lernenContainer">
LernenContainer
{
console.log(this.props.firestoreData),
// prints array correctly
console.log(this.props.firestoreData.length)
// is undefined
}
</div>
);
}
}
const mapStateToProps = state => {
return {
firestoreData: state.firestoreData
};
};
const mapDispatchToProps = dispatch => {
return {
// todo Achievements
};
};
export default connect(mapStateToProps, mapDispatchToProps) (LernenContainer);
console.log(this.props.firestoreData):
Try below code
import React, { Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types'
class LernenContainer extends Component {
constructor(props) {
super(props);
}
static propTypes = {
firestoreData: PropTypes.object.isRequired
}
render() {
const { firestoreData } = this.props
console.log(firestoreData);
console.log(firestoreData.length);
return (
<div className="lernenContainer">
</div>
);
}
}
const mapStateToProps = (state) => ({
firestoreData: state.firestoreData
})
const mapDispatchToProps = (dispatch) => ({
})
export default connect(mapStateToProps,mapDispatchToProps)(LernenContainer);
I am using react native and react navigation for routing.
How to update state from another component/page?
HomeScreen
export class HomeScreen extends Component {
constructor(){
this.state = {
test: ''
}
}
updateState = ()=>{
this.setState({test:'new value'});
}
}
SideMenuScreen
import { HomeScreen } from "./home";
export class SideMenuScreen extends Component {
updateHomeState = ()=>{
let oHome = new HomeScreen();
oHome.updateState();
}
}
My App.js and routing and sidemenu config as below :
import { createAppContainer, createDrawerNavigator } from "react-navigation";
import { SideMenuScreen } from "./screens/Sidemenu";
import { HomeScreen } from "./screens/Home";
export default class App extends Component {
render() {
return(
<AppContainer></AppContainer>
);
}
}
const AppNavigator = createDrawerNavigator(
{Home: HomeScreen,
other: otherpage},
{
contentComponent: SideMenuScreen
}
);
const AppContainer = createAppContainer(AppNavigator);
updateState executing but not updating state.
If you have to update from the child component
You will have to pass down the Handlers from the component which holds the state to update the values, child component can make use of these handlers to update the state
If you have to update from some other location
You will have to do a level up the State and follow the same has above.
LevelUpComponent
export class App extends Component {
constructor(){
this.state = {
test: ''
}
}
updateState = (values)=>{
this.setState(values);
}
render(){
return <div>
<HomeScreen></HomeScreen>
<SideMenuScreen updateState={this.updateState}></SideMenuScreen>
</div>
}
}
Since you're not haveing Parent-Child relationship between your components ... thi s could be accomplished through Redux Action
HomeScreen;
export class HomeScreen extends Component {
constructor() {
this.state = {
test: ""
};
}
componentWillReceiveProps(nextProps) {
const { test: nextTest } = nextProps;
const { test } = this.props;
if (nextTest !== test) {
this.setState({ test: nextTest });
}
}
}
const mapStateToProps = ({ yourReducerName: test }) => ({ test });
export connect(mapStateToProps)(HomeScreen);
import { HomeScreen } from "./home";
import { connect } from "tls";
class SideMenuScreen extends Component {
updateHomeState = () => {
const { updateHomeStateAction } = this.props;
updateHomeStateAction({ test: 'New Value' });
};
}
export default connect(null, { updateHomeStateAction })(SideMenuScreen);
when you navigate to next screen pass this function in params,
this.props.navigate("SideMenuScreen",{update:this.updateState});
And in your side menu screen,
call it using props,
this.props.navigation.state.params.update();//you can pass params also if needed
You can do this by using ref.
HomeScreen
export class HomeScreen extends Component {
constructor(){
this.state = {
test: ''
}
}
updateState = ()=>{
this.setState({test:'new value'});
}
}
SideMenuScreen
import { HomeScreen } from "./home";
export class SideMenuScreen extends Component {
updateHomeState = ()=>{
this.homeScreen.updateState();
}
render(){
return(
<HomeScreen ref={(ele) => this.homeScreen = ele}/>
);
}
}
I am using routes with react-router as below
<Route path="product/:id" component={Product}/>
I am having component product as below code as below
import React, {PropTypes} from 'react';
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import { asyncConnect } from 'redux-async-connect';
import {load, isLoaded} from 'redux/modules/viewlodging';
#asyncConnect([{
promise: ({ store: { dispatch, getState } }) => {
const promises = [];
if (!isLoaded(getState())) {
promises.push(dispatch(load()));
}
return Promise.all(promises);
}
}])
#connect(
state => ({viewdata: state.viewlodging.data}),
dispatch => bindActionCreators({load}, dispatch)
)
export default class Product extends React.Component {
static propTypes = {
viewdata: PropTypes.object,
location: PropTypes.object,
load: PropTypes.func.isRequired
}
render() {
console.log(this.props.routeParams.id); // here I get routeparameter
const { viewdata } = this.props;
return (
<div>
<div>Sample test</div>
</div>
<Footer/>
<Viewfootertext viewdata={viewdata}/>
</div>
);
}
}
I want to pass parameter id to reducer method load, How to pass route parameter here in correct way?
You can send it in either componentWillMount() or componentDidMount(). Don't send it in render method since it fires every time you have new props or state changes.
you can access route params from this.props.params.
So try like this in your container
componentDidMount(){
const {id} = this.props.params;
this.props.load(id); //you can send params values after component get mounted.
}
And your container will look something like this
import React, {PropTypes} from 'react';
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {asyncConnect} from 'redux-async-connect';
import {load, isLoaded} from 'redux/modules/viewlodging';
#asyncConnect([{
promise: ({
store: {
dispatch,
getState
}
}) => {
const promises = [];
if (!isLoaded(getState())) {
promises.push(dispatch(load()));
}
return Promise.all(promises);
}
}])
#connect(
state => ({
viewdata: state.viewlodging.data
}),
dispatch => bindActionCreators({
load
}, dispatch)
)
export default class Product extends React.Component {
static propTypes = {
viewdata: PropTypes.object,
location: PropTypes.object,
load: PropTypes.func.isRequired
}
componentDidMount(){
const {id} = this.props.params;
this.props.load(id); //you can send params values after component get mounted.
}
render() {
console.log(this.props.routeParams.id); // here I get routeparameter
//don't send in render method, since it'll be called many times
const {
viewdata
} = this.props;
return ( < div >
< div > Sample test < /div> < /div> < Footer / >
< Viewfootertext viewdata = {
viewdata
}
/> < /div>
);
}
}
#asyncConnect([{
promise: ({ store: { dispatch, getState }, params: { id }, }) => {
const promises = [];
if (!isLoaded(getState())) {
promises.push(dispatch(load(id)));
}
return Promise.all(promises);
}
}])
Passing id with params worked for me