Passing props to Parent component - reactjs

I am really novice to React and I am stuck with this one.
I want to pass data from NewAction component to its parent NewActionSet.
I dont know what i am missing.
I am developing an on-boarding platform with a lot a components and I aim to send all the data entered into all the components to a server.
React parent Component:
import React from 'react'
import './NewActionSet.css'
import axios from 'axios'
import { Container, Segment, Header, Input } from 'semantic-ui-react'
import NewAction from './NewAction'
import 'bootstrap/dist/css/bootstrap.min.css'
class NewActionSet extends React.Component {
constructor (props) {
super(props)
this.state = {
actions: [],
actionType: '',
actionValue: '',
creationStatus: undefined
}
}
handleActions = value => {
this.setState({
actionsList: value
})
console.log(this.state.actionsList)
}
handleSubmit = event => {
event.preventDefault()
console.log(this.state)
axios
.post(
'/assistant/actions/',
{ ...this.state.values },
{ headers: {
xsrfHeaderName: 'X-CSRFToken',
xsrfCookieName: 'csrftoken'
},
withCredentials: true
}
)
.then(response => {
console.log(response)
this.setState({
creationStatus: true
})
})
.catch(error => {
console.log(error)
this.setState({
creationStatus: false
})
})
}
addNewAction = () => {
let { actions } = this.state
this.setState({
actions: [...actions, <NewAction onNewAction={this.handleActionstoParent} />]
})
}
handleActionstoParent = (action2Value, selectedAction) => {
this.setState({
actionType : selectedAction,
actionValue : action2Value
})
// console.log(this.state.actionType, this.state.actiondValue)
}
renderActions () {
return this.state.actions.map((action, index) => {
return (
<NewAction
key={index}
type={this.props.actionType}
content={action.content}
onNewAction={this.handleActionstoParent}
/>
)
})
}
render () {
let index = 0
return (
<Container>
<Header> Action sets </Header>
<Header color='grey' as='h3'>
SET #{index + 1}
</Header>
{this.renderActions()}
<button onClick={() => this.addNewAction()}> New Action </button>
</Container>
)
}
}
export default NewActionSet
React child component
import React from 'react'
import './NewActionSet.css'
import { Header, Dropdown } from 'semantic-ui-react'
import NewSpeechText from './NewSpeechText'
import NewAddPageURL from './NewAddPageURL'
import 'bootstrap/dist/css/bootstrap.min.css'
class NewAction extends React.Component {
constructor (props) {
super(props)
this.state = {
availableActions: [
{ key: 1, text: 'Navigate to page', value: 'Navigate to page' },
{ key: 2, text: 'Play speech', value: 'Play speech' }
],
selectedAction: '',
actionValue: '',
currentElement: ''
}
}
handleActionURL = (value) => {
this.setState({
actionValue: value
})
console.log(this.state.selectedAction, this.state.actionValue)
}
handleActionSpeech = (value) => {
this.setState({
actionValue: value
})
console.log(this.state.selectedAction, this.state.actionValue)
}
// Props to pass data to parent component --> NewActionSet.js
handleActionstoParent = (selected) => {
var action2Value = this.state.actionValue;
console.log(action2Value)
var action2Type = this.state.actionType
this.props.onNewAction(action2Value, action2Type)
console.log(action2Type)
// console.log(this.state.actionValue, this.state.selectedAction)
}
handleChange = (e, { value }) => {
let element
this.setState({
selectedAction: value
})
if (value === 'Navigate to page') {
element = <NewAddPageURL onNewAddPageURL={this.handleActionURL} onChange={this.handleActionstoParent()} />
} else if (value === 'Play speech') {
element = <NewSpeechText onNewSpeechText={this.handleActionSpeech} onChange={this.handleActionstoParent()} />
}
this.setState({
currentElement: element
})
}
render () {
const { value } = this.state
let index = 0
return (
<div className='action'>
<div className='container'>
<Header color='grey' as='h4'>
ACTION #{index + 1}
</Header>
<div className='row'>
<div className='col-md-4'>
<Dropdown
onChange={this.handleChange}
options={this.state.availableActions}
placeholder='Choose an action'
selection
value={value}
/>
</div>
<div className='col-md-4' />
<div className='col-md-4' />
</div>
<div style={{ marginBottom: '20px' }} />
{this.state.currentElement}
</div>
</div>
)
}
}
export default NewAction
Can you please assist?
Thanks a lot

