Can't pass useState() 'set' function to grand child - javascript

I'm having issues trying to get my useState variable to work. I create the state in my grandparent then pass it into my parent. Here's a simplified version of my code:
export function Grandparent(){
return(
<div>
const [selectedID, setSelectedID] = useState("0")
<Parent setSelectedID2={setSelectedID} .../> //(elipses just mean that I'm passing other params too)
<div />
)}
Parent:
const Parent = ({setSelectedID2 ...}) => {
return(
<div>
{setSelectedID2("5")} //works
<Child setSelectedID3={setSelectedID2} />
</div>
)
}
From the parent I can use 'setSelectedID2' like a function and can change the state. However, when I try to use it in the child component below I get an error stating 'setSelectedID3' is not a function. I'm pretty new to react so I'm not sure if I'm completely missing something. Why can I use the 'set' function in parent but not child when they're getting passed the same way?
Child:
const Child = ({setSelectedID3 ...}) => {
return(
<div >
{setSelectedID3("10")} //results in error
</div>
);
};

In React you make your calculations within the components/functions (it's the js part) and then what you return from them is JSX (it's the html part).
export function Grandparent(){
const [selectedID, setSelectedID] = useState("0");
return(
<div>
<Parent setSelectedID2={setSelectedID} .../> //(elipses just mean that I'm passing other params too)
<div />
)}
You can also use (but not define!) some js variables in JSX, as long as they are "renderable" by JSX (they are not Objects - look for React console warnings).
That's your React.101 :)

Here's a working example with everything you have listed here. Props are passed and the function is called in each.
You don't need to name your props 1,2,3.., they are scoped to the function so it's fine if they are the same.
I moved useState and function calls above the return statement, because that's where that logic should go in a component. The jsx is only used for logic dealing with your display/output.
https://codesandbox.io/s/stupefied-tree-uiqw5?file=/src/App.js
Also, I created a working example with a onClick since that's what you will be doing.
https://codesandbox.io/s/compassionate-violet-dt897?file=/src/App.js
import React, { useState } from "react";
export default function App() {
return <Grandparent />;
}
const Grandparent = () => {
const [selectedID, setSelectedID] = useState("0");
return (
<div>
{selectedID}
<Parent setSelectedID={setSelectedID} selectedID={selectedID} />
</div>
);
};
const Parent = ({ selectedID, setSelectedID }) => {
setSelectedID("5");
return (
<div>
{selectedID}
<Child setSelectedID={setSelectedID} selectedID={selectedID} />
</div>
);
};
const Child = ({ selectedID, setSelectedID }) => {
setSelectedID("10");
return <div>{selectedID}</div>;
};
output
10
10
10

const [selectedID, setSelectedID] = useState("0")
should be outside return

Related

Calling Stateless Functional Components

