How can I access a functional components state outside the component? - React - javascript

For example, if I have the functional component Dropdown that receives data from an api to populate a dropdown selection, and uses the following hook to update the state of the value selected
const [value, setValue] = React.useState();
How might I then access/read the value (in this case an array of numbers) outside of the Dropdown component?
For better context I will include firstly where the Dropdown component is used:
import React from 'react';
import Dropdown from '../Components/Dropdown.js'
import GraphTypeDropdown from '../Components/GraphTypeDropdown.js'
function CreateGraph() {
//function receiving and returning data (array of data points and their heading) from api
async function getData() {
const response = await fetch('/dropdowndata');
const receivedData = await response.json();
return receivedData;
}
//assigning data to myData variable
const myData = getData();
//myData is passed to Dropdown components as a prop to allow selection of data columns
//GraphTypeDropdown allows selection of the graph "type"
return (
<div>
<Dropdown myData={myData} />
<Dropdown myData={myData} />
<Dropdown myData={myData} />
<Dropdown myData={myData} />
<Dropdown myData={myData} />
<GraphTypeDropdown />
</div>
)
}
export default CreateGraph;
And also the full code of the dropdown functional component
import React from 'react';
import './Dropdown.css';
function Dropdown(props) {
const [columns, setColumns] = React.useState([]);
const [value, setValue] = React.useState();
//maps the prop myData to be the label and value of the dropdown list
React.useEffect(() => {
async function getColumns() {
props.myData.then(function(result) {
setColumns(result.map(({ heading, values }) => ({ label: heading, value: values })));
});
}
getColumns();
}, [props.myData]);
//creates a dropdown where value (array of data points) can be selected with label (column headings)
//value selected is then saved in the state of the dropdown
return (
<select className='DropDown'
value={value}
onChange={(e) => setValue(e.currentTarget.value)}
>
{columns.map(({ label, heading, value }) => (
<option className='DropDown'
key={heading}
value={value}
>
{label}
</option>
))}
</select>
);
}
export default Dropdown;
How will I be able to use/access the arrays assigned to value in the five Dropdown components?

The answer to your question "access the value outside of the component?" would be:
You generally can not access the state of a functional component from outside that component.
What you can do is:
pass props "down" (to children)
props can be callback functions, this way you could pass state "up"
use a framework with global state, like redux.
Now it depends a lot on your use case what the best solution is.
An example for passing the state "up" would be:
function Dropdown(props) {
// ...
React.useEffect(() => {
props.onLoaded( columns );
}, [ columns ]);
// ...
}
function CreateGraph() {
// ...
<Dropdown
myData={ myData }
onLoaded={
(columns) => {
console.log('columns:', columns);
}
}
/>
// ...
}

Related

How do I pass a value from parent to child, then edit it and pass it back in React?

Using hooks.
The parent component passes a value to the child component, which the child component displays. But I'd like that component editable and once the user clicks save, its new value should be passed back to parent component and is then used to update.
parent component:
const [value, setValue] = useState("");
// value is set by some function
<Child
value={value}
/>
child component:
<h2 contentEditable=true >props.value</h2>
// somehow save the value, even to a different variable, and pass it back to parent component to setValue
I have tried setting in my child component file something like
const childValue=props.value
or
const [childValue, setChildValue] = useState(props.value)
and I tried console.log on those values but they're all empty, and if I passed them to h2 in the child component, nothing displays.
--- EDIT
I have tried passing the function to set value to parent but I'm struggling getting the changed value.
For child component I'd like to save either onChange or on clicking a button, but I'm missing a way to capture the new value.
parent component has a saveValue(newValue) function which I pass to child
in child component
const {value, saveValue} = props;
return
<h2
onChange={() => saveValue(e.target.value)}
contentEditable=true> {value} </h2>
I have saveValue in parent component and tried changing the function to print out the argument in the console but nothing gets logged.
I also tried saving the changes with a button, I have no method of capturing the actual changes made to h2 though, as my code looks something like this:
const {value, setValue} = props;
<h2 contentEditable=true>{value}</h2>
<button onClick={()=>setValue(value)}>Save</button>
This just sets the value to the old value and not the edited one
Pass setValue to the Child component as a prop, and call it inside. For example:
const ChildComponent = ({value, setValue}) => {
return (
<>
<button onClick={() => setValue('my new value')}>
change value
</button>
<span>{value}</span>
</>
)
}
const ParentComponent = () => {
const [value, setValue] = useState("");
return <ChildComponent value={value} setValue={setValue}/>
}
You can approach this problem in 2 ways. Both the approach are basically the same thing, as you are ultimately passing a function to handle the state change.
Approach 1: Create a handler function (say handleValueChange) to manage the state change using setValue in the parent component. Then you can pass this handler function to the child component.
Parent component:
const Parent = () => {
const [value, setValue] = useState("");
function handleChange() {
// Some logic to change the "value" state
setValue("A new value");
}
return <Child value={value} handlerFunction={handleChange} />
}
Child component:
const Child = (props) => {
const { value, handlerFunction } = props;
// Utilize the props as per your needs
return (
<>
<h2 contentEditable>Value: {value}</h2>
<button onClick={handlerFunction}>Change state</button>
</>
);
};
Approach 2: Pass on the setValue function to the child component as props.
Parent component:
const Parent = () => {
const [value, setValue] = useState("");
return <Child value={value} setValue={setValue} />
}
Child component:
const Child = (props) => {
const { value, setValue } = props;
// Utilize the props to change the state, as per your needs
function handleChange() {
setValue("A new value");
}
return (
<>
<h2 contentEditable>Value: {value}</h2>
<button onClick={handleChange}>Change state</button>
</>
);
};