The handleActionstoParent function in NewAction component is the problem.
When you send data from child to parent, actually the data is not updated data.
// Props to pass data to parent component --> NewActionSet.js
handleActionstoParent = (e) => {
this.setState({ [e.target.name]: e.target.value }, () => {
var action2Value = this.state.actionValue;
var action2Type = this.state.actionType;
this.props.onNewAction(action2Value, action2Type);
});
}

You could pass a function to NewAction, in example below we pass handleDataFlow function to our child component and then use it in our child component to pass data higher:
import React from 'react'
import './NewActionSet.css'
import { Header, Dropdown } from 'semantic-ui-react'
import NewSpeechText from './NewSpeechText'
import NewAddPageURL from './NewAddPageURL'
import 'bootstrap/dist/css/bootstrap.min.css'
class NewAction extends React.Component {
constructor (props) {
super(props)
this.state = {
availableActions: [
{ key: 1, text: 'Navigate to page', value: 'Navigate to page' },
{ key: 2, text: 'Play speech', value: 'Play speech' }
],
selectedAction: '',
actionValue: '',
currentElement: ''
}
}
handleActionURL = (value) => {
this.setState({
actionValue: value
})
console.log(this.state.selectedAction, this.state.actionValue)
}
handleActionSpeech = (value) => {
this.setState({
actionValue: value
})
console.log(this.state.selectedAction, this.state.actionValue)
}
// Props to pass data to parent component --> NewActionSet.js
handleActionstoParent = (selected) => {
var action2Value = this.state.actionValue;
console.log(action2Value)
var action2Type = this.state.actionType
this.props.onNewAction(action2Value, action2Type)
console.log(action2Type)
// console.log(this.state.actionValue, this.state.selectedAction)
}
handleChange = (e, { value }) => {
let element
this.setState({
selectedAction: value
})
this.props.handleDataFlow(value)
if (value === 'Navigate to page') {
element = <NewAddPageURL onNewAddPageURL={this.handleActionURL} onChange={this.handleActionstoParent()} />
} else if (value === 'Play speech') {
element = <NewSpeechText onNewSpeechText={this.handleActionSpeech} onChange={this.handleActionstoParent()} />
}
this.setState({
currentElement: element
})
}
render () {
const { value } = this.state
let index = 0
return (
<div className='action'>
<div className='container'>
<Header color='grey' as='h4'>
ACTION #{index + 1}
</Header>
<div className='row'>
<div className='col-md-4'>
<Dropdown
onChange={this.handleChange}
options={this.state.availableActions}
placeholder='Choose an action'
selection
value={value}
/>
</div>
<div className='col-md-4' />
<div className='col-md-4' />
</div>
<div style={{ marginBottom: '20px' }} />
{this.state.currentElement}
</div>
</div>
)
}
}
export default NewAction

