Passing props to nested child component - javascript

Here's my structure :
Main.js (Parent)
MainContainer.js
|
|_ Article.js
|
|__ Comments.js
Now i want to set click handler on comment component (recursive component) and dispatch an action.
here's my code on comment.js
class Comment extends Component {
deleteComment = (id) => {
this.props.handleDelete(id)
}
render() {
var comment = this.props.comment
return (
<div className={styles.commentsWrapper}>
<ul>
<li>
<div className={styles.commentsName}>
<a onClick={() => this.deleteComment(comment.id)} className={styles.commentsNameRight}>
</a>
</div>
<p>{comment.body}</p>
{comment.children.length > 0 && comment.children.map(function(child) {
return <Comment comment={child} key={child.id}/>
})}
</li>
</ul>
</div>
);
}
}
export default Comment;
and Article.js :
class Article extends Component {
handleDeleteComment = (id) => {
this.props.deleteComment(id)
}
render() {
return (
<article className={styles.articleItem}>
{this.props.comments.map(item =>
<Comment handleDelete={this.handleDeleteComment} comment={item} key={item.id}/>)}
</article>
);
}
}
export default Article;
And the Main.js
class Main extends Component {
deleteComment = (id) => {
this.props.deleteCommentRequest(id)
}
render() {
return (
<div className="">
<Header />
<section className="container">
<div>
{
!this.props.articles.loading && this.props.articles.articles? (
<div>
{this.props.articles.articles.map(item =>
<Article
bodytext={item.selftext}
key={item.id}
comments={item.finalComments}
deleteComment={this.deleteComment}
/>)}
</div>
) : (
<div className={styles.loading}> <Spin /> </div>
)
}
</div>
</section>
</div>
);
}
}
export default Main;
so what i did here is: pass deleteComment as props from main to article and pass again handleDelete from article to comment.
not sure if it's a good way of doing this ?
Thanks in advance

Nothing wrong with this pattern for 2 - 3 depth of components, as that is how data should flow from children to ancestors. But if your application is getting heavier with several layers, consider a different state management such as redux where a global state is maintained and any component can subscribe to it and dispatch actions. More on that here.
Alternatively you can also achieve the same with React Hooks with useContext where you can set the context and any child component can subscribe to it. Example:
const MyContext = React.createContext();
export default function App({ children }) {
const [items, setItems] = React.useState([]);
return (
<MyContext.Provider value={{ items, setItems }}>
{children}
</MyContext.Provider>
);
}
export { MyContext };
Now in any child at any level of depth as long as it is within App component's children, you can do this:
import {MyContext} from './filename';
function TodoItem() {
const { items, setItems } = React.useContext(MyContext);
return (
<div onClick={() => setItems(1)}>
</div>
);
}

you can use context API to have the props in the wrapper and easily accessible from child component.
there is a great tutorial from wesbos on youtube
class App extends Component {
render() {
return (
<MyProvider>
<div>
<p>I am the app</p>
<Family />
</div>
</MyProvider>
);
}
}
class MyProvider extends Component {
state = {
name: 'Wes',
age: 100,
cool: true
}
render() {
return (
<MyContext.Provider value={{
state: this.state,
growAYearOlder: () => this.setState({
age: this.state.age + 1
})
}}>
{this.props.children}
</MyContext.Provider>
)
}
}

Related

How to pass a state from a child to a parent in React?