Get value from appended child component in React

I have a main react component which appends child components say <Child /> on button click
My Child component is of the format
<form>
<input .... />
<button type="submit">Submit</button>
<form>
Now I need to get the value of these input elements from every Child component which is appended but am not able to figure out a way to do so.
I won't know how many Child components would be added as the user can click the button any number of times to append yet another <Child /> so I can't have a fixed number of variables exported from the Child component to a variable in parent component.
Any suggestions would be highly appreciated.
Edit:
Code to append the child:
The submit function:
const [val, setVal] = useState([]);
const submit = () => {
setVal([...val, <Child />]);
}
Append button:
<Button onClick={submit}>Add Child</Button>
To render:
{val}
Since val is an array, it prints all the components inside it
I took some liberties because I don't know what would be the expected output of all the forms.
What I basically did is to create a map-like structure where I will map each child form with its value/s (depending on your needs could be modified) and I passed a submit function to the child components in order to store the values on the parent.
In that way on a child submit I will be able to get the values passed from the child as well as its index.
Parent component
const Parent = () => {
const [val, setVal] = useState([]);
// Map like structure to store the values (is just an example, you can use whatever you want)
const [mapOfValues, setMapOfValues] = useState({});
// Pass submit function as prop
const submit = () => {
setVal([...val, <Child submit={onChildSubmit} />]);
};
// Submit function that stores the value for the child
const onChildSubmit = (childIndex, value) => {
setMapOfValues({
...mapOfValues,
[childIndex]: value
});
};
return (
<div className="App">
{val.map((value, index) =>
// Key to prevent React warning and childIndex to map the value to the child form
React.cloneElement(value, { key: index, childIndex: index })
)}
<button onClick={submit}>Add</button>
</div>
);
}
Child component
const Child = (props) => {
const [value, setValue] = useState("");
const submitForm = (e) => {
e.preventDefault();
props.submit(props.childIndex, value);
};
return (
<form onSubmit={submitForm}>
<input onChange={(e) => setValue(e.target.value)} value={value} />
<button>Submit</button>
</form>
);
};
Once you are done, and you want to submit from the parent component, you could use a reduce, map or whatever you need to format the values as you want.
The link to the sandbox is this
If you have any other question let me know.

Send searchParam data from one Component to another component in reactjs