Data flow in React is unidirectional. Data has one, and only one, way to be transferred: from parent to child.
To update parent state from child you have to send action (in props).
<NewAction updateParentState={this.doSmth} />
...
const doSmth = params => { this.setState({ ... })
and in NewAction you can call it in specific case
let parentUpdateState = ....
this.props.updateParentState(parentUpdateState);

Related

How to add page number to the URL

Could someone please tell me how can I add page number to my url. The component is as follows:
/** NPM Packages */
import React, { Component } from "react";
import { connect } from "react-redux";
import { Spinner, Pagination } from "react-bootstrap";
//import styles from "./App.module.css";
/** Custom Packages */
import List from "../List";
//import fetchCategories from "../../../actions/configuration/category/fetchCategories";
import deleteCategory from "../../../actions/configuration/category/deleteCategory";
import API from "../../../../app/pages/utils/api";
class Category extends Component {
constructor(props) {
super(props);
this.state = {
mesg: "",
mesgType: "",
isLoading: true,
total: null,
per_page: null,
current_page: 1,
pdata: []
};
this.fetchCategoriesAPI = this.fetchCategoriesAPI.bind(this);
}
fetchCategoriesAPI = async pno => {
await API.get("categories?offset=" + (pno.index+1))
.then(res => this.setState({ pdata: res.data }))
.then(() => this.props.passToRedux(this.state.pdata))
.catch(err => console.log(err));
};
componentDidMount = async () => {
const { state } = this.props.location;
if (state && state.mesg) {
this.setState({
mesg: this.props.location.state.mesg,
mesgType: this.props.location.state.mesgType
});
const stateCopy = { ...state };
delete stateCopy.mesg;
this.props.history.replace({ state: stateCopy });
}
this.closeMesg();
await this.fetchCategoriesAPI(1);
this.setState({ isLoading: false });
};
onDelete = async id => {
this.props.removeCategory(id);
await deleteCategory(id).then(data =>
this.setState({ mesg: data.msg, mesgType: "success" })
);
this.closeMesg();
};
closeMesg = () =>
setTimeout(
function() {
this.setState({ mesg: "", mesgType: "" });
}.bind(this),
10000
);
/** Rendering the Template */
render() {
let activePage = this.state.pdata.currPage;
let items = [];
let totalPages = Math.ceil(this.state.pdata.totalCount / 10);
for (let number = 1; number <= totalPages; number++) {
items.push(
<Pagination.Item key={number} active={number == activePage}>
{number}
</Pagination.Item>
);
}
const paginationBasic = (
<div>
<Pagination>
{items.map((item,index)=>{
return <p key={index} onClick={() => this.fetchCategoriesAPI({index})}>{item}</p>
})}
</Pagination>
<br />
</div>
);
const { mesg, mesgType, isLoading } = this.state;
return (
<>
{mesg ? (
<div
className={"alert alert-" + mesgType + " text-white mb-3"}
role="alert"
>
{mesg}
</div>
) : (
""
)}
{isLoading ? (
<div className="container-fluid">
<h4
className="panel-body"
style={{ "text-align": "center", margin: "auto" }}
>
Loading
<Spinner animation="border" role="status" />
</h4>
</div>
) : (
<div>
<List
listData={this.props.categories}
listName="category"
_handleDelete={this.onDelete.bind(this)}
/>
{paginationBasic}
</div>
)}
</>
);
}
}
const matchStatestoProps = state => {
return { categories: state.categories };
};
const dispatchStatestoProps = dispatch => {
return {
passToRedux: pload =>
dispatch({ type: "FETCH_CATEGORIES", payload: pload }),
removeCategory: id => dispatch({ type: "DELETE_CATEGORY", payload: id })
};
};
export default connect(matchStatestoProps, dispatchStatestoProps)(Category);
the route is as follows:
<Route exact path="/categories/:page?" component={Category} />
So basically I want the page number to be displayed in the URL. Also if I change the page number, the data should load the corresponding page. Please help me
Could someone please help me out?
In a class component:
Your router will pass match in as a prop. When your component mounts, get this.props.match.params.page and load the data accordingly:
class MyComponent extends React.Component {
componentDidMount () {
// get the 'page' param out of the router props.
// default to 0 if not specified.
const { page = 0 } = this.props.match.params;
// it comes in as a string, parse to int
const p = parseInt(page, 10);
// do whatever you need to do (load data, etc.)
}
}
In a function component:
In a function component, you can get the page param via react-router's useParams hook:
import { useParams } from 'react-router-dom';
function MyComponent () {
const { page } = useParams(); // get the 'page' router param
const p = parseInt(page, 10); // comes in as a string, convert to int
// do whatever you need to do with it
}
If you need prev/next navigation you can deduce those page numbers from the current page.
I made this quick example that demonstrates how to access and use the route's url parameters via react router's useParams hook and how to do it via the match prop with a class component.
You can get page number from props like this:
const matchStatestoProps = (state, ownProps) => {
return { id: ownProps.match.params.id; categories: state.categories };
};
In your routes:
<Route path="/page/:id" component={Page} />

How do I manage my array of children components' states?

I'm new to react, so forgive me. I'm having a problem understanding states, specifically those of children.
Purpose: I'm trying to create a form that a user can append more and more components -- in this case, images.
What happens: User appends 2 or more images. User tries to upload an image with UploadButton component, but both the images are the same. I believe this has to do with both appended children sharing the same state.
Question: How do I give each appended child its own image without affecting the other appended children?
class Page extends Component
constructor (props) {
super(props);
this.state = {
id: '',
numChildren: 0,
images: [],
}
this.onAddChild = this.onAddChild.bind(this);
}
showModal() {
this.setState({
numChildren: 0,
images: [],
});
}
renderModal()
const children = [];
//Here's my array of child components
for(var i = 0; i < this.state.numChildren; i += 1) {
children.push(<this.ChildComponent key={i} />);
}
return (
<ReactModal>
<this.ParentComponent addChild={this.onAddChild}>
{children}
</this.ParentComponent>
</ReactModal>
)
}
onAddChild = () => {
this.setState({
numChildren: this.state.numChildren + 1
})
}
ParentComponent = (props) => (
<div>
{props.children}
<Button onClick={props.addChild}>Add Item</Button>
</div>
);
ChildComponent = () => (
<div>
<UploadButton
storage="menus"
value={this.state.images}
onUploadComplete={uri => this.setState({images: uri})}
/>
</div>
);
}
Here's the code for UploadButton:
import React, { Component } from 'react';
import uuid from 'uuid';
import firebase from '../config/firebase';
class UploadButton extends Component {
constructor(props) {
super(props);
this.state = {
isUploading: false
}
}
handleClick() {
const input = document.createElement("INPUT");
input.setAttribute("type", "file");
input.setAttribute("accept", "image/gif, image/jpeg, image/png");
input.addEventListener("change", ({target: {files: [file]}}) => this.uploadFile(file));
input.click();
}
uploadFile(file) {
console.log('F', file);
const id = uuid.v4();
this.setState({ isUploading: true })
const metadata = {
contentType: file.type
};
firebase.storage()
.ref('friends')
.child(id)
.put(file, metadata)
.then(({ downloadURL }) => {
this.setState({ isUploading: false })
console.log('Uploaded', downloadURL);
this.props.onUploadComplete(downloadURL);
})
.catch(e => this.setState({ isUploading: false }));
}
render() {
const {
props: {
value,
style = {},
className = "image-upload-button",
},
state: {
isUploading
}
} = this;
return (
<div
onClick={() => this.handleClick()}
className={className}
style={{
...style,
backgroundImage: `url("${this.props.value}")`,
}}>
{isUploading ? "UPLOADING..." : !value ? 'No image' : ''}
</div>
);
}
}
export default UploadButton;
I tried to exclude all unnecessary code not pertaining to my problem, but please, let me know if I need to show more.
EDIT: This is my attempt, it doesn't work:
//altered my children array to include a new prop
renderModal() {
const children = [];
for (var i = 0; i < this.state.numChildren; i += 1) {
children.push(<this.ChildComponent imageSelect={this.onImageSelect} key={i} />);
}
//...
};
//my attempt to assign value and pass selected image back to images array
ChildComponent = () => (
<div>
<UploadButton
storage="menus"
value={uri => this.props.onImageSelect(uri)} //my greenness is really apparent here
onUploadComplete={uri => this.setState({images: uri})}
/>
//...
</div>
);
//added this function to the class
onImageSelect(uri) {
var el = this.state.images.concat(uri);
this.setState({
images: el
})
}
I know I'm not accessing the child prop correctly. This is the most complexity I've dealt with so far. Thanks for your time.
When you write this.state in Child / Parent component, you are actually accessing the state of Page. Now, I would recommend that you pass in the index of the child to the Child like so
children.push(<this.ChildComponent key={i} index={i}/>)
so that each children deals with only its own image like so
ChildComponent = ({index}) => (
<div>
<UploadButton
storage="menus"
value={this.state.images[index]}
onUploadComplete={uri => {
let images = this.state.images.slice()
images[index] = uri
this.setState({images})
}}
/>
</div>
);

