ref.current is always undefined? - reactjs

I need to use functional component ,so I can't use useRef directly. here is my code:
import React,{forwardRef,useImperativeHandle,useRef,useEffect} from "react";
import "./style.css";
export default function App() {
const ref_ = useRef();
useEffect(()=>{
console.log(ref_)
})
console.log(ref_)
return (
<div>
<Demo/>
</div>
);
}
const Demo = forwardRef((props,ref)=>{
useImperativeHandle(ref,()=>({
toggle(){
console.log(123);
}
}))
return(
<div ref={ref}>1234</div>
)
})
online code :
https://stackblitz.com/edit/forwardref-z68drh?file=src/App.js
and I want to trigger child component's function on parent component... can anyone help me?

You need to pass the ref itself to Demo component:
function App() {
const ref = useRef();
return <Demo ref={ref}/>;
}
Then you can use ref.current.toggle() due to useImperativeHandle.
https://stackblitz.com/edit/forwardref-z68drh-epz2xf?file=src/App.js

Related

React, render dinamic child passing prop

I'm slowly getting introduced into advanced React and best practices for production apps. I want to know what is considered the "best practice" way of rendering a dinamic child passing props regarding maintainability, readability etc:
Here is the codeSandBox for more details
First method, render children as function passing the prop:
import { useState } from "react"
import "./Mouses.css"
export default function MouseChildren ({children}) {
const [mousePos, setMousePos] = useState(undefined)
function handleMouseMove(e) {
setMousePos({x: e.clientX, y: e.clientY})
}
/* this is bad because if more than one child is passed it will break */
return (
<div className="Mouse-container" onMouseMove={handleMouseMove}>
{children(mousePos)}
</div>
)
}
and then you call it this way:
<MouseChildren>
{position => <RandomDiv position={position} method="children as function method" />}
</MouseChildren>
Second method, render function as prop:
import { useState } from "react"
import "./Mouses.css"
export default function MouseRenderMethod ({render}) {
const [mousePos, setMousePos] = useState(undefined)
function handleMouseMove(e) {
setMousePos({x: e.clientX, y: e.clientY})
}
return (
<div className="Mouse-container" onMouseMove={handleMouseMove}>
{render(mousePos)}
</div>
)
}
and you call it like this:
<MouseRenderMethod render={position => <RandomDiv position={position} method="render as prop method:" />} />
and the third method is using React.Children:
import React, { useState } from "react"
import "./Mouses.css"
export default function MouseCreateComponent({children}) {
const [mousePos, setMousePos] = useState(undefined)
function handleMouseMove(e) {
setMousePos({x: e.clientX, y: e.clientY})
}
return (
<div className="Mouse-container" onMouseMove={handleMouseMove}>
{React.Children.map(React.Children.toArray(children), child => {
if (React.isValidElement(child)) return React.cloneElement(child, {position: mousePos}, null)
})}
</div>
)
}
and you call it like this:
<MouseCreateComponent>
<RandomDiv method="React.CloneElement method" />
</MouseCreateComponent>
I'm not sure which way is considered to be best over the others. If you from your experience can explain a bit
If you want to pass anything in your code without retyping it, you need to use useContext and the structure is:
One example with full-functionality which it may help you is:
const AppContext = React.createContext();
const AppProvider = ({ children }) => {
const [userData, setUserData]=useState('nothing here')
// any code you want to pass in the code
//e.g. a function
const randomFunction = ()=>{
//do something here
}
return (
<AppContext.Provider
value={{
userData,
setUserData,
randomFunction
}}
>
{children}
</AppContext.Provider>
);
};
export const useGlobalContext = () => {
return useContext(AppContext);
};
export { AppContext, AppProvider };
then all you have to do is to wrap all the components (children) you want, e.g. wrap <App /> so, more or less everything:
<AppProvider>
<App />
</AppProvider>
So now in this case you can use everything from your AppContext in all your code, you can pass more variables and functions if you want, and you import that by using:
import { useGlobalContext } from '/pathYouHaveIt';
function App() {
const {
userData,
setUserData,
randomFunction,
} = useGlobalContext();
// now you can use those like you have them set-up in the App()

How do I get a state variable from child to parent?

I am learning React.
I have Component structure like this -
index.js
import React from "react";
import Button from "./Button/Button"
export default function Index() {
return (
<>
<Button />
<div>Value of flag in Index.js = {}</div>
</>
);
}
Button.js
import React, { useState } from "react";
import "./button.css";
export default function Button(props) {
const [flag, setFlag] = useState(true);
const clickHandler = () => {
setFlag(!flag);
};
return (
<div className="btn" onClick={clickHandler}>
Value of flag in Button.js = {flag.toString()}
</div>
);
}
My question is "How do I get flag value from Button.js to index.js" ? (child to parent).
1) You can lift state in parent component and pass state and handler as a prop to children.
Note: This is will work because you need flag in the JSX, But if you will pass event handler as a prop in the child component then you have to invoke the handler to get the value. So Either lift the state or use Redux
Live Demo
App.js
const App = () => {
const [flag, setFlag] = useState( true );
return (
<>
<Button flag={flag} setFlag={setFlag} />
<div>Value of flag in Index.js = { flag.toString() }</div>
</>
);
};
Button.js
export default function Button({ flag, setFlag }) {
const clickHandler = () => {
setFlag(oldFlag => !oldFlag);
};
return (
<div className="btn" onClick={clickHandler}>
Value of flag in Button.js = {flag.toString()}
</div>
);
}
2) You can pass handler as a prop in child component as shown in the Harsh Patel answer
3) You can use state management tool i.e. Redux.
You can send a value by method, refer to this:
index.js
import React from "react";
import Button from "./Button/Button"
export default function Index() {
let getFlagValue = (flag) => {
//here you'll get a flag value
console.log(flag)
}
return (
<>
<Button sendFlagValue=(getFlagValue)/>
<div>Value of flag in Index.js = {}</div>
</>
);
}
Button.js
import React, { useState } from "react";
import "./button.css";
export default function Button(sendFlagValue) {
const [flag, setFlag] = useState(true);
const clickHandler = () => {
setFlag(!flag);
sendFlagValue(flag)
};
return (
<div className="btn" onClick={clickHandler}>
Value of flag in Button.js = {flag.toString()}
</div>
);
}
There are two types state:
global state for all
private state for component
For starter, you must obey some policies, not try abuse state, otherwise you will have some troubles.
For global STATE, you can use Redux or dva or umijs.
For private STATE, you seems already known.

