React/Gatsby component interaction (lifting state up) with components in MDX files - javascript

I use Gatsby with the MDX plugin. So I can use React components in markdown. That's fine.
I have components, talking to each other. To do this I use the Lifting State Up Pattern. That's fine.
Here is a basic Counter example, to show my proof of concept code.
import React from "react"
export class Counter extends React.Component {
constructor(props) {
super(props)
this.state = { count: 0 }
this.handleCounterUpdate = this.handleCounterUpdate.bind(this)
}
handleCounterUpdate() {
this.setState({ count: this.state.count + 1 })
}
render() {
const children = React.Children.map(this.props.children, child => {
const additionalProps = {}
additionalProps.count = this.state.count
additionalProps.handleCounterUpdate = this.handleCounterUpdate
return React.cloneElement(child, additionalProps)
})
return <div>{children}</div>
}
}
export function Display({ count }) {
return <h2>Current counter is: {count}</h2>
}
export function UpdateButton({ handleCounterUpdate }) {
return <button onClick={handleCounterUpdate}>Increment couter by one</button>
}
With this setup, one can use the components like this
<Counter>
<Display />
<UpdateButton />
</Counter>
or even like this
<Counter>
<Display />
<UpdateButton />
<Display />
<Display />
</Counter>
That's fine.
In real-world, the enclosing Counter component (state holder), will be something like a Layout component. The <Layout> is used in a template and renders the MDX pages. This looks like that:
<SiteLayout>
<SEO title={title} description={description} />
<TopNavigation />
<Display /> // The state holder is <SiteLayout>, not <Counter>
<Breadcrumb location={location} />
<MDXRenderer>{page.body}</MDXRenderer> // The rendered MDX
</SiteLayout>
The <UpdateButton> (in real-world something like <AddToCartButton>) is on the MDX page and not anymore a direct child from the <Layout> component.
The pattern does not work anymore.
How can I resolve this?
Thanks all

import React from "react"
// This is a proof of concept (POC) for intercomponent communication using
// React Context
//
// In the real world Gatsby/React app we use a kind of cart summary component
// at top right of each page. The site consists of thousands of pages with detailed
// product information and a blog. Users have the possibility to add products directly
// from product information pages and blog posts. Posts and pages are written in
// MDX (Mardown + React components). All <AddToCartButtons> reside in MDX files.
//
// This code uses a "increment counter button" (= add to cart button) and a
// display (= cart summary) as POC
//
// More information at
// https://reactjs.org/docs/context.html#updating-context-from-a-nested-component
export const CounterContext = React.createContext()
// The <Layout> component handles all business logic. Thats fine. We have
// very few app features.
export class Layout extends React.Component {
constructor(props) {
super(props)
this.handleCounterUpdate = this.handleCounterUpdate.bind(this)
this.state = { count: 0, increment: this.handleCounterUpdate }
}
handleCounterUpdate() {
this.setState({ count: this.state.count + 1 })
}
render() {
return (
<div style={{ backgroundColor: "silver", padding: "20px" }}>
<CounterContext.Provider value={this.state}>
{this.props.children}
</CounterContext.Provider>
</div>
)
}
}
export class UpdateButton extends React.Component {
static contextType = CounterContext
render() {
const count = this.context.count
const increment = this.context.increment
return (
<button onClick={increment}>
Increment counter (current value: {count})
</button>
)
}
}
export class Display extends React.Component {
static contextType = CounterContext
render() {
return (
<div
style={{
backgroundColor: "white",
padding: "10px",
margin: "10px 0 0 0"
}}
>
<div>I'm Display. I know the count: {this.context.count}</div>
<div>{this.props.children}</div>
</div>
)
}
}
// Function components use <CounterContext.Consumer>. Class components
// use static contextType = CounterContext
export function AChild() {
return (
<CounterContext.Consumer>
{context => (
<span>
I'm a child of Display. I know the count too: {context.count}
</span>
)}
</CounterContext.Consumer>
)
}
Use it like this
<Layout>
<Display>
<AChild />
</Display>
// UpdateButton inside MDX files
<MDXRenderer>{page.body}</MDXRenderer>
// Or elsewhere
<UpdateButton />
</Layout>

