In my react component I do get some data from graphQL server using react-apollo.
That's working fine, but this component is a child component and I need to get the graphQL data to a parent component. Is this possible at all or do I have to change my structure?
Child
export class Child extends Component {
const { contentList } = this.props
render () {
contentList.map(elm => return <div>elm</div>)
}
}
export default compose(
graphql(
gql`
query {
contentList {
_id
title
}
}
`, { name: 'contentList' }
)
)(Child)
Parent
export class Parent extends Component {
constructor () {
super()
this.state = {
data = null // <-- Need to get the data of child here
}
}
render () {
const { data } = this.state
// Now I can use the data, which is fetched by the child component
return <Child />
}
}
You can pass a function of parent to the childComponent via props and invoke that function from child when you have data.
export class Parent extends Component {
constructor () {
super();
this.handleData = this.handleData.bind(this);
this.state = {
data = null // <-- Need to get the data of child here
}
}
handleData(data){
//you can use data here
}
render () {
const { data } = this.state
// Now I can use the data, which is fetched by the child component
return <Child parentHandler="this.handleData" />
}
}
export class Child extends Component {
const { contentList, parentHandler } = this.props;
//call parentHandler with the data when you have data
render () {
contentList.map(elm => return <div>elm</div>)
}
}
export default compose(
graphql(
gql`
query {
contentList {
_id
title
}
}
`, { name: 'contentList' }
)
)(Child)
Related
I'm new to react and I am trying to fetch data from an API and pass the data to a child component. I've passed the data to the state on my parent component, however, when I pass it to the child component as props it logs as an empty array. I'm sure there is something simple I am overlooking but I don't know what, my code is below
PARENT COMPONENT
import React, {Component} from 'react';
import Child from '../src/child';
import './App.css';
class App extends Component {
constructor(props) {
super(props);
this.state = {
properties: []
}
}
getData = () => {
fetch('url')
.then(response => {
return response.text()
})
.then(xml => {
return new DOMParser().parseFromString(xml, "application/xml")
})
.then(data => {
const propList = data.getElementsByTagName("propertyname");
const latitude = data.getElementsByTagName("latitude");
const longitude = data.getElementsByTagName("longitude");
var allProps = [];
for (let i=0; i<propList.length; i++) {
allProps.push({
name: propList[i].textContent,
lat: parseFloat(latitude[i].textContent),
lng: parseFloat(longitude[i].textContent)
});
}
this.setState({properties: allProps});
});
}
componentDidMount = () => this.getData();
render () {
return (
<div>
<Child data={this.state.properties} />
</div>
)
}
}
export default App;
CHILD COMPONENT
import React, {Component} from 'react';
class Child extends Component {
initChild = () => {
console.log(this.props.data); // returns empty array
const properties = this.props.data.map(property => [property.name, property.lat, property.lng]);
}
componentDidMount = () => this.initChild();
render () {
return (
<div>Test</div>
)
}
}
export default Child;
Change the componentDidMount in the child to componentDidUpdate.
The componentDidMount lifecycle method is called only once in the starting. Whereas, the componentDidUpdate lifecycle method gets called whenever there is a change in the state of the application. Since api calls are asynchronous, the initChild() function is already called once before the api call's results are passed to the child.
You can use conditional rendering
import React, {Component} from 'react';
class Child extends Component {
initChild = () => {
if(this.props.data){
const properties = this.props.data.map(property => [property.name, property.lat, property.lng]);
}
}
componentDidMount = () => this.initChild();
render () {
return (
<div>Test</div>
)
}
}
export default Child;
I'm using some HOC components in my nextJS application to set some prop values via getInitialProps.
But I need to use dynamic values for these props.
In my index component I'm calling withServerProps. Is it possible to pass some string array to it?
index.js
import React, { Component } from 'react'
import { withTranslation } from 'i18n'
import withServerProps from 'with-server-props'
class Index extends Component {
render () {
return (<div>Some content</div>)
}
}
export default withServerProps( // <-- How to pass an array with strings?
withTranslation()(Index)
)
I need to get the string array in this function:
with-server-props.js
import React, { Component } from 'react'
export default WrappedComponent =>
class extends Component {
static async getInitialProps (context) {
const { query } = context
return {
id: query && query.id
target: PASSED_ARRAY // <-- Need to recieve the array here
}
}
render () {
return <WrappedComponent {...this.props} />
}
}
Yes you definitely can. Just add some arguments during the export in index.js.
export default withServerProps(withTranslation()(Index), ["hello"])
Then in your HOC:
export default function handleServerProps(WrappedComponent, arr) {
class Hoc extends Component {
static async getInitialProps (context) {
const { query } = context
return {
id: query && query.id,
target: arr,
}
}
render () {
return <WrappedComponent {...this.props} />
}
}
return Hoc;
}
I want to keep some functions outside of my component for easier testing. However, I cannot change state with these functions because they cannot reference the component's state directly.
So I currently have the hacky solution where I set the function to a variable then call this.setState. Is there a better convention/more efficient way to do this?
Example function code in Tester.js:
const tester = () => {
return 'new data';
}
export default tester;
Example component code in App.js (without imports):
class App extends Component {
constructor() {
super();
this.state = {
data: ''
}
}
componentDidMount(){
let newData = tester();
this.setState({ data: newData })
}
render() {
return(
<div>{this.state.data}</div>
)
}
}
You could bind your tester function like this (this approach doesn't work with arrow functions):
function tester() {
this.setState({ data: 'new Data' });
}
class App extends Component {
constructor() {
super();
this.state = {
data: '',
};
this.tester = tester.bind(this);
}
componentDidMount() {
this.tester();
}
render() {
return (
<div>{this.state.data}</div>
);
}
}
But I would prefer a cleaner approach, where you don't need your function to access this (also works with arrow functions):
function tester(prevState, props) {
return {
...prevState,
data: 'new Data',
};
}
class App extends Component {
constructor() {
super();
this.state = {
data: '',
};
}
componentDidMount() {
this.setState(tester);
}
render() {
return (
<div>{this.state.data}</div>
);
}
}
You can pass a function to setState() that will return a new object representing the new state of your component. So you could do this:
const tester = (previousState, props) => {
return {
...previousState,
data: 'new data',
};
}
class App extends Component {
constructor() {
super();
this.state = {
data: ''
}
}
componentDidMount(){
this.setState(tester)
}
render() {
return(
<div>{this.state.data}</div>
)
}
}
The reason being that you now have access to your component's previous state and props in your tester function.
If you just need access to unchanging static placeholder values inside of your app, for example Lorem Ipsum or something else, then just export your data as a JSON object and use it like that:
// testData.js
export const testData = {
foo: "bar",
baz: 7,
};
...
// In your app.jsx file
import testData from "./testData.js";
const qux = testData.foo; // "bar"
etc.
I am trying to wrap my head around ReactJS and I am stumped with an issue where I want to be able to update the value of a local variable and return the updated value.
I've read about state and I've used that when working with React Components, however, this class is just defined as const and it doesn't extend React.Component.
Is there a different way I should be defining setting the variable?
Here is a simplified version of my code:
import React from 'react';
const WelcomeForm = ({welcome}) => {
var welcomeMsg = 'Test';
DynamicContentApi.loadDynamicContent('welcome_test').then((response) => {
// response.text has content
welcomeMsg = response.text;
}).catch(() => {
welcomeMsg = '';
});
return (
<p>{welcomeMsg}</p> // Returns 'Test'
);
};
export default WelcomeForm;
The easiest option here is to change your stateless component to a stateful component.
Stateless components are just JavaScript functions. They take in an
optional input, called prop.
Stateful components offer more features, and with more features comes more baggage. The primary reason to choose class components (stateful) over functional components (stateless) is that they can have state, that is what you want to update to re-render.
Here is what you can do:
class WelcomeForm extends React.Component {
state = {
welcomeMsg: ''
}
fetchFromApi() {
DynamicContentApi.loadDynamicContent("welcome_test")
.then(response => {
this.setState({welcomeMsg: response.text});
})
.catch((e) => console.log(e));
}
componentDidMount() {
fetchFromApi();
}
render() {
return (
<p>{welcomeMsg}</p>
);
}
};
If you want, for any reason, to keep your component stateless, you will have to put the loadDynamicContent() function on the Parent and pass the text to WelcomeForm as a prop. For example:
// Your WelcomeForm Component
const WelcomeForm = ({welcomeMsg}) => (
<p>{welcomeMsg}</p>
);
// Whatever it's Parent Component is
class Parent extends React.Component {
state = {
welcomeMsg: ''
}
fetchFromApi() {
DynamicContentApi.loadDynamicContent("welcome_test")
.then(response => {
// response.text has content
this.setState({welcomeMsg: response.text});
})
.catch((e) => console.log(e));
}
componentDidMount() {
fetchFromApi();
}
render() {
<WelcomeForm welcomeMsg={this.state.welcomeMsg} />
}
}
As suggested in the comments, you can pass the DynamicContentApi logic to outside:
import ReactDOM from 'react-dom'
DynamicContentApi.loadDynamicContent('welcome_test').then((response) => {
ReactDOM.render(<WelcomeForm data={response.text} />, document.getElementById('where you wanna render this'));
}).catch(() => {
console.log('error while fetching...');
});
And where you have your component:
import React from 'react';
export default class WelcomeForm extends React.Component {
render() {
return (
<p>{this.props.data}</p>
);
}
}
Here is my code:
ChartActions.js
import * as types from './ChartTypes.js';
export function chartData(check){
return { type: types.CHART_DATA,check };
}
ChartTypes.js
export const CHART_DATA = 'CHART_DATA';
ChartReducers.js
import {
CHART_DATA,
}from './ChartTypes.js';
const initialState = {
chartData : [],
}
export default function ChartReducers(state = initialState, action) {
switch (action.type) {
case CHART_DATA :
return Object.assign({}, state, {
chartData : action.check
});
default:
return state;
}
}
I am so sure that I setup redux quite accurate and it works perfectly. My problem is:
In a component A I dispatch a function:
handleClick(){
this.props.ChartActions.chartData("test string")
}
so in theory, a component B in my project will receive the string "test string" right after the handeClick function triggered, like this
componentWillReceiveProps(){
console.log(this.props.chartData) // test string
}
But I have no idea why SOMETIMES (it only happens sometimes) I have to trigger handleClick function TWO times in component A so that the component B could be able to get the updated state (in this example, it is "test string"). I supposed it's a bug.
I need the component B will receive the updated state (i.e "test string") RIGHT AFTER the handleClick is triggered only ONE TIME.
I have a container:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as ChartActions from '../../components/center-menu/services/ChartActions.js';
import CenterMenu from '../../components/center-menu/center-menu-index.js'
import RightMenu from '../../components/right-content/right-content-index.js'
class Home extends Component {
render() {
return (
<div>
<CenterMenu
ChartActions = {this.props.ChartActions}
/>
<RightMenu
ChartProps={this.props.ChartProps}
/>
</div>
);
}
}
function mapStateToProps(state) {
return {
ChartProps: state.ChartReducers
};
}
function mapDispatchToProps(dispatch) {
return {
ChartActions: bindActionCreators(ChartActions, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Home);
Here is the component A where I fire an actions:
import React, { Component } from 'react';
class CenterMenu extends Component {
constructor(props) {
super(props);
constructor(props) {
super(props);
this.state = {};
}
}
handleClick(){
this.props.ChartActions.chartData('test string')
}
render() {
return (
<div className="center_menu" onClick={this.handleClick.bind(this)}>
Some stuff
</div>
)
}
}
export default CenterMenu;
And in another component B:
import React, { Component } from 'react';
class RightMenu extends Component {
constructor(props) {
super(props);
constructor(props) {
super(props);
this.state = {};
}
}
componentWillReceiveProps(){
console.log(this.props.ChartProps.chartData, "right here")
}
render() {
return (
<div className="center_menu">
Some stuff
</div>
)
}
}
export default RightMenu;
Weird thing:
In Component A, if I trigger the handleClick function by clicking in a div tag, it fires an action that change the initial state to "test string"
But...
In the component B the statement
console.log(this.props.ChartProps.chartData, "right here")
show empty string first like this:
right here
But when I trigger the handleClick function the SECOND TIME in component A , then in component B, in the statement
console.log(this.props.ChartProps.chartData, "right here")
it show the following:
test string "right here"
which is the result I want to achieve.
But I don't understand why I HAVE TO trigger the handleClick function twice. I need it by one click.
The problem is your Home component doesn't rerender the children. Try keeping ChartProps in a state in Home like so:
class Home extends Component {
state = {
ChartProps: null //you can use some default value, this might cause undefined is not an object error in you children
}
componentDidMount() {
const { ChartProps } = this.props
this.setState(() => ({ ChartProps }))
}
componentWillReceiveProps() {
const { ChartProps } = this.props
this.setState(() => ({ ChartProps }))
}
render() {
const { ChartProps } = this.state
return (
<div>
<CenterMenu
ChartActions={this.props.ChartActions}
/>
<RightMenu
ChartProps={ChartProps}
/>
</div>
);
}
}
function mapStateToProps(state) {
return {
ChartProps: state.ChartReducers
};
}
function mapDispatchToProps(dispatch) {
return {
ChartActions: bindActionCreators(ChartActions, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Home);