Pass Prop to Child component onClick - javascript

Depending on which link is clicked, I would like to update the img src in the MapImage Component
import React from 'react'
import NavLink from './NavLink'
var MapImage = React.createClass({
render: function() {
return <img src={'./img/' + this.props.img + '.png'} />
}
});
export default React.createClass({
getInitialState: function () {
return { img: '1' }
},
loadImage: function () {
this.setState({
img: this.props.img
});
},
render() {
return (
<div>
<h2>Maps</h2>
<ul>
<li><NavLink onClick={this.loadImage} to="/maps/firstfloor" img='1'>1st Floor</NavLink></li>
<li><NavLink onClick={this.loadImage} to="/maps/secondfloor" img='2'>2nd Floor</NavLink></li>
<li><NavLink onClick={this.loadImage} to="/maps/thirdfloor" img='3' >3rd Floor</NavLink></li>
</ul>
{this.props.children}
<MapImage img={this.state.img} />
</div>
)
}
})
The image src is updated to ./img/undefined.png

You don't have that image value in the props when you're doing:
this.setState({
img: this.props.img
});
Try to pass a parameter to the loadImage function, and use it in the setState:
// in JSX
onClick={ function () { this.loadImage(1); } }
// function
loadImage: function (img) {
this.setState({
img: img
});
}
For each NavLink image.
In general, I'd recommend having an array and iterating over it, like:
var images = [{
value: 'firstfloor',
text: '1st Floor'
},
{ ... // other objects }]
And then iterate like this (or change values depending on your logic):
{
images.map((image, index) => {
return (
<li>
<NavLink
onClick={ function () { this.loadImage(index); } }
to={ '/maps/' + image.value }
img={ index }>
{ image.text }
</NavLink>
</li>
);
});
}

Related

My react child callback returns the previous data (navbar)

I am learning React and I have a problem with my childCallback, for return data from child to parent.
I make a navbar and when I click on the navButton, the parent receive the name of the precedent button, I have to click two times for display the good data.
Here is my code :
//PARENT COMPONENT
export const Profil = () => {
const [navActive, setNavActive] = useState("infos");
const handleCallback = (childData, e) => {
setNavActive(childData);
};
// View
return (
<div>
<Navbar callback={handleCallback} />
<Infos />
<Gpg />
{navActive}
</div>
);
};
//CHILD COMPONENT
export const Navbar = ({ callback }) => {
// Nav Items
let nav = [
{ key: 1, name: "infos" },
{ key: 2, name: "gpg" },
{ key: 3, name: "compte" },
{ key: 4, name: "otp" },
{ key: 5, name: "journal" },
];
// State
const [activeTitle, setActiveTitle] = useState(nav[0].name);
// Change the selected nav
function selectNav(e) {
let selected = e.target.innerText;
setActiveTitle(selected);
}
const onTrigger = () => {
callback(activeTitle);
};
// View
return (
<Nav tabs>
{nav.map((item) => (
<NavItem key={item.key} onClick={onTrigger}>
<NavLink
style={{ cursor: "pointer" }}
onClick={selectNav}
active={activeTitle === item.name}
>
{item.name}
</NavLink>
</NavItem>
))}
</Nav>
);
};
I don't know how to correct that.
Try to remove onclick from NavItem and call your callback function from selectNav function directly.
The problem come from the setActiveTitle(selected) in Navbar, with console.log I test and the value is good before this.

Inside of a function the state is always same/initial

The remove() function is called from an object. How can I get updated state value inside of that remove() function.
const [InfoBoxPin, setInfoBoxPin] = useState([])
const createInfoBoxPin = (descriptions) =>{
var newPin = {
"location":currentLoc,
"addHandler":"mouseover",
"infoboxOption": {
title: 'Comment',
description: "No comment Added",
actions: [{
label:'Remove Pin',
eventHandler: function () {
remove(newPin.location) //FUNCTION CALLED HERE
}
}] }
}
setInfoBoxPin((InfoBoxPin)=>[...InfoBoxPin, newPin ]) // UPDATE STATE. Push the above object.
}
const remove = (pos) =>{
console.log(InfoBoxPin) //NEVER GETTING UPDATED STATE HERE.
//Other codes here......
}
This is a bing map Info card. Eventhandler creates a button which can call any function.
The problem is that you are referring to old state information in the remove function.
When you call setInfoBoxPin - the state of InfoBoxPin is registered for an update on the next render of UI. This means that in current state it will be the same (empty) and all links to it will refer to an empty array.
In order to fix this, you will have to pass your new state to appropriate functions from the View itself.
Example #1
Here, I have created a CodeSandBox for you:
https://codesandbox.io/s/react-setstate-example-4d5eg?file=/src/App.js
And here is the code snipped from it:
import React, { useState } from "react";
import "./styles.css";
export default function App() {
const [state, setState] = useState({
InfoBoxPin: [],
output: []
});
const createInfoBoxPin = (descriptions) => {
var newPin = {
location: Math.round(Math.random(10) * 1000),
addHandler: "mouseover",
infoboxOption: {
title: "Comment",
description: "No comment Added",
actions: [
{
label: "Remove Pin",
eventHandler: removePin
},
{
label: "Update Pin",
eventHandler: updatePin
}
]
}
};
setState({ ...state, InfoBoxPin: [...state.InfoBoxPin, newPin] });
};
const updatePin = (key, state) => {
var text = `Updating pin with key #${key} - ${state.InfoBoxPin[key].location}`;
setState({ ...state, output: [...state.output, text] });
console.log(text, state.InfoBoxPin);
};
const removePin = (key, state) => {
var text = `Removing pin with key #${key} - ${state.InfoBoxPin[key].location}`;
setState({ ...state, output: [...state.output, text] });
console.log(text, state.InfoBoxPin);
};
return (
<div className="App">
<h1>React setState Example</h1>
<h2>Click on a button to add new Pin</h2>
<button onClick={createInfoBoxPin}>Add new Pin</button>
<div>----</div>
{state.InfoBoxPin.map((pin, pin_key) => {
return (
<div key={pin_key}>
<span>Pin: {pin.location} </span>
{pin.infoboxOption.actions.map((action, action_key) => {
return (
<button
key={action_key}
onClick={() => action.eventHandler(pin_key, state)}
>
{action.label}
</button>
);
})}
</div>
);
})}
<h4> OUTPUT </h4>
<ul style={{ textAlign: "left" }}>
{state.output.map((txt, i) => {
return <li key={i}>{txt}</li>;
})}
</ul>
</div>
);
}
As you can see I am providing a new state with InfoBoxPin value to a function named eventHandler for onclick event listener of a button.
And then in that function, I can use the new InfoBoxPin value from state how I need it.
Example #2 (ES6)
In this example, I am using a bit different structure for App - using class (ES6)
By using a class for App, we can manipulate App state using different methods.
func.bind(this) can be used on defined function on initialization
func.call(this) can be used to call a dynamic function without arguments
func.apply(this, [args]) can be used to call a dynamic function with arguments
CodeSandBox Link:
https://codesandbox.io/s/react-setstate-example-using-class-cz2u4?file=/src/App.js
Code Snippet:
import React from "react";
import "./styles.css";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
InfoBoxPin: [],
pinName: ""
};
/* ------------ method #1 using .bind(this) ------------ */
this.setPinName = this.setPinName.bind(this);
}
remove(key) {
this.state.InfoBoxPin.splice(key, 1);
this.setState({ InfoBoxPin: this.state.InfoBoxPin });
}
add(pinName) {
this.state.InfoBoxPin.push(pinName);
this.setState({ InfoBoxPin: this.state.InfoBoxPin });
}
processPinNameAndAdd() {
let pinName = this.state.pinName.trim();
if (pinName === "") pinName = Math.round(Math.random() * 1000);
this.add(pinName);
}
setPinName(event) {
this.setState({ pinName: event.target.value });
}
render() {
return (
<div className="shopping-list">
<h1>Pin List</h1>
<p>Hit "Add New Pin" button.</p>
<p>(Optional) Provide your own name for the pin</p>
<input
onInput={this.setPinName}
value={this.state.pinName}
placeholder="Custom name"
></input>
{/* ------------ method #2 using .call(this) ------------ */}
<button onClick={() => this.processPinNameAndAdd.call(this)}>
Add new Pin
</button>
<ul>
{this.state.InfoBoxPin.map((pin, pinKey) => {
return (
<li key={pinKey}>
<div>pin: {pin}</div>
{/* ------------ method #3 using .apply(this, [args]) ------------ */}
<button onClick={() => this.remove.apply(this, [pinKey])}>
Delete Pin
</button>
</li>
);
})}
</ul>
</div>
);
}
}
export default App;
Example #3 (ES6) without access to the created element
This example will show how to handle callbacks from third-party libraries with our own arguments and state data from the event of an auto-generated HTML element
CodeSandBox Link:
https://codesandbox.io/s/react-setstate-example-using-class-no-element-control-lcz5d?file=/src/App.js
Code Snippet:
import React from "react";
import "./styles.css";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
InfoBoxPin: [],
lastPinId: 0,
pinName: ""
};
this.setPinName = this.setPinName.bind(this);
}
remove(id) {
let keyToRemove = null;
this.state.InfoBoxPin.forEach((pin, key) => {
if (pin.id === id) keyToRemove = key;
});
this.state.InfoBoxPin.splice(keyToRemove, 1);
this.setState({ InfoBoxPin: this.state.InfoBoxPin });
}
add(data, id) {
this.state.InfoBoxPin.push({ id: id, data: data });
this.setState({
InfoBoxPin: this.state.InfoBoxPin,
lastPinId: id
});
}
processPinNameAndAdd() {
let pinName = this.state.pinName.trim();
if (pinName === "") pinName = Math.round(Math.random() * 1000);
var newPinId = this.state.lastPinId + 1;
var newPin = {
location: pinName,
addHandler: "mouseover",
infoboxOption: {
title: "Comment",
description: "No comment Added",
actions: [
{
label: "Remove Pin #" + newPinId,
// [ES6 class only] using () => func() for callback function
// By doing so we don't need to use bind,call,apply to pass class ref [this] to a function.
eventHandler: () => this.remove(newPinId)
}
]
}
};
this.add(newPin, newPinId);
}
setPinName(event) {
this.setState({ pinName: event.target.value });
}
render() {
return (
<div className="shopping-list">
<h1>Pin List</h1>
<p>Hit "Add New Pin" button.</p>
<p>(Optional) Provide your own name for the pin</p>
<input onInput={this.setPinName} value={this.state.pinName}></input>
{/*
[ES6 class only] Using {() => func()} for event handler.
By doing so we don't need to use func.bind(this) for passing class ref at constructor
*/}
<button onClick={() => this.processPinNameAndAdd()}>Add new Pin</button>
<ul>
{this.state.InfoBoxPin.map((pin, pKey) => {
return (
<li key={pKey}>
<div>pin: {pin.data.location}</div>
{pin.data.infoboxOption.actions.map((action, aKey) => {
return (
<button key={aKey} onClick={action.eventHandler}>
{action.label}
</button>
);
})}
</li>
);
})}
</ul>
</div>
);
}
}
export default App;
I have created lastPinId entry in a State to track newly created Pin's ids.
Pin id can be used later to find the desired pin in the InfoBoxPin collection for removal.
The most important part how to register your eventHandler is this:
eventHandler: () => this.remove(newPinId)
Please note that using arrow function () => func is important to pass class ref to remove function.

