Transform Function Component to a Class Component - reactjs

I am still learning React and I am wondering if it is possible to make the function component below into a class component.
Component:
import React, { useState } from "react"
import { useStaticQuery, graphql } from "gatsby"
import Navbar from "./navbar"
const Layout = ({ location, title, children }) => {
const rootPath = `${__PATH_PREFIX__}/`
const [classNames, setClassNames] = useState('')
const updateClasses = (classNames) => {
setClassNames(classNames)
}
const data = useStaticQuery(graphql`
{
site {
siteMetadata {
menuLinks {
link
name
}
}
}
}
`)
return (
<div>
<Navbar pages={ data.site.siteMetadata.menuLinks } updateClassNames={updateClasses} />
<main className={classNames}>{children}</main>
</div>
)
}
export default Layout
My biggest issue is with the parameters that are passed to the function location, title, children. What will happen with them. I am not using them at the moment, but will need them later.

Class or function component is not much different except using hooks.
With your current function component, just use the location, title, etc props like other variables in a normal function.
Why do you need to convert into class component?

You don't need a class component to lift up a class, value, or any data in a child component, it's the same behavior rather than class-based component or functional component. You just need to pass via props a function that it will be triggered in a child component to lift up some data again to the parent.
In your parent component, you need to set the function. Without knowing its structure, it would look like:
someFunction= value => {
console.log('I have the value: ', value)
}
return <Layout someFunction={someFunction}
Disclaimer: you may need to adapt the code to your component. The idea is to set a function and pass it via props in the return.
Then, in your <Layout> component, you can destructure the function as you do with location, title and children, and trigger when you need it:
import React, { useState } from "react"
import { useStaticQuery, graphql } from "gatsby"
import Navbar from "./navbar"
const Layout = ({ location, title, children, someFunction }) => {
const rootPath = `${__PATH_PREFIX__}/`
const [classNames, setClassNames] = useState('')
const updateClasses = (classNames) => {
setClassNames(classNames)
}
const handleClick=()=>{
someFunction('hello')
}
const data = useStaticQuery(graphql`
{
site {
siteMetadata {
menuLinks {
link
name
}
}
}
}
`)
return (
<div>
<Navbar pages={ data.site.siteMetadata.menuLinks } updateClassNames={updateClasses} onClick={()=>handleClick} />
<main className={classNames}>{children}</main>
</div>
)
}
export default Layout
In this dummy example, you will be passing 'hello' to the parent component when the <Navbar> is clicked, of course, you can pass any desired value or use a useEffect hook or whatever you need. This is the way to pass data from child to parent component.

Related

Pass the data from child functional component to parent class component