I have one Component which shows a list of data in a dropdown and there is an option to search these data which works as a filter. Here is my code:
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import Popover from '../../Popover';
import Input from '../../Input';
import Icon from '../../Icon';
import IconButton from '../../IconButton';
const DropDownFilter = props => {
const { label, options, onChange, isSearchEnabled } = props;
const [activeOption, setActiveOption] = useState({});
const [filter, setfilter] = useState('');
const searchFilter = event => {
setfilter(event.target.value);
};
const removeFilter = () => {
setfilter('');
};
const lowercasedFilter = filter.toLowerCase();
const filteredData = options.filter(item => {
return Object.keys(item).some(
key => typeof item[key] === 'string' && item[key].toLowerCase().includes(lowercasedFilter)
);
});
const labelText = activeOption.label ? activeOption.label : label;
const handleSelectedOption = option => {
setActiveOption(option);
onChange(option);
};
return (
<div className="filter">
<Popover linkText={labelText} size="small" direction="bottom-left">
{isSearchEnabled && (
<div className="filter__search">
<Input
value={filter}
onChange={searchFilter}
preIcon={
<div role="presentation">
<Icon name="search" />
</div>
}
placeholder="Search"
postIcon={
filter.length > 0 && (
<IconButton
icon={<Icon name="close" />}
size="tiny"
onClick={removeFilter}
standalone={true}
isIconOnly={true}
/>
)
}
/>
</div>
)}
<ul className="filter__options filter__options--scrollbar">
{filteredData.map(option => (
<li
key={option.value}
role="presentation"
className={classNames('filter__options-option', {
'filter__options-option--active': option.value === activeOption.value,
})}
onClick={() => handleSelectedOption(option)}
>
{option.label}
</li>
))}
</ul>
</Popover>
</div>
);
};
DropDownFilter.defaultProps = {
label: 'Filter Menu',
options: [],
isSearchEnabled: true,
};
DropDownFilter.propTypes = {
label: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
options: PropTypes.arrayOf(
PropTypes.shape({
label: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
})
),
onChange: PropTypes.func.isRequired,
isSearchEnabled: PropTypes.bool,
};
export default DropDownFilter;
Here is a gif of it: https://recordit.co/HtalUtuPsj
Now during searching I want to send the value of the search param to another component, the value will be used to search from a DB or any other external data source which is being handled in that new component. Such as, if I am searching for Ratings, this component should search for it in the existing options list it has in its own component, as well as the same time it will search for Ratings in any other external data source or DB. This external network call, search or any other functionality will be processed in the other component. So this component will only send the search param; for example Ratings to the other component in real time.
I can think of an idea like I will get the searchParam in a state and pass the setState value to a new props which will be called through an onSearchParamChange function, this new function will pass the data through a callback and the other component will get the data through calling that props of this component. I am not sure if this is the correct way and also I am not able to implement this thought in the code either. Is there any better way to do it? if so what would be that coding implementation?
If you need to pass to a parent component you should be able to use for example the onChange prop which is passed to your component, like you are doing in the handleSelectedOption function. That function is in fact passing the chosen option to the parent component. If you want to pass to the parent component when the user is typing, then you should call the onChange function also in searchFilter:
const searchFilter = event => {
const option = event.target.value);
setfilter(option);
onChange(option);
};
If you want to pass it to a child component, the you can just pass it as prop:
<ChildComponent filter={ filter } />

How do I add the ability to edit text within a react component?