On click go back to previous state or trigger function in Reactjs

I am trying to make filter navigation and want to go back to previous state or trigger function to get the data from another API.
On click of this state, I should be able to clear the filter to return the response from another API.
To understand it completely, please look at the sample App I have created below
Stackblitz : https://stackblitz.com/edit/react-3bpotn
Below is the component
class Playground extends Component {
constructor(props) {
super(props);
this.state = {
selectedLanguage: 'All', // default state
repos: null
};
this.updateLanguage = this.updateLanguage.bind(this);
this.updateLanguagenew = this.updateLanguagenew.bind(this);
}
componentDidMount() {
this.updateLanguage(this.state.selectedLanguage);
}
updateLanguage(lang) {
this.setState({
selectedLanguage: lang,
repos: null
});
fetchPopularRepos(lang).then(
function (repos) {
this.setState(function () {
return { repos: repos };
});
}.bind(this)
);
}
updateLanguagenew(lang) {
if (lang === 'All') {
this.updateLanguage(lang);
return;
}
this.setState({
selectedLanguage: lang,
repos: null
});
fetchPopularReposUpdated(lang).then(
function (repos) {
this.setState(function () {
return { repos: repos };
});
}.bind(this)
);
}
render() {
return (
<div>
<div>
This is the current state : <strong style={{padding: '10px',color:'red'}}>{this.state.selectedLanguage}</strong>
</div>
<div style={{padding: '10px'}}>
On click of above state I should be able to trigger this function <strong>(updateLanguage)</strong> again to clear the filter and load data from this API
</div>
<p>Click the below options</p>
<SelectLanguage
selectedLanguage={this.state.selectedLanguage}
onSelect={this.updateLanguagenew}
/>
{//don't call it until repos are loaded
!this.state.repos ? (
<div>Loading</div>
) : (
<RepoGrid repos={this.state.repos} />
)}
</div>
);
}
}
SelectLanguage component mapping for filter options:
class SelectLanguage extends Component {
constructor(props) {
super(props);
this.state = {
searchInput: '',
};
}
filterItems = () => {
let result = [];
const { searchInput } = this.state;
const languages = [ {
"options": [
{
"catgeory_name": "Sigma",
"category_id": "755"
},
{
"catgeory_name": "Footner",
"category_id": "611"
}
]
}
];
const filterbrandsnew = languages;
let value
if (filterbrandsnew) {
value = filterbrandsnew[0].options.map(({catgeory_name})=>catgeory_name);
console.log (value);
}
const brand = value;
if (searchInput) {
result = this.elementContainsSearchString(searchInput, brand);
} else {
result = brand || [];
}
return result;
}
render() {
const filteredList = this.filterItems();
return (
<div className="filter-options">
<ul className="languages">
{filteredList.map(lang => (
<li
className={lang === this.props.selectedLanguage ? 'selected' : ''}
onClick={this.props.onSelect.bind(null, lang)}
key={lang}
>
{lang}
</li>
))}
</ul>
</div>
);
}
}
Note: This is having the current state {this.state.selectedLanguage}, on click of this I should be able to trigger this function. updateLanguage
The way you are doing set state is not correct
Change
fetchPopularRepos(lang).then(
function (repos) {
this.setState(function () {
return { repos: repos };
});
}.bind(this)
);
To
fetchPopularRepos(lang).then(
function (repos) {
this.setState({
repos: repos
});
}.bind(this)
);
Also Change
fetchPopularReposUpdated(lang).then(
function (repos) {
this.setState(function () {
return { repos: repos };
});
}.bind(this)
);
To
fetchPopularReposUpdated(lang).then(
function (repos) {
this.setState({
repos: repos
});
}.bind(this)
);

