React Passing Props to Second-level Children - javascript

I faced the problem of passing props while coding in React. Yes, I have seen this issue before, but this time it's a second level-children component and things are a bit weird. My code (comments along the way):
class EditForm extends React.Component {
handleSubmit = (event, idx) => {
event => event.preventDefault();
postData('/', {idx: idx})
.then(data => {if (data.success) {window.location = '/';}});
console.log(idx); // results printed from here
}
render() {
return (
<Form
onFinish={() => this.handleSubmit(idx)} // output 1
onFinish={() => this.handleSubmit(this.props.idx)} // output 2
>
</Form>
);
}
}
class UpdateModal extends React.Component {
render() {
return (
<Modal>
<EditForm idx={ this.props.idx } /> // value is still not undefined
</Modal>
);
}
}
Outputs:
// 1
useForm.js:766 ReferenceError: idx is not defined
// 2
undefined
Can anyone please explain why I can't pass the props two times in a row? As a matter of fact, the values are still valid when they are in UpdateModal but is gone somehow afterward.
Thanks in advance.

You should pass in the event object to your handlers:
class EditForm extends React.Component {
handleSubmit = (event, idx) => {
event => event.preventDefault();
postData('/', {idx: idx})
.then(data => {if (data.success) {window.location = '/';}});
console.log(idx); // results printed from here
}
render() {
return (
<Form
onFinish={(event) => this.handleSubmit(event, idx)} // output 1
onFinish={(event) => this.handleSubmit(event, this.props.idx)} // output 2
>
</Form>
);
}
}
class UpdateModal extends React.Component {
render() {
return (
<Modal>
<EditForm idx={ this.props.idx } /> // value is still not undefined
</Modal>
);
}
}

class EditForm extends React.Component {
constructor(props) {
super(props);
}
// ...
}
class UpdateModal extends React.Component {
constructor(props) {
super(props);
}
// ...
}
// <EditForm idx={this.state.idx}></EditForm>
// <UpdateModal idx={this.state.idx}></UpdateModal>

Related

Cant render Component in switch statement in React

