I'm trying to update a sibling from another sibling but for some reasons it does not update in all cases.
I would appreciate if you can help me to find the issue.
So what I'm trying to do is to update InfoBox component from ProductSync component
I would like my output to look like this:
Case1(sync one product):
history:
Sync started ...
Message from Server
Case2 (sync multi products):
history:
Sync started ...
Message from Server
Sync started ...
Message from Server
Sync started ...
Message from Server
.
.
.
What I actually get is this:
Case1(sync one product):
history:
Message from Server
Case2 (sync multi products):
history:
(Dont get any more message here)
export class Admin extends React.Component {
constructor() {
super();
this.syncProduct = this.syncProduct.bind(this);
this.syncMultiProducts = this.syncMultiProducts.bind(this);
this.updateInfoBox = this.updateInfoBox.bind(this);
this.state = {
infoBox: ["history:"]
};
}
updateInfoBox(newText) {
const newStateArray = this.state.infoBox.slice();
newStateArray.push(newText);
this.setState({
infoBox: newStateArray
});
}
syncProduct(item) {
$.ajax({
datatype: "text",
type: "POST",
url: `/Admin/Sync`,
data: { code: item.Code },
async: false,
success: (response) => {
this.updateInfoBox (response.InfoBox);
},
error: (response) => {
this.updateInfoBox (response.InfoBox);
}
});
}
syncMultiProducts(items) {
/*this does not re-render InfoBox component*/
items.map((item, index) => {
this.syncProduct(item);
});
}
render() {
return (
<div>
<InfoBox infoBox={this.state.infoBox}/>
<ProductSync syncProduct={this.syncProduct} syncMultiProducts={this.syncMultiProducts} updateInfoBox={this.updateInfoBox}/>
</div>
);
}
}
ReactDOM.render(
<Admin />,
document.getElementById("admin")
);
First child(ProductSync.jsx):
export class ProductSync extends React.Component {
constructor() {
super();
this.syncProduct= this.syncProduct.bind(this);
}
syncProduct(item) {
this.props.updateInfoBox("Sync started...");/*this does not re-render InfoBox component*/
this.props.syncProduct(getItemFromDB());
}
syncMultiProducts() {
this.props.updateInfoBox("Sync started...");/*this does not re-render InfoBox component*/
this.props.syncMultiProducts(getItemsFromDB());
}
render() {
return (
<div>
<button onClick={() => this.syncMultiProducts()}>Sync all</button>
<button onClick={() => this.syncProduct()}>Sync one</button>
</div>
);
}
}
Second Child(InfoBox.jsx)
export class InfoBox extends React.Component {
constructor(props) {
super(props);
this.state = {
infoBox: props.infoBox
};
}
componentWillReceiveProps(nextProps) {
this.setState({ infoBox: nextProps.infoBox});
}
render() {
const texts =
(<div>
{this.state.infoBox.map((text, index) => (
<p key={index}>{text}</p>
))}
</div>);
return (
<div>
{Texts}
</div>
);
}
}
Thanks in advance
Related
I have a menu component in my header component. The header component passes a function to the menu component => default menu component. It's working but the function returns unwanted data.
the path my function is traveling through is:
homepage => header => menu => defaultMenu
The function is:
changeBodyHandler = (newBody) => {
console.log(newBody)
this.setState({
body: newBody
})
}
I pass the function from homepage => header like this:
<HeaderDiv headerMenuClick={() => this.changeBodyHandler}/>
then through header => menu => defaultMenu using:
<Menu MenuClick={this.props.headerMenuClick} />
//==================== COMPONENT CODES ==========================//
homepage:
class Homepage extends Component {
constructor(props){
super(props);
this.state = {
body: "Homepage"
}
this.changeBodyHandler = this.changeBodyHandler.bind(this)
}
changeBodyHandler = (newBody) => {
console.log(newBody)
this.setState({
body: newBody
})
}
render() {
return (
<div>
<HeaderDiv headerMenuClick={() => this.changeBodyHandler}/>
{ this.state.body === "Homepage" ?
<HomepageBody />
: (<div> </div>)}
</div>
);
}
}
header:
class HeaderDiv extends Component {
constructor(props) {
super(props);
this.state = {
showMenu: 'Default',
}
}
render(){
return (
<Menu MenuClick={this.props.headerMenuClick}/>
);
}
}
menu:
import React, { Component } from 'react';
import DefaultMenu from './SubCompMenu/DefaultMenu';
import LoginCom from './SubCompMenu/LoginCom';
import SingupCom from './SubCompMenu/SingupCom';
class Menu extends Component {
//==================================================================
constructor(props){
super(props);
this.state = {
show: this.props.shows
};
this.getBackCancelLoginForm = this.getBackCancelLoginForm.bind(this);
}
//===============================================================
//getBackCancelLoginForm use to hindle click event singin & singup childs
//===============================================================
getBackCancelLoginForm(e){
console.log("Hi")
this.setState({
show : "Default"
})
}
//=================================================================
// getDerivedStateFromProps changes state show when props.shows changes
//===============================================================
componentWillReceiveProps(nextProps) {
if(this.props.show != this.nextProps){
this.setState({ show: nextProps.shows });
}
}
//======================================================================
render() {
return (
<div>
{ this.state.show === "Singin" ?
<LoginCom
cencelLogin={this.getBackCancelLoginForm.bind(this)}
/>
: (<div> </div>)}
{ this.state.show === "Singup" ?
<SingupCom
cencelLogin={this.getBackCancelLoginForm.bind(this)}
/>
: (<div> </div>)}
{ this.state.show === "Default" ?
<DefaultMenu MenuClicks={this.props.MenuClick}/> : (<div> </div>)}
</div>
);
}
}
Default menu:
class DefaultMenu extends Component {
render() {
return (
<div className="box11" onClick={this.props.MenuClicks("Homepage")}>
<h3 className="boxh3" onClick={this.props.MenuClicks("Homepage")}>HOME</h3>
);
}
}
//================ Describe expected and actual results. ================//
I'm expecting the string "Homepage" to be assigned to my state "body"
but console.log shows:
Class {dispatchConfig: {…}, _targetInst: FiberNode, nativeEvent: MouseEvent, type: "click", target: div.box11, …}
instead of "Homepage"
Use arrow functions in onClick listener, in above question Change DefaultMenu as:
class DefualtMenu extends Component {
render() {
return (
<div className="box11" onClick={() => this.props.MenuClicks("Homepage")}>
<h3 className="boxh3">HOME</h3>
</div>
);
} }
For arrow functions learn from mozilla Arrow Functions
I hope this helps.
I have react component in react native app and this will return Smth like this:
constructor(){
...
this.Comp1 = <Component1 ..... >
this.Comp2 = <Component2 ..... >
}
render(){
let Show = null
if(X) Show = this.Comp1
else Show = this.Comp1
return(
{X}
)
}
and both of my Components have an API request inside it ,
so my problem is when condition is changed and this toggle between Components , each time the Components sent a request to to that API to get same result ,
I wanna know how to save constructed Component which they wont send request each time
One of the ways do that is to handle the hide and show inside each of the child component comp1 and comp2
So you will still render both comp1 and comp2 from the parent component but you will pass a prop to each one of them to tell them if they need to show or hide inner content, if show then render the correct component content, else just render empty <Text></Text>
This means both child components exist in parent, and they never get removed, but you control which one should show its own content by the parent component.
So your data is fetched only once.
Check Working example in react js: https://codesandbox.io/s/84p302ryp9
If you checked the console log you will find that fetching is done once for comp1 and comp2.
Also check the same example in react native below:
class Parent extends Component {
constructor(props)
{
super(props);
this.state={
show1 : true //by default comp1 will show
}
}
toggleChild= ()=>{
this.setState({
show1 : !this.state.show1
});
}
render(){
return (
<View >
<Button onPress={this.toggleChild} title="Toggle Child" />
<Comp1 show={this.state.show1} />
<Comp2 show={!this.state.show1} />
</View>
)
}
}
Comp1:
class Comp1 extends Component
{
constructor(props) {
super(props);
this.state={
myData : ""
}
}
componentWillMount(){
console.log("fetching data comp1 once");
this.setState({
myData : "comp 1"
})
}
render(){
return (
this.props.show ? <Text>Actual implementation of Comp1</Text> : <Text></Text>
)
}
}
Comp2:
class Comp2 extends Component {
constructor(props) {
super(props);
this.state = {
myData2: ""
}
}
componentWillMount() {
console.log("fetching data in comp2 once");
this.setState({
myData2: "comp 2"
});
}
render() {
return (
this.props.show ? <Text>Actual implementation of Comp2</Text> : <Text></Text>
)
}
}
I think, you should move all your logic to the main component (fetching and saving data, so you component1 and component2 are simple dumb components. In component1 and component2 you can check "does component have some data?", if there isn't any data, you can trigger request for that data in parent component.
Full working example here: https://codesandbox.io/s/7m8qvwr760
class Articles extends React.Component {
componentDidMount() {
const { fetchData, data } = this.props;
if (data && data.length) return;
fetchData && fetchData();
}
render() {
const { data } = this.props;
return (
<div>
{data && data.map((item, key) => <div key={key}>{item.title}</div>)}
</div>
)
}
}
class App extends React.Component{
constructor(props){
super(props);
this.state = {
news: [],
articles: [],
isNews: false
}
}
fetchArticles = () => {
const self = this;
setTimeout( () => {
console.log('articles requested');
self.setState({
articles: [{title: 'article 1'}, {title: 'articles 2'}]
})
}, 1000)
}
fetchNews = () => {
const self = this;
setTimeout(() => {
console.log('news requested');
self.setState({
news: [{ title: 'news 1' }, { title: 'news 2' }]
})
}, 1000)
}
handleToggle = (e) => {
e.preventDefault();
this.setState({
isNews: !this.state.isNews
})
}
render(){
const { news, articles, isNews} = this.state;
return (
<div>
<a href="#" onClick={this.handleToggle}>Toggle</a>
{isNews? (
<News data={news} fetchData={this.fetchNews} />
): (
<Articles data={articles} fetchData={this.fetchArticles} />
)}
</div>
)
}
}
I have a small exercise I'm working on to learn ReactJS. I'm making an API call to display a list from iTunes, and when the individual list items are clicked on, they move over to a new list (kind of like a queue). When clicked on in the new list, they move back to the original list, however the list item is not being removed from the new list when it gets moved back. Here is a jsfiddle of my problem: https://jsfiddle.net/6k9ncbr6/
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
searchResults: [],
moveResults: []
}
this.handleEvent = this.handleEvent.bind(this);
}
showResults = (response) => {
this.setState({
searchResults: response.results,
moveResults: []
})
}
search = (URL) => {
$.ajax({
type: 'GET',
dataType: 'json',
url: URL,
success: function(response) {
this.showResults(response);
}.bind(this)
});
}
handleEvent = (clickedTrack) => {
const { searchResults, moveResults } = this.state;
const isInSearchResults = searchResults.some(result => result.trackId === clickedTrack.trackId);
this.setState({
searchResults: isInSearchResults ? searchResults.filter(i => i.trackId !== clickedTrack.trackId) : [...searchResults, clickedTrack],
moveResults: isInSearchResults ? [...moveResults, clickedTrack] : moveResults.filter(i => i.trackId !== clickedTrack)
});
}
componentDidMount() {
this.search('https://itunes.apple.com/search?term=broods');
}
render(){
return (
<div>
<Results
searchResults={this.state.searchResults}
handleEvent={this.handleEvent}/>
<Results
searchResults={this.state.moveResults}
handleEvent={this.handleEvent} />
</div>
);
}
}
class Results extends React.Component {
render(){
const { handleEvent, searchResults } = this.props;
return(
<ul>
{this.props.searchResults.map((result, idx) =>
<ResultItem
key={`${result.trackId}-${idx}`}
trackName={result.trackName}
track={result}
handleClick={handleEvent} />
)}
</ul>
);
}
}
class ResultItem extends React.Component {
render(){
const { handleClick, track, trackName } = this.props
return <li onClick={() => handleClick(track)}> {trackName} </li>;
}
}
ReactDOM.render(
<App />, document.getElementById('root')
);
You left off a property name:
moveResults: isInSearchResults ? [...moveResults, clickedTrack] : moveResults.filter(i => i.trackId !== clickedTrack.trackId)
The clickedTrack.trackId is missing the .trackId in your existing code.
Here's the working jsFiddle.
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.
I have seen a lot loader plugins that work for the Mount life cycle but none for the update part and I wonder how to handle it?
What I tried was following setup for parent:
class App extends React.Component {
constructor() {
super()
this.state = {loader_wrap:false};
this.hideLoader = this.hideLoader.bind(this);
this.showLoader = this.showLoader.bind(this);
}
hideLoader(){
this.setState({loader_wrap: false});
}
showLoader() {
this.setState({loader_wrap: true});
}
render() {
var loaderStyle;
if (this.state.loader_wrap) {
loaderStyle = {display:"block"};
} else {
loaderStyle = {display:"none"};
}
return (
<div>
<div id="content">
{React.cloneElement(content, {
hideLoader: this.hideLoader,
showLoader: this.showLoader
})}
</div>
<div id="loader-wrap" style={loaderStyle}>
<img className="loader hidden-sm hidden-xs" src='source/file/'>
</div>
</div>
)
}
}
And this is the child calling the methods:
class Childextends React.Component {
constructor() {
super();
this.state = {results:[]};
this.calculate = this.calculate.bind(this);
}
calculate(dict) {
this.props.showLoader();
Actions.action(dict)
.then(results => {
this.setState({results: results});
})
.catch((err) => {
var errResp = JSON.parse(err.response);
console.log(errResp);
this.setState({responseErrors: errResp});
});
}
componentDidMount() {
this.props.hideLoader();
}
componentDidUpdate() {
this.props.hideLoader();
}
componentWillReceiveProps(values){
this.setState({results:values.results});
}
render() {
return (
/*stuff to be returned*/
)
}
}
I also tried to use the Will methods .. which worked even worser :D
Any ideas how to implement this? I use react with flux but don't now how to use it in this case ..
Why not just call hideLoader() in the callback of the action's promise?
class Childextends React.Component {
constructor() {
super();
this.state = {results:[]};
this.calculate = this.calculate.bind(this);
}
calculate(dict) {
this.props.showLoader();
Actions.action(dict)
.then(results => {
this.setState({results: results});
})
.catch((err) => {
var errResp = JSON.parse(err.response);
console.log(errResp);
this.setState({responseErrors: errResp});
})
.then(() => {
this.props.hideLoader();
});
}
render() {
return (
/*stuff to be returned*/
)
}
}
Edit: A different approach to the parent component as well - rather than hiding the element with a style, just don't render it if it isn't required.
render() {
return (
<div>
<div id="content">
{React.cloneElement(content, {
hideLoader: this.hideLoader,
showLoader: this.showLoader
})}
</div>
{this.state.loader_wrap &&
<div id="loader-wrap" style={loaderStyle}>
<img className="loader hidden-sm hidden-xs" src='source/file/'>
</div>
}
</div>
)
}