Pass setState as props or create a handle - reactjs

Can I pass the setPlaying as props? Or I have to do something like this example code?
Components Two and Three can be in their own file.
export const myComponent = () => {
const [playing, setPlaying] = useState(false);
const handleChange = (show) => {
setPlaying(show);
};
return (
<>
<One />
{!playing ? (
<Two handleChange={handleChange} />
) : (
<Three handleChange={handleChange} />
)}
</>
)
};
const Two = ({ handleChange }) => {
return (
<Container>
<Button onClick={{(e) => handleChange(true)}}>Click to Show Component Three</Button>
</Container>
);
};
const Three = ({ handleChange }) => {
return (
<Container>
<Button onClick={{(e) => handleChange(false)}}>Click to Show Component Two</Button>
</Container>
);
};

Of course. Wrapping setPlaying with handleChange is advantageous if you wish to do any further processing in myComponent. And the conditional rendering will prevent needless re-renders.

Yes, you can do that. Also, it is a good practice that you did with the handler.
ref: Passing setState to child component using React hooks

Related

My event onClick in my map does not work. Very strange behavior

My Onclick on bestmovies map does not work. If I place it on a H1, for example, works. onClick={handleClickMovie}
// imports....
const Movies = () => {
const [popularMovies, setPopularMovies] = useState([])
const [bestMovies, setBestMovies] = useState([])
const [showPopUp, setShowPopUp] = useState(false)
const handleClickMovie = () => {
setShowPopUp(console.log('Clicked'))
}
useEffect(() => {
async function getMovies() {
const responsePopularMovies = await getPopularMovies()
setPopularMovies(responsePopularMovies.results)
const responseBestMovies = await getBestMovies()
setBestMovies(responseBestMovies.results)
}
getMovies()
}, [])
return (
<div>
<Wrapper>
{showPopUp ? <MoviePopUp /> : null}
<h1>Filmes Populares</h1>
<Content>
{popularMovies.map(item => (
<MovieItem movie={item} />
))}
</Content>
<h1>Filmes Bem Avaliados</h1>
<Content>
{bestMovies.map(item => (
<MovieItem movie={item} onClick={handleClickMovie} />
))}
</Content>
</Wrapper>
</div>
)
}
export default Movies
MovieItem.js
import React from 'react'
import { Cover, Score, Title } from './MovieItem.styles'
const MovieItems = ({ movie }) => {
return (
<Cover key={movie.id}>
<img
src={`https://image.tmdb.org/t/p/original${movie.poster_path}`}
alt="capas"
/>
<Score>{movie.vote_average}</Score>
<Title>{movie.title}</Title>
</Cover>
)
}
export default MovieItems
try wrapping in a div
<Content>
{bestMovies.map(item => (
<div onClick={handleClickMovie}>
<MovieItem movie={item} onClick={handleClickMovie} />
</div>
))}
</Content>
As #anthony_718 answered, you are calling onClick on a JSX component. JSX components aren't in the DOM and don't have click events (although they can render HTML elements if they contain them).
If you want, you can also pass the props all the way up to an actual html element the <Cover> renders.
#anthony_718's answer is correct.
The reason it didn't work it's because <MovieItem> doesn't have onClick in his props.
However, to facilitate reusability, you can modify your component like so:
const MovieItems = ({ movie, onClick }) => {
return (
<div onClick={onClick}>
<Cover key={movie.id}>
// ... rest of your stuff
</Cover>
</div>
)
}
export default MovieItems
It's essentially the same solution, but by placing <div onClick> within the component definition, you make it more reusable than the other option.
check this
bestMovies.map((item, i) => { return( <MovieItem movie={item} onClick={handleClickMovie} /> )})

How can I force update React.memo child component?

