how to render child component of only one generated child in react - javascript

In a React app, I have a component to display the results of a search called ResultsContainer. I am generating children by mapping a prop that has been passed down from a parent and creating a list element from each item in the array. Each of these children has a component that I (DetailsContainer) want to render when a button is clicked, and ONLY for the child that had the button clicked. As of now, the component renders for all of these children. I am not sure how to target the individual child so that the component only renders for that specific one.
ResultsContainer:
var React = require('react');
var DetailsContainer = require('./DetailsContainer.jsx');
var ResultsContainer = React.createClass ({
render: function () {
var queryType = this.props.queryType;
var details = this.props.details;
var that = this;
if (this.props.results == null) {
var searchResult = "Enter search terms"
} else {
var searchResult = this.props.results.map(function(result, index){
//if artist search
if (queryType == "artist") {
if (result.type == "artist") {
return <li className="collection-item" key={index}> {result.title} <button onClick={that.props.handleDetailClick}>Get details</button> <DetailsContainer details={details} /> </li> ;
}
}
});
};
return (
<div className="collection">
{searchResult}
</div>
);
},
});
module.exports = ResultsContainer;
When the button is clicked, I want to render the DetailsContainer component for that child ONLY, not all of them. How could I do this?
DetailsContainer:
var React = require('react');
var DetailsContainer = React.createClass({
render: function(){
if (this.props.details !== null) {
var detailsDisplay = "deets"
}
return (
<div className="detailsDisplay">
{detailsDisplay}
</div>
);
},
});
module.exports = DetailsContainer;
Here is handleDetailClick a function being passed down from the parent as a prop:
handleDetailClick: function() {
this.setState({details: "details"});
},
Also, in ResultsContainer, I could not figure out how to use .bind() to be able to use this.props.handleDetailClick from inside the callback of the .map, so i just created a variable "that" and set it equal to "this" outside the function. Is this bad practice? What is the proper way to do it using .bind()? Thanks so much.

First off map has an option to pass this. map(function(item){}, this); You have at least two options to limit the list. One is to use state like so;
render() {
let useList;
if (this.state.oneSelected)
useList = this.props.list[this.state.oneSelected];
else
useList = this.props.list;
let useListing = useList.map(function(item){}, this);
return ({useListing})
}
The other option is to pass the selected item to the parent this.props.selectOne(item.id) and have the parent return just the one item as a list.

Related

React component always gives last given props value

Child Component
export class MultiSelectDrawer extends React.Component {
componentDidMount(){
window.onpopstate = ()=>{
console.log(this.props.id);
}
}
render() {
const {input, id, label, showActionDrawer, toggleActionDrawer, items, closeActionDrawer} = this.props;
return (
<div>
</div>
);
}
}
MultiSelectDrawer.contextTypes = {
t: PropTypes.func.isRequired
}
export default MultiSelectDrawer;
Parent component
<td style={Object.assign({},styles.filters, styles.leftAlign)}>
<Field id="stateSearchFilter" name="stateSearchFilter" component={MultiSelectDrawer} label={this.context.t("State")} items={states} titleField='value' toggleActionDrawer = {toggleActionDrawerState} showActionDrawer = {this.state.showActionDrawerState} closeActionDrawer = {closeActionDrawerStateFilter} filterBar={filterBar}></Field>
</td>
<td style={Object.assign({},styles.filters, styles.leftAlign)}>
<Field id="prioritySearchFilter" name="prioritySearchFilter" component={MultiSelectDrawer} label={this.context.t("Priority")} items={priorities} titleField='label' toggleActionDrawer = {toggleActionDrawerPriority} showActionDrawer = {this.state.showActionDrawerPriority} closeActionDrawer = {closeActionDrawerPriorityFilter} filterBar={filterBar}></Field>
</td>
In the child componentDidMount window.onpopstate log I am expecting to see both the ids printed when browser back button is pressed. But it is only printing the second id which is prioritySearchFilter. I am quite new to React. What is wrong i am doing here?
The problem is that you are overwriting your first function.
You are calling:
window.onpopstate = ()=>{
console.log(this.props.id);
}
window.onpopstate = ()=>{
console.log(this.props.id);
}
twice (in each component instance). So its like when you assign two values to the same variable (here the variable is window.onpopstate). The last assignment overwrites the first.
var x = 1;
x = 2 // now x is 2. previous value is overwritten.
Or in your case:
window.onpopstate = func1;
window.onpopstate = func2;
After these two calls window.onpopstate now points to func2