React - not repeat a component

I have the following react component for a dropdown:
var UIDropdown = React.createClass({
getDefaultProps: function () {
return {
isOpen: false
};
},
render: function () {
if (this.props.isOpen) {
return (
<div className="dropdown">
<ul className="uk-nav uk-nav-dropdown tm-svg-center">
{this.props.mapOpacityValues.map(function (list, i) {
return (
<li onClick={this.props.opacityThermatic.bind(this, list) } key={"list" + i}>{`${list * 100}%`}</li>
);
}, this) }
</ul>
</div>
);
}
return null;
}
});
I'm looping through some data which outputs some list items, but I have a number for different data items.
How can I add the following in the component without repeating the dropdown component code:
{this.props.mapOpacityValues.map(function (list, i) {
return (
<li onClick={this.props.opacityThermatic.bind(this, list) } key={"list" + i}>{`${list * 100}%`}</li>
);
}, this) }
Example but with a single dropdown component
https://jsfiddle.net/zidski/ddLdg84s/
If I understand you correctly, you need to reuse your Dropdown component
Then do something like this
DropdownItems component
var DropdownItems = React.createClass({
render: function () {
return(
<ul className="uk-nav uk-nav-dropdown tm-svg-center">
{this.props.mapOpacityValues.map(function (list, i) {
return (
<li onClick={this.props.opacityThermatic.bind(this, list) } key={"list" + i}>{`${list * 100}%`}</li>
);
}, this)
}
</ul>
)
}
});
UIDropdown component
var UIDropdown = React.createClass({
getDefaultProps: function () {
return {
isOpen: false
};
},
render: function () {
if (this.props.isOpen) {
return (
<div className="dropdown">
{this.props.children}
</div>
);
}
return null;
}
});
Then you can create any number UIDropdowns like
<UIDropdown>
<DropdownItems mapOpacityValues={someData} opacityThermatic={someFunction}>
</UIDropdown>
Here you need to repeat neither dropdown nor li items. You just resue them in your implementation.

