I am trying to build a react grid component that has design similar to the one shown in image.
(1) Someone passes raw data to grid as props, (2) which gets converted to multiple GridData objects and stored within GRID. (3) Those objects are iterated over in render function to render grid items. (4) Now, someone performs an action outside of grid (may be in toolbar or something), that triggers property change for some/all GridData objects stored within Grid (in this case select all). (5) This results in all grid items getting updated property (in this case all items will be selected).
However, when I update the attribute of an object in Grid, the child (GridItem) does not check the checkbox. How can I fix that?
The code for the design looks something like this:
Grid.js
class Grid extends Component {
constructor(props) {
super(props);
this.state = {
gridData: props.data,
};
}
componentWillReceiveProps(nextProps) {
this.setState({
gridData:
typeof nextProps.gridData !== 'undefined' && nextProps.gridData
? nextProps.gridData
: this.state.gridData,
});
}
// PubSub System to receive notification
subscriber(msg, data) {
if(msg === 'SELECT_ALL_ITEMS'){
this.state.gridData.forEach(gridItem => {
gridItem.setChecked(true);
}
}
renderGridItem(gridItem) {
return (
<GridItem
key={gridItem.getItemId()}
title={gridItem.getTitle()}
isChecked={gridItem.isChecked()}
/>
);
}
render() {
return (
<div>
{this.state.gridData !== 'undefined' && this.state.gridData ? (
this.state.gridData.map(gridItem => this.renderGridItem(gridItem))
) : (
<div />
)}
</div>
);
}
}
GridItem.js
class GridItem extends Component {
constructor(props) {
super(props);
this.state = {
isChecked: typeof props.isChecked !== 'undefined' && props.isChecked ? props.isChecked : false,
title: typeof props.title !== 'undefined' && props.title ? props.title : '',
},
};
}
render() {
const { classes } = this.props;
return (
<div>
<Checkbox
checked={this.state.isChecked}
/>
{this.state.properties.title}
</div>
);
}
}
GridData.js
export default class GridData {
constructor(item) {
this._title = item.title;
this._itemId = item.itemId;
}
getItemId() {
return this._entryId;
}
isChecked() {
return this._isChecked;
}
setChecked(isChecked) {
this._isChecked = isChecked;
}
getTitle() {
return this._title;
}
}
I think you need to put your subscriber into componentDidMount
This method is a good place to set up any subscriptions. If you do that, don’t forget to unsubscribe in componentWillUnmount()
And you have to update state with setState in your subscriber.
Calling setState() in componentDidMount method will trigger an extra rendering
Related
I have created the following React component. It uses an input box to accept a user's answer to a riddle. As soon as the user's input matches the desired answer, the input box become read-only (a bit of a strange way to use them). It also has an "isHidden" prop to determine whether the riddle is rendered.
class Riddle extends React.Component {
constructor(props) {
super(props);
this.answer = props.answer.toUpperCase();
this.state = {
text: "",
isAnswered: false
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
let userInput = event.target.value.toUpperCase();
if (userInput == this.answer) {
this.setState({
text: userInput,
isAnswered: true
});
} else {
this.setState({
text: userInput,
isAnswered: false
});
}
}
render() {
if (this.props.isHidden) {
return <div></div>;
} else {
return (
<div>
<p>{this.props.prompt}</p>
<input type="text" value={this.state.text}
readOnly={this.state.isAnswered}></input>
</div>
);
}
}
}
Here it is in practice:
function App() {
return (
<div>
<Riddle prompt='The first three letters in the alphabet.' answer="abc" isHidden="false"/>
</div>
);
}
ReactDOM.render(<App />, document.getElementById('root'));
What I would like to do is have a bunch of these riddles in sequence, but have riddles only be visible when the previous one was solved. The trouble is that I don't know how to cause the visibility update to happen.
I have read about lifting state from children to a parent component, and I've tried to see if I could create a RiddleSequence component with Riddles as its children, and have RiddleSequence manage visibility. My problem is that currently it is part of Riddle's state whether or not it's solved, and I don't know how RiddleSequence can read that information since child state should remain hidden. This seems like a reasonable way to encapsulate Riddle's functionality, but maybe I'm wrong given my goals.
I have also considered making Riddles be children of other riddles they depend on, since I can just pass state/props to children:
<Riddle prompt="first riddle"...>
<Riddle prompt="depends on first riddle"...>
<Riddle prompt="depends on second riddle"...>
</Riddle>
</Riddle>
</Riddle>
But if I have an app with 100 riddles, this seems to get ridiculous. This also reduces flexibility for a more expanded set of features (such as making one riddle depend on a group of 3 riddles).
How can I make the visibility of my Riddle components depend on the state of other riddles?
A simple solution would be to have a container component as you said:
class Riddle extends Component {
constructor(props) {
this.state = {
text: ''
}
this.answer = props.answer.toUpperCase()
}
handleChange = event => {
const userInput = event.target.value.toUpperCase()
const callback = userInput == this.answer ? this.props.onSolved : undefined
this.setState({ text: userInput }, callback)
}
render() {
const { text, isAnswered } = this.state
const { prompt } = this.props
if (this.props.isHidden) {
return null
}
return (
<div>
<p>{prompt}</p>
<input type="text" value={text} readOnly={isAnswered}></input>
</div>
)
}
}
and container should hold visibility like this:
class RiddleSequence extends Component {
state = {}
riddles = [
{
id: 1,
prompt: 'The first three letters in the alphabet.',
answer: 'abc',
prev: null
},
{
id: 2,
prompt: 'The last three letters in the alphabet.',
answer: 'xyz',
prev: 1
}
]
render() {
return (
<div>
{this.riddles.map(r => {
const { id, prev } = r
const visible = !prev || this.state[prev]
return (
<Riddle
key={id}
isHidden={!visible}
onSolved={() => this.setState({ [r.id]: true })}
{...r}
/>
)
})}
</div>
)
}
}
I am doing a todo app to practice React. I hit a blocker and now I'm trying to figure out how to uniquely edit a card.
Currently when I click on edit, all my cards are set to isEditing == true. I've tried adding a key and index, but doesn't seem to uniquely identify the selected card.
As seen in my gif:
Obviously the expected outcome is that it should only set isEditing == true to the selected card.
See Code below.
For more context: there is stateful component that passes the props to this component, I'm using react-bootstrap (hence Panel, Button), and I removed some code for brevity (construct and whatnot).
edit() {
this.setState({
isEditing: true
})
}
renderEditDoneButtons() {
return (
<div>
<Button onClick={this.edit}>edit</Button>
</div>
)
}
renderNote(note) {
return (
<p> {note} </p>
)
}
renderCard(note, i) {
return (
<Panel key={i}
index={i}>
{
this.state.isEditing ?
this.renderForm() :
this.renderNote(note.note)
}
</Panel>
)
}
render() {
return (
<div>
{this.props.notes.map(this.renderCard)}
</div>
)
}
All three are changing based on your single isEditing state, which is why you're seeing all three being shown when you click any of the "Edit" buttons. Instead of a single isEditing key in state, use an array to maintain all three states like so:
constructor(props) {
super(props);
// Sets a true/false editing state for all three panels
this.state = {
editingPanels: Array(3).fill(false)
}
}
edit(i) {
// Switches editing state to false/true for given i
const editingPanels = this.state.editingPanels.slice();
editingPanels[i] = !editingPanels[i];
this.setState({
editingPanels: editingPanels
})
}
renderEditDoneButtons(i) {
return (
<div>
<Button onClick={()=>this.state.edit(i)}>edit</Button>
</div>
)
}
renderNote(note) {
return (
<p> {note} </p>
)
}
renderCard(note, i) {
return (
<Panel key={i}
index={i}>
{
this.state.editingPanels[i] ?
this.renderForm() :
this.renderNote(note.note)
}
</Panel>
)
}
render() {
return (
<div>
{this.props.notes.map(this.renderCard)}
</div>
)
}
You can use a separate component for each todo list item and use it inside the map method.The following example gives an idea on how to implement this.I am using another example as you have not provided the full code.
class EditText extends React.Component {
constructor(props) {
super(props)
this.state = {value:props.data,newValue:'hi'}
this.editValue = this.editValue.bind(this)
}
editValue() {
this.setState({value:this.state.newValue})
}
render() {
return(
<div>
{this.state.value}
<button onClick={this.editValue}>Change text to Hi</button>
</div>
)
}
}
class App extends React.Component {
constructor() {
super()
this.state = {tempDate : ['hello','how']}
}
render() {
return (
<div className="App">
{this.state.tempDate.map(data=>(<EditText data={data}/>))}
</div>
);
}
}
You need to have state variable isEditing for each particular card.
If there are 3 cards, you need to have 3 variables.
Edit 1 :-
Example is already shared by Kody R.
One Thing i noticed is instead of hard-coding array size to 3,we could assign array size by number of notes recieved in props.
this.state = {
editingPanels: Array(3).fill(false)
}
To
this.state = {
editingPanels: Array(this.props.notes.length).fill(false)
}
Hope this helps,
Cheers !!
I have an array of objects, and I want to display the value from its status which is an attribute inside the object.
this.state = {
objetcs: [{
value: 1,
status: false
}, {
value: 2,
status: true
}]
}
}
// const object = [{ value: 1, status: false }, {value: 2, status: true} ]
If the status is true I render it, if not, not render.
How can I do this ?
This is the most common pattern I see for conditional rendering in React, adapted for your particular structure:
this.state.objects.map(obj => (
{obj.status && (
<div>{obj.value}</div>
)}
))
If you're doing this frequently, its easier to do this in a component:
const SomeObject = ({ state, value }) => {
if (state) {
return(
<div>{value}</div>
)
} else {
return null
}
}
Or, if your component JSX is longer, extract a function:
class SomeObject extends React.PureComponent {
renderObj = () => (
<div>
{this.props.value}
Other HTML here
</div>
)
render() {
return this.props.state ? this.renderObj() : null
}
}
This keeps the conditional logic out of your parent component and simplifies the JSX portion.
make a separate component for easier working , and then send status and value as prop to component in your component have a if else
if(this.props.status)
return //your render items
else return null;
(Pardon the verbose question. I'm brand new to React and ES6, and I'm probably overly-convoluting this.)
I am writing an app that contains a button component. This button calls a method onAddChild that creates another component of class ColorModule by adding a value to an array stored in the App's state.
In each newly created ColorModule, I want to include another button that will remove the module. Since this component is created by an array.map method, my thought is that if I can find the index of the array item that corresponds with the component and use that index in array.splice then perhaps that component will be removed (untested theory). That said, I'm not really sure how to find the index where I would use this in my onRemoveModule method.
Two part question: 1) How would I go about finding the index of the array item in my state, and 2) if I'm completely off base or there's a better way to do this altogether, what does that solution look like?
imports...
class App extends Component {
static propTypes = {
children: PropTypes.node,
};
constructor(props) {
super(props);
this.state = {
// Here's the array in question...
moduleList: [1],
};
this.onAddChild = this.onAddChild.bind(this);
this.onRemoveModule = this.onRemoveModule.bind(this);
this.className = bemClassName.bind(null, this.constructor.name);
}
onAddChild(module) {
const moduleList = this.state.moduleList;
this.setState({ moduleList: moduleList.concat(1) });
}
onRemoveModule( e ) {
e.preventDefault();
...¯\_(ツ)_/¯
}
render() {
const { className } = this;
return (
<div className={className('container')}>
<Header onAddChild={this.onAddChild} /> /* Add module button lives here */
<div className="cf">
{this.state.moduleList.map(
( delta, index ) => {
return (
<ColorModule
className="cf"
onRemove={this.onRemoveModule}
key={index}
moduleId={'colorModule' + index}
/>
); /* Remove module button would live in the module itself */
}
)}
</div>
</div>
);
}
}
export default App;
Well this part is pretty easy, all you need to do is pass the index as prop to the ColorModule component and when calling the onRemove method in it you could pass it back to the onRemoveModule. However react optimizes based on keys and its a really good idea to have a unique id given to each module instance.
class App extends Component {
static propTypes = {
children: PropTypes.node,
};
constructor(props) {
super(props);
this.state = {
// Here's the array in question...
moduleList: [1],
};
this.onAddChild = this.onAddChild.bind(this);
this.onRemoveModule = this.onRemoveModule.bind(this);
this.className = bemClassName.bind(null, this.constructor.name);
}
onAddChild(module) {
const moduleList = this.state.moduleList;
this.setState({ moduleList: moduleList.concat(uuid()) }); //uuid must return a unique id everytime to be used as component key
}
onRemoveModule( index ) {
// now with this index you can update the moduleList
}
render() {
const { className } = this;
return (
<div className="cf">
{this.state.moduleList.map(
( delta, index ) => {
return (
<ColorModule
className="cf"
index={index}
onRemove={this.onRemoveModule}
key={delta}
moduleId={'colorModule' + delta}
/>
);
}
)}
</div>
);
}
}
Now in ColorModule component
class ColorModule extends React.Component {
onRemoveClick=() => {
this.props.onRemove(this.props.index);
}
}
Check this answer for more details on how to pass data from Child component to Parent
I ended up solving this problem using some of the guidance here from #ShubhamKhatri (didn't know about unique ID generation!), but I took a slightly different approach and handled the solution using state manipulation in App without needing a new method in my ColorModule component. I also never knew about currying in ES6, so that discovery made passing in the index values needed to manipulate my state array possible
If I'm off-base here or being inefficient, I'm definitely still open to feedback on a better way!
class App extends Component {
constructor(props) {
super(props);
this.state = {
moduleList: [{ id: UniqId(), removeModule: false }],
};
this.onAddChild = this.onAddChild.bind(this);
this.className = bemClassName.bind(null, this.constructor.name);
}
onAddChild(module) {
const moduleList = this.state.moduleList;
this.setState({
moduleList: moduleList.concat({
id: UniqId(),
removeModule: false,
}),
});
}
onRemoveModule = ( i, arr ) => (e) => {
const moduleList = this.state.moduleList;
e.preventDefault();
moduleList[i].removeModule = true;
this.setState({ moduleList: moduleList });
}
render() {
const { className } = this;
return (
<div className={className('container')}>
<Header onAddChild={this.onAddChild} />
<div className="cf">
{this.state.moduleList.map(
( delta, index ) => {
if ( !this.state.moduleList[index].removeModule ) {
return (
<ColorModule
className="cf"
onRemove={this.onRemoveModule( index, this.state.moduleList )}
index={index}
key={delta.id}
moduleId={'colorModule' + delta}
/>
);
}
}
)}
</div>
</div>
);
}
}
I have a list of dynamically generated inputs.
input --> onClick new Input beneath
[dynamically added]input
input
How can give just this dynamically added input focus?
The input has the textInput ref. This partly works:
componentWillUpdate(){
this.textInput.focus();
}
Yet, just works or the first new Input. Then it seems like the logic breaks.
the inputs are .map() from an array. Is there a way to either say, if the current rendered element has el.isActive to focus it. Or just say focus the input with the index 5?
CODE
Inputsgenerating file/component
import React from 'react';
import ReactDOM from 'react';
import _ from 'lodash'
class SeveralInputs extends React.Component {
constructor(props) {
super(props);
this.state = {
value: ' '
}
this.showIndex = this
.showIndex
.bind(this)
this.map = this
.map
.bind(this)
this.handleChange = this
.handleChange
.bind(this);
}
componentWillUpdate() {
this.textinput && this
.textInput
.focus();
}
render() {
return (
<ul>
{this.map()}
</ul>
)
}
map() {
{
return this
.props
.data
.map((name, index) => <li
onKeyPress={this
.showIndex
.bind(this, index)}
key={index}><input
onChange={this
.handleChange
.bind(this, index)}
task={this.task}
value={name.value}
ref={(input) => {
this.textInput = input;
}}
type="text"/>{name.value}</li>)
}
}
handleChange(index, e) {
let data = this
.props
.data
.splice(index, 1, {
value: e.target.value,
isActive: true
})
this
.props
.refreshState(data);
}
showIndex(index, e) {
if (e.which === 13 || e.keyPress === 13) {
let data = this.props.data[index].isActive = false
data = this
.props
.data
.splice(index + 1, 0, {
value: ' ',
isActive: true
})
this
.props
.refreshState(data);
} else {
return null
}
}
}
export default SeveralInputs
The data that lives in the parent component
const data = [
{
value: 0,
isActive: true
}, {
value: 2,
isActive: false
}
]
The parents state:
this.state = {
error: null,
data
};
The parents render
render() {
return (
<div>
{/* <Input/> */}
{/* <SeveralItems refreshState={this.refreshState} data={this.state.data.value}/> */}
<SeveralInputs refreshState={this.refreshState} data={this.state.data}/> {/* <SeveralInputsNested refreshState={this.refreshState} data={this.state.data}/> {this.items()} */}
</div>
);
}
refreshState(data) {
this.setState({data: this.state.data})
console.log(this.state.data)
}
The first issue I see is that in refreshState you pass some data that you do not handle, try this:
refreshState(newData) {
this.setState({data: newData})
}
And trying to log this.state right after won't work because :
setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall. Instead, use componentDidUpdate or a setState callback (setState(updater, callback)), either of which are guaranteed to fire after the update has been applied.