How to test code that uses a custom hook based on useContext with react-testing-library and jest

I've created a custom context hook - and I'm struggling to figure out how to pass values to its provider during testing.
My hook:
import React, { createContext, useContext, useState } from 'react';
const Context = createContext({});
export const ConfigurationProvider = ({ children }) => {
// Use State to keep the values
const [configuration, setConfiguration] = useState({});
// pass the value in provider and return
return (
<Context.Provider
value={{
configuration,
setConfiguration,
}}
>
{children}
</Context.Provider>
);
};
export const useConfigurationContext = () => useContext(Context);
export const { Consumer: ConfigurationConsumer } = Context;
This is how it's used in the application:
function App() {
return (
<ConfigurationProvider>
<div className="app">
<ComponentA />
</div>
</ConfigurationProvider>
);
}
And in ComponentA:
const ComponentA = () => {
// Get configuration
const configuration = useConfigurationContext();
return (
<div>{JSON.stringify(configuration)}</div>
)
}
This all works fine - considered that I'm calling setConfiguration from another component and set an object. Now for the testing part:
import React, { Component, createContext } from 'react';
import { render, waitFor } from '#testing-library/react';
import ComponentA from 'componentA';
const config = {
propertyA: 'hello',
};
test('renders the config', async () => {
const ConfigurationContext = createContext();
const { queryByText } = render(
<ConfigurationContext.Provider value={config}>
<ComponentA />
</ConfigurationContext.Provider>
);
expect(queryByText('hello')).toBeInTheDocument();
});
This doesn't work - I'm expecting the value that I'm sending in would be rendered in the div, but the context is an empty object. What am I doing wrong?
Thanks to Carle B. Navy I got the reason why it doesn't work. For other people two wonder what the solution is I fixed it by doing the following:
In my context hook, I changed the last line to export the provider as well:
export const { Consumer: ConfigConsumer, Provider: ConfigProvider } = Context;
Then in my test case, instead of creating a new context, I import the ConfigProvider at the top, and then:
const { queryByText } = render(
<ConfigProvider value={config}>
<ComponentA />
</ConfigProvider>
);
Thanks for helping me solve this and hope this helps someone else.

React custom hooks not working as expected

I am trying to implement a very simple custom hook called useOpen that simply returns a boolean (isOpen). I want to show or hide some text in App.js based on isOpen state. Currently, nothing is being rendered and trying to console.log(isOpen) in App.js gives me undefined. Thanks in advance!
App.js
import React from 'react'
import useOpen from './CustomHooks/useOpen'
function App () {
const {isOpen} = useOpen;
return (
<div className='App'>
{isOpen && <p>isOpen</p>}
</div>
)
}
export default App
useOpen.js
import { useState } from 'react'
export default function useOpen() {
const [isOpen, setIsOpen] = useState(true)
return { isOpen }
}
You’re missing the parens on useOpen. Should be useOpen().
const {isOpen} = useOpen; // missing ()
const {isOpen} = useOpen();
Try to call function useOpen at the component.
you need to execute the hook to get its value
and you don't need to put it in a object to deconstruct it on the other side
App.js
import React from 'react'
import useOpen from './CustomHooks/useOpen'
function App () {
//const {isOpen} = useOpen;
const isOpen = useOpen();
return (
<div className='App'>
{isOpen && <p>isOpen</p>}
</div>
)
}
export default App
useOpen.js
import { useState } from 'react'
export default function useOpen() {
const [isOpen, setIsOpen] = useState(true)
//return { isOpen }
return isOpen
}

Consumer not reading provider value

This is my Provider component
import React, {useState} from 'react';
import MyComponent from './MyComponent/MyComponent';
export default function App(props) {
const [header, setHeader] = useState(null);
function setHeaderText(header) {
setHeader(header);
}
const contextItems = {
setHeaderText: setHeaderText,
what: 'what'
};
return <React.Fragment>
<AppContext.Provider value={contextItems}>
<MyComponent request={props.request}/>
</AppContext.Provider>
</React.Fragment>
}
export const AppContext = React.createContext({
setHeaderText: () => {},
what: ''
});
I am using the consumer to try and pass the context value to MyComponent which is a functional component.
function MyComponent() {...}
export default (props) => {
return <AppContext.Consumer>
{ value => <MyComponent context={value} {...props} />}
</AppContext.Consumer>
}
My issue is that it only passes the default value of AppContext not the value that i am passing in.
Can anyone see anything wrong with my code at all?
I am using the latest version of react 16.8.2.
Thanks for any help.
Side note: I know i could just use composition in this case but the header value will be used throughout the app.

Resources