Related

Class extends using functions inside render?

I'm looking for a way to reduce code redundancy through class extending/inheritance on JavaScript/react.js.
For example, I want to have two types of UserCard components, that both represent a user's info, but one is for detailed (normal) view, other one is for list (mini) view. (e.g. imagine the normal one is something you may see on /user/:id and mini one is on /users)
Specifically, I wanted to have, for the normal ones, 1. its username and bio, icon, etc 2. its latset posts 3. extra actions (e.g. following/DM), and for the mini ones, exclude the 2. and 3. from the normal UserCard.
To implement the above model I thought I should use a JS extends or something (I'm not very familiar with extends), so basically I tried something similar to the following.
I know the following doesn't work, but I really don't have a good idea to how. What should I do with the issue? Thanks.
class UserCardBase extends Component {
constructor(props) {
super(props);
this.state = {
page_size: 3,
newly_order: true,
};
};
componentDidMount() {
{/* gets the user data */ }
axios.get(`/api/users/${this.props.id}`)
.then((response) => { this.setState({ user: response.data, updated: true, }); })
.catch((error) => { console.log(error); toast.error(() => <>failed.</>); });
};
render() {
const renderBlogcards = (props) => {
{/* renders blogcards */ }
return this.state.posts?.map((post, i) => <BlogCard key={i} data={post} />)
}
const extraActions = (props) => {
{/* enables follow/message */ }
return <div className="card__user-actions">
<button className="card__user-actions-follow">Follow</button>
<button className="card__user-actions-message">Message</button>
</div>
}
// mini sized usercard
const ContentMini = (props) => (
<>
<div className="__user_card">
<div className="card" >
<main className="card__user">
<img src={this.state.user?.userprofile.avatar} alt="" className="card__user-image"></img>
<div className="card__user-info">
<Link to={`/user/${this.state.user?.id}`}><h2 className="card__user-info__name">#{this.state.user?.username}</h2></Link>
<p className="card__user-info__desc">{this.state.user?.userbio.bio}</p>
</div>
</main>
</div>
</div>
</>
)
// default sized usercard
const Content = (props) => (
<>
<div className="__user_card">
<div className="card" >
<main className="card__user">
<img src={this.state.user?.userprofile.avatar} alt="" className="card__user-image"></img>
<div className="card__user-info">
<Link to={`/user/${this.state.user?.id}`}><h2 className="card__user-info__name">#{this.state.user?.username}</h2></Link>
<p className="card__user-info__desc">{this.state.user?.userbio.bio}</p>
</div>
<extraActions />
</main>
<renderBlogcards />
</div>
</div>
</>
)
return (
<>
{/* by default, uses the mini usercard */ }
<ContentMini />
</>
);
}
}
class UserCard extends UserCardBase {
constructor(props) {
super(props);
};
render() {
return (
<>
{/* assume trying overrides the render so uses the normal usercard replacing the mini */ }
<Content />
</>
)
}
}

Passing Function parameter to another component in react

I a learning react and stuck at this place. I am creating an app In which user will see a list of product with different id and name. I have created another component in which the detail of the product will open . I am collection the id and value of the particular product in my addList component by onClick function. And now i want to send those value in DetailList component so that i can show the detail of that particular product.
A roadmap like
Add list -> (user click on a product) -> id and name of the product passes to the DetailList component -> Detail list component open by fetching the product detail.
Here is my code of Add list component
export default class Addlist extends Component {
constructor(props) {
super(props)
this.state = {
posts : []
}
}
passToDetailList(id) {
console.log( id)
}
async componentDidMount() {
axios.get('http://localhost:80/get_add_list.php')
.then(response => {
console.log(response);
this.setState({posts: response.data})
})
.catch(error => {
console.log(error);
})
}
render() {
const { posts } = this.state;
// JSON.parse(posts)
return (
<Fragment>
<div className="container" id="listOfAdd">
<ul className="addUl">
{
posts.map(post => {
return (
<li key={post.id}>
<div className="row">
<div className="col-md-4">
<img src={trialImage} alt=""></img>
</div> {/* min col end */}
<div className="col-md-8">
<b><h2>{post.add_title}</h2></b>
{/* This button is clicked by user to view detail of that particular product */}
<button onClick={() => this.passToDetailList(post.id)}>VIEW</button>
</div> {/* min col end */}
</div> {/* row end */}
</li>
);
})}
</ul>
</div>{/* container end */}
</Fragment>
)
}
}
You should pass the data through the routes -
<Route path="/details/:id" component={DetailList} /> // router config
passToDetailList(id) {
this.props.history.push('/details/'+id)
}
and then in the DetailList Component, you can access the value through -
console.log(this.props.match.params.id) - //here is the passed product Id
You need to elevate the state for id to a common parent between AddList and DetailList and then create a function in parent component to set the id and pass the id and setId function to your AddList Component through props , then just use setId function to set the id state in passToDetailList function.
finally you can use the id in your DetailList Component to fetch its details
so Here is how your AddList Component would look like:
export default class Addlist extends Component {
constructor(props) {
super(props);
this.state = {
posts: []
};
}
passToDetailList(id) {
this.props.setId(id);
}
// The rest of your code
}
and here is how your DetailList Component will look like:
export default class DetailList extends Component {
componentDidMount(){
// Use the id to fetch its details
console.log(this.props.id)
}
}
and finally here is your CommonParent Component:
export default class CommonParent extends Component {
constructor(props) {
super(props);
this.state = {
id: ''
};
this.setId = this.setId.bind(this);
}
setId(id){
this.setState({
id
})
}
render(){
return(
<>
<AddList setId={this.setId} />
<DetailList id={this.state.id} />
</>
)
}
}
if your Components are very far from each other in component tree you can use react context or redux for handling id state

Reactjs Changing the state of another child component

My Parent class has two children
Counter component has state 'counter' which increments by the second;
class Counter extends Component {
constructor(props) {
super(props)
this.resetCount = this.resetCount.bind(this);
this.state = {
count : 0
}
}
resetCount() {
this.setState({
count : 0
});
}
componentDidMount() {
setInterval(() => {
this.setState({
count: this.state.count + 1
});
}, 1000);
}
render() {
const {count} = this.state;
const {color,size} = this.props;
return (
<Text style={{color, fontSize: size}}>{count}</Text>
);
}
}
In the Button Component, I have an onpress thing
<Button
onPress={resetCount}
title="Reset COunt"
color="#841584"
/>
In my main Parent Class I render
<Counter color={'green'} size={90} />
<Button/>
But I'm getting an error
'can't find variable resetCount' in App.js
You have to use 'this.resetCount' when using 'Button' inside Counter.render()
<Button
onPress={this.resetCount}
title="Reset COunt"
color="#841584"
/>
If Button is its own Component as mentioned you have to inherit the function onPress
Component Button
<Button onPress={this.props.onResetCount} ... />
Component Counter
render(){
return (
<Text style={{color, fontSize: size}}>{count}</Text>
<Button onResetCount={this.resetCount} title="Reset Count" color="... />
);
)
}
More detailed: https://reactjs.org/docs/faq-functions.html
This is due to Button not being able to access the class method inside its sibling Counter component. If your reorganise your code a little by moving the shared methods to the parent component you can a) achieve what you want, and b) make your code a little simpler. In other words make Counter the main component made up of two smaller dumb components / pure functions.
// No need for a component, just a function that returns
// a styled div
function Text({ count }) {
return <div>{count}</div>;
}
// Another function to return a button
function Button({ resetCount, text }) {
return <button onClick={resetCount}>{text}</button>;
}
// The main component that keeps the state which it passes
// to the dumb Text component
class Counter extends React.Component {
constructor() {
super()
this.state = { count: 0 };
this.resetCount = this.resetCount.bind(this);
}
componentDidMount() {
setInterval(() => {
this.setState({
count: this.state.count + 1
});
}, 1000);
}
resetCount() {
this.setState({ count: 0 });
}
render() {
return (
<div>
<Text count={this.state.count} />
<Button resetCount={this.resetCount} text="Reset count" />
</div>
)
}
}
ReactDOM.render(
<Counter />,
document.getElementById('container')
);
DEMO
You get the error because you can't do onPress={resetCount} this way. It is searching for the variable. But you don't have a variable, it's a function. So you should use this.resetCount if you want to access the function resetCount().
Here's an example how you can access the function of your parent component from the button in the child component:
// Parent component:
resetCount() {
// your code
}
render() {
return(
<Button resetCount={this.resetCount} /* your other stuff */ />
);
}
// Button component:
<button onPress={this.props.resetCount}>Click me</button>
Note: You can't update a sibling this way. You should move your functions from <Counter/> to your parent component.