Hello i have a child component and a parent component. In the child component there is a state. The state has to toggle between classNames in the parent component. How can i do that?
export function Parent({ children, darkMode }) {
return (
<div className={cx(styles.component, darkMode && styles.darkMode)}>
{ children }
</div>
)
}
export function Child() {
const [darkMode, setDarkMode] = React.useState(false)
return (
<header>
<div className={styles.component}>
<div className={styles.content}>
<button onClick={colorSwith} className={styles.toggle}>Toggle</button>
</div>
</div>
</header>
)
function colorSwith() {
setDarkMode(true)
}
}
With state it's 1 directional
It's not possible to pass state up the tree. In the solution below you might need to bind the function. You can mod props of children via the clone element React method.
export function Parent({ children, darkMode }) {
const [darkMode, setDarkMode] = React.useState(false)
return (
<div className={cx(styles.component, darkMode && styles.darkMode)}>
{React.cloneElement(children, { setDarkMode })}
</div>
)
}
export function Child(props) {
return (
<header>
<div className={styles.component}>
<div className={styles.content}>
<button onClick={colorSwith} className={styles.toggle}>Toggle</button>
</div>
</div>
</header>
)
function colorSwith() {
props.setDarkMode(true)
}
}
Use the context api
You can also use the context api to access state anywhere in the tree. This way any component that has access to the context will rerender on change and data is passable and changable to any point in the tree.
Check out this example from the react docs
const themes = {
light: {
foreground: "#000000",
background: "#eeeeee"
},
dark: {
foreground: "#ffffff",
background: "#222222"
}
};
const ThemeContext = React.createContext(themes.light);
function App() {
return (
<ThemeContext.Provider value={themes.dark}>
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}
function ThemedButton() {
const theme = useContext(ThemeContext);
return (
<button style={{ background: theme.background, color: theme.foreground }}>
I am styled by theme context!
</button>
);
}
See how the context is set on the `App` level with a `Provider` and then changed in the `ThemeButton` with the `useContext` hook. This is a simple use case that seems simmilar to yours.

Add class after action onClick React / Redux

I have table and action icon 'delete' for example:
http://joxi.ru/gmvzWx4CxKbX5m
After click on icon, in my Redux Action i get TR and add class 'adm-pre-removed'
import * as types from '../constant/domain.const.js';
export function deleteDomain(event){
return (dispatch, getState) => {
(async () => {
let domainIdElement = event.target.closest('tr'),
id = domainIdElement.getAttribute('data-id');
domainIdElement.classList.add('adm-pre-removed');
setTimeout(() => {
dispatch({
type: types.REMOVE_DOMAIN_SUCCESS,
id: id
})
}, 3000)
})()
}
}
The problem is after the dispatch state is triggered and the render happens but the class remains.
I know that this is not a react way. And I really do not want to create a property in an article and to control this class is too much code. And to all this class should appear when clicking and disappear when the state has changed.
You can of course create another action and order the event onMouseDown but this is also a lot of code.
Which option is correct and how best to do it?
Render (Controll view)
function mapStateToProps (state) {
return {...state.menu}
}
function mapDispatchToProps(dispatch){
return {
...bindActionCreators({
...listMenu,
...deleteMenu,
...addMenu,
...removeError
}, dispatch),
dispatch
}
}
class Menu extends React.Component {
constructor (props, context) {
super(props, context);
}
componentDidMount() {
this.props.listMenu()
}
componentDidUpdate(){
//FormHelper.reactReRender('.adm-domain-form');
}
render() {
return (<div>
{this.props.errorMessage ? <Notification close={this.props.removeError} errorMessage={this.props.errorMessage} /> : ""}
<Sidebar />
<MenuPreview {...this.props} />
</div>);
}
}
Menu.contextTypes = {
router: PropTypes.object
}
export default connect(mapStateToProps, mapDispatchToProps)(Menu);
Render Component:
class MainEventsView extends React.Component {
render(){
return (
<div className="adm-content">
<Header />
<div className="container-fluid adm-card-head">
<Add {...this.props} />
<List {...this.props} />
</div>
</div>
);
}
}
export default MainEventsView;
Render List:
class ListMenu extends React.Component {
render(){
return(
<div className="col-xs-12 col-sm-12 col-md-6 col-lg-6">
<div className="adm-card adm-card-table">
<div className="table-responsive">
{this.props.menu && this.props.menu.length > 0 ?
<Table {...this.props} /> :
<p className="adm-card-table__text-no-available turn-center">Available menu are not found</p>
}
</div>
</div>
</div>
)
}
}
export default ListMenu;
Render Table:
class Table extends React.Component {
render () {
return (
<table className="adm-users-table__wrapper-table">
<Thead {...this.props} />
<Tbody {...this.props} />
</table>
);
}
}
export default Table;
Render Tbody:
class Cell extends React.Component {
render () {
let usersTemplate = this.props[this.props.name].map((item, i) => {
return (
<tr key={i} data-id={item.id}>
{this.template(item)}
</tr>
);
});
return (
<tbody>
{usersTemplate}
</tbody>
);
}
template (items) {
let keyArray = Object.keys(items),
uniqKey,
field,
userCell = keyArray.map((item, i) => {
uniqKey = i;
field = items[item] instanceof Object ? _.values(items[item]).join(', ') : items[item];
return (
<td key={i} className="adm-users-table__wrapper-table-data relative-core">{field}</td>
);
});
userCell.push(<Actions key={uniqKey + 1} id={items.id} {...this.props} />)
return userCell;
}
}
export default Cell;
Maybe, it could be. React renders elements in virtual dom and update changed elements in real-dom.
So your class is remained still.
I think that you have to figure out another way.

Prop not being passed to Child

This is bizarre. My console.log produces a company:
but for some reason in my child, when I try pulling it from props, it's null
CompanyDetailContainer
class CompanyDetailContainer extends Component {
async componentDidMount() {
const { fetchCompany } = this.props,
{ companyId } = this.props.match.params;
await fetchCompany(companyId);
}
render(){
const { company } = this.props;
console.log(company) // this outputs a company
return (
<CompanyDetail className="ft-company-detail" company={company} />
);
}
}
const mapStateToProps = state => ({
company: state.company.company
});
const mapDispatchToProps = {
fetchCompany: fetchCompany
};
export default connect(mapStateToProps, mapDispatchToProps)(CompanyDetailContainer);
CompanyDetail
export default class CompanyDetail extends Component {
render(){
const callToAction = 'test';
const { company } = this.props;
console.log(company) // this is null! why??? I've never had this problem before
const title = `${company.name} Details`;
return (
<Main>
<MainLayout title={title}>
<div>
<div id='ft-company-detail'>
<div className="panel vertical-space">
<CompanyHeader className="ft-company-header" company={company} />
<div className="ft-call-to-action-interview">{callToAction}</div>
<CompanyProfile className="ft-company-profile" company={company} />
<RelatedLinks className="ft-company-resources" company={company} />
</div>
</div>
</div>
</MainLayout>
</Main>
);
}
}
///// UPDATE ////
this worked:
return (
company && <CompanyDetail className="ft-company-detail" company={company} />
);
But then why does this combo work fine? it's setup pretty much the same way. This is the first route hit on my app, renders this container:
HomepageContainer
class HomePageContainer extends Component {
async componentDidMount() {
await this.props.fetchFeaturedCompanies();
await this.props.fetchCompanies();
await this.props.fetchCountries();
}
render(){
return (<HomePage
className='ft-homepage'
companies={this.props.companies}
countries={this.props.countries}
featuredCompanies={this.props.featuredCompanies}
/>);
}
}
const mapStateToProps = state => ({
countries: state.country.countries,
companies: state.company.companies,
featuredCompanies: state.company.featuredCompanies
});
const mapDispatchToProps = {
fetchCountries: fetchCountries,
fetchCompanies: fetchCompanies,
fetchFeaturedCompanies: fetchFeaturedCompanies
};
export default connect(mapStateToProps, mapDispatchToProps)(HomePageContainer);
HomePage
export default class HomePage extends Component {
render(){
return (
<Main>
<MainLayout title='Test'>
<div className="homepage panel vertical-space margin-bottom-300">
<FeaturedCompanies companies={this.props.featuredCompanies} />
<div>
<div className="column-group">
<div className="all-100 width-100 align-center fw-300 extralarge">
test
</div>
</div>
</div>
<CompanyList className="ft-company-list" companies={this.props.companies} countries={this.props.countries} />
</div>
</MainLayout>
</Main>
);
}
}
To the fella who commented on my theme, the first image above is from Chrome tools dark theme. Here is my actual theme in WebStorm which I think is even better :P:
componentDidMount is called after the render and your async call is in the componentDidMount, so for the first render the parent and the child both get null, and since you use company.name in child without a conditional check it errors out. Provide a conditional check in the child and it will work fine
const { company } = this.props;
console.log(company)
const title = company ? `${company.name} Details`: null;

Scrolling to a particular item

In my React component, I'm displaying a list of items -- each in its own DIV element with a unique id i.e. <div id="abc-123">.
I'm also using react-perfect-scrollbar to make the whole thing nicer looking.
I keep a variable in my reducer named activeElementId and when the value of activeElementId changes, I want to automatically scroll to that item on the screen.
Setting the activeElementId is the easy part but I'm not sure how to scroll to that element and would appreciate some pointers.
This is the parent component that contains the ListComponent.
class MyComponent extends Component {
constructor(props) {
super(props);
}
render() {
return(
<div>
{this.props.items.length > 0
?
<PerfectScrollBar>
<ListComponent items={this.props.items} />
</PerfectScrollBar>
: null}
</div>
);
}
}
My ListComponent is a presentational component:
const ListComponent = ({ items }) => {
return(
<ul className="pretty-list">
{items.map(item => <ItemComponents item={item} />)}
</ul>
);
}
export default ListComponent;
And the ItemComponent is a presentational component as well:
const ItemComponent = ({ Item }) => {
return(
<li>
<div id={item.id}>
{item.someProperty}
</div>
</li>
);
}
export default ItemComponent;
I really like the idea of keeping ListComponent and ItemComponent separate and as presentational components as that helps keep the code simpler and easier to manage. Not sure if that would make it difficult to implement the auto scroll logic though.
The library you use has a method called setScrollTop. You can use it with getBoundingClientRect. To use getBoundingClientRect you need to have the dom-element. You didn't give any code about how you are setting or getting the active element but I'll try to give you an example. Maybe it will help you to implement on your code.
Example
class MyComponent extends Component {
constructor(props) {
super(props);
}
_onListItemChange = (itemsPosition) => {
this.scrollbar.setScrollTop(itemsPosition.top);
}
render() {
return (
<div>
{this.props.items.length > 0 ?
<PerfectScrollBar ref={(scrollbar) => { this.scrollbar = scrollbar; }}>
<ListComponent
items={this.props.items}
onListItemChange={this._onListItemChange} />
</PerfectScrollBar>
: null}
</div>
);
}
const ListComponent = ({ items, onListItemChange }) => {
return(
<ul className="pretty-list">
{items.map(item => (
<ItemComponents
item={item}
onListItemClick={onListItemChange} />
))}
</ul>
);
}
export default ListComponent;
import { render, findDOMNode } from 'react-dom';
class ListItem extends React.Component {
_onClick = () => {
let domElement = findDOMNode(this.item);
this.props.onListItemClick(domElement.getBoundingClientRect());
}
render() {
const { item } = this.props;
return(
<li>
<div id={item.id} ref={(item) => { this.item = item; }} onClick={this._onClick}>
{item.someProperty}
</div>
</li>
);
}
}

React - Passing State between siblings?

Basically new to React, I'm a bit confused on how to properly pass states between components. I found a similar question already React – the right way to pass form element state to sibling/parent elements?
but I wonder if you can give me a specific answer for the code below.
Currently the structure of the app includes:
parent component - App
2 childs: SearchBar and RecipesList
The goal is to make an async search on my Meteor collection and display only the recipes that match the search term.
Right now, I'm just showing all the recipes in my Meteor collection.
I've created a stateful component named SearchBar which holds the input value as this.state.term. The idea is to pass the state to RecipesList but I'm not sure if it's the right thing to do. Alternatively I'd let App deal with the state and passing it to the childs. I believe this is a very common scenario, how do you do it?
App
class App extends Component {
render( ) {
return (
<div>
<Navbar/>
<SearchBar/>
<RecipesList/>
</div>
);
}
}
SearchBar
export default class SearchBar extends Component {
constructor( props ) {
super( props );
this.state = {
term: ''
};
}
onInputChange( term ) {
this.setState({ term });
}
render( ) {
return (
<div className=" container-fluid search-bar">
<input value={this.state.term} onChange={event => this.onInputChange(event.target.value.substr( 0, 50 ))}/>
Value: {this.state.term}
</div>
);
}
}
RecipesList
const PER_CLICK = 5;
class RecipesList extends Component {
componentWillMount( ) {
this.click = 1;
}
handleButtonClick( ) {
Meteor.subscribe('recipes', PER_CLICK * ( this.click + 1 ));
this.click++;
}
renderList( ) {
return this.props.recipes.map(recipe => {
return (
<div key={recipe._id} className="thumbnail">
<img src={recipe.image} alt="recipes snapshot"/>
<div className="caption">
<h2 className="text-center">{recipe.recipeName}</h2>
</div>
</div>
);
});
}
render( ) {
return (
<ul className="list-group">
{this.renderList( )}
<div className="container-fluid">
<button onClick={this.handleButtonClick.bind( this )} className="btn btn-default">More</button>
</div>
</ul>
);
}
}
// Create Container and subscribe to `recipes` collection
export default createContainer( ( ) => {
Meteor.subscribe( 'recipes', PER_CLICK );
return {recipes: Recipes.find({ }).fetch( )};
}, RecipesList );
App
class App extends Component {
constructor(props, ctx){
super(props, ctx)
this.state = {
searchQuery: ''
}
this.searchInputChange = this.searchInputChange.bind(this)
}
searchInputChange(event) {
this.setState({
searchQuery: event.target.value.substr( 0, 50 )
})
}
render( ) {
const { searchQuery } = this.state
return (
<div>
<Navbar/>
<SearchBar onChange={this.searchInputChange} value={searchQuery}/>
<RecipesList searchQuery={searchQuery}/>
</div>
)
}
}
The App component takes care of the state and this is then passed down to the children as props the seach term is available to RecipesList through props.searchQuery.
The searchInputChange handler is passed down to the SearchBar as props.
SearchBar
export default const SearchBar = ({value, onChange}) => (
<div className=" container-fluid search-bar">
<input value={value} onChange={onChange}/>
Value: {value}
</div>
)
Since the SearchBar delegated state to the parent component, we can use a stateless react component as we only need information from the props to render it.
In general it is always best to have a logical or stateful or controller component take care of state and the logic, this component then passes down state and methods to presentational or view components which take care of what the user sees and interacts with.
Define the state term up in to the App component.
Also write the handleInput function and pass it to the SearchBar component as porps
handleInput(val) {
this.setState({
term: val,
});
}
When something in the search bar is typed(onKeyUp) add the listener handleInput.
Also create <RecipesList searchQuery={this.state.term}/>
now in the render function RecipesList filter out the recipes you want to display from your list

Categories