Bind 'this' to NavigationBarRouteMapper non-function

I have a NavBarRouteMapper object that I pass to my navbar. However, in the onpress function of one of the buttons I need to access the state, but I'm not sure how to bind 'this' to the object, since it is a non-function. Relevant code as follows
class app extends Component {
state: {
sideMenuIsOpen: boolean,
};
constructor(props: Object) {
super(props);
this.state = {
sideMenuIsOpen: false,
};
};
static NavigationBarRouteMapper = {
LeftButton(route, navigator, index, navState) {
if (index > 0) {
return (
<SimpleButton
// TODO: Make this work, Menu button needs to go to this
// The problem is here. this.state is undefined
onPress={console.log(this.state)}
customText="Back"
style={styles.navBarLeftButton}
textStyle={styles.navBarButtonText}
/>
);
}
},
RightButton(route, navigator, index, navState) {
// TODO change add button for admins
switch (route.title) {
case "Login":
return null;
default:
return (
<SimpleButton
onPress={() => {
navigator.push({
title: "Profile",
component: Profile,
});
}}
customText="Add"
style={styles.navBarRightButton}
textStyle={styles.navBarButtonText}
/>
);
}
},
Title(route, navigator, index, navState) {
return (
<Text style={styles.navBarTitleText}>{route.title}</Text>
);
},
};
render() {
return (
<SideMenu
menu={<Menu navigate={this.navigate} />}
isOpen={this.state.sideMenuIsOpen}
>
<Navigator
ref="rootNavigator"
initialRoute={{
title: "Login",
component: LoginScene,
navigator: this.refs.rootNavigator,
}}
renderScene = {this.renderScene}
navigationBar={
<Navigator.NavigationBar
// Since this is an object, I can't bind 'this' to it,
// and the documentation calls for it to be passed an object
routeMapper={app.NavigationBarRouteMapper}
style={styles.navBar}
/>
}
/>
</SideMenu>
);
};
}
So you're trying to return the parent's state from the child onClick? If so you can add an onClick which calls the parent onClick that you can pass to the child via props.
var Hello = React.createClass({
getInitialState: function() {
return {test: "testing 1 2 3"};
},
clickMethod: function () {
alert(this.state.test);
},
render: function() {
return <div onClick={this.clickMethod}>Hello {this.props.name}</div>;
}
});
var Child = React.createClass({
render: function() {
return <div>Hello {this.props.name}</div>;
}
});
ReactDOM.render(
<Hello name="World" />,
document.getElementById('container')
);
https://jsfiddle.net/reactjs/69z2wepo/
Also if you had a list of components where each component needs to pass a unique piece of data to the parent you can do so using bind.
var Hello = React.createClass({
getInitialState: function() {
return {test: "testing 1 2 3"};
},
clickMethod: function (argument) {
alert(argument);
},
render: function() {
return <div onClick={this.clickMethod.bind(this, "custom argument")}>Hello {this.props.name}</div>;
}
});
var Child = React.createClass({
render: function() {
return <div>Hello {this.props.name}</div>;
}
});
ReactDOM.render(
<Hello name="World" />,
document.getElementById('container')
);
https://jsfiddle.net/chrshawkes/64eef3xm/1/

Categories

Resources