I am quite new to react and I have been trying to figure out how to pass the data from child function to parent class component. In my child, I only have a variable that stores username from the URL. But I can't seem to figure out how to pass it to the parent component.
Here is my child.
const GetUsername = () => {
const params = useParams();
const [param, getParam] = useState(params.name);
return <p>{params.name}</p>;
};
export blabla
I would like to know how I would have to access that params.name in my parent component.
Here is my parent.
export class test extends Component {
constructor(props) {
super(props);
this.data = React.createRef();
}
render() {
console.log(this.data);
return ({some code});
}
When I ran this, I got null. Thank you so much in advance for helping me!
In React, Data flows in one direction only in form of props from Parent to Child.
Either move the logic to fetch params name to parent and pass it to child as props or have a central data storage such as redux and dispatch an action from child to save the information there and fetch it from there in Parent.
If you want to pass a child to parent component data then use usecontext or use redux to pass the data I recommend you can use redux.
parent component is handling the state
import GetUsername from "./GetUserName"
import { useState } from 'react'
const Test = () => {
const [param, getParam] = useState('');
console.log(param)
return (
<div>
<GetUsername getParam={getParam}/>
</div>
)
}
export default Test
and the children GetUserName utilizes the getParam useState setter to set the state handled in its parent component Test
import { useEffect } from "react";
import { useParams } from "react-router-dom";
const GetUsername = ({getParam}) => {
const params = useParams();
useEffect(()=>{
getParam(params)
},[])
return <p>children component</p>;
};
export default GetUsername
you will see that the console.log in the Test component will output your param received in its child component GetUserName.
in case you don't want to pass the props inline, you can use useContext, or redux, or any other state management library like zustand, recoil, etc...
You can pass the setState hook as a prop from the parent to the child and when you have access to the data. Use the setState hook.
Parent Component:
export default function App() {
const [name, setName] = useState();
return (
<div className="App">
<h1>Hello {name}</h1>
<Child setName={setName} />
</div>
);
}
Child Component:
export default function Child({ setName }) {
const [param, getParam] = useState("blabla");
useEffect(() => {
setName(param);
}, []);
return (
<>
<input onChange={(e) => setName(e.target.value)} />
</>
);
}

React/Typescript: How do I pass and "type" props down through multiple components

In my React/Typescript project, I have a challenge:
From the Child component I pass a prop class down:
Child class={{ myBanner: styles.myBanner } />
I typed class prop as follows:
import { SerializedStyles } from '#emotion/react';
import { Class as MyCustomBannerClass } from './MyBanner/MyBanner.types';
type Class = Partial<Record<'root', SerializedStyles>> & {
myBanner: MyCustomBannerClass;
};
export type Props = {
class: Class;
};
Inside Child component I have <MyBanner/> component, where I also have a class prop:
export type Class = Partial<Record<'root', SerializedStyles>>;
export type Props = {
class?: Class;
};
<MyBanner class={props.class?.myBanner} />
This is all working fine.
Now from within Parent component, through Child and <MyBanner/>, I am able to override a css style in Parent from <MyBanner /> component.
The challenge:
Now I have a case:
Inside Child, I have another child <AnotherChild/>.
And within <AnotherChild />, I have <MyBanner/> component.
Question:
How do I pass and type class={{ myBanner: styles.myBanner } through both...
Child:<Child class={{ myBanner: styles.myBanner } />
And AnotherChild: <AnotherChild class={???} />
...and pass it down to <MyBanner class={props.class?.myBanner} />?
This looks like a problem that can easily be solved by using React.useContext ...in other words, the ContextAPI. Here's the official docs about it.
To tackle your issue specifically, you can useContext like this:
Parent.tsx
/** PARENT **/
import React from 'react';
import { SerializedStyles } from '#emotion/react';
import { Class as MyCustomBannerClass } from './MyBanner/MyBanner.types';
// define parent context
type Class = Partial<Record<'root', SerializedStyles>> & {
myBanner: MyCustomBannerClass;
};
interface Props {
parentClass?: Class,
};
export const ParentContext = React.createContext({} as Props);
export const Parent = props => {
const parentClass = React.useRef({ myBanner: styles.myBanner });
const contextProps = {
parentClass: ..., // add props to be passed down
};
return (
// We pass (or literally provide) "parentClass" (and more)
// to all the children of "Parent"
<ParentContext.Provider value={{ ...contextProps }}>
<Child />
</ParentContext.Provider>
)
};
Child.tsx
// FIRST-CHILD
import React from 'react';
// define context
type Class = Partial<Record<'root', SerializedStyles>>;
interface Props {
childClass?: Class,
};
export const ChildContext = React.createContext({} as Props);
// define component
export const Child = props => {
const childClass = React.useRef({ ... })
const contextProps = {
childClass: ..., // add props to be passed down
};
return (
// Both "MyBanner" and "AnotherChild" are provided with both `class` props
// from "Parent" and "Child" and it can go on and on...
<ChildOneContext.Provider value={{ ...contextProps }}>
<MyBanner />
<AnotherChild />
</ChildOneContext.Provider>
)
};
AnotherChild.tsx (You can do the same in "MyBanner")
// SECOND-CHILD
import React from 'react';
import { ParentContext } from './Parent';
import { ChildContext } from './Child';
// define component
const AnotherChild = props => {
const { parentClass } = React.useContext(ParentContext);
const { childClass } = React.useContext(ChildContext);
return (
<div className={ParentClass}>
<div className={childClass}></div>
</div>
)
};
Note:
If you want a child component to override the class prop in any case, you can define the classes with useState instead of useRef as I've done in this case.
That said, at the back of your mind, remember that ContextAPI is designed to share data that can be considered “global” for a tree of React components, such as your case!

pass data to a Layout component gatsbyJS

I want to pass data to layout Component, the data is from an API, cockpitCMS to be exact and the data is slug to be exact too.
io have tried this
import { useStaticQuery, graphql } from "gatsby"
export const slugs = () => {
const { data } = useStaticQuery(
graphql`
query slug{
allCockpitPost {
edges {
node {
title {
slug
}
}
}
}
}
`
)
return data.allCockpitPost.edges.node.title
}
but, I get this instead... React Hook "useStaticQuery" is called in function "slugs" which is neither a React function component or a custom React Hook function
maybe because we can't use usestaticQuery twice, and it is already been used in the SEO component.
Your problem here is the way you use hook. Basically you have some ways to use useStaticQuery or any hook in a function:
That function NEED to return a React Component.
Other wise, that should be a HOC.
Example code for first way:
import AnyComponent from '../some-where';
const MockComponent = (props) => {
const { mockData } = useStaticQuery(anyQuery);
return (
<AnyComponent mockData={mockData}>
)
}
export default MockComponent
Example code for second way:
const withMockHOC = (AnyOtherComponent) => (props) => {
const { mockData } = useStaticQuery(anyQuery);
return <AnyOtherComponent {...props} mockData={mockData} />;
};
Hope this help.

react context with componentdidupdate

I am running a pattern like so, the assumption is that SearchResultsContainer is mounted and somewhere a searchbar sets the input.
class SearchResults {
render() {
return(
<ResultsContext.Consumer value={input}>
{input => <SearchResultsContainer input=input}
</ResultsContext.Consumer>
)
}
class SearchResultsContainer
componentDidUpdate() {
//fetch data based on new input
if (check if data is the same) {
this.setState({
data: fetchedData
})
}
}
}
this will invoke a double fetch whenever a new context value has been called, because componentDidUpdate() will fire and set the data. On a new input from the results context, it will invoke componentDidUpdate(), fetch, set data, then invoke componentDidUpdate(), and fetch, then will check if data is the same and stop the loop.
Is this the right way to be using context?
The solution I used is to transfer the context to the props through a High Order Component.
I have used this very usefull github answer https://github.com/facebook/react/issues/12397#issuecomment-374004053
The result looks Like this :
my-context.js :
import React from "react";
export const MyContext = React.createContext({ foo: 'bar' });
export const withMyContext = Element => {
return React.forwardRef((props, ref) => {
return (
<MyContext.Consumer>
{context => <Element myContext={context} {...props} ref={ref} />}
</MyContext.Consumer>
);
});
};
An other component that consumes the context :
import { withMyContext } from "./path/to/my-context";
class MyComponent extends Component {
componentDidUpdate(prevProps) {
const {myContext} = this.props
if(myContext.foo !== prevProps.myContext.foo){
this.doSomething()
}
}
}
export default withMyContext(MyComponent);
There must be a context producer somewhere :
<MyContext.Provider value={{ foo: this.state.foo }}>
<MyComponent />
</MyContext.Provider>
Here is a way to do it that doesn't require passing the context through props from a parent.
// Context.js
import { createContext } from 'react'
export const Context = createContext({ example: 'context data' })
// This helps keep track of the previous context state
export class OldContext {
constructor(context) {
this.currentContext = context
this.value = {...context}
}
update() {
this.value = {...this.currentContext}
}
isOutdated() {
return JSON.stringify(this.value) !== JSON.stringify(this.currentContext)
}
}
// ContextProvider.js
import React, { Component } from 'react'
import { Context } from './Context.js'
import { MyComponent } from './MyComponent.js'
export class ContextProvider extends Component {
render(){
return (
<MyContext.provider>
{/* No need to pass context into props */}
<MyComponent />
</MyContext.provider>
)
}
}
// MyComponent.js
import React, { Component } from 'react'
import { Context, OldContext } from './Context.js'
export class MyComponent extends Component {
static contextType = Context
componentDidMount() {
this.oldContext = new OldContext(this.context)
}
componentDidUpdate() {
// Do all checks before updating the oldContext value
if (this.context.example !== this.oldContext.value.example) {
console.log('"example" in context has changed!')
}
// Update the oldContext value if the context values have changed
if (this.oldContext.isOutdated()) {
this.oldContext.update()
}
}
render(){
return <p>{this.props.context.example}</p>
}
}
You could pass just the value that is changing separately as a prop.
<MyContext.Provider value={{ foo: this.state.foo }}>
<MyComponent propToWatch={this.state.bar}/>
</MyContext.Provider>
The extent -> props wrapper seems to a recommended by the react staff. However, they dont seem to address if its an issue to wrap context in a prop for an then consume the context directly from the child of the child, etc.
If you have many of these props you are needing to watch, especially when not just at the ends of branches for the component tree, look at Redux, its more powerful that the built in React.extent.

Is there a way to create my own helper function in React?

In a several components i have a function returning URL of user avatar:
import defaultAvatar from 'assets/images/default-pic.jpg'
class MyComponent extends Component {
userAvatar () {
const { profile } = this.props
if (profile.has_avatar) {
return profile.avatar
} else {
return defaultAvatar
}
}
}
is there a way to DRY this function between multiple components?
With Default Props
If you make an avatar component that accepts avatar as a top level property, then you can just use default props to specify the value when it's not provided.
function Avatar({ avatar }) {
return <img src={avatar} />;
}
Avatar.defaultProps = { avatar: defaultAvatar };
Then render this new component from inside your existing one.
return (
<Avatar profile={props.profile} />
);
This way you can keep everything declarative and remove the need for a has_avatar property.
As a Utility Function
But you could also just rip it straight out and fiddle the arguments so you can call it from anywhere.
function getUserAvatar(profile) {
if (profile.has_avatar) {
return profile.avatar
} else {
return defaultAvatar
}
}
Then rewrite your original code.
class MyComponent extends Component {
userAvatar () {
const { profile } = this.props
return getUserAvatar(profile);
}
}
As a Higher Order Component
It would also be possible to implement this as a higher order component.
function WithAvatar(Component) {
return function(props) {
const { profile } = props;
const avatar = getUserAvatar(profile);
return <Component avatar={avatar} {...props} />;
};
}
This would allow you to wrap any existing component with the WithAvatar component.
function Profile(props) {
const { profile, avatar } = props;
return (
<div>
<img src={avatar.src} />
<span>{profile.name}</span>
</div>
);
}
const ProfileWithAvatar = WithAvatar(Profile);
render(
<ProfileWithAvatar profile={exampleProfile} />,
document.getElementById('app')
);
Passing profile as a prop to the outer component causes WithAvatar to process it and select the correct avatar, then pass it down as a prop to the wrapped component.
If you've used the React.createClass approach you could able to use mixins to share code across components. Since you are using ES6 approach you can checkout HOCs (Higher Order Components)
Reference: https://gist.github.com/sebmarkbage/ef0bf1f338a7182b6775

Resources