My main functional component performs a huge amount of useQueries and useMutations on the child component hence I have set it as React.memo so as to not cause re-rendering on each update. Basically, when new products are selected I still see the old products because of memo.
mainFunction.js
const [active, setActive] = useState(false);
const handleToggle = () => setActive(false);
const handleSelection = (resources) => {
const idsFromResources = resources.selection.map((product) => product.variants.map(variant => variant.id));
store.set('bulk-ids', idsFromResources); //loal storage js-store library
handleToggle
};
const emptyState = !store.get('bulk-ids'); // Checks local storage using js-store library
return (
<Page>
<TitleBar
title="Products"
primaryAction={{
content: 'Select products',
onAction: () => {
setActive(!active)
}
}}
/>
<ResourcePicker
resourceType="Product"
showVariants={true}
open={active}
onSelection={(resources) => handleSelection(resources)}
onCancel={handleToggle}
/>
<Button >Add Discount to Products</Button> //Apollo useMutation
{emptyState ? (
<Layout>
Select products to continue
</Layout>
) : (
<ChildComponent />
)}
</Page>
);
ChildComponent.js
class ChildComponent extends React {
return(
store.get(bulk-ids).map((product)=>{
<Query query={GET_VARIANTS} variables={{ id: variant }}>
{({ data, extensions, loading, error }) => {
<Layout>
// Query result UI
<Layout>
}}
</Query>
})
)
}
export deafult React.memo(ChildComponent);
React.memo() is useful when your component always renders the same way with no changes. In your case you need to re-render <ChildComponent> every time bulk-id changes. So you should use useMemo() hook.
function parentComponent() {
... rest of code
const bulkIds = store.get('bulk-ids');
const childComponentMemo = useMemo(() => <ChildComponent ids={bulkIds}/>, [bulkIds]);
return <Page>
... rest of render
{bulkIds ?
childComponentMemo
:(
<Layout>
Select products to continue
</Layout>
)}
</Page>
}
useMemo() returns the same value until buldIds has not changed. More details about useMemo() you can find here.

React hooks set functions not working from child component

I've a isView and setIsView in the ParentComponent and passing them down to the ChildComponent as props and trying to do show/hide conditional rendering but setIsView seems not to be working and isView value in the props remains same.
const ParentComponent = props => {
const [isView, setIsView] = useState(true);
const onChange = selectedOption => {
selectedOption === 'Report'
? setIsView(true)
: setIsView(false);
};
return (
<div>
<ChildComponent
isView={isView}
onChange={onChange}
/>
</div>
);
};
const ChildComponent = props => {
const {isView, onChange} = props;
return (
<div>
<RadioButton
onChange={() => onChange('Not-Report')}
/>
<If condition={isView}>
<ChildComponent2>
</If>
</div>
);
};
Edit: changed onChange={onChange('Not-Report')} to onChange={() => onChange('Not-Report')} as suggested by some. still not working.
Try feeding the onChange method as a callback function instead.
const ChildComponent = props => {
const {isView, onChange} = props;
return (
<div>
<RadioButton
onChange={() => onChange('Not-Report')} // <- Here
/>
<If condition={isView}>
<ChildComponent2>
</If>
</div>
);
};
Update child component onChange function as follows:
<RadioButton
onChange={() => onChange('Not-Report')}
/>
If you pass onChange only, it will be regarded with the function that has event as a parameter rather than the prop's onChange function.
To make it work like your way,
const ChildComponent = ({isView, onChange}) => {
const onRadioChange = () => {
onChange('Not-Report')}
}
return (
<div>
<RadioButton
onChange={onRadioChange}
/>
<If condition={isView}>
<ChildComponent2>
</If>
</div>
);
};
i am wrote this code
ParentComponent
const ParentComponent = (props) => {
const [isView, setIsView] = useState(true);
const onChange = (selectedOption) => {
console.log("selectedOption = ", selectedOption);
selectedOption === "Report" ? setIsView(true) : setIsView(false);
};
return (
<div>
<ChildComponent isView={isView} onChange={onChange} />
</div>
);
};
ChildComponent
const ChildComponent = (props) => {
const { isView, onChange } = props;
return (
<div>
<input
type="radio"
checked={isView}
onClick={() => {
onChange("Not-Report");
}}
/>
isView = {isView ? "true" : "false"}
</div>
);
};
i change onChange to onClick and use checked
Work Demo