I'm currently learning React and i am working through 'The Road to React' by Robin Wieruch.
I've just refactored some code from a stateful to a functional stateless component like so:
function Search(props) {
const { value, onChange, children } = props;
return (
<form>
{children} <input
type="text"
value={value}
onChange={onChange}
/>
</form>
);
}
Gets Refactored to:
const Search = ({ value, onChange, children }) => {
<form>
{children} <input
type="text"
value={value}
onChange={onChange}
/>
</form>
}
However nothing is rendering anymore. Are functional stateless components called the same was as stateful ones?
This is how I'm calling the Search component in the App class:
render() {
const { searchTerm, list } = this.state;
return (
<div className="App">
<Search
value = { searchTerm }
onChange = { this.onSearchChange }
>
Search
</Search>
<Table
list = { list }
pattern = { searchTerm }
onDismiss = { this.onDismiss }
/>
</div>
)
I'm not receiving an error at all, so i'm not getting much that's pointing me in the right direction, i'm hoping i'm just missing something silly.
Thanks in advance!
In both cases, it's a stateless function only as there's no state and it's not an class component either.
1st case is working correctly because it's returning the element with the return keyword.
2nd refactored case is also correct but you are not returning anything you need to return the element for it to be rendered.
return example
const func = () => {
... // any more calculations or code
return ( // you are returning the element here
<div>
...
</div>
)
}
If there's no calculation or any additional code and you have to return only an element you can directly return it by using (...) instead of {...} as follows
const func = () => ( // you are directly returning element
<div>
...
</div>
)
PS: for more info you can check into arrow functions

React Context without nesting a provider?

I'm wondering if it's possible to create a context and consume it without passing the context down the whole section of the dom tree.
To that extent I've created the following example:
./components/count-context.js
import * as React from 'react'
const CountContext = React.createContext({count : 0} )
const CountContextProvider = (props) => {
const [count, setCount] = React.useState(0);
const incrementCount = () => {
console.log("increment count", count)
setCount(count + 1);
};
const decrementCount = () => {
setCount(count - 1);
}
return (
<CountContext.Provider value={{ count, setCount, incrementCount, decrementCount }}>
{props.children}
</CountContext.Provider>
);
}
const CountDisplayNoProvider = (props) => {
const { count } = React.useContext(CountContext)
return (
<p>{count}</p>
)
}
const CountDisplaySelfProvided = (props) => {
const { count } = React.useContext(CountContext)
return (
<CountContextProvider>
<p>{count}</p>
</CountContextProvider>
)
}
const IncrementCountButton = (props) => {
const { count, incrementCount, setCount } = React.useContext(CountContext)
console.log(`count is a `, typeof(count))
console.log(`incrementCount is a `, typeof(incrementCount))
console.log(`setCount is a `, typeof(setCount))
return (
<button onClick={incrementCount}>IncrementCountButton</button>
)
}
export {
CountContextProvider,
CountDisplayNoProvider,
CountDisplaySelfProvided,
IncrementCountButton
}
And: ./App.js
import './App.css';
import { CountContextProvider, CountDisplaySelfProvided, CountDisplayNoProvider, IncrementCountButton} from './components/count-context'
function App() {
return (
<div className="App">
<p>NO CONTEXT</p>
CountDisplayNoProvider: <CountDisplayNoProvider />
<IncrementCountButton />
<br />
CountDisplaySelfProvided: <CountDisplaySelfProvided />
<IncrementCountButton />
<CountContextProvider>
<p>CountDisplayNoProvider inside CountContextProvider</p>
<CountDisplayNoProvider />
<IncrementCountButton />
</CountContextProvider>
</div>
);
}
export default App;
The funny thing is that the CountDisplayNoProvider and CountDisplaySelfProvided both show a number: 0
but
count-context.js:34 incrementCount is a undefined
count-context.js:35 setCount is a undefined
--- so why is it that the count gets passed to NO CONTEXT, but not the functions?
And even stranger, why is it that I can't put the provider in the count's own component? (CountDisplaySelfProvided)
Thank you!!
Screenshot of the rendered output after rage clicking all the buttons
I think I understand as to why those other components did get some value like 0 from the context. While we do need the provider to consume the values, different things happen to the other components.
First CountDisplayNoProvider
This only consumes the initial value of count which you've provided when creating the Context. const CountContext = React.createContext({count : 0}). However, if you've tried consuming the other functions you've passed on the Provider, then it would only return undefined because of it not being set initially when the context was created.
CountDisplaySelfProvided
This explains the same thing with the First <CountDisplayNoProvider/>. The problem with this one is you're already using the Context then providing it inside the return.
In this case, you're consuming it before you get everything from the
Provider. So you wouldn't be able to use the functions here too. While you did add a Provider, useContext runs first thus the undefined functions.
If it had children who would later consume it via useContext, then it should definitely work and have their own count, setCount & everything else you've provided.
The first two incrementCountButtons did not work as it can not get CountContext.They are outside CountContextProvider now, you probably want to move it inside the CounterContextCounter
<div className="App">
<CountContextProvider> // Move CounterContextProvider here
<p>NO CONTEXT</p>
CountDisplayNoProvider: <CountDisplayNoProvider />
<IncrementCountButton />
<br />
CountDisplaySelfProvided: <CountDisplaySelfProvided />
<IncrementCountButton />
<p>CountDisplayNoProvider inside CountContextProvider</p>
<CountDisplayNoProvider />
<IncrementCountButton />
</CountContextProvider> // end of counter context provider
</div>

Passing refs in React

In my App component, I have 2 components Navbar and View. In my Navbar component, I have an ExportButton component which onClick should generate a screenshot of the View component by passing its ref.
App.js
function App() {
const view = useRef();
return (
<div className="App">
<Navbar takeSnap={view}/>
<View ref={view}/>
</div>
);
}
Navbar.js
const Navbar = ({ takeSnap }) => {
return (
<>
<Lists />
<ExportButton takeSnap={takeSnap} />
</>
);
};
Button.js
const ExportButton = ({ takeSnap }) => {
function handleClick(takeSnap) {
domtoimage.toBlob(takeSnap.current, {}).then(function (blob) {
saveAs(blob, "myImage.png");
});
}
return (
<Button onClick={() => handleClick(takeSnap)} />
);
};
I having some trouble passing ref of View to use the library dom-to-image to take a screenshot. The error says "Uncaught (in promise) TypeError: Cannot read property 'cloneNode' of undefined
at makeNodeCopy". This might be a quick fix but I'm not sure where I'm going wrong.
You cannot create a ref for a component, a ref can only reference a DOM element.
When you do:
<View ref={view}/>
ref is a reserved keyword and it won't be passed down to your View render function.
You can use forwardRef to solve this problem, or simply use a different keyword such as myRef:
<View myRef={view}/>
Then when you render your View, you can assign this ref to the element you want the screenshot from:
<div ref={myRef} ...

export Hooks in React for Nested Components?

I'm exporting hooks with nested components so that the parent can toggle state of a child. How can I make this toggle work with hooks instead of classic classes or old school functions?
Child Component
export let visible;
export let setVisible = () => {};
export const ToggleSwitch = () => {
const [visible, setVisibile] = useState(false);
return visible && (
<MyComponent />
)
}
Parent
import * as ToggleSwitch from "ToggleSwitch";
export const Parent: React.FC<props> = (props) => {
return (
<div>
<button onClick={() => ToggleSwitch.setVisible(true)} />
</div>
)
}
Error: Linter says [setVisible] is unused variable in the child... (but required in the parent)
You can move visible state to parent like this:
const Child = ({ visible }) => {
return visible && <h2>Child</h2>;
};
const Parent = () => {
const [visible, setVisible] = React.useState(false);
return (
<div>
<h1>Parent</h1>
<Child visible={visible} />
<button onClick={() => setVisible(visible => !visible)}>
Toggle
</button>
</div>
);
};
If you have many child-components you should make more complex logic in setVisible. Put object to useState where properties of that object will be all names(Ids) of child-components
as you know React is one-way data binding so if you wanna pass any props or state you have only one way to do that by passing it from parent to child component and if the logic becomes bigger you have to make it as a global state by using state management library or context API with react hooks use reducer and use effect.

How to avoid use of document.getElementById in Reactjs? [duplicate]

How do I select certain bars in react.js?
This is my code:
var Progressbar = React.createClass({
getInitialState: function () {
return { completed: this.props.completed };
},
addPrecent: function (value) {
this.props.completed += value;
this.setState({ completed: this.props.completed });
},
render: function () {
var completed = this.props.completed;
if (completed < 0) { completed = 0 };
return (...);
}
I want to use this React component:
var App = React.createClass({
getInitialState: function () {
return { baction: 'Progress1' };
},
handleChange: function (e) {
var value = e.target.value;
console.log(value);
this.setState({ baction: value });
},
handleClick10: function (e) {
console.log('You clicked: ', this.state.baction);
document.getElementById(this.state.baction).addPrecent(10);
},
render: function () {
return (
<div class="center">Progress Bars Demo
<Progressbar completed={25} id="Progress1" />
<h2 class="center"></h2>
<Progressbar completed={50} id="Progress2" />
<h2 class="center"></h2>
<Progressbar completed={75} id="Progress3" />
<h2 class="center"></h2>
<span>
<select name='selectbar' id='selectbar' value={this.state.baction} onChange={this.handleChange}>
<option value="Progress1">#Progress1</option>
<option value="Progress2">#Progress2</option>
<option value="Progress3">#Progress3</option>
</select>
<input type="button" onClick={this.handleClick10} value="+10" />
<button>+25</button>
<button>-10</button>
<button>-25</button>
</span>
</div>
)
}
});
I want to execute the handleClick10 function and perform the operation for my selected progressbar.
But the result I get is:
You clicked: Progress1
TypeError: document.getElementById(...) is null
How do I select the certain Element in react.js?
You can do that by specifying the ref
EDIT: In react v16.8.0 with function component, you can define a ref with useRef. Note that when you specify a ref on a function component, you need to use React.forwardRef on it to forward the ref to the DOM element of use useImperativeHandle to to expose certain functions from within the function component
Ex:
const Child1 = React.forwardRef((props, ref) => {
return <div ref={ref}>Child1</div>
});
const Child2 = React.forwardRef((props, ref) => {
const handleClick= () =>{};
useImperativeHandle(ref,() => ({
handleClick
}))
return <div>Child2</div>
});
const App = () => {
const child1 = useRef(null);
const child2 = useRef(null);
return (
<>
<Child1 ref={child1} />
<Child1 ref={child1} />
</>
)
}
EDIT:
In React 16.3+, use React.createRef() to create your ref:
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
render() {
return <div ref={this.myRef} />;
}
}
In order to access the element, use:
const node = this.myRef.current;
DOC for using React.createRef()
EDIT
However facebook advises against it because string refs have some issues, are considered legacy, and are likely to be removed in one of the future releases.
From the docs:
Legacy API: String Refs
If you worked with React before, you might be
familiar with an older API where the ref attribute is a string, like
"textInput", and the DOM node is accessed as this.refs.textInput. We
advise against it because string refs have some issues, are considered
legacy, and are likely to be removed in one of the future releases. If
you're currently using this.refs.textInput to access refs, we
recommend the callback pattern instead.
A recommended way for React 16.2 and earlier is to use the callback pattern:
<Progressbar completed={25} id="Progress1" ref={(input) => {this.Progress[0] = input }}/>
<h2 class="center"></h2>
<Progressbar completed={50} id="Progress2" ref={(input) => {this.Progress[1] = input }}/>
<h2 class="center"></h2>
<Progressbar completed={75} id="Progress3" ref={(input) => {this.Progress[2] = input }}/>
DOC for using callback
Even older versions of react defined refs using string like below
<Progressbar completed={25} id="Progress1" ref="Progress1"/>
<h2 class="center"></h2>
<Progressbar completed={50} id="Progress2" ref="Progress2"/>
<h2 class="center"></h2>
<Progressbar completed={75} id="Progress3" ref="Progress3"/>
In order to get the element just do
var object = this.refs.Progress1;
Remember to use this inside an arrow function block like:
print = () => {
var object = this.refs.Progress1;
}
and so on...
For getting the element in react you need to use ref and inside the function you can use the ReactDOM.findDOMNode method.
But what I like to do more is to call the ref right inside the event
<input type="text" ref={ref => this.myTextInput = ref} />
This is some good link to help you figure out.
With newer versions of React you can use and manipulate the DOM via hooks like this:
import React, { useEffect, useRef } from "react";
const MyComponent = () => {
const myContainer = useRef(null);
useEffect(() => {
console.log("myContainer..", myContainer.current);
});
return (
<>
<h1>Ref with react</h1>
<div ref={myContainer}>I can use the DOM with react ref</div>
</>
);
};
export default MyComponent;
Whenever you want to access your DOM element just use myContainer.current
You can replace
document.getElementById(this.state.baction).addPrecent(10);
with
this.refs[this.state.baction].addPrecent(10);
<Progressbar completed={25} ref="Progress1" id="Progress1"/>
Disclaimer: While the top answer is probably a better solution, as a beginner it's a lot to take in when all you want is something very simple. This is intended as a more direct answer to your original question "How can I select certain elements in React"
I think the confusion in your question is because you have React components which you are being passed the id "Progress1", "Progress2" etc. I believe this is not setting the html attribute 'id', but the React component property. e.g.
class ProgressBar extends React.Component {
constructor(props) {
super(props)
this.state = {
id: this.props.id <--- ID set from <ProgressBar id="Progress1"/>
}
}
}
As mentioned in some of the answers above you absolutely can use document.querySelector inside of your React app, but you have to be clear that it is selecting the html output of your components' render methods. So assuming your render output looks like this:
render () {
const id = this.state.id
return (<div id={"progress-bar-" + id}></div>)
}
Then you can elsewhere do a normal javascript querySelector call like this:
let element = document.querySelector('#progress-bar-Progress1')
You have to follow two different ways to do it in Class and Functional components.
For class components
<input type="text" ref={ref => this.myTextInput = ref} />
Look at the above code. Use "ref" attribute to refer to the relevant element. Then you will be able to refer to that element using that reference. In this example, I can use "this.myTextInput" to refer to the above input element.
For functional components
const textInput = useRef(null)
Use the "useRef" hook and set that variable name as the value of the "ref" attribute of the element you want to refer to (like below).
<input type="text" ref={textInput} />
An example for this on functional components.
import React, {useRef} from 'react'
function CustomTextInput(props) {
// textInput must be declared here so the ref can refer to it
const textInput = useRef(null);
function handleClick() {
textInput.current.focus();
}
return (
<div>
<input type="text" ref={textInput} />
</div>
);
}
Want to learn more? Here you go
Since React uses JSX code to create an HTML we cannot refer dom using regulation methods like documment.querySelector or getElementById.
Instead we can use React ref system to access and manipulate Dom as shown in below example:
constructor(props){
super(props);
this.imageRef = React.createRef(); // create react ref
}
componentDidMount(){
**console.log(this.imageRef)** // acessing the attributes of img tag when dom loads
}
render = (props) => {
const {urls,description} = this.props.image;
return (
<img
**ref = {this.imageRef} // assign the ref of img tag here**
src = {urls.regular}
alt = {description}
/>
);
}
}
In my case, I wasn't able to use ref because elements were somewhere between many child components and I have to access them by class and id instead of ref. So, trying with useEffect hook didn't work as it can't find the element:
useEffect(() => {
const el1 = document.querySelector('.el1')
const el2 = document.querySelector('.el2')
}, [])
The element is undefined because when it is mounted the children components also doesn't mounted before this parent component.
So, what I did is to use timeout:
useEffect(() => {
const timer = setTimeout(() => {
const el1 = document.querySelector('.el1')
const el2 = document.querySelector('.el2')
},500)
return () => {
clearTimeout(timer)
}
}, [])
Now, it worked fine. It found the DOM and I was able to manipulate with them. Hope, this helps someone!
The equivalent of document.getElementById() in React is document.querySelector().

Categories