How to update the props of a rendered react component from App.js?

I have a React component MoviesGallery.js with the following configuration:
class MoviesGallery extends Component {
constructor(props) {
super(props)
this.state = { currentImage: 0 };
this.closeLightbox = this.closeLightbox.bind(this);
this.openLightbox = this.openLightbox.bind(this);
this.gotoNext = this.gotoNext.bind(this);
this.gotoPrevious = this.gotoPrevious.bind(this);
}
componentWillReceiveProps(nextProps) {
this.setState({movies_genre: nextProps.movies_genre})
}
I have rendered the component in my main App.js file like so:
class App extends Component {
render() {
return (
<MuiThemeProvider muiTheme={getMuiTheme(darkBaseTheme)}>
<div className="App">
<header className="App-header">
<h1 className="App-title">Welcome to React</h1>
<RaisedButton primary={true} label="Query" className="header_buttons"/>
<RaisedButton secondary={true} label="Reset" className="header_buttons"/>
</header>
<MoviesGallery/>
</div>
</MuiThemeProvider>
);
}
}
I want to update the props of my MoviesGallery component without recreating the component. Since I already added the componentWillReceiveProps() to MoviesGallery component, how can I make it so when 'Query' button is clicked, it will pass new props to the already rendered MoviesGallery and componentWillReceiveProps() should cause it to re-render since the state will change.
Just confused about the function that will change the props themselves on-click of the rendered MoviesGallery component.
Thanks in advance!
When a parent pass a new (value) prop to the child, the child component will call the render method automatically. There is no need to set a local state inside the child component to "store" the new prop.
Here is a small example of a Counter that receives a count prop and just displays it, while the parent App in this case will change the value in its state and pass the new value to Counter:
class Counter extends React.Component {
render() {
const { count } = this.props;
return (
<div>{count}</div>
);
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
}
}
onClick = () => {
this.setState({ count: this.state.count + 1 });
}
render() {
const { count } = this.state;
return (
<div>
<Counter count={count} />
<button onClick={this.onClick}>Add to counter</button>
</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>
you can use the 'state' for your MovieGallery.js props because the state is an object that changes and you must your code like below :
class App extends Component {
state = {
query : null
}
myFunction(query){
this.setState({query});
}
render() {
return (
<MuiThemeProvider muiTheme={getMuiTheme(darkBaseTheme)}>
<div className="App">
<header className="App-header">
<h1 className="App-title">Welcome to React</h1>
<RaisedButton primary={true} label="Query" className="header_buttons" onClick={this.myFunction = this.myfunction.bind(this)}/>
<RaisedButton secondary={true} label="Reset" className="header_buttons"/>
</header>
<MoviesGallery newProps = {this.state.query}/>
</div>
</MuiThemeProvider>
);
}
}
i hope it helps

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