Appcelerator - Programmatic Toggle of Switch in Custom TableViewRow

I have a custom TableViewRow with a title and switch like below:
rowFilter.xml
<TableViewRow id="rowFilter">
<View id="vwItemHeader">
<Label id="lblItemHeader"></Label>
</View>
<View id="vwFilterStatus">
<Switch id="swtFilterStatus" onChange="swtFilterStatusChange"></Switch>
</View>
</TableViewRow>
rowFilter.js
var args = arguments[0] || {};
var swtFilterStatusChange_callback;
initialize();
function initialize() {
// Initialize filter row UI
$.lblItemHeader.text = args.title;
$.swtFilterStatus.value = args.value;
// Callback
swtFilterStatusChange_callback = args.callback;
};
In a view I call Browse, I programmatically add these custom rows as follows:
var args = { title: item.title, value: item.value, callback: swtFilterStatusChanged, };
var newRow = Alloy.createController('rowFilter', args).getView('rowFilter');
This works just as intended. However now I want to add a check/uncheck all row. How do I programmatically toggle a switch within my custom rows?
I've tried creating the following function in rowFilter.js (and a similar one for toggle off):
exports.toggleOn = function() {
if ($.swtFilterStatus.value == false) {
$.swtFilterStatus.value = true;
swtFilterStatusChange();
}
};
And I've also tried this:
$.toggleOn = function() {
}
::EDIT:: Here is how I handle the check/uncheck all switch.
function allSwitch_Change(value) {
$.tblFilters.data[0].rows.forEach(function(row) {
if (value) {
row.toggleOn();
}
else {
row.toggleOf();
}
}
}
Then changing the Browse.js row above that does Alloy.createController with the following:
var newRow = require('rowFilter');
newRow.initialize(args);
But I just get an exception on the line with the require statement saying "Couldn't find module:rowFilter for architecture: x86_64".
What am I doing wrong and how do I implement a check/uncheck all row?
You can do something like this:
rowFilter.js
var args = arguments[0] || {};
var swtFilterStatusChange;
initialize();
function initialize() {
// Initialize filter row UI
$.lblItemHeader.text = args.title;
$.swtFilterStatus.value = args.value;
// Callback
swtFilterStatusChange = args.callback;
};
$.on('toggleOn',function(){
if ($.swtFilterStatus.value == false) {
$.swtFilterStatus.value = true;
swtFilterStatusChange();
}
});
browser.js
var args = { title: item.title, value: item.value, callback: swtFilterStatusChanged, };
var newRow = Alloy.createController('rowFilter', args);
newRow.trigger('toggleOn');
Other options to achieve your goal:
Global Events - Ti.App.addEventListener and Ti.App.fireEvent. Easy to implement, but has potential memory leak. Make sure to call Ti.App.removeEventListener after table row removed.
Get the switch view from the table row's children. Manually fire the event on it.
Store the reference to the callback swtFilterStatusChanged somewhere and call it later.

React render component multiple times in container with different props?

I am trying to create a reusable "tag" React component, so that users can create tags onclick and see that information displayed in the DOM.
Here's the module:
module.exports = React.createClass({
render: function() {
return (
<div className="language chip" data-lang={this.props.language} data-lang-level={this.props.level}>
{this.props.language} ({this.props.level})
<i className="material-icons">close</i>
</div>
);
}
});
And the onclick call:
var addLanguage = $('a#add-language');
addLanguage.click(function() {
var languageLearning = $('#language-learning');
var levelLearning = $('#language-level');
if (languageLearning != null && levelLearning != null) {
ReactDOM.render(
<LanguageChip
language={languageLearning.val()}
level={levelLearning.val()}
/>,
document.getElementById('language-chips')
);
languageLearning.select2('val', '');
levelLearning.select2('val', '');
}
})
I didn't realise that when using React.DOM, "Any existing DOM elements inside are replaced when first called." This means when adding a second chip, the first is removed. I want users to be able to have multiple chips.
How can I do this?
I don't know if you've got a good reason to not add the form used to create a tag on your component, but it would be much simpler if you could.
Then you just have to add your tags on an array and display them with your LanguageChip component.
I've made an example here: https://jsfiddle.net/snahedis/69z2wepo/28193/
I don't know what's your level of understanding of React so if something isn't clear let me know :)
Edit: the same example inside a preexistent form:
https://jsfiddle.net/snahedis/69z2wepo/28217/
You need to use array to store multiple chips data. Take a look to this simplified example: http://output.jsbin.com/seficu
var LanguageChips = React.createClass({
render: function() {
return (
<div>
{
(this.props.chipsArray).map(function(chip, index) {
return <LanguageChip
key={index}
language={chip.languageLearning}
level={chip.levelLearning}
/>
})
}
</div>
);
}
});
var LanguageChip = React.createClass({
render: function() {
return (
<div className="language chip" data-lang={this.props.language} data-lang-level={this.props.level}>
{this.props.language} ({this.props.level})
<i className="material-icons"></i>
</div>
);
}
});
var chipsArray = [];
document.getElementById('add-language').addEventListener("click", function() {
var languageLearning = 'test1';
var levelLearning = 'test2';
if (languageLearning != null && levelLearning != null) {
chipsArray.push({
languageLearning: languageLearning,
levelLearning: levelLearning
});
ReactDOM.render(
<LanguageChips chipsArray={chipsArray} />,
document.getElementById('language-chips')
);
}
})

Mixin functions only work in render()

For some reason, it appears that mixin functions in my code only work properly in render() function. It could be that I'm not calling them in the right manner outside of the render(), but shouldn't it be exactly the same way?
This way everything works fine (but I can't stick with this since I have to add some extra stuff to click handling, at the same time not altering the mixin):
var Row = React.createClass({
mixins: [someMixin]
},
render: function () {
var clickHandler = null;
var btn = null;
if (firstCase) {
clickHandler = this.order(this.props.name, this.props.else);
btn = (<a href="" onClick={clickHandler}>Order</a>);
} else if (secondCase) {
clickHandler = this.noOrder(this.props.name, this.props.else);
btn = (<a href="" onClick={clickHandler}>No order</a>);
}
return (
<div>
{btn}
</div>
);
}
});
But when I do the obvious and include the mixin functions in another function to handle the click - like this - everything fails and even 'test' is not printed in the console:
var Row = React.createClass({
mixins: [someMixin]
},
handleOrderClick(type) {
console.log('test');
if (type == 'order') {
this.order(this.props.name, this.props.else);
} else if (type == 'no-order') {
this.noOrder(this.props.name, this.props.else);
}
},
render: function () {
var clickHandler = null;
var btn = null;
if (firstCase) {
clickHandler = this.handleOrderClick('order');
btn = (<a href="" onClick={clickHandler}>Order</a>);
} else if (secondCase) {
clickHandler = this.handleOrderClick('no-order');
btn = (<a href="" onClick={clickHandler}>No order</a>);
}
return (
<div>
{btn}
</div>
);
}
});
EDIT:
order and noOrder functions look like this:
order: function (name, else) {
return function (event) {
event.preventDefault();
var term = name + '&&ยค%' + else;
Order.order(name, else, period, function (data) {
if (term === (global || window).MAIN_NAME + '.' + (global || window).MAIN) {
$(window).trigger('Name:update');
}
}.bind(this));
}.bind(this);
},
noOrder: function (name, else) {
return function (event) {
event.preventDefault();
if (!this.state.transferModalOpen) {
this.toggleTransferModal();
}
}.bind(this);
}
In order to use this.setState in handleOrderClick you'll have to use the bind method in your render method. Therefore handleOrderClick will become:
handleOrderClick(type, event) {
this.setState({foo: 'bar'});
if (type == 'order') {
this.order(this.props.name, this.props.else)(event);
} else if (type == 'no-order') {
this.noOrder(this.props.name, this.props.else)(event);
}
},
and your render method becomes:
render: function () {
var clickHandler = null;
var btn = null;
if (firstCase) {
clickHandler = this.handleOrderClick.bind(this, 'order');
btn = (<a href="" onClick={clickHandler}>Order</a>);
} else if (secondCase) {
clickHandler = this.handleOrderClick(this, 'no-order');
btn = (<a href="" onClick={clickHandler}>No order</a>);
}
return (
<div>
{btn}
</div>
);
}
You'll notice that the functions that are returned by this.order and this.noOrder are no longer returned by handleOrderClick, but are instead executed immediately. This should provide the effect you desire.
I've put the code in your example into a jsfiddle and it now seems to be working correctly. I've had to change the prop 'else' to 'alt' because 'else' is a reserved word. I've also just applied the mixin to the class directly for simplicity. I have simpilfied the order and noOrder functions as I don't have access to the Order object and we are only interested in them firing at the correct time. I've also added a second button that you can click to flip the cases so the other button is rendered, causing the component to render again. I've added a label that will display which function had been called when the button was last pressed.
for reference, you can find more information about the bind method here.
Hope this helps ^_^