How to add props to state

I have a problem when trying to add my redux array to a component’s state:
componentDidMount() {
this.props.cardAction()
this.setState({ showCards: this.props.cardAction() })
}
hhhhhh undefined console log undefined
Here’s my dashboard code:
import React, { Component } from 'react'
import { connect } from 'react-redux'
import PropTypes from 'prop-types'
import Header from '../../common/Header/'
import Masonry from '../../common/Masonry/'
import { cardAction } from '../../store/actions/Cards'
import Arrow_Down from '../../assets/img/arrow-down.svg'
class Dashboard extends Component {
componentDidMount() {
this.props.cardAction()
this.setState({ showCards: this.props.cardAction() })
}
constructor(props) {
super(props)
this.state = {
collapsed: true,
class: 'collapsed',
showCards: {},
}
this.toggleCollapse = this.toggleCollapse.bind(this);
}
toggleCollapse(i, info) {
console.log('i', info, 'iiiii', i)
this.setState({
collapsed: !this.state.collapsed,
class: this.state.collapsed ? '' : 'collapsed',
showCards: info
}, () => {
// my state is updated here !
console.log('cardsss', this.state.showCards)
})
if (this.state.showCards === 'active') {
let carddd = this.state.showCards
this.setState({
showCards: {
...this.state.showCards,
open: 'inactive'
}
});
}
else {
this.setState({
showCards: {
...this.state.showCards,
open: 'active'
}
});
}
}
render() {
console.log('hhhhhh', this.state.showCards)
const cardList = this.props.Cards.map((info, i) => {
return (
<div className={(info.open === 'active') ? 'collapsed' : ''} key={i}>
<div className={(info.open === 'active') ? 'header flex space-between active' : 'header flex space-between'}>
<h2>{info.title}</h2>
<span onClick={() => { this.toggleCollapse(i, info) }}><img src={Arrow_Down} alt='Arrow' /></span>
</div>
<div className='content'>
<p>{info.description}</p>
</div>
</div>
)
})
return (
<div>
<Header />
<Masonry columns={3} gap={20}>
{cardList}
</Masonry>
</div>
)
}
}
Dashboard.defaultProps = {
columns: 2,
gap: 20,
Cards: []
}
Dashboard.propTypes = {
Cards: PropTypes.array.isRequired,
}
const mapStateToProps = state => {
return { Cards: state.cards.result }
}
const mapDispatchToProps = dispatch => ({
cardAction: () => dispatch(cardAction())
})
export default connect(mapStateToProps, mapDispatchToProps)(Dashboard)
this.props.cardAction() is a redux action, it's not meant for you to directly assign to state, reason is redux action will return to reducer, not component. You should remove the setState in componentDidMount
componentDidMount() {
this.props.cardAction();
}
When you invoked this.props.cardAction(), it will call the function that defined in redux action file, and the result will be available at this.props.Cards as you mentioned above
const mapStateToProps = state => {
return { Cards: state.cards.result }
}

