ReactJs: Creating components from array leads to old props - javascript

I have an array of usernames ["frank", "john", "stevie"]. looping through each element i create a ChatBox component:
{
popups.map((x, i)=>(
<ChatBox
addMessage={this.addMessage}
delMessage={this.delMessage}
setMessages={this.setMessages}
getMessages={this.getMessages}
name={x}
users={this.state.users}
id={x}
key={i}
user={this.fetchUser(x)}
/>
))
}
It looks pretty straight forward. I need to make an ajax call once the components are rendered, so I call this:
componentDidMount() {
console.log(this.props.name)
//make ajax call using `this.props.name`
}
The problem: I noticed that the rendered components set the name props with the initial value of "frank" in ["frank", "john", "stevie"]. i.e if I have five elements in the array, the last four components have an initial value of the first element in the array!. And its very important an ajax call is made once the component is rendered, but if the calls are made using the value of the first element in the array, the resulting components will all have the same data as the first component. that's flawed.
I tried getDerivedStateFromProps() and componentDidUpdate() but this looks hacky as the first component that is rendered has the correct value and rarely changes so the ajax call isn't made.
Is there a way I can have all rendered components to have the correct props so i can make the ajax call on componentDidMount.

#FrankDupree this might be happening as you are passing array index as the key value to the chatbox component. Try passing the itemName as key value for the ChatBox component.
<ChatBox
addMessage={this.addMessage}
delMessage={this.delMessage}
setMessages={this.setMessages}
getMessages={this.getMessages}
name={x}
users={this.state.users}
id={x}
key={x}
user={this.fetchUser(x)}
/>
I think this might work for you.
Please go through: https://medium.com/#vraa/why-using-an-index-as-key-in-react-is-probably-a-bad-idea-7543de68b17c for a better understanding.

Related

ReactJS How to pass a prop to a component in state

function App() {
const [currentPanel, setCurrentPanel] = useState(ProfilePanel); // ProfilePanel is a component
return (
<div className={styles.App}>
{currentPanel}
</div>
);
}
In code i set the component "ProfilePanel" to a "curentPanlel" state, then in App i change the component in state, and this render an another panel. the problem is that i dont know how to pass props when i render it like this.
i tried the {currentPanel()} but is return an error.
please help to find a method to solve this, or if this method to render a component in state are absolutly wrong tell how to do this another way.
the problem is that i dont know how to pass props when i render it like this
You'd do it by using an initial capital letter for the state member (CurrentPanel instead of currentPanel), and then using it as normal (<CurrentPanel someProp="some value" />). (It has to be initially-capped because that's how JSX knows it's supposed to be a component, not a tag name.) But, you'll struggle to set a different component function in state, because component functions are, well, functions, and when you pass a function to a state setter, it thinks you're using the callback version of the state setter and calls your function, rather than setting it in state.
If you absolutely have to hold a component function in state, wrap it in an object, but it's much more likely that there's a better solution to the overall problem you're trying to solve.

React - Change state from external component