I have a registration view where in my table i have command to show modal with confirmation:
(...)
render: (rowData) => (
<button
onClick={() => RenderModals(rowData, 'DELETE_USER_MODAL')}
>
Remove
</button>
),
(...)
My RenderModals function looks like this:
type RenderModalProps = {
data: any;
modalCommand: string;
};
export const RenderModals = (data, modalCommand) => {
console.log(data);
switch (modalCommand) {
case 'DELETE_USER_MODAL':
return <DeleteUserModal data={data} />;
case 'SOME_MODAL':
return console.log('some modal');
default:
undefined;
}
};
and I can see console.log(data) in the example above. But... I cant see any console.log from DeleteUserModal component.
DeleteUserModal:
type DeleteUserModalProps = {
data: any;
};
export const DeleteUserModal = ({ data }: DeleteUserModalProps) => {
console.log(`show data ${data}`);
return <div>some text...</div>;
};
I can't figure out what I'm doing wrong ?
Why console.log from DeleteUserModal doesn't trigger?
The way you currently have things set up, this would work:
class RegistrationExampleOne extends React.Component {
constructor(props) {
super(props);
this.state = {component: null};
}
render() {
return (
<div>
<button onClick={() => this.setState({component: RenderModals(rowData, 'DELETE_USER_MODAL')})}>Remove</button>
{this.state.component}
</div>
);
}
}
Option one is not necessarily the better way of doing things, but it is more dynamic.
Option two (as mentioned in the comments by #Brian Thompson) would be similar to this:
import DeleteModal from './path';
class RegistrationExampleTwo extends React.Component {
constructor(props) {
super(props);
this.state = {showDeleteModal: null};
}
render() {
return (
<div>
<button onClick={() => this.setState({showDeleteModal: true})}>Remove</button>
{this.state.showDeleteModal && <DeleteModal data={rowData} />}
</div>
);
}
}

change state in Grandparent Component with button

Hello i'm new on react and have to do a Tutorial where i have to change the state of the Child Component with a button onClick function.
currently i'm use a button in my Parent component to do it and it works but now i have to use call this button in other child components and not directly in the Parent to restart my Tutorial.
but i dont know how i can do it.
ill happy about every suggestion.
class Child extends React.Component {
constructor (props) {
super(props)
this.state = {
run: this.props.run,
stepIndex: this.props.stepIndex
}
}
componentWillReceiveProps (props) {
this.setState({ run: props.run, stepIndex: props.stepIndex })
}
callback = (tour) => {
const { action, index, type } = tour
// if you delete action === 'skip', the Tutorial will not start on other pages after skipped once.
if (action === 'close' || action === 'skip' || type === 'tour:end') {
this.setState({ run: false })
} else if ([EVENTS.STEP_AFTER, EVENTS.TARGET_NOT_FOUND].includes(type)) {
this.setState({ stepIndex: index + (action === ACTIONS.PREV ? -1 : 1) })
}
}
render () {
let { run, stepIndex, steps } = this.state
if (this.props.location.pathname === '/') {
steps = []
run = false
} else if (this.props.location.pathname === '/matches/' || this.props.location.pathname.length === '/matches') {
steps = matchSiteSteps
} else if (this.props.location.pathname.startsWith('/matches/') && this.props.location.pathname.endsWith('/edit/')) {
steps = matchEditorSteps
} else if (this.props.location.pathname.startsWith('/matches/') && this.props.location.pathname.includes('sequence')) {
steps = matchSequenceSteps
} else if (this.props.location.pathname.startsWith('/matches/') && this.props.location.pathname.match('\\d+')) {
steps = matchSteps
}
return (
<>
<Joyride
callback={this.callback}
run={run}
stepIndex={stepIndex}
steps={steps}
continuous
disableOverlayClose
spotlightClicks
showSkipButton
locale={{
back: <span>Zurück</span>,
last: (<span>Beenden</span>),
next: (<span>Weiter</span>)
}}
/>
</>
)
}
}
class Parent extends Component {
constructor (props) {
super(props)
this.handler = this.handler.bind(this)
this.state = {
run: true,
stepIndex: 0,
}
}
handler () {
this.setState({ run: true, stepIndex: 0 })
}
render () {
return (
//some other stuff
<RestartButton handler={this.handler} />
<Tutorial run={this.state.run} stepIndex={this.state.stepIndex} />
//some other stuff
)
}
}
class RestartButton extends React.Component {
render () {
return (
<button className='restartButton' onClick={() => this.props.handler()}>click</button>
)
}
}
You shouldn't store props in the child component state if state.run and state.stepIndex are always going to be the same as props.run and props.stepIndex. Instead you should just use those props directly when you need to use them in the render method. stepIndex={this.props.stepIndex} will pass exactly the same values to the Joyride component as setting the child component's state.stepIndex equal to props.stepIndex and then passing stepIndex={this.state.stepIndex}.
If you want to change the value of the parent component's state from a child component, you can pass the handler method (bound to the parent component) through as many layers of components as you want, or to as many different children as you want.
class Tutorial extends React.Component {
constructor(props) {
super(props)
}
render() {
return (
<>
<RestartButton handler={this.props.handler}/>
<Joyride
callback={this.callback}
run={this.props.run}
stepIndex = {this.props.stepIndex}
steps={steps}
continuous
disableOverlayClose
spotlightClicks
showSkipButton
locale={{
back: < span > Zurück < /span>,
last: ( < span > Beenden < /span>),
next: ( < span > Weiter < /span>)
}}
/>
</>
)
}
}
class Parent extends Component {
constructor(props) {
super(props)
this.handler = this.handler.bind(this)
this.state = {
run: true,
stepIndex: 0,
}
}
handler() {
this.setState({run: true, stepIndex: 0})
}
render() {
return (
<Tutorial
run={this.state.run}
stepIndex={this.state.stepIndex}
/>
)
}
}
class RestartButton extends React.Component {
render() {
return (
<button
className='restartButton'
onClick={() => this.props.handler()}
> click </button>
)
}
}
(Also, componentWillReceiveProps is deprecated and you should use componentDidUpdate instead, if you do need to do something on component update).

How to Set State with arguments passed to a function in React

I'm trying to pass an array (titles) from a child component to the parent, then set the state of the parent with the array. However, when handling the change in the increaseReads() method, I cannot change the articlesRead state
You will see two console.log() statements; the first one is successfully logging the titles but the second is logging an empty array - the previous state
The Child:
export class Publication extends React.Component {
constructor() {
super();
this.state = {
items: []
};
}
componentDidMount() {
fetch(this.props.url)
.then(response => {
return response.json();
}).then(({ items })=> {
this.setState({ items });
});
}
handleClick () => {
this.props.openArticle();
}
render() {
return (
<div className='publication'>
<h4>{this.props.name}</h4>
<ul>
{this.state.items.map(item => (
<li><a href={item.link} target='_blank' onClick={this.handleClick}>{item.title}</a></li>
))}
</ul>
</div>
);
}
}
The Parent:
export class Latest extends React.Component {
constructor(props) {
super(props);
this.state = {
totalReads: 0,
articlesRead: []
};
}
handleChange = () => {
this.props.increaseTotal();
}
increaseReads(titles) {
this.setState({
totalReads: this.state.totalReads + 1,
articlesRead: titles
})
// Won't log correctly
console.log(this.state.articlesRead);
this.handleChange();
}
render() {
return (
<div className='container'>
<Publication total={(titles) => {this.increaseReads(titles)}} name='Free Code Camp' api={'https://api.rss2json.com/v1/api.json?rss_url=https%3A%2F%2Fmedium.freecodecamp.org%2Ffeed%2F'}/>
<Publication total={() => {this.increaseReads()}} name='Code Burst' api={'https://api.rss2json.com/v1/api.json?rss_url=https%3A%2F%2Fcodeburst.io%2Ffeed%2F'}/>
<Publication total={() => {this.increaseReads()}} name='JavaScript Scene' api={'https://api.rss2json.com/v1/api.json?rss_url=https%3A%2F%2Fmedium.com%2Ffeed%2Fjavascript-scene%2F'}/>
<Publication total={() => {this.increaseReads()}} name='Hacker Noon' api={'https://api.rss2json.com/v1/api.json?rss_url=https%3A%2F%2Fhackernoon.com%2Ffeed'}/>
</div>
)
}
}
I'm sure it is something small, but any help would be greatly appreciated!
The issue might be that you are expecting this.setState to be synchronous. See the documentation here.
Take a look at this CodeSandbox demo. this.setState accepts a callback as the second argument. This callback is invoked after this.setState has completed.
Notice how in the console.log output, we can see the old and new state values.

Handling event on array item won't work - ReactJS

I try to map an array and put click event on the array items. I know it's a bit different because of how JavaScript handles functions but I can't make it work. I get the error: Cannot read property 'saveInStorage' of undefined. Can anyone tell me what I'm doing wrong? Thanks in advance! Here is my code:
import React from "react";
const data = require('../data.json');
export default class Gebruikers extends React.Component {
constructor() {
super();
this.state = {
users: data.users
};
this.saveInStorage = this.saveInStorage.bind(this)
}
saveInStorage(e){
console.log("test");
}
renderUser(user, i) {
return(
<p key={i} onClick={this.saveInStorage(user)}>f</p>
);
}
render() {
return (
<div>
{
this.state.users.map(this.renderUser)
}
</div>
);
}
}
this is undefined in renderUser()
You need to bind this for renderUser() in your constructor.
Also, you are calling saveInStorage() every time the component is rendered, not just onClick, so you'll need to use an arrow function in renderUser
import React from "react";
const data = require('../data.json');
export default class Gebruikers extends React.Component {
constructor() {
super();
this.state = {
users: data.users
};
this.saveInStorage = this.saveInStorage.bind(this);
this.renderUser = this.renderUser.bind(this);
}
saveInStorage(e){
console.log("test");
}
renderUser(user, i) {
return(
<p key={i} onClick={() => this.saveInStorage(user)}>
);
}
render() {
return (
<div>
{
this.state.users.map(this.renderUser)
}
</div>
);
}
}
Instead of binding you can also use an arrow function (per mersocarlin's answer). The only reason an arrow function will also work is because "An arrow function does not have its own this; the this value of the enclosing execution context is used" (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions). The enclosing execution in your case is your render, where this is defined.
You need to make two changes to your code which are outlined below.
You are invoking the function when the component is rendered. To fix this update this line to the following
<p key={i} onClick={() => this.saveInStorage(user)}>
This means that the function will only be invoked when you click on the item.
You also need to bind the renderUser in your constructor or else use an arrow function.
this.renderUser = this.renderUser.bind(this);
See working example here.
Your onClick event handler is wrong.
Simply change it to:
onClick={() => this.saveInStorage(user)}
Don't forget to also bind renderUser in your constructor.
Alternatively, you can choose arrow function approach as they work the same as with bind:
class Gebruikers extends React.Component {
constructor(props) {
super(props)
this.state = {
users: [{ id: 1, name: 'user1' }, { id: 2, name: 'user2' }],
}
}
saveInStorage = (e) => {
alert("test")
}
renderUser = (user, i) => {
return(
<p key={i} onClick={() => this.saveInStorage(user)}>
{user.name}
</p>
)
}
render() {
return (
<div>{this.state.users.map(this.renderUser)}</div>
);
}
}
ReactDOM.render(
<Gebruikers />,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
Paul Fitzgeralds answer is the correct one, although I'd like to propose a different way of handling this, without all the binding issues.
import React from "react";
const data = require('../data.json');
export default class Gebruikers extends React.Component {
constructor() {
super();
this.state = {
users: data.users
};
}
saveInStorage = (e) => {
console.log("test");
};
render() {
return (
<div>
{this.state.users.map((user, i) => {
return (<p key={i} onClick={() => this.saveInStorage(user)}>f</p>);
})}
</div>
);
}
}
With saveInStorage = (e) => {}; you are binding the saveInStorage function to the this context of your class. When invoking saveInStorage you'll always have the (at least I guess so in this case) desired this context.
The renderUser function is basically redundant. If you return one line of JSX, you can easily do this inside your render function. I think it improves readability, since all your JSX is in one function.
You are not sending the parameters to this.renderUser
this.state.users.map((user, i) => this.renderUser(user, i))
Also your onClick function should be slightly changed. Here's the full code changed:
import React from "react";
const data = require('../data.json');
export default class Gebruikers extends React.Component {
constructor() {
super();
this.state = {
users: data.users
};
this.saveInStorage = this.saveInStorage.bind(this)
}
saveInStorage(e){
console.log("test");
}
renderUser(user, i) {
return(
<p key={i} onClick={() => this.saveInStorage(user)}>f</p>
);
}
render() {
return (
<div>
{
this.state.users.map((user, i) => this.renderUser(user, i))
}
</div>
);
}
}

How can I use `setState` with objects nested in an array in React JS?

With this code, I am able to successfully use setState on a simple object – when I click on "Joey" the name changes to "Igor".
class Card extends React.Component {
myFunc = () => {this.props.change('Igor')};
render() {
return (
<p onClick={this.myFunc}>{this.props.name}</p>
)
}
}
class Parent extends React.Component {
constructor(props) {
super(props)
this.state = { name: "Joey" }
}
toggle = (newname) => {
this.setState((prevState, props) => ({
name: newname
}));
}
render() {
return (
<Card change={this.toggle} name={this.state.name} />
);
}
}
But with this code, which has multiple objects nested in an array, setState is either not able to change each name to "Igor" or it must be modified in some way.
class Card extends React.Component {
myFunc = () => {this.props.change('Igor')};
render() {
return (
<p onClick={this.myFunc}>{this.props.name}</p>
)
}
}
class Parent extends React.Component {
constructor(props) {
super(props)
this.state = {
names: [
{
name: "Joey"
},
{
name: "Sally"
},
{
name: "Billy"
},
]
}
}
toggle = (newname) => {
this.setState((prevState, props) => ({
// what can I put here to change the name I click on to "Igor"
}));
}
render() {
const names = this.state.names.map((name, index) => (
<Card key={index} change={this.toggle} {...name} />
))
return (
<div>
{names}
</div>
);
}
}
Even though I know this is not how setState works, I tried to access name by passing index and then writing this.state.names[index].name: newname. No surprises here, it didn't work.
I have researched and cannot find similar questions on SO about this although I have found a lot of mentions with regards to immutability helpers. But I am still not sure if that is the way to go.
What is the best way to use setState to modify objects nested in an array?
Have modified your code and the working example can be found here.
The changes can be found here:
toggle = (index, newname) => {
this.setState((prevState, props) => ({
// Return new array, do not mutate previous state.
names: [
...prevState.names.slice(0, index),
{ name: newname },
...prevState.names.slice(index + 1),
],
}));
}
render() {
const names = this.state.names.map((name, index) => (
// Need to bind the index so callback knows which item needs to be changed.
<Card key={index} change={this.toggle.bind(this, index)} {...name} />
))
return (
<div>
{names}
</div>
);
}
The idea is that you need to pass the index into the callback function via .bind, and return a new state array with the modified name. You need to pass the index so that the component knows which object to change the name to newname.
I would use this for the toggle method:
toggle = (nameYouWantChanged, nameYouWantItChangedTo) => {
this.setState({
names: this.state.names.map(obj =>
obj.name === nameYouWantChanged
? { name: nameYouWantItChangedTo }
: obj
)
})
}

Categories