So here's the user function I'm trying to create:
1.) User double clicks on text
2.) Text turns into input field where user can edit text
3.) User hits enter, and upon submission, text is updated to be edited text.
Basically, it's just an edit function where the user can change certain blocks of text.
So here's my problem - I can turn the text into an input field upon a double click, but how do I get the edited text submitted and rendered?
My parent component, App.js, stores the function to update the App state (updateHandler). The updated information needs to be passed from the Tasks.jsx component, which is where the text input is being handled. I should also point out that some props are being sent to Tasks via TaskList. Code as follows:
App.js
import React, {useState} from 'react';
import Header from './Header'
import Card from './Card'
import cardData from './cardData'
import Dates from './Dates'
import Tasks from './Tasks'
import Footer from './Footer'
import TaskList from './TaskList'
const jobItems= [
{
id:8,
chore: 'wash dishes'
},
{
id:9,
chore: 'do laundry'
},
{
id:10,
chore: 'clean bathroom'
}
]
function App() {
const [listOfTasks, setTasks] = useState(jobItems)
const updateHandler = (task) => {
setTasks(listOfTasks.map(item => {
if(item.id === task.id) {
return {
...item,
chore: task.chore
}
} else {
return task
}
}))
}
const cardComponents = cardData.map(card => {
return <Card key = {card.id} name = {card.name}/>
})
return (
<div>
<Header/>
<Dates/>
<div className = 'card-container'>
{cardComponents}
</div>
<TaskList jobItems = {listOfTasks} setTasks = {setTasks} updateHandler = {updateHandler}/>
<div>
<Footer/>
</div>
</div>
)
}
export default App;
Tasks.jsx
import React, {useState} from 'react'
function Tasks (props) {
const [isEditing, setIsEditing] = useState(false)
return(
<div className = 'tasks-container'>
{
isEditing ?
<form>
<input type = 'text' defaultValue = {props.item.chore}/>
</form>
: <h1 onDoubleClick ={()=> setIsEditing(true)}>{props.item.chore}</h1>
}
</div>
)
}
export default Tasks
TaskList.jsx
import React from 'react'
import Tasks from './Tasks'
function TaskList (props) {
const settingTasks = props.setTasks //might need 'this'
return (
<div>
{
props.jobItems.map(item => {
return <Tasks key = {item.id} item = {item} setTasks = {settingTasks} jobItems ={props.jobItems} updateHandler = {props.updateHandler}/>
})
}
</div>
)
}
export default TaskList
You forgot onChange handler on input element to set item's chore value.
Tasks.jsx must be like below
import React, {useState} from 'react'
function Tasks (props) {
const [isEditing, setIsEditing] = useState(false)
const handleInputChange = (e)=>{
// console.log( e.target.value );
// your awesome stuffs goes here
}
return(
<div className = 'tasks-container'>
{
isEditing ?
<form>
<input type = 'text' onChange={handleInputChange} defaultValue = {props.item.chore}/>
</form>
: <h1 onDoubleClick ={()=> setIsEditing(true)}>{props.item.chore}</h1>
}
</div>
)
}
export default Tasks
So, first of all, I would encourage you not to switch between input fields and divs but rather to use a contenteditable div. Then you just use the onInput attribute to call a setState function, like this:
function Tasks ({item}) {
return(
<div className = 'tasks-container'>
<div contenteditable="true" onInput={e => editTask(item.id, e.currentTarget.textContent)} >
{item.chore}
</div>
</div>
)
}
Then, in the parent component, you can define editTask to be a function that find an item by its id and replaces it with the new content (in a copy of the original tasks array, not the original array itself.
Additionally, you should avoid renaming the variable between components. (listOfTasks -> jobItems). This adds needless overhead, and you'll inevitably get confused at some point which variable is connected to which. Instead say, <MyComponent jobItems={jobItems} > or if you want to allow for greater abstraction <MyComponent items={jobItems} > and then you can reuse the component for listable items other than jobs.
See sandbox for working example:
https://codesandbox.io/s/practical-lewin-sxoys?file=/src/App.js
Your Task component needs a keyPress handler to set isEditing to false when enter is pressed:
const handleKeyPress = (e) => {
if (e.key === "Enter") {
setIsEditing(false);
}
};
Your updateHandler should also be passed to the input's onChange attribute, and instead of defaultValue, use value. It also needs to be reconfigured to take in the onChange event, and you can map tasks with an index to find them in state:
const updateHandler = (e, index) => {
const value = e.target.value;
setTasks(state => [
...state.slice(0, index),
{ ...state[index], chore: value },
...state.slice(index + 1)
]);
};
Finally, TaskList seems like an unnecessary middleman since all the functionality is between App and Task; you can just render the tasks directly into a div with a className of your choosing.
react-edit-text is a package I created which does exactly what you described.
It provides a lightweight editable text component in React.
A live demo is also available.

How to access the parent's component method from its nested component in ReactJS

I have one component CreateOCSServiceForm which is calling other component ListPage from its render method. This ListPage is expecting a component named NodeList from its attribute named ListComponent and again the NodeList is expecting a component NodeTableRow from its attribute named Row like this -
//Getting called from NodeList's attribute
const NodeTableRow: React.FC<NodeTableRowProps> = ({ obj: node, index, key, style, customData }) => {
return (
<TableRow index={index} trKey={key} style={style}>
<td data-key="0" className="pf-c-table__check" role="gridcell">
<input type="checkbox" checked={false}
onChange={(e) => { onSelect(e, e.target.checked, index, node) }}
/>
</td>
<TableData>
---
</TableData>
<TableData>
---
</TableData>
<TableData>
----
</TableData>
<TableData>
----
</TableData>
</TableRow>
);
};
//Getting called from ListPage's attribute
const NodesList = props => <Table customData={props.data} {...props} Row={NodeTableRow} onSelect={onSelect} />;
//main component which is calling ListPage
export const CreateOCSServiceForm: React.FC<CreateOCSServiceFormProps> = (props1) => {
const title = 'Create New OCS Service';
const [error, setError] = React.useState('');
const [inProgress, setProgress] = React.useState(false);
const [rowData, setRowData] = React.useState(null);
const onSelect = (event, isSelected, virtualRowIndex, rowData1) => {
console.log(rowData1, 'customData');
console.log('isSelected', isSelected, 'virtualRowIndex', virtualRowIndex, 'rowData', rowData1);
rowData[virtualRowIndex].selected = true;
setRowData(rowData);
};
return (
<ListPage kind={NodeModel.kind} showTitle={false} ListComponent={NodeList} />}
)
}
The problem is that the NodeList and NodeTableRow components have to accees onSelect method which is defined on the CreateOCSServiceForm component, but how should I expose this onSelect method to them. Don't want to make onSelect method global as I have to use CreateOCSServiceForm` states inside it. I can't modify NodeList and ListPage component, its existing components in my code base and modifying it will result in breaking other pages. We need to pass the functionality of the OnSelect property in NodeList component.
Any guidance will be much appreciated. Thanks in advance
Given the constrains:
NodeList and NodeTableRow components have to accees onSelect method
I can't modify NodeList and ListPage component
I would assume that ListPage already supports either the onSelect prop:
return (
<ListPage onSelect={onSelect} kind={NodeModel.kind} showTitle={false} ListComponent={NodeList} />
)
or some other way to pass the method (but you need to look in the documentation), e.g.:
return (
<ListPage options={{ListComponentOnSelect: onSelect}} ... />
)

Categories

Resources