React Hook : Send data from child to parent component

I'm looking for the easiest solution to pass data from a child component to his parent.
I've heard about using Context, pass trough properties or update props, but I don't know which one is the best solution.
I'm building an admin interface, with a PageComponent that contains a ChildComponent with a table where I can select multiple line. I want to send to my parent PageComponent the number of line I've selected in my ChildComponent.
Something like that :
PageComponent :
<div className="App">
<EnhancedTable />
<h2>count 0</h2>
(count should be updated from child)
</div>
ChildComponent :
const EnhancedTable = () => {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Click me {count}
</button>
)
};
I'm sure it's a pretty simple thing to do, I don't want to use redux for that.
A common technique for these situations is to lift the state up to the first common ancestor of all the components that needs to use the state (i.e. the PageComponent in this case) and pass down the state and state-altering functions to the child components as props.
Example
const { useState } = React;
function PageComponent() {
const [count, setCount] = useState(0);
const increment = () => {
setCount(count + 1)
}
return (
<div className="App">
<ChildComponent onClick={increment} count={count} />
<h2>count {count}</h2>
(count should be updated from child)
</div>
);
}
const ChildComponent = ({ onClick, count }) => {
return (
<button onClick={onClick}>
Click me {count}
</button>
)
};
ReactDOM.render(<PageComponent />, document.getElementById("root"));
<script src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root"></div>
You can create a method in your parent component, pass it to child component and call it from props every time child's state changes, keeping the state in child component.
const EnhancedTable = ({ parentCallback }) => {
const [count, setCount] = useState(0);
return (
<button onClick={() => {
const newValue = count + 1;
setCount(newValue);
parentCallback(newValue);
}}>
Click me {count}
</button>
)
};
class PageComponent extends React.Component {
callback = (count) => {
// do something with value in parent component, like save to state
}
render() {
return (
<div className="App">
<EnhancedTable parentCallback={this.callback} />
<h2>count 0</h2>
(count should be updated from child)
</div>
)
}
}
To make things super simple you can actually share state setters to children and now they have the access to set the state of its parent.
example:
Assume there are 4 components as below,
function App() {
return (
<div className="App">
<GrandParent />
</div>
);
}
const GrandParent = () => {
const [name, setName] = useState("i'm Grand Parent");
return (
<>
<div>{name}</div>
<Parent setName={setName} />
</>
);
};
const Parent = params => {
return (
<>
<button onClick={() => params.setName("i'm from Parent")}>
from Parent
</button>
<Child setName={params.setName} />
</>
);
};
const Child = params => {
return (
<>
<button onClick={() => params.setName("i'm from Child")}>
from Child
</button>
</>
);
};
so grandparent component has the actual state and by sharing the setter method (setName) to parent and child, they get the access to change the state of the grandparent.
you can find the working code in below sandbox,
https://codesandbox.io/embed/async-fire-kl197
IF we Have Parent Class Component and Child function component this is how we going to access child component useStates hooks value :--
class parent extends Component() {
constructor(props){
super(props)
this.ChildComponentRef = React.createRef()
}
render(){
console.log(' check child stateValue: ',
this.ChildComponentRef.current.info);
return (<> <ChildComponent ref={this.ChildComponentRef} /> </>)
}
}
Child Component we would create using
React.forwardRef((props, ref) => (<></>))
. and
useImperativeHandle(ref, createHandle, [deps])
to customizes the instance value that is exposed to parent components
const childComponent = React.forwardRef((props, ref) => {
const [info, setInfo] = useState("")
useEffect(() => {
axios.get("someUrl").then((data)=>setInfo(data))
})
useImperativeHandle(ref, () => {
return {
info: info
}
})
return (<> <h2> Child Component <h2> </>)
})
I had to do this in type script. The object-oriented aspect would need the dev to add this callback method as a field in the interface after inheriting from parent and the type of this prop would be Function. I found this cool!
Here's an another example of how we can pass state directly to the parent.
I modified a component example from react-select library which is a CreatableSelect component. The component was originally developed as class based component, I turned it into a functional component and changed state manipulation algorithm.
import React, {KeyboardEventHandler} from 'react';
import CreatableSelect from 'react-select/creatable';
import { ActionMeta, OnChangeValue } from 'react-select';
const MultiSelectTextInput = (props) => {
const components = {
DropdownIndicator: null,
};
interface Option {
readonly label: string;
readonly value: string;
}
const createOption = (label: string) => ({
label,
value: label,
});
const handleChange = (value: OnChangeValue<Option, true>, actionMeta: ActionMeta<Option>) => {
console.group('Value Changed');
console.log(value);
console.log(`action: ${actionMeta.action}`);
console.groupEnd();
props.setValue(value);
};
const handleInputChange = (inputValue: string) => {
props.setInputValue(inputValue);
};
const handleKeyDown: KeyboardEventHandler<HTMLDivElement> = (event) => {
if (!props.inputValue) return;
switch (event.key) {
case 'Enter':
case 'Tab':
console.group('Value Added');
console.log(props.value);
console.groupEnd();
props.setInputValue('');
props.setValue([...props.value, createOption(props.inputValue)])
event.preventDefault();
}
};
return (
<CreatableSelect
id={props.id}
instanceId={props.id}
className="w-100"
components={components}
inputValue={props.inputValue}
isClearable
isMulti
menuIsOpen={false}
onChange={handleChange}
onInputChange={handleInputChange}
onKeyDown={handleKeyDown}
placeholder="Type something and press enter..."
value={props.value}
/>
);
};
export default MultiSelectTextInput;
I call it from the pages of my next js project like this
import MultiSelectTextInput from "../components/Form/MultiSelect/MultiSelectTextInput";
const NcciLite = () => {
const [value, setValue] = useState<any>([]);
const [inputValue, setInputValue] = useState<any>('');
return (
<React.Fragment>
....
<div className="d-inline-flex col-md-9">
<MultiSelectTextInput
id="codes"
value={value}
setValue={setValue}
inputValue={inputValue}
setInputValue={setInputValue}
/>
</div>
...
</React.Fragment>
);
};
As seen, the component modifies the page's (parent page's) state in which it is called.
I've had to deal with a similar issue, and found another approach, using an object to reference the states between different functions, and in the same file.
import React, { useState } from "react";
let myState = {};
const GrandParent = () => {
const [name, setName] = useState("i'm Grand Parent");
myState.name=name;
myState.setName=setName;
return (
<>
<div>{name}</div>
<Parent />
</>
);
};
export default GrandParent;
const Parent = () => {
return (
<>
<button onClick={() => myState.setName("i'm from Parent")}>
from Parent
</button>
<Child />
</>
);
};
const Child = () => {
return (
<>
<button onClick={() => myState.setName("i'm from Child")}>
from Child
</button>
</>
);
};

Is having a lot of render methods within a components bad practice?

I had written a component and there are some other instances of small components within that components. Is it a bad practice to use a renderxxx for each instance? thank you
It depends. If you are using some kind of logic for rendering your components, then you should go with dedicated renderX methods. But if all you are doing is to return the components, you should simply describe it with JSX.
Without logic
const MyComponent = ({ options }) => (
<div>
<SelectList options={options} />
</div>
);
With logic
const MyComponent = ({ options }) => {
const renderOptions = () => {
if (options.length < 5) {
return <RadioButtonGroup options={options} />;
}
return <SelectList options={options} />;
};
return (
<div>
{renderOptions()}
</div>
);
};
Another approch would be wrapping another component around every renderX method.
const OptionsRenderer = ({ options }) => {
if (options.length < 5) {
return <RadioButtonGroup options={options} />;
}
return <SelectList options={options} />;
};
const MyComponent = ({ options }) => (
<div>
<OptionsRenderer options={options} />
</div>
);

Resources