I know that I will ask a question that brake some rules about the core/basic way to use React... but maybe with this example, someone helps me to solve the problem that I facing.
This is not the full code of my project, but show exactly the idea of my problem:
https://codesandbox.io/s/change-state-from-external-component-zi79e
The thing is I need to change a state from a child component from the parent component, but I don't want to run a render method in my parent or handle the state in the parent component.
Exists a way to achieve this? In my project, I have a parent that creates multiple generic children and it will be more difficult to handle this request.
And specifically, I need to change the state of one child (MyFirstChild), after another child (SecondChild) read the keystroke and run an API to get some values from my backend; after that, I need to send the change to "MyFirstChild" to change his state.
The parent component has ~50 child components and I blocked the re-render method (With the method shouldComponentUpdate)
The expected answer is: "It's not possible, or, you broke the good use of React"...
But, maybe using forwardRef or ref, or something else that I not see can help me to work around this...
To change the state of a child component from the parent without having to run a render method (in the parent): one possible solution would be to use Redux. With Redux from the parent you can dispatch an Action (execute an Action that changes the state of Redux).
And in the child component, you receive that part of the state that you change (it could be a string, object, etc) as a prop. So when it is changed from the parent, your child component will render again without having to run a render method in the parent.
https://react-redux.js.org/introduction/basic-tutorial
You could also use Context for the same purpose.
However, I saw your code example, I am not sure the exact reason of why you don't want to make a render in the parent, but the easiest solution for what you need would be to send the function that you want to execute in the parent as a prop and also the title.
If you want to change things from the parent, without re-rendering again with Redux, it would be something like this:
In the parent
const changeTitle = (newTitle) => {
this.props.setTitle(newTitle);
}
return (
<div className="App">
<ChildComponent />
</div>
);
const mapDispatchToProps = dispatch => ({
setTitle: newTitle => dispatch(setTitleACTION(newTitle)),
});
In the child
return (
<div>
<h1>
{this.props.title}
</h1>
<h2>Start editing to see some magic happen!</h2>
</div>
);
const mapStateToProps = ({ title }) => ({
title,
});
export default connect(mapStateToProps, null)(ChildComponent);
Again if you make this with Redux, you can get the "title" prop from the Redux store, and in the parent, you will change that variable (calling to a Redux action) without rendering the parent again.
If you want to fire the event from the child component, you can dispatch the action from the child component or you could call a function (that you receive from props from the parent) and call that function whenever you need.
Can't we use props here for passing data instead of trying to manipulate state from outside of component?
Based on #david paley explanation...
If the only way to achieve this is using Redux, I post my solution (It's the same example, but, implementing Redux)... hope that works for anyone else.
https://codesandbox.io/s/change-state-from-external-component-redux-rmzes?file=/src/App.js

react.js does not update DOM after having changed state array

I am trying to implement a game in react where I have the board as an two dimensional array in the initial state of the parent class. Tiles are rendered by iterating through that array. I pass those children a function as a prop so that they can change that state array.
Now, when I use that function to change the array, the HTML does not update. The array gets updated when I call setState but it never rerenders. I tried this.forceUpdate() but still no luck. What I then did was to pass a function from that child to the parent through the function to update that child's state and this works, but I need the function from the parent to call itself recursively to update the board. I feel like I might have hit an anti-pattern. How could I change my code in order for the DOM to update, please?
There is parts missing but those are all the components involved. statusBoard is the internal version of the board featuring the solution. I hope this is clear.
Whenever you are linking props and state together you have to create a componentWillReceiveProps function like so:
componentWillReceiveProps: function(nextProps) {
this.setState({
positionX: nextProps.columnPosition,
positionY: nextProps.rowPosition
});
}
When games state changes and passes it down as props to Field, fields own internal state doesn't get updated at that point because it is relying on its own state. this is an anti pattern and you should avoid Field having to have any state and just rely on its props it gets passed and have a parent component that is handling the state of everything.
https://facebook.github.io/react/tips/props-in-getInitialState-as-anti-pattern.html

What's the proper way of passing a ref to a prop?

I'm trying to pass a ref of a component to another component. Since string refs are being deprecated I'm using callback refs.
So I have something similar to this:
<One ref={c => this.one = c}/>
<Two one={this.one}/>
The problem is that whenever I try to access this.props.one inside Two I get undefined.
I have even tried this on Two:
componentDidMount(){
setTimeout(()=>{
console.log(this.props.one);
},5000)
}
It seems the problem is that when the prop is created, the ref doesn't exist yet since it's created once One is mounted. But I don't know how to "refresh" the props on Two to get the ref to the mounted component.
So what's the proper way of passing a ref to another component?
Edit
Some users have suggested to encapsulate that logic in a higher component, which in itself renders those other child components.
The problem with that approach is that you can't create reusable logic and you have to repeat the same logic over and over in those encapsulating components.
Let's say you want to create a generic <Form> component which encapsulates the submit logic to your store, error checking, etc. And you do something like this:
<Form>
<Input/>
<Input/>
<Input/>
<Input/>
<SubmitButton/>
</Form>
In this example <Form> can't access the instances (and methods) of the children since this.props.children doesn't return those instances. It returns some list of pseudo components.
So how can you check if a certain <Input/> has detected a validation error without passing a ref?
You have to encapsulate those components in another component with the validation logic. For example in <UserForm>. But since each form is different the same logic has to be copied in <CategoryForm>, <GoupForm>, etc. This is terribly inefficient which is why I want to encapsulate the validation logic in <Form> and pass references of the <Input> components to <Form>.
In general the "ref" feature is an anti-pattern in React. It exists to enable side-effect driven development, however in order to benefit the most from the React way of programming you should try to avoid "refs" if possible.
As for your particular issue, passing a child a ref to it's sibling is a chicken vs. egg scenario. The ref callback is fired when the child is mounted, not during render which is why your example doesn't work. One thing you can try is pushing the ref into state and then reading from state into the other child. So:
<One ref={c => !this.state.one && this.setState({ one: c })}/>
<Two one={this.state.one}/>
Note: without the !this.state.one this will cause an infinite loop.
Here is a codepen example of this working (look at the console to see the sibling ref logged): http://codepen.io/anon/pen/pbqvRA
This is now much simpler using the new ref api (available since React 16 - thanks to perilandmishap for pointing that out).
class MyComponent extends React.Component {
constructor (props) {
super(props);
this.oneRef = React.createRef();
}
render () {
return (
<React.Fragment>
<One ref={this.oneRef} />
<Two one={this.oneRef} />
</React.Fragment>
}
}
}
You would consume the prop in Two like:
this.props.one.current
A few things of note with this approach:
The ref will be an object with a current property. That property will be null until the element/component is mounted. Once it's mounted, it will be the instance of One. It should be safe to reference it once <Two /> is mounted.
Once the <One /> instance is unmounted, the current property on the ref returns to being null.
In general, if you need to pass a reference to something that may not be set at call time, you can pass a lambda instead:
<One ref={c => this.one = c}/>
<Two one={() => this.one}/>
and then reference it as
this.props.one()
If it has been set when you call it, you'll get a value. Before that, you'll get undefined (assuming it hasn't otherwise been initialized).
It bears noting that you won't necessarily re-render when it becomes available, and I would expect it to be undefined on the first render. This is something that using state to hold your reference does handle, but you won't get more than one re-render.
Given all that, I would recommend moving whatever code was using the ref to One in Two up into the component that is rendering One and Two, to avoid all the issues with both this strategy, and the one in #Carl Sverre's answer.

setState not triggering a re-render when data has been modified

Just started working with React, and it's been going relatively smooth up until this point.
I have a list, that contains X pre-defined items, which means that the list is always rendered with a given amount of rows.
This data is collected from a REST API, prior to the list rendering. The data contains variables relating to the list, as well as an array that contains each item within the list.
I chose the easy route, so I populate every component with a single JSON object named 'data', that contains everything necessary.
Rendering the list looks something like this:
<MyList data={data} />
Then, in the getInitialState of MyList:
dataInt: this.props.data.dataInt || 0,
dataStr: this.props.data.dataStr || '',
rows: this.props.data.rows || []
So I store my array of items (JSON) in the initial state of the list (parent), and when I choose to render the list, I first create all components (children) in an array, like this:
var toRender = [];
for(i = 0; i < this.state.rows.length; i++) {
toRender.push(<ItemRow data={this.state.rows[i]} />);
}
and then I render like this:
return (
<div className="item-container">
<table className="item-table">
{toRender}
</table>
</div>
);
The render-function of MyItem look something like this:
return (
<tr>
<td>{this.state.dataFromItem}</td>
</tr>
);
Now, let's say I want to modify the child data from within the parent, with some new data I just got from the API. Same structure, just the value of one field/column has changed:
i = indexOfItemToBeUpdated;
var newRows = this.state.rows;
newRows[i] = data; // New JSON object with same keys, different values.
this.setState({ rows: newRows }); // Doesn't trigger re-render
this.forceUpdate(); // Doesn't trigger re-render
What am I doing wrong?
At first I thought it was because I was wrapping the render function of MyItem in a , but since it renders perfectly fine on the initial render, I will assume that is an acceptable wrapper.
After some testing, it seems that the parent view is re-rendered, but not the children (which are based on the data that is updated within the parent).
JSfiddle: https://jsfiddle.net/zfenub6f/1/
I think the problem could be the your are only using getInitialState for the Row. You are setting state with the passed in props. If the children get new props, getInitialState won't get called again. I always refer back to https://facebook.github.io/react/docs/component-specs.html to see all the lifecyle events. Try using componentWillReceiveProps(object nextProps) to set the state again. If you don't actually need state in the child, remove the use of state and just use props and it probably will work.
If you're children are using State and state is not updating, what about props? does that render just fine? If the parent changes the state of the children and the children don't reflect that change those child components most likely need a unique key assigned to them which tells React that these items need to be updated when the state is updated on the parent. I ran into a similar issue where props inside the child updated to reflect the parent but the state of the child would not update. Once I added keys, the solution was resolved. For further reading on this check out this blog that initially hinted the problem to me.
http://blog.arkency.com/2014/10/react-dot-js-and-dynamic-children-why-the-keys-are-important/
There is also the official documentation from React that explains this situation.
http://facebook.github.io/react/docs/multiple-components.html#dynamic-children

Categories

Resources