let variable null when passing from parent to child | React - javascript

I have a variable that I don't want to bind to state in React. So I declared it as let with initial value as null. Later with event I set its value in parent and then pass it to child. But in child its value is getting null. Not sure what mistake I am making. Below is the code.
function Parent() {
const[showChild, setShowChild] = useState(false);
let data = null;
const setData = () => {
data = 'Test';
setShowChild(true);
console.log('function called');
};
return (
<>
<button onClick={setData}>
Click Me
</button>
{showChild && <Child data={data} />}
</>
);
}
function Child({data}) {
console.log('data ' + data);
return (
<>
<h2>
{data}
</h2>
</>
);
}
ReactDOM.render(
<Parent />,
document.getElementById('mountNode'),
);

If you want to persist state you have to use useState as you did in the first line. so instead of manually let data ... and const setData you should have something like the following:
const [data, setData] = useState(null)

You sould use useState, whenever the state changes in parent, child will be render again.
I changed your example a little bit to show you what happen exactly, child just shows the value that parent has sent.
const {useState} = React;
const Parent=()=> {
const[showChild, setShowChild] = useState(false);
const[data, setData] = useState(0);
//let data = null;
const handleClick = () => {
setData((prev=>prev+1));
setShowChild(true);
console.log('function called');
};
return (
<div>
<button onClick={handleClick}>
Click Me
</button>
{showChild && <Child data={data} />}
</div>
);
}
const Child=({data})=> {
console.log('data ' + data);
return (
<div>
<h2>
{data}
</h2>
</div>
);
}
ReactDOM.render( <Parent/> ,
document.getElementById("mountNode")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="mountNode"></div>

Related

How to render a component when state and props are changed?

I need to show the props value (which is a simple string). Each time I get new search results, I'm sending in the props. At the very first render the props will always be undefined.
Edit:
Header.jsx
function Header() {
const [searchString, setString] = useState('');
const onChangHandler = (e) => {
setString(e.target.value);
};
const activeSearch = () => {
if (searchString.length > 0) {
<Home searchResults={searchString} />;
}
};
return (
<div>
<input
placeholder='Search here'
value={searchString}
onChange={(e) => onChangHandler(e)}
/>
<button onClick={activeSearch}>Search</button>
</header>
</div>
);
}
I searched for previous stackoverflow questions and reactjs.org but found no answer.
Home.jsx
import React, { useEffect, useState } from 'react';
function Home({ searchResults }) {
const [itemSearchResults, setResults] = useState([]);
const [previousValue, setPreviousValue] = useState();
// What function will re-render when the props are first defined or changed ?
useEffect(() => { // Doesn't work
setResults(searchResults);
}, [searchResults]);
return (
<div>
<h3>Home</h3>
<h1>{itemSearchResults}</h1>
</div>
);
}
export default Home;
App.js
function App() {
return (
<div className='App'>
<Header />
<Home />
<Footer />
</div>
);
}
I'm sending the input string only to check if the props will change at the child component ("Home").
Any experts here know what's the problem?
Why it doesn't work?
It's because the Home component is never used, even if it's included in the following snippet:
const activeSearch = () => {
if (searchString.length > 0) {
<Home searchResults={searchString} />;
}
};
The activeSearch function has a couple problems:
it is used as an event handler though it uses JSX (outside the render phase)
it doesn't return the JSX (would still fail inside the render phase)
JSX should only be used within the render phase of React's lifecycle. Any event handler exists outside this phase, so any JSX it might use won't end up in the final tree.
The data dictates what to render
That said, the solution is to use the state in order to know what to render during the render phase.
function Header() {
const [searchString, setString] = useState('');
const [showResults, setShowResults] = useState(false);
const onChangHandler = (e) => {
// to avoid fetching results for every character change.
setShowResults(false);
setString(e.target.value);
};
const activeSearch = () => setShowResults(searchString.length > 0);
return (
<div>
<input
value={searchString}
onChange={(e) => onChangHandler(e)}
/>
<button onClick={activeSearch}>Search</button>
{showResults && <Home query={searchString} />}
</div>
);
}
useEffect to trigger effects based on changing props
And then, the Home component can trigger a new search request to some service through useEffect.
function Home({ query }) {
const [results, setResults] = useState(null);
useEffect(() => {
let discardResult = false;
fetchResults(query).then((response) => !discardResult && setResults(response));
// This returned function will run before the query changes and on unmount.
return () => {
// Prevents a race-condition where the results from a previous slow
// request could override the loading state or the latest results from
// a faster request.
discardResult = true;
// Reset the results state whenever the query changes.
setResults(null);
}
}, [query]);
return results ? (
<ul>{results.map((result) => <li>{result}</li>))}</ul>
) : `Loading...`;
}
It's true that it's not optimal to sync some state with props through useEffect like the article highlights:
useEffect(() => {
setInternalState(externalState);
}, [externalState]);
...but in our case, we're not syncing state, we're literally triggering an effect (fetching results), the very reason why useEffect even exists.
const { useState, useEffect } = React;
const FAKE_DELAY = 5; // seconds
function Home({ query }) {
const [results, setResults] = useState(null);
useEffect(() => {
let queryChanged = false;
console.log('Fetch search results for', query);
setTimeout(() => {
if (queryChanged) {
console.log('Query changed since last fetch, results discarded for', query);
return;
}
setResults(['example', 'result', 'for', query])
}, FAKE_DELAY * 1000);
return () => {
// Prevent race-condition
queryChanged = true;
setResults(null);
};
}, [query]);
return (
<div>
{results ? (
<ul>
{results.map((result) => (
<li>{result}</li>
))}
</ul>
) : `Loading... (${FAKE_DELAY} seconds)`}
</div>
);
}
function Header() {
const [searchString, setString] = useState('');
const [showResults, setShowResults] = useState(false);
const onChangHandler = (e) => {
// to avoid fetching results for every character change.
setShowResults(false);
setString(e.target.value);
};
const activeSearch = () => setShowResults(searchString.length > 0);
return (
<div>
<input
placeholder='Search here'
value={searchString}
onChange={(e) => onChangHandler(e)}
/>
<button onClick={activeSearch}>Search</button>
{showResults && <Home query={searchString} />}
</div>
);
}
ReactDOM.render(<Header />, document.querySelector("#app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<div id="app"></div>
Better solution: Uncontrolled inputs
Another technique in your case would be to use an uncontrolled <input> by using a ref and only updating the search string on click of the button instead of on change of the input value.
function Header() {
const [searchString, setString] = useState('');
const inputRef = useRef();
const activeSearch = () => {
setString(inputRef.current.value);
}
return (
<div>
<input ref={inputRef} />
<button onClick={activeSearch}>Search</button>
{searchString.length > 0 && <Home query={searchString} />}
</div>
);
}
const { useState, useEffect, useRef } = React;
const FAKE_DELAY = 5; // seconds
function Home({ query }) {
const [results, setResults] = useState(null);
useEffect(() => {
let queryChanged = false;
console.log('Fetch search results for', query);
setTimeout(() => {
if (queryChanged) {
console.log('Query changed since last fetch, results discarded for', query);
return;
}
setResults(['example', 'result', 'for', query])
}, FAKE_DELAY * 1000);
return () => {
// Prevent race-condition
queryChanged = true;
setResults(null);
};
}, [query]);
return (
<div>
{results ? (
<ul>
{results.map((result) => (
<li>{result}</li>
))}
</ul>
) : `Loading... (${FAKE_DELAY} seconds)`}
</div>
);
}
function Header() {
const [searchString, setString] = useState('');
const inputRef = useRef();
const activeSearch = () => {
setString(inputRef.current.value);
}
return (
<div>
<input
placeholder='Search here'
ref={inputRef}
/>
<button onClick={activeSearch}>Search</button>
{searchString.length > 0 && <Home query={searchString} />}
</div>
);
}
ReactDOM.render(<Header />, document.querySelector("#app"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<div id="app"></div>
Passing the state around
[The following line] brings the Home component inside the Header component, which makes duplicate
{searchString.length > 0 && <Home query={searchString} />}
In order to make the Header component reusable, the quickest way would be to lift the state up.
// No state needed in this component, we now receive
// a callback function instead.
function Header({ onSubmit }) {
const inputRef = useRef();
const activeSearch = () => {
// Uses the callback function instead of a state setter.
onSubmit(inputRef.current.value);
}
return (
<div>
<input ref={inputRef} />
<button onClick={activeSearch}>Search</button>
</div>
);
}
function App() {
// State lifted up to the parent (App) component.
const [searchString, setString] = useState('');
return (
<div className='App'>
<Header onSubmit={setString} />
{searchString.length > 0 && <Home query={searchString} />}
<Footer />
</div>
);
}
If that solution is still too limited, there are other ways to pass data around which would be off-topic to bring them all up in this answer, so I'll link some more information instead:
Thinking in React
What's the right way to pass form element state to sibling/parent elements?
Passing data to sibling components with react hooks?
Application State Management with React
How can I update the parent's state in React?
Top 5 React state management libraries in late 2020 (Redux, Mobx, Recoil, Akita, Hookstate)
if your props are passed as searchResults, then change the props to,
function Home({ searchResults}) {...}
and use
useEffect(() => { // code, function },[searchResults]) ).

Heading text not getting on button click in reactJs?

I have button & i'm getting button text in console.log. But now i want to this button text in <h2> tag but this is not working whats wrong with it?
My Code:-
const Forms = () => {
const handleClick =( event) => {
const theText = event.target.textContent;
console.log(theText);
};
return (
<div>
<h2>{theText}</h2>
<button onClick={handleClick}>click</button>
</div>
);
};
ReactDOM.render(<Forms />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
Comment this line <h2>{theText}</h2> to get button text in console.log.
Thanks for your efforts!
The variable theText is scoped to the {} block. Since you want to change something in the view using this variable, it should be in your state.
Try using the useState hook. Now everytime your theText changes there is a rerender and the view gets updated.
const Forms = () => {
const [theText,setTheText] = useState("default");
const handleClick =( event) => {
const theText = event.target.textContent;
setTheText(theText);
};
return (
<div>
<h2>{theText}</h2>
<button onClick={handleClick}>click</button>
</div>
);
};
Notice how I have the same variable name theText but there will be no conflicts. This is a major advantage of using const/let they are block scoped.
import {useState} from 'react'
const Forms = () => {
const [text,setText] = useState();
const handleClick =( event) => {
const theText = event.target.textContent;
setText(theText);
console.log(theText);
};
return (
<div>
<h2>{text}</h2>
<button onClick={handleClick}>click</button>
</div>
);
};
This can be achieved with state.
This is how you can save to state and access
const Form = () => {
const [selectedText, setSelectedText] = React.useState("");
const handleClick = (event) => {
const theText = event.target.textContent;
setSelectedText(theText);
};
return (
<div>
<h2>{selectedText}</h2>
<button onClick={handleClick}>click</button>
</div>
);
}
The basic workflow of React is that a component 1) initially renders 2) the state of the component changes, 3) the component re-renders based on that new state.
So you need to introduce state to your component so that when you click the button the state of the text changes.
const { useState } = React;
function Forms() {
// Initially set the state to an empty string
const [text, setText] = useState('');
function handleClick(event) {
// Now grab the textContent from the button
// and set the state. The component will immidiately
// re-render, and the H2 will be set to `text`
const { textContent } = event.target;
setText(textContent);
};
return (
<div>
<h2>{text}</h2>
<button onClick={handleClick}>click</button>
</div>
);
};
// Render it
ReactDOM.render(
<Forms />,
document.getElementById('react')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>

React | Call a function outside a component

I want to call the increment function of the CounterText from the CounterButton.
Note: This is not the particular case I'm trying to solve, but it's very similar. I have a stipulation: I can’t change the order of the items, they have to stay at one level, they have to be separate.
I have one root component looks like this:
function App() {
return (
<div>
<CounterText/>
<CounterButton/>
</div>
);
}
CounterText looks like this:
const count = useRef(0);
function CounterText() {
function Increment() {
count.current++;
}
return(
<h2>{count.current}</h2>
);
}
CounterButton looks like this:
function CounterButton() {
return (
<button>Increment</button>
);
}
PS.: Sorry for my English.
The usual solution is to lift state up into the parent component, and pass it down to child components as props or context, along with a function they can use to update the state if needed, something like this:
function App() {
const [counter, setCounter] = useState(0);
const increment = useCallback(
() => setCounter(c => c + 1),
[]
);
return (
<div>
<CounterText counter={counter} />
<CounterButton increment={increment} />
</div>
);
}
function CounterText({counter}) {
return(
<h2>{counter}</h2>
);
}
function CounterButton({increment}) {
return (
<button onClick={increment}>Increment</button>
);
}
Live Example:
const {useState, useCallback} = React;
function App() {
const [counter, setCounter] = useState(0);
const increment = useCallback(
() => setCounter(c => c + 1),
[]
);
return (
<div>
<CounterText counter={counter} />
<CounterButton increment={increment} />
</div>
);
}
function CounterText({counter}) {
return(
<h2>{counter}</h2>
);
}
function CounterButton({increment}) {
return (
<button onClick={increment}>Increment</button>
);
}
ReactDOM.render(<App/>, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
The useCallback call there isn't required, but it makes the increment function stable, which prevents unnecessarily re-rendering CounterButton when counter changes.

Should renderItem of FlatList be wrapped with react useCallback hook?

const Component = React.memo(props => {
const { url } = props;
const keyExtractor = useCallback(item => item.id, []);
const handleClick = useCallback(() => {
Linking.openURL(url);
}, [url]);
const renderItem = useCallback(({ item }) => {
return (
<TouchableOpacity onPress={handleClick}>
<Text>Test</Text>
</TouchableOpacity>
);
}, [handleClick]);
return (
<FlatList
data={data}
keyExtractor={keyExtractor}
renderItem={renderItem}
/>
);
});
Just like the code above.
If the renderItem function is wrapped with a useCallback hook. it must check if the handleClick function reference is changed because the handleClick function may be changed if url is different. and it looks a little strange.
I wonder if there is a significant difference in the performance of using useCallback to wrap renderItem.
What is the best practice of using hooks for renderItem functions? Thanks
Because you already use React.memo the Component will not be executed unless url changes, if url changes the useCallback will re create the functions anyway so you can leave them out provided that is the only prop that can change.
Here is some code demonstrating this, you can re render App as much as you want but it won't re render other components unless you change url.
const SubComponent = ({ onClick }) => {
console.log('sub component render');
return <button onClick={onClick}>log url</button>;
};
const PureComponent = React.memo(function PureComponent({
url,
}) {
console.log('pure component render', url);
//even if I do React.useCallback(fn,[url]) that would mean
// it creates onClick when url changes but it would already
// only create onClick when url changes because memo will
// memoize the component result and not execute PureComponent
// unless the url changes
const onClick = () => console.log('url is', url);
return <SubComponent onClick={onClick} />;
});
const App = () => {
const [, reRenderApp] = React.useState({});
const [url, setUrl] = React.useState(
new Date().toUTCString()
);
console.log('rendering App');
return (
<div>
<button onClick={() => reRenderApp({})}>
re render app
</button>
<button
onClick={() => setUrl(new Date().toUTCString())}
>
set url
</button>
<PureComponent url={url} />
</div>
);
};
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

set state is not updating state

I am trying to use the state hook in my react app.
But setTodos below seems not updating the todos
link to my work: https://kutt.it/oE2jPJ
link to github: https://github.com/who-know-cg/Todo-react
import React, { useState } from "react";
import Main from "./component/Main";
const Application = () => {
const [todos, setTodos] = useState([]);
// add todo to state(todos)
const addTodos = message => {
const newTodos = todos.concat(message);
setTodos(newTodos);
};
return (
<>
<Main
addTodos={message => addTodos(message)}
/>
</>
);
};
export default Application;
And in my main.js
const Main = props => {
const input = createRef();
return (
<>
<input type="text" ref={input} />
<button
onClick={() => {
props.addTodo(input.current.value);
input.current.value = "";
}}
>
Add message to state
</button>
</>
);
};
I expect that every time I press the button, The setTodos() and getTodos() will be executed, and the message will be added to the todos array.
But it turns out the state is not changed. (still, stay in the default blank array)
If you want to update state of the parent component, you should pass down the function from the parent to child component.
Here is very simple example, how to update state with hook from child (Main) component.
With the help of a button from child component you update state of the parent (Application) component.
const Application = () => {
const [todos, setTodos] = useState([]);
const addTodo = message => {
let todosUpdated = [...todos, message];
setTodos(todosUpdated);
};
return (
<>
<Main addTodo={addTodo} />
<pre>{JSON.stringify(todos, null, 2)}</pre>
</>
);
};
const Main = props => {
const input = createRef();
return (
<>
<input type="text" ref={input} />
<button
onClick={() => {
props.addTodo(input.current.value);
input.current.value = "";
}}
>
Add message to state
</button>
</>
);
};
Demo is here: https://codesandbox.io/s/silent-cache-9y7dl
In Application.jsx :
You can pass just a reference to addTodos here. The name on the left can be whatever you want.
<Main addTodos={addTodos} />
In Main.jsx :
Since getTodo returns a Promise, whatever that promise resolves to will be your expected message.
You don't need to pass message as a parameter in Main, just the name of the function.
<Main addTodos={addTodos} />
You are passing addTodos as prop.
<Main
addTodos={message => addTodos(message)}
/>
However, in child component, you are accessing using
props.addTodo(input.current.value);
It should be addTodos.
props.addTodos(input.current.value);

Categories

Resources