I have a state variable which stores JSX code that will be rendered to the DOM in the render() method.
Here is the structure of state and the state variable jsxComp that I currently have,
state = {
isLoggedin: false,
jsxComp: null
}
What I am trying to do is I am trying to append JSX code ( a div ) into this variable inside a for loop as follows,
for(let i=0; i<postCount; i++){
this.setState({
//Append a div to the jsxComp variable
})
}
How can I do that? + operator does not seem to work in this case.
Never store actual elements in state. State should only contain the pure data to render, not the actual markup. Instead make your render() method conditionally render based on the state:
class MyComp extends React.Component {
state = {
isLoggedIn: false,
};
render() {
const {isLoggedIn} = this.state;
return (
<div>
{/* conditionally render based on isLoggedIn */}
<p>{isLoggedIn ? 'You are logged in' : 'Please log in.'}</p>
<button onClick={() => this.setState({isLoggedIn: true})}>
Log In
</button>
</div>
);
}
}
Your render() method will be called whenever the state changes. React will then diff the result of the render and update the DOM where elements changed.
You also should not call setState() in a loop. First collect the changes and then call setState with the whole changed array. To actually append something to an existing array in state you would do:
this.setState(state => {jsxComp : [...state.jsxComp, newElement]});
Related
How can I convert this hook-based code to class-based code? Does the code still work?
I'm using a react/ASP.net Core template. My project consists of several components that are siblings.
I'm trying to send a state from a component to another one.
import { useState } from "react";
//the change is reflected in the ImageEditor component
const ImageEditor = ({ yourState }) => (
<p>State in the ImageEditor = {yourState}</p>
);
//ImageTile changes the state through the setYourState method
const ImageTile = ({ yourState, setYourState }) => (
<button onClick={() => setYourState("World!")}>
Change State from the ImageTile
</button>
);
//App is the parent component and contains both image editor and tile
const App = () => {
//the state which holds the image ID is declared in the parent
const [imageId, setImageId] = useState("Hello");
return (
<div>
<ImageTile yourState={imageId} setYourState={setImageId} />
<ImageEditor yourState={imageId} />
</div>
);
};
export default App;
You can see the complete code on:
https://codesandbox.io/s/billowing-brook-9y9y5?file=/src/App.js:0-775
A parent passes it’s state to a child via props. The child is not allowed to change its parents state, if a child wants to change a parents state then the parent passes a callback to the child that the child can call to change the state. This is fundamental to reacts state management. A child does not need to know how a parent stores it’s state (class instance, hook instance or state library).
if your application uses a global state manager like redux, then global state is mapped to props and a store action can be called to update global state. In this case the child does not need to know who else is using the state because it’s global.
class Foo extends Component {
constructor (props) {
super(props);
this.state = { myState: 0 };
this.setMyState = this.setMyState.bind(this);
}
setMyState (value) {
this.setState({
myState: value
});
}
render () {
return (
<MyChildCompoent myStat={this.state.myState} setMyState={this.setMyState} />
);
}
}
you'll need to declare the state in the parent:
state = {
someKey: '',
};
And then in the parent, define some function to update it:
updateSomeKey = (newValue) => {
this.setState({ someKey: newValue });
}
And then pass both of these values as props to your sibling components:
render() {
return (
<div>
<Sib1 someKey={this.state.someKey} updateSomeKey={this.updateSomeKey} />
<Sib2 someKey={this.state.someKey} updateSomeKey={this.updateSomeKey} />
</div>
)
}
You shouldn't need to in order to update the 'shared' state. In the code above, the someKey state can be updated in either component by calling the updateSomeKey function that is also available as a prop.
If either component calls that function (this.props.updateSomeKey('hello!')) the updated state will propagate to both components.
I'm attempting to update the state of a Parent component after the user updates the value of a Child component's select element.
Whilst I've got it somewhat working, I've noticed that when I fire the onChange event on my select, it will always return the previous value instead of the value that has just been selected.
class Parent extends Component{
constructor(props){
super(props);
this.state = { data: {
condition: "any"
}};
}
update = data => {
this.setState({ data: {
...this.state.data,
...data
}});
// This gets called every time I change the value of my select element.
console.log(this.state.data);
}
render(){
return (
<Child
condition={this.state.data.condition}
onUpdate={data => this.update(data)} />
);
}
}
class Child extends Component{
updateParent = data => {
this.props.onUpdate(data);
}
render(){
const options = [
["any", "Any Condition"],
["new", "Brand New"],
["used", "Used"]
];
return (
<select
defaultValue={ props.selected }
onChange={({ target }) => this.updateParent({ condition: target.value })}>
{ options.map(([id, name]) => (
<option key={id} value={id}>{ name }</option>
)}
</select>
);
}
}
In this example, if I select used, the console will return any. Then, if I select new, the console will return used.
setState operations are async in nature. So whenever a setState op is done, its not guaranteed that the updated value of state will be available just after the setState
statement
From React Doc
React may batch multiple setState() calls into a single update for
performance.
Because this.props and this.state may be updated asynchronously, you
should not rely on their values for calculating the next state.
Now, if you want to use the new state value, you should store the value, in this case the data, in a variable, set your state, but use the variable to perform other operation inside the function, like calling API,etc.
Edit (As pointed out by #Grover):
setState also provides a second argument which is a callback that gets fired after the update operation takes place. One can get the updated state value in it and can use this to perform operations with the updated values.
this.setState({foo: 'bar'}, () => {
// actions
});
However, the React Doc suggests using componentDidUpdate instead of setState callback. This answer tries to explain it why: What is the advantage of using componentDidUpdate over the setState callback?
I have a function that renders content to page based on a state populated by API data, but I need to have an onClick event to refine that content;
So currently getPosts returns information from the state 'posts' which is provided with data from our API, but i want to filter this content further, so my idea is to have some sort of event listener, and if actioned, change the data coming out of getPosts.
constructor() {
super();
this.state = {
posts: ""
}
this.getPosts = this.getPosts.bind(this);
}
async componentWillMount(){
var data = await api.posts();
this.setState({posts: data.data});
console.log(this.state.posts);
}
getPosts(type){
if(this.state.posts.length){
return this.state.posts.map((content,index) => {
var url = content.Title.replace(/[^\w\s]/gi, '');
url = url.replace(/\s+/g, '-').toLowerCase();
if(type === content.PostType){
//output something different
}
else{
return(
<Col md={4} className="mb-4" key={index}>
{content.title}
</Col>
);
}
})
}
}
render() {
return (
<div>
<p><button onClick={()=>{this.getPosts('blog')}}>blog</button> <button onClick={()=>{this.getPosts('news')}}>news</button></p>
{this.getPosts()}
</div>
)
}
So my getPosts works fine without any type, how to do tell it to re-output the function on the page based in the onClick event?
Without getting into the complexities of context and keys, a component requires a change in props or state to re-render. To read more about state and component life-cycle, the docs have a great explanation for class components.
Your component does not re-render after the onClick event handler's call to getPosts because getPosts does not update internal component state. getPosts works within render because those values are being returned to React. By using getPosts as an onClick event handler, you are creating React elements and trying to return them to the window.
What follows should be treated as psuedo code that shows how to trigger your component to render different posts:
Consider adding another key to state in your constructor,
constructor(props) {
super(props);
this.state = {
posts: "",
type: null
};
this.getPosts = this.getPosts.bind(this);
this.onClick = this.onClick.bind(this);
}
and creating a click handler that doesn't try to return React elements
function onClick(evt) {
this.setState({ type: evt.target.value });
}
and values to your buttons
<button onClick={this.onClick} type="button" value="blog">blog</button>
Now your button will update state with your new post type, causing your component to re-render:
render() {
return (
<div>
<p>
<button onClick={this.onClick} type="button" value="blog">blog</button>
<button onClick={this.onClick} type="button" value="news">news</button>
</p>
{this.getPosts()}
</div>
);
}
With the content type being stored in state, you can now implement your getPosts call in any way that works for you. Good luck!
It strays from the question asked, but it is worth noting componentWillMount is being deprecated, and componentDidMount is a preferable life-cycle function for side-effects and asynchronous behavior. Thankfully, the documentation has lots of details!
Ok so you should start by changing your default this.state to
this.state = {
posts: []
}
remember that you want to iterate over an array of data instead of iterate a string, that will throw an error if you do that, so better keep from the beginning the data type you want to use.
Then you need to separate the responsibility for your getPosts method, maybe getPostByType is a better name for that, so you have
getPostByType(type) {
// if type is same as content.PostType then return it;
const nextPosts = this.state.posts.filter((content) => type === content.PostType);
this.setState({ posts: nextPosts });
}
and finally you can iterate over posts, like this
render() {
// better use content.id if exists instead of index, this is for avoid problems
// with rerender when same index key is applied.
const posts = this.state.posts.map((content, index) => (
<Col md={4} className="mb-4" key={content.id || index}>
{content.title}
</Col>
));
return (
<div>
<button onClick={() => this.getPostByType('blog')}>Show Blog Posts</button>
{posts}
</div>
);
}
Then you can use getPostsByType any time in any click event passing the type that you want to render.
I ran into an issue with updating part of the state that is a list that's passed on to children of a component.
I pass in a list to a child, but then have trouble to update that list and have the child reflect the new state;
<ItemsClass items={this.state.items1} />
When I change the value of this.state.items1, the component doesn't render with the new value.
this.setState({items1: []}); // this has no effect
However, if I change the already existing array (not replacing it new a new empty one), the component renders as I wish;
this.setState(state => { clearArray(state.items1); return state; });
That means the state updating function isn't pure, which React states it should be.
The HTML;
<div id='app'></div>
The js;
class ItemsClass extends React.Component {
constructor(props){
super(props);
this.state = {items: props.items};
}
render() {
var items = this.state.items.map(it => <div key={it.id}>{it.text}</div>);
return(
<div>{items}</div>
);
}
}
function ItemsFunction(props) {
var items = props.items.map(it => <div key={it.id}>{it.text}</div>);
return(
<div>{items}</div>
);
}
class App extends React.Component {
constructor(props){
super(props);
var items = [{id:1, text: 'item 1'}, {id: 2, text: 'item 2'}];
this.state = {
items1: items.slice(),
items2: items.slice(),
items3: items.slice()
};
this.clearLists = this.clearLists.bind(this);
}
clearLists() {
// for items1 and items2, clear the lists by assigning new empty arrays (pure).
this.setState({items1: [], items2: []});
// for items3, change the already existing array (non-pure).
this.setState(state => {
while (state.items3.length) {
state.items3.pop();
}
})
}
render() {
return (
<div>
<button onClick={this.clearLists}>Clear all lists</button>
<h2>Items rendered by class, set list to new empty array</h2>
<ItemsClass items={this.state.items1} />
<h2>Items rendered by class, empty the already existing array</h2>
<ItemsClass items={this.state.items3} />
<h2>Items rendered by function</h2>
<ItemsFunction items={this.state.items2} />
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('app'));
Try it out on codepen.
It seems that the ItemsClass doesn't update even though it's created with <ItemsClass items={this.state.items1}/> and this.state.items1 in the parent changes.
Is this the expected behavior? How can I update the state in the ItemsClass child from the parent?
I'm I missing something? This behavior seems quite error prone, since it's easy to assume that the child should follow the new state, the way it was passed in when the child was created.
You're copying the props of ItemsClass into the state when the component gets initialized - you don't reset the state when the props change, so your component's updates don't get displayed. To quote the docs:
Beware of this pattern, as state won't be up-to-date with any props update. Instead of syncing props to state, you often want to lift the state up.
If your component has to do something when the props change, you can use the componentWillReceieveProps lifecycle hook to do so (note that it doesn't get run when the component initially mounts, only on subsequent prop updates).
That said, there's zero reason for you to be duplicating the props here (and honestly there's rarely a good reason to do so in general) - just use the props directly, as you're doing with ItemsFunction, and everything will stay in sync:
class ItemsClass extends React.Component {
render() {
var items = this.props.items.map(it => <div key={it.id}>{it.text}</div>);
return(
<div>{items}</div>
);
}
}
Here's a working version of your Codepen: http://codepen.io/anon/pen/JNzBPV
I have some problem with splice() method in my React.js app.
So, this is an example app. Deletion not works now. What's wrong here? Part of code:
class CardList extends React.Component {
static propTypes = {
students: React.PropTypes.array.isRequired
};
// ADD DELETE FUNCTION
deletePerson(person) {
this.props.students.splice(this.props.students.indexOf(person), 1)
this.setState()
}
render() {
let that = this
return <div id='list'>
{this.props.students.map((person) => {
return <Card
onClick={that.deletePerson.bind(null, person)}
name={person.name}>
</Card>
})}
</div>
}
}
class Card extends React.Component {
render() {
return <div className='card'>
<p>{this.props.name}</p>
{/* ADD DELETE BUTTON */}
<button onClick={this.props.onClick}>Delete</button>
</div>
}
}
http://codepen.io/azat-io/pen/Vaxyjv
Your problem is that when you call
onClick={that.deletePerson.bind(null, person)}
You bind this value to null. So inside of your deletePerson function this is null instead of actual component. You should change it to
onClick={that.deletePerson.bind(this, person)}
And everything would work as expected =)
Changing the bind value to this will definitely cause the call to this.setState() to work, thus triggering the re-render, however I strongly recommend against the approach you've taken.
Props are supposed to be immutable. Instead use internal state and replace with new values rather than mutate them. To do this, set the state of your component in the constructor by doing something like:
constructor(props) {
super(props)
this.state = {
students: ...this.props.students
}
}
And now when you need to delete a person:
deletePerson(person) {
// notice the use of slice vs splice
var newStudents = this.props.students.slice(this.props.students.indexOf(person), 1)
this.setState({ students: newStudents })
}
And finally use this.state.students in your render method instead.
The reasoning behind this is that props are passed directly from the parent container component so modifying them wouldn't really make sense. To make more sense of my own code, I tend to pass in the prop named initialStudents and set my state to students: ...initialStudents to ensure I make the distinction between my prop variable and my state variable.