I am trying to render a component in App.js based on a MobX observable value. I want to define an action that takes a component and some new custom prop assignments. Here's what I have now:
// App.js inside render()
{ ui_store.main_area_sequence }
.
// ui_store.js
// OBSERVABLES / state
main_area_sequence = <ChoosePostType />;
// ACTIONS / state mutators
update_ui_in_main_section(ComponentTag, props) {
this.main_area_sequence = <ComponentTag {props} />
}
The component argument works as intended. But I can't get the props argument working. Ideally I would use it to build something like:
<ChildComponentToBeSwitchedIn name={ui_store.name} handleClick={this.handleClick} />
A button in <ChoosePostType /> might reassign the observable value in main_area_sequence to the above on click, as one example.
Does anyone know how to pass prop assignments as an argument in order to do this?
I suspect you're overthinking this.
Recall that JSX is just JavaScript. When you type this:
<Foo bar={123}>Baz!</Foo>
...you're really writing this:
React.createElement(Foo, { bar: 123 }, 'Baz!');
What if the component you want to render is dynamic, though? That's fine; it still works. For example, suppose we kept the component and props in our React component's state:
render() {
return (
<this.state.dynamicComponent
{...this.state.dynamicComponentProps}
name={this.props.name}
onClick={this.handleClick}
/>
);
}
The above JSX translates to this JavaScript:
React.createElement(this.state.dynamicComponent,
{ ...this.state.dynamicComponentProps,
name: this.props.name,
onClick: this.handleClick,
}
);
As you can see, we get props from this.state.dynamicComponentProps, but we also add a name prop from this.props.name and an onClick prop from this.handleClick. You can imagine this.handleClick calling this.setState to update dynamicComponent and dynamicComponentProps.
Actually, you don't have to imagine it, because you can see it working in the below snippet:
class DynamicComponent extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
this.state = {
dynamicComponent: ShowMeFirst,
dynamicComponentProps: { style: { color: 'blue' }},
};
}
render() {
return (
<this.state.dynamicComponent
{...this.state.dynamicComponentProps}
name={this.props.name}
onClick={this.handleClick}
/>
);
}
handleClick() {
this.updateDynamicComponent(ShowMeSecond, { style: { color: 'red' }});
}
updateDynamicComponent(component, props) {
this.setState({
dynamicComponent: component,
dynamicComponentProps: props,
});
}
}
const ShowMeFirst = ({ name, style, onClick }) => (
<button type="button" style={style} onClick={onClick}>
Hello, {name}!
</button>
);
const ShowMeSecond = ({ name, style, onClick }) => (
<button type="button" style={style} onClick={onClick}>
Goodbye, {name}!
</button>
);
ReactDOM.render(<DynamicComponent name="Alice"/>, document.querySelector('div'));
<script src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div></div>
I've used plain old React state above, but the same thing works with MobX, as you can see below. (I've never used MobX before, so my code might be less than idiomatic, but hopefully you'll get the idea.)
const { action, decorate, observable } = mobx;
const { observer } = mobxReact;
class UIStore {
dynamicComponent = ShowMeFirst;
dynamicComponentProps = { style: { color: 'blue' }};
updateDynamicComponent(component, props) {
this.dynamicComponent = component;
this.dynamicComponentProps = props;
}
}
decorate(UIStore, {
dynamicComponent: observable,
dynamicComponentProps: observable,
updateDynamicComponent: action,
});
const DynamicComponent = observer(({ store, name }) => (
<store.dynamicComponent
{...store.dynamicComponentProps}
name={name}
onClick={() =>
store.updateDynamicComponent(ShowMeSecond, { style: { color: 'red' }})
}
/>
));
const ShowMeFirst = ({ name, style, onClick }) => (
<button type="button" style={{...style}} onClick={onClick}>
Hello, {name}!
</button>
);
const ShowMeSecond = ({ name, style, onClick }) => (
<button type="button" style={{...style}} onClick={onClick}>
Goodbye, {name}!
</button>
);
ReactDOM.render(<DynamicComponent name="Alice" store={new UIStore()} />, document.querySelector('div'));
<script src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/mobx/lib/mobx.umd.js"></script>
<script src="https://unpkg.com/mobx-react#5.2.3/index.js"></script>
<div></div>
I had to use decorate here since Stack Overflow's Babel configuration doesn't include decorators, but I've posted the same thing with decorators on CodeSandbox: https://codesandbox.io/s/qlno63zkw4
If you aren't passing props, then don't include a props parameter.
update_ui_in_main_section(ComponentTag) {
this.main_area_sequence = <ComponentTag handleClick={this.handleClick} />;
}
Related
I am trying to get this componentFunction to re-render with the new data field on the state change that occurs changeValue and I don't know where I'm going wrong.
class Classname extends React.Component {
constructor() {
super();
this.state = {
value: "OriginalString",
};
}
changeValue = (newString) => {
this.setState({ value: newString });
this.forceUpdate();
};
componentFunction = () => {
return (
<div>
<component data={this.state.value} />
</div>
);
};
render() {
return (
<div>
<button
onClick={() => {
this.changeValue("updatedString");
}}
>
Update
</button>
<div className="control-section">
<DashboardLayoutComponent
id="dashboard_default"
columns={5}
cellSpacing={this.cellSpacing}
allowResizing={false}
resizeStop={this.onPanelResize.bind(this)}
>
<PanelsDirective>
<PanelDirective
sizeX={5}
sizeY={2}
row={0}
col={0}
content={this.componentFunction}
/>
</PanelsDirective>
</DashboardLayoutComponent>
</div>
</div>
);
}
}
ReactDOM.render(<Classname />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
Issue
The issue here is a stale enclosure of this.state.value in componentFunction.
Solution(s)
From what I can tell, the content prop of PanelDirective expects anything that returns, or resolves, to valid JSX (JSX attribute). A function callback to provide the content, a React component, or JSX literal all work.
Callback to reenclose updated state. Convert to a curried function that can enclose the current state when component is rendered. When attaching the callback you invoke the first function and pass the state value, the returned function is what PanelDirective will use when calling for the content value.
componentFunction = (data) => () => (
<div>
<component data={data} />
</div>
);
...
<PanelDirective
sizeX={5}
sizeY={2}
row={0}
col={0}
content={this.componentFunction(this.state.value)}
/>
React component. Convert componentFucntion to a React component and pass.
ComponentFunction = ({ data }) => (
<div>
<component data={data} />
</div>
);
...
<PanelDirective
sizeX={5}
sizeY={2}
row={0}
col={0}
content={<ComponentFunction data={this.state.value} />}
/>
JSX literal
<PanelDirective
sizeX={5}
sizeY={2}
row={0}
col={0}
content={
<div>
<component data={this.state.value} />
</div>
}
/>
Also, in case it wasn't obvious, you should remove the this.forceUpdate(); call in the changeValue handler. React state updates are sufficient in triggering the component to rerender.
try to pass in the value as param for the componentFunction, then each time the status value changed, the current component re-render, the trigger the function to re-render the child component using new state value.
class classname extends React.Component {
constructor() {
super();
this.state = {
value: "OriginalString",
};
}
changeValue = (newString) => {
this.setState({ value: newString });
this.forceUpdate();
}
componentFunction = (val) => {
return (
<div>
<component data={val} />
</div>
);
}
render() {
return (
<div>
<button onClick={() => { this.changeValue('updatedString') }}>Update</button>
<div className="control-section">
<DashboardLayoutComponent id="dashboard_default" columns={5} cellSpacing={this.cellSpacing} allowResizing={false} resizeStop={this.onPanelResize.bind(this)} >
<PanelsDirective>
<PanelDirective sizeX={5} sizeY={2} row={0} col={0} content={this.componentFunction(this.state.value)} />
</PanelsDirective>
</DashboardLayoutComponent>
</div>
</div>
);
}
}
I'm trying to implement a drop down wrapper. The element wrapped by the DropDownWrapper is expected to toggle the display of drop-down through onClick.
class DropdownMenuWrapper extends React.Component {
constructor(props) {
super(props);
this.state = {
active: true,
};
this.handleDropDownDisplay = this.handleDropDownDisplay.bind(this);
this.displayDropDownItems = this.displayDropDownItems.bind(this);
}
displayDropDownItems(menuItems, onSelectMenuItem, children) {
return (
menuItems.map(item => (
<a
className={cx('navItem')}
key={item.value}
onClick={() => onSelectMenuItem(children)}
>
{item.logo}
{item.display}
</a>
))
);
}
handleDropDownDisplay() {
this.setState({
active: !this.state.active,
});
}
render() {
const {
className, menuItems, onSelectMenuItem, children, label,
} = this.props;
return (
<div>
{children}
<nav className={className} aria-label={label}>
{this.state.active && this.displayDropDownItems(menuItems, onSelectMenuItem, children)}
</nav>
</div>
);
}
}
export default DropdownMenuWrapper;
Here I want to achieve dropdown toggle on the wrapped button below
<DropdownMenuWrapper
menuItems={[
{ value: 'dashboard', display: 'Dashboard' },
{ value: 'myProfile', display: 'My Profile' },
]}
>
<Button />
</DropdownMenuWrapper>
Is there a way I can use {children} onClick to change the state of the Parent component (DropdownMenuWrapper) in this case?
Thankfully this problem has been solved on various platforms, including this one. I'd like to get you started:
You can pass a function from the parent component down to the child component via its props and from there use it to alter the parent component's state.
The function, defined in the parent component, takes care of updating the parent component's state. The child component must then bind the function to an event that takes place in the child component and once the event is triggered the function takes place in the parent component and does the change it's implemented to do.
You can see a detailed code implementation in an already existing answer here: How to update parent's state in React?. Pardon me if this is not what you're looking for, in that case you should make your question more clear.
In the future you should try to search for an existing answer since there's a good chance the problem has been solved. A simple search engine search did the job for me and I like to tag Stack overflow as a part of the search query for a higher chance of a good answer.
This helped me out
reference: https://reactjs.org/docs/react-api.html#reactchildren
class DropdownMenuWrapper extends React.Component {
constructor(props) {
super(props);
this.state = {
active: false,
};
}
displayDropDownItems = (menuItems, onSelectMenuItem) => (
menuItems.map(item => (
<a
className={cx('navItem')}
key={item.value}
onClick={() => onSelectMenuItem(item.value)}
>
{ item.icon && <span className={cx('icon')}>{item.icon}</span>} {item.display}
</a>
))
);
toggleDropDown = () => {
this.setState({
active: !this.state.active,
});
};
render() {
const {
className, menuItems, onSelectMenuItem, children, label,
} = this.props;
const childrenWithProps = React.Children.map(children, child =>
React.cloneElement(child, { toggleDropDown: this.toggleDropDown }));
return (
<div>
{childrenWithProps}
<nav className={className} aria-label={label}>
{this.state.active && this.displayDropDownItems(menuItems, onSelectMenuItem, children)}
</nav>
</div>);
}
}
export default DropdownMenuWrapper;
I am new to React and need some help to my specific situation. I have a top-level app.js where I render
export default class Page extends React.Component {
constructor(props) {
super(props);
this.state = {
currentGuess: '',
historicGuess: '',
result: ''
};
}
handleCurrentGuess(event) {
console.log(event)
this.setState({currentGuess: event.target.value})
}
handleSend() {
console.log(this.state.currentGuess)
}
render() {
return (
<div className="wrapper">
<Header />
<Logic handleCurrentGuess={this.handleCurrentGuess}/>
<Result />
</div>
)
}
}
The component has to be stateful, and I enter the currentGuess value into state.
The <Logic /> looks like this:
export default function Logic(props) {
console.log(props)
return (
<div className="logic">
<form>
<input type="text" onChange={props.handleCurrentGuess}/>
<button onClick={(e) => {
e.preventDefault()
props.handleSend
}}>Send</button>
</form>
</div>
)
}
The issue now is that I cannot find documentation on how to pass both pass the function on to the AND get returned a value from the input. Most docs show onChange via the input directly, but I want to fetch the value ONLY when someone clicks on the submit button (or hits enter). So,
how do I pass the correct function to the child, and how do I get the text value back on button press within the Logic component?
If you want to console.log the state right now (for testing purposes obviously) here is the two problems with your code.
First, you are not passing your handleSend function as a prop to Logic component.
Second, on your button, you are not invoking this handleSend function in your onClick handler.
Here is a working example.
const Logic = props => (
<div className="logic">
<form>
<input type="text" onChange={props.handleCurrentGuess} />
<button onClick={props.handleSend}>Send</button>
</form>
</div>
);
class Page extends React.Component {
state = {
currentGuess: '',
historicGuess: '',
result: ''
};
handleCurrentGuess = event =>
this.setState({ currentGuess: event.target.value })
handleSend = (e) => {
e.preventDefault();
console.log(this.state.currentGuess)
}
render() {
return (
<div className="wrapper">
<Logic
handleCurrentGuess={this.handleCurrentGuess}
handleSend={this.handleSend} />
</div>
)
}
}
ReactDOM.render(<Page />, 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>
I slightly changed the code. Use some arrow functions so no need to .bind them, remove the unnecessary constructor, use class-fields, etc. I also used the function reference for onClick in the button.
I have two components. If I hovered over one I'd like to move (by changing style proporties) the other one component.
How can I achieve that?
In pure js it's simply
let elem1 = document.querySelector('.elem1');
let elem2 = document.querySelector('.elem2');
elemt1.addEventListener('mouseover', () => {
elem2.style.right = "200px" //or any other style property
})
So.. in react we can use "ref" and this works if I define static ref
import React, {Component} from 'react';
class MainCanvas extends Component {
onHover(){
console.log(this.refs.mybtntest);
}
render(){
return(
<div>
<h1 onMouseEnter={() => this.onHover()}> Testing</h1>
<button ref="mybtntest">Close</button>
</div>
);
}
}
export default MainCanvas
However in my case I need to each component should has dynamically added "ref" atribute.. so my code looks like below
import React, {Component} from 'react';
class Test extends Component {
onHover(e, dynamicRef){
console.log(dynamicRef); //correct number of ref
console.log(this.refs.dynamicRef); //undefined
console.log(this.refBtnName); //button reference but eachtime is overrided
console.log(this.dynamicRef);//undefinded
}
render(){
const elements = this.props.elements.map( element => {
let refBtnName = element.id + "btn";
return [
<ComponentElement
onHover={(e) => this.onHover(e, refBtnName)}
key={element.id} {...element}
/>,
<button key={element.id*2}
ref={refBtnName => this.refBtnName = refBtnName} //each time he will be overrided :(
className={`${refBtnName} deleteComponentBtn`} >
Close
</button>
]
})
return(
<div>
{elements}
</div>
);
}
}
export default Test
A real goal is that I want to positioning the button relative to the element. I could use div for this purpose as a wrapper but I don't want it.
So I thought to use for example this piece of code
onHover(e, dynamicRef){
Math.trunc(e.target.getBoundingClientRect().right)
dynamicRef.style.right = `${right}px`;
}
If you need dynamic object keys you shouldn't use the dot . and instead use the brackets:
ref={ref=> this[refBtnName] = ref}
Note that i changed the inline parameter to ref instead of refBtnName so you won't get variable names conflicts.
Running example:
class App extends React.Component {
state = {
items: [
{ name: 'John', id: 1 },
{ name: 'Mike', id: 2 },
{ name: 'Jane', id: 3 },
]
}
move = refName => e => {
this[refName].style.right = '-90px';
}
render() {
const { items } = this.state;
return (
<div>
{
items.map(item => {
return (
<div key={item.id} >
<div
ref={ref => { this[item.name] = ref }}
style={{ position: 'relative' }}
>
{item.name}
</div>
<button onClick={this.move(item.name)}>Move {item.name}</button>
</div>
)
})
}
</div>
)
}
}
ReactDOM.render(<App />, 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>
I'm ReactJS newbie (well, to be specific I'm programming newbie) looking for some answers, please don't exclude me for obvious stupidity :) I'm having big troubles with passing props and understanding 'this' context.
This is my mess, two simple Todo app components. TodoApp:
import React from 'react';
import uuid from 'uuid';
import style from './App.css';
import Title from '../components/Title.js';
import TodoList from '../components/TodoList.js';
class App extends React.Component {
constructor(props) {
super(props);
this.title = "Todo Application"
this.state = {
data: [{
id: 1,
text: 'clean room'
}, {
id: 2,
text: 'wash the dishes'
}, {
id: 3,
text: 'feed my cat'
}]
};
}
removeTodo(id) {
const remainder = this.state.data.filter(item => item.id !== id);
this.setState({data: remainder});
}
render() {
return (
<div className={style.TodoApp}>
<Title title="Todo Application" added={this.state.data.length} />
<TodoList data={this.state.data} remove={this.removeTodo}></TodoList>
</div>
);
}
}
export default App;
And TodoList:
import React from 'react';
const TodoList = props => (
<ul>
{props.data.map((item, index) =>
<li key={index}>
{item.text}
<button value={index} onClick={() => props.remove(index)}>x</button>
</li>
)}
</ul>
);
export default TodoList;
My question is how to correctly pass props to child component (TodoList) so that remove button would work?
To make sure the this always refers to the App context where your removeTodo method is defined, you can add the following inside your constructor (after setting the initial state):
this.removeTodo = this.removeTodo.bind(this);
Otherwise, your code isn't messy at all. It's even surprisingly concise and well thought out, even much more so for a self-proclaimed beginner. Congratulations!
As pointed out by Sag1v in the comment below, you're also not passing the correct value to your removeTodo method. You're passing the index of the item being iterated on, instead of its id.
Change your <button> invocation to the following:
<button value={index} onClick={() => props.remove(item.id)}>x</button>
Note that you could also achieve the same with the following:
<button value={index} onClick={props.remove.bind(null, item.id)}>x</button>
You got 2 main problems here:
As Jaxx mentioned you are not binding the handler removeTodo to
the class.
There are couple of ways to do it.
Bind it in the constructor:
this.removeTodo = this.removeTodo.bind(this);
Or use the ES2015(ES6) Arrow functions which will use the lexical context for this:
removeTodo = (id) => {
const remainder = this.state.data.filter(item => item.id !== id);
this.setState({data: remainder});
}
The 2nd problem is that inside TodoList onClick handler you are
not passing the correct id to the handler, you are passing the
index position.
onClick={() => props.remove(index)}
You should change that to:
onClick={() => props.remove(item.id)}
There is another problem with this approach which i'll explain next.
Here is a working example:
const Title = ({title}) => <h1>{title}</h1>
const TodoList = props => (
<ul>
{props.data.map((item, index) =>
<li key={index}>
{item.text}
<button value={index} onClick={() => props.remove(item.id)}>x</button>
</li>
)}
</ul>
);
class App extends React.Component {
constructor(props) {
super(props);
this.title = "Todo Application"
this.state = {
data: [{
id: 1,
text: 'clean room'
}, {
id: 2,
text: 'wash the dishes'
}, {
id: 3,
text: 'feed my cat'
}]
};
}
removeTodo = (id) => {
const remainder = this.state.data.filter(item => item.id !== id);
this.setState({data: remainder});
}
render() {
return (
<div>
<Title title="Todo Application" added={this.state.data.length} />
<TodoList data={this.state.data} remove={this.removeTodo}></TodoList>
</div>
);
}
}
ReactDOM.render(<App/>,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>
As i said, there is a problem with this approach, you are passing a new instance of a function on each render with this line of code:
onClick={() => props.remove(item.id)
This is considered as bad practice because this can interrupt the Reconciliation and diffing algorithm
But as we know, event Handlers should get a function reference, hence you can't just pass a function invocation like this
onClick={props.remove(item.id)}
This will pass the function's return type (if any) and not the reference for the function.
So the proper way of passing a function reference is like this:
onClick={props.remove}
But that is not good for your case as you need to pass back to the parent the current item id, but i'm afraid that the browser will only pass back the event parameter.
So what are the alternatives you ask?
Create another component and control the data you pass in and out from your component instead of relying on the goodwill of the browsers.
Here is another working example but this time without creating a new function instance on each render
const Title = ({title}) => <h1>{title}</h1>
class TodoItem extends React.Component {
handleClick = () => {
const{item, onClick} = this.props;
onClick(item.id);
}
render(){
const {item} = this.props;
return(
<li>
{item.text}
<button value={item.id} onClick={this.handleClick}>x</button>
</li>
);
}
}
const TodoList = props => (
<ul>
{props.data.map((item, index) =>
<TodoItem key={index} item={item} onClick={props.remove} />
)}
</ul>
);
class App extends React.Component {
constructor(props) {
super(props);
this.title = "Todo Application"
this.state = {
data: [{
id: 1,
text: 'clean room'
}, {
id: 2,
text: 'wash the dishes'
}, {
id: 3,
text: 'feed my cat'
}]
};
}
removeTodo = (id) => {
const remainder = this.state.data.filter(item => item.id !== id);
this.setState({data: remainder});
}
render() {
return (
<div>
<Title title="Todo Application" added={this.state.data.length} />
<TodoList data={this.state.data} remove={this.removeTodo}></TodoList>
</div>
);
}
}
ReactDOM.render(<App/>,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>