Custom image toggle button in ReactJS

I have this ReactJS code to show a custom image button that toggles between 2 different images for ON and OFF state. Is there a simpler way to do this? I was hoping CSS might be less lines of code, but wasn't able to find a simple example.
The code below passes state up from <MyIconButton> to <MyPartyCatButton> then to <MyHomeView>. My app will have 4 of these custom buttons on the home screen, which is why I factored out <MyIconButton>.
btw - this is for a mobile App and I read (and noticed this myself) it's really slow using checkboxes on mobile browsers; that's why I chose to try this without using checkboxes.
ReactJS code
var MyIconButton = React.createClass({
handleSubmit: function(e) {
e.preventDefault();
console.log("INSIDE: MyIconButton handleSubmit");
// Change button's state ON/OFF,
// then sends state up the food chain via
// this.props.updateFilter( b_buttonOn ).
var b_buttonOn = false;
if (this.props.pressed === true) {
b_buttonOn = false;
}
else {
b_buttonOn = true;
}
// updateFilter is a 'pointer' to a method in the calling React component.
this.props.updateFilter( b_buttonOn );
},
render: function() {
// Show On or Off image.
// ** I could use ? : inside the JSX/HTML but prefer long form to make it explicitly obvious.
var buttonImg = "";
if (this.props.pressed === true) {
buttonImg = this.props.onpic;
}
else {
buttonImg = this.props.offpic;
}
return (
<div>
<form onSubmit={this.handleSubmit}>
<input type="image" src={buttonImg}></input>
</form>
</div>
);
}
});
// <MyPartyCatButton> Doesn't have it's own state,
// passes state of <MyIconButton>
// straight through to <MyHomeView>.
var MyPartyCatButton = React.createClass({
render: function() {
return (
<MyIconButton pressed={this.props.pressed} updateFilter={this.props.updateFilter} onpic="static/images/icon1.jpeg" offpic="static/images/off-icon.jpg"/>
);
}
});
//
// Main App view
var MyHomeView = React.createClass({
getInitialState: function() {
// This is where I'll eventually get data from the server.
return {
b_MyPartyCat: true
};
},
updatePartyCategory: function(value) {
// Eventually will write value to the server.
this.setState( {b_MyPartyCat: value} );
console.log("INSIDE: MyHomeView() updatePartyCategory() " + this.state.b_MyPartyCat );
},
render: function() {
return (
<div>
<MyPartyCatButton pressed={this.state.b_MyPartyCat} updateFilter={this.updatePartyCategory}/>
</div>
// Eventually will have 3 other categories i.e. Books, Skateboards, Trees !
);
}
});
if you update the coponent 'pressed' prop dynamically (like you did), simply
var MyIconButton= React.createClass({
render: function(){
var pic= this.props.pressed? this.props.onpic : this.props.offpic
return <img
src={pic}
onClick={this.props.tuggleSelection} //updateFilter is wierd name
/>
}
})
(EDIT: this way, on MyPartyCatButton component, you can pass function to handle 'tuggleSelection' event. event function argument is an event object, but you have the button state allready in the wrapper state (the old one, so you should invert it). your code will be something like that:
render: function(){
return <MyIconButton pressed={this.state.PartyCatPressed} tuggleSelection={this.updatePartyCategory} />
}
updatePartyCategory: function(e){
this.setState(
{PartyCatPressed: !this.state.PartyCatPressed} //this invert PartyCatPressed value
);
console.log("INSIDE: MyHomeView() updatePartyCategory() " + this.state.b_MyPartyCat )
}
)
but if you don't, use prop for defult value:
var MyIconButton= React.createClass({
getInitialState: function(){
return {pressed: this.props.defultPressed}
},
handleClick: function(){
this.setState({pressed: !this.state.pressed})
},
render: function(){
var pic= this.state.pressed? this.props.onpic : this.props.offpic
return <img
src={pic}
onClick={this.handleClick}
/>
}
})

Categories

Resources