How to rerender one sibling component due to change of second sibling component

I have this structure:
<Filter>
<Departure setDeparture={this.setDeparture} />
<Destination setDestination={this.setDestination} iataDeparture={this.state.departure} />
<DatePicker setDates={this.setDates} />
<SearchButton />
</Filter>
Now, I try to rerender Destination component when I update Departure component. Unfortunatelly my code doesn't work.
I don't use redux because I don't know it yet, so I try solutions without redux.
Please, help me with this problem.
Here goes code for each component:
Filter:
import React, { Component } from 'react';
import axios from 'axios';
import Departure from './Departure';
import Destination from './Destination';
import DatePicker from './DatePicker';
import SearchButton from './SearchButton';
class Filter extends Component {
constructor(props) {
super(props);
this.state = {
departure: '',
destination: '',
startDate: '',
endDate: '',
flights: []
}
}
handleSubmit = event => {
const getFlights = `https://murmuring-ocean-10826.herokuapp.com/en/api/2/flights/from/${this.state.departure}/to/${this.state.destination}/${this.state.startDate}/${this.state.endDate}/250/unique/?limit=15&offset-0`;
event.preventDefault();
console.log(this.state.departure);
console.log(this.state.destination);
console.log(this.state.startDate);
console.log(this.state.endDate);
axios.get(getFlights)
.then(response => {
this.setState({ flights: response.data.flights });
console.log(getFlights);
console.log(this.state.flights);
this.props.passFlights(this.state.flights);
});
}
setDeparture = departure => {
this.setState({ departure: departure });
}
setDestination = destination => {
this.setState({ destination: destination });
}
setDates = (range) => {
this.setState({
startDate: range[0],
endDate: range[1]
});
}
render() {
return (
<section className='filter'>
<form className='filter__form' onSubmit={this.handleSubmit}>
<Departure setDeparture={this.setDeparture} />
<Destination setDestination={this.setDestination} iataDeparture={this.state.departure} />
<DatePicker setDates={this.setDates} />
<SearchButton />
</form>
</section>
);
}
}
export default Filter;
Departure:
import React, { Component } from 'react';
import axios from 'axios';
const url = 'https://murmuring-ocean-10826.herokuapp.com/en/api/2/forms/flight-booking-selector/';
class Departure extends Component {
constructor(props) {
super(props);
this.state = {
airports: [],
value: '',
iataCode: ''
}
}
componentDidMount() {
axios.get(url)
.then(data => {
const airports = data.data.airports;
const updatedAirports = [];
airports.map(airport => {
const singleAirport = [];
singleAirport.push(airport.name);
singleAirport.push(airport.iataCode);
updatedAirports.push(singleAirport);
return singleAirport;
});
this.setState({
airports: updatedAirports,
value: airports[0].name,
iataCode: airports[0].iataCode
});
this.props.setDeparture(this.state.iataCode);
});
}
handleChange = event => {
const nameValue = event.target.value;
const iataCode = this.state.airports.find(airport => {
return airport[0] === nameValue;
});
this.setState({
value: event.target.value,
iataCode: iataCode[1]
});
this.props.setDeparture(iataCode[1]);
}
render() {
const departureNames = this.state.airports;
let departureOptions = departureNames.map((item, index) => {
return (
<option value={item[0]} key={index}>{item[0]}</option>
);
});
return (
<div className='filter__form__select'>
<select value={this.state.value} onChange={this.handleChange}>
{departureOptions}
</select>
</div>
);
}
}
export default Departure;
Destination:
import React, { Component } from 'react';
import axios from 'axios';
const url = 'https://murmuring-ocean-10826.herokuapp.com/en/api/2/forms/flight-booking-selector/';
class Destination extends Component {
constructor(props) {
super(props);
this.state = {
routes: {},
airports: [],
value: '',
iataCode: '',
iataDestinationAirports: '',
options: []
}
}
componentDidMount() {
axios.get(url)
.then(data => {
const routes = data.data.routes;
const airports = data.data.airports;
const updatedAirports = [];
airports.map(airport => {
const singleAirport = [];
singleAirport.push(airport.name);
singleAirport.push(airport.iataCode);
updatedAirports.push(singleAirport);
return singleAirport;
});
this.setState({
routes: routes,
airports: updatedAirports,
});
})
.then(() => {
this.getNamesFromIataCode();
this.props.setDestination(this.state.iataDestinationAirports);
});
}
componentDidUpdate(prevProps) {
if (this.props.iataDeparture !== prevProps.iataDeparture) {
this.setState({ iataCode: this.props.iataDeparture });
() => this.getNamesFromIataCode();
};
}
handleChange = (event) => {
const nameValue = event.target.value;
const iataCode = this.state.airports.find(airport => {
return airport[0] === nameValue;
});
this.setState({
value: event.target.value,
iataDestinationAirports: iataCode[1]
});
this.props.setDestination(iataCode[1]);
}
getNamesFromIataCode = () => {
const iataCode = this.state.iataCode;
console.log(iataCode);
const destinationNames = this.state.routes[iataCode];
let destionationAirports = destinationNames.map(item => {
return this.state.airports.filter(el => {
return el[1] === item;
});
});
let arrayOfOptions = [];
let firstOptionIataCode = '';
let firstOptionName = '';
let destinationOptions = destionationAirports.map((item, index) => {
console.log(item);
arrayOfOptions.push(item[0]);
return (
<option value={item[0][0]} key={index}>{item[0][0]}</option>
);
});
firstOptionIataCode = arrayOfOptions[0][1];
firstOptionName = arrayOfOptions[0][0];
console.log(firstOptionIataCode);
this.setState({
options: destinationOptions,
iataDestinationAirports: firstOptionIataCode,
value: firstOptionName
});
console.log(this.state.iataDestinationAirports);
console.log(this.state.options);
return destinationOptions;
}
render() {
const selectionOptions = this.state.options;
return (
<div className='filter__form__select'>
<select value={this.state.value} onChange={this.handleChange}>
{selectionOptions}
</select>
</div>
);
}
}
export default Destination;
As Tholle mentioned, you need to lift the state up. Here's an example:
import React from "react";
import ReactDOM from "react-dom";
const A = ({ users, selectUser }) => {
return (
<React.Fragment>
<h1>I am A.</h1>
{users.map((u, i) => {
return <button onClick={() => selectUser(i)}>{u}</button>;
})}
</React.Fragment>
);
};
const B = ({ user }) => {
return <h1>I am B. Current user: {user}</h1>;
};
const C = ({ user }) => {
return <h1>I am C. Current user: {user}</h1>;
};
class App extends React.Component {
state = {
users: ["bob", "anne", "mary"],
currentUserIndex: 0
};
selectUser = n => {
this.setState({
currentUserIndex: n
});
};
render() {
const { users, currentUserIndex } = this.state;
const currentUser = users[currentUserIndex];
return (
<React.Fragment>
<A selectUser={this.selectUser} users={users} />
<B user={currentUser} />
<C user={currentUser} />
</React.Fragment>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Working example here.

React force componentDidMount

I have the following:
import React from 'react';
import axios from 'axios';
class FirstName extends React.Component {
constructor(props) {
super(props);
this.state = {
submitted: false
};
}
getName () {
var name = this.refs.firstName.value;
this.setState(function() {
this.props.action(name);
});
}
handleSubmit (e) {
e.preventDefault();
this.setState({ submitted: true }, function() {
this.props.actionID(2);
this.props.activeNav('color');
});
}
render () {
return (
<div>
<h2>tell us your first name</h2>
<form>
<input
type="text"
ref="firstName"
onChange={this.getName.bind(this)}
/>
<div className="buttons-wrapper">
<button href="#">back</button>
<button onClick={this.handleSubmit.bind(this)}>continue</button>
</div>
</form>
</div>
);
}
};
class PickColor extends React.Component {
backToPrevious (e) {
e.preventDefault();
this.props.actionID(1);
this.props.activeNav('name');
}
goToNext (e) {
e.preventDefault();
this.props.actionID(3);
this.props.activeNav('design');
this.props.displayIconsHolder(true);
}
getColorValue(event) {
this.props.color(event.target.getAttribute("data-color"));
}
render () {
var colors = ['red', 'purple', 'yellow', 'green', 'blue'],
colorsLink = [];
colors.forEach(el => {
colorsLink.push(<li
data-color={el}
key={el}
onClick={this.getColorValue.bind(this)}
ref={el}>
{el}
</li>
);
});
return (
<section>
<ul>
{colorsLink}
</ul>
<button onClick={this.backToPrevious.bind(this)}>back</button>
<button onClick={this.goToNext.bind(this)}>continue</button>
</section>
);
}
}
class ConfirmSingleIcon extends React.Component {
goBack () {
this.props.goBack();
}
confirmCaptionandIcon (event) {
var optionID = event.target.getAttribute("data-option-id"),
name = event.target.getAttribute("data-option-name");
this.props.setOptionID(optionID);
this.props.setIcon(1, name, optionID, false);
}
goNext () {
this.props.goNext();
}
render () {
console.log(this.props.currentState);
var options = [],
that = this;
this.props.iconOptionsList.forEach(function(el){
options.push(<li onClick={that.confirmCaptionandIcon.bind(that)} key={el.option} data-option-name={el.option} data-option-id={el.id}>{el.option}</li>);
});
return (
<div>
<h2>Choose your caption</h2>
<h3>
{this.props.selectedIcon}
</h3>
<ul>
{options}
</ul>
<button onClick={this.goBack.bind(this)} >back</button>
<button onClick={this.goNext.bind(this)} >confirm</button>
</div>
);
}
}
class ConfirmCaption extends React.Component {
handleClick () {
var currentState = this.props.currentState;
this.props.setIcon(currentState.icon_ID, currentState.selectedIcon, currentState.option_ID, true);
this.props.setIconVisiblity(true);
this.props.setIconListVisiblity(false);
}
render () {
console.log(this.props.currentState);
return (
<div>
<p onClick={this.handleClick.bind(this)}>confirm icon and caption</p>
</div>
);
}
}
class ChooseIcon extends React.Component {
constructor(props) {
super(props);
this.state = {
icons: [],
iconList: true,
confirmIcon: false,
confirmCaption: false,
selectedIconOptions: '',
icon_ID: '',
option_ID: '',
selectedIcon: ''
};
this.setOptionID = this.setOptionID.bind(this);
this.setIconVisiblity = this.setIconVisiblity.bind(this);
this.setIconListVisiblity = this.setIconListVisiblity.bind(this);
}
setOptionID (id) {
this.setState({ option_ID: id })
}
setIconVisiblity (onOff) {
this.setState({ confirmIcon: onOff })
}
setIconListVisiblity (onOff) {
this.setState({ iconList: onOff })
}
componentDidMount() {
var url = `http://local.tshirt.net/get-options`;
axios.get(url)
.then(res => {
this.setState({ icons:res.data.icons });
});
}
handleClick (event) {
var iconId = event.target.getAttribute("data-icon-id"),
that = this;
this.state.icons.forEach(function(el){
if(el.id == iconId){
that.setState(
{
confirmIcon: true,
iconList: false,
selectedIcon: el.name,
icon_ID: iconId,
selectedIconOptions: el.option
}
);
}
});
}
goBack () {
this.setState(
{
confirmIcon: false,
iconList: true
}
);
}
goNext () {
this.setState(
{
confirmIcon: false,
iconList: false,
confirmCaption: true
}
);
}
render () {
var icons = [];
this.state.icons.forEach(el => {
icons.push(<li data-icon-id={el.id} onClick={this.handleClick.bind(this)} key={el.name}>{el.name}</li>);
});
return (
<div>
{this.state.iconList ? <IconList icons={icons} /> : ''}
{this.state.confirmIcon ? <ConfirmSingleIcon goBack={this.goBack.bind(this)}
goNext={this.goNext.bind(this)}
setIcon={this.props.setIcon}
selectedIcon={this.state.selectedIcon}
iconOptionsList ={this.state.selectedIconOptions}
setOptionID={this.setOptionID}
currentState={this.state} /> : ''}
{this.state.confirmCaption ? <ConfirmCaption currentState={this.state}
setIcon={this.props.setIcon}
setIconVisiblity={this.setIconVisiblity}
setIconListVisiblity={this.setIconListVisiblity} /> : ''}
</div>
);
}
}
class IconList extends React.Component {
render () {
return (
<div>
<h2>Pick your icon</h2>
<ul>
{this.props.icons}
</ul>
</div>
);
}
}
class Forms extends React.Component {
render () {
var form;
switch(this.props.formID) {
case 1:
form = <FirstName action={this.props.action} actionID={this.props.switchComponent} activeNav={this.props.activeNav} />
break;
case 2:
form = <PickColor displayIconsHolder={this.props.seticonsHolder} color={this.props.colorVal} actionID={this.props.switchComponent} activeNav={this.props.activeNav} />
break;
case 3:
form = <ChooseIcon setIcon={this.props.setOptionA} />
break;
}
return (
<section>
{form}
</section>
);
}
}
export default Forms;
"ChooseIcon" is a component that will get used 3 times therefore everytime I get to it I need to bring its state back as if it was the first time.
Ideally I would need to make this ajax call everytime:
componentDidMount() {
var url = `http://local.tshirt.net/get-options`;
axios.get(url)
.then(res => {
this.setState({ icons:res.data.icons });
});
}
is there a way to manually call componentDidMount perhaps from a parent component?
React handles component lifecycle through key attribute. For example:
<ChooseIcon key={this.props.formID} setIcon={this.props.setOptionA} />
So every time your key (it can be anything you like, but unique) is changed component will unmount and mount again, with this you can easily control componentDidMount callback.
If you are using the ChooseIcon component 3 times inside the same parent component, I would suggest you to do the ajax in componentDidMount of the parent component like this (exaclty how you have in your example, in terms of code)
componentDidMount() {
var url = `http://local.tshirt.net/get-options`;
axios.get(url)
.then(res => {
this.setState({ icons:res.data.icons });
});
}
and then pass this data down to the ChooseIcon component
render() {
return (
//do your stuff
<ChooseIcon icons={this.state.icons}/>
)
}
after this you will only need to set the received props in your ChooseIconcomponent, for that you only need to change one line in it's constructor:
constructor(props) {
super(props);
this.state = {
icons: props.icons, // Changed here!
iconList: true,
confirmIcon: false,
confirmCaption: false,
selectedIconOptions: '',
icon_ID: '',
option_ID: '',
selectedIcon: ''
};
this.setOptionID = this.setOptionID.bind(this);
this.setIconVisiblity = this.setIconVisiblity.bind(this);
this.setIconListVisiblity = this.setIconListVisiblity.bind(this);
}
The parent component can use a ref to call the function directly.
However, trying to force this function feels like a smell. Perhaps lifting the state higher up the component tree would solve this problem. This way, the parent component will tell ChooseIcon what to show, and there will not be a need to call componentDidMount again. Also, I assume the Ajax call can also occur once.

Resources