How can I reference a function inside a component from the outside? - reactjs

I have an array of fragments that I'm passing into ChildComponent. The fragments must have onChange attributes set to a handler that exists inside the ChildComponent, but as they are written outside of it doing so breaks the app. I can't define the fragments inside ChildComponent and I can't define ChildComponent inside ParentComponent. How do I do this properly?
const fragments = [
const Screen1 = () => {
return (
<>
<input type="text" id="screen1_input1" onChange={onChangeHandler} />
</>
)
};
const Screen2 = () => {
return (
<>
<input type="text" id="screen2_input1" onChange={onChangeHandler} />
</>
)
};
]
ChildComponent.js
const ChildComponent = (props) => {
let index = props.index
const fragments = props.fragments
const onChange = (e) => {
//whatever
}
return (
<>
<h2 className="screens">
{fragments[index]()}
</h2>
</>
)
}
ParentComponent.js
import ChildComponent from './ChildComponent'
const ParentComponent = (props) => {
let index = 3
return (
<ChildComponent index='3'/>
)
}

You can convert fragments array into a function which takes onChangHandler and return an array itself.
Below is the refactored code, for simplicity I'm just logging the input element id and the value that's being inputted.
const { Fragment } = React;
const fragments = (onChangeHandler) =>
[
<input type="text" id="screen1_input1" onChange={onChangeHandler} />,
<input type="text" id="screen2_input1" onChange={onChangeHandler} />
];
const ChildComponent = ({ index, fragments }) => {
const onChange = e => {
const { target: {id, value} } = e;
console.log(id, value);
};
return (
<Fragment>
<h2 className="screens">{fragments(onChange)[index]}</h2>
</Fragment>
);
};
const ParentComponent = props => {
return <ChildComponent index={1} fragments={fragments}/>;
};
ReactDOM.render(<ParentComponent />, document.getElementById("react"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="react"></div>

Define fragments like this
const fragments = [
const Screen1 = (onChangeHandler) => {
return (
<>
<input type="text" id="screen1_input1" onChange={onChangeHandler} />
</>
)
};
const Screen2 = (onChangeHandler) => {
return (
<>
<input type="text" id="screen2_input1" onChange={onChangeHandler} />
</>
)
};
]
ChildComponent.js
const ChildComponent = (props) => {
let index = props.index
const fragments = props.fragments
const onChange = (e) => {
//whatever
}
return (
<>
<h2 className="screens">
{fragments[index](onChange)}
</h2>
</>
)
}

Related

I want to pass data to parent component foom a child component using props

I'm trying to pass data to the parent component Top.js using props from a child component TagsInput.js where I can add tags but
I don't understand what is causing the error...
What I want to achieve
I want to pass "tags" to the parent component Top.js from TagsInput.js in the child component with props.
I got the error like
props.setTagsinput is not a function
TagsInput.js
import React from "react";
const TagsInput = (props) => {
//1, Define the tags variable to store the entered tags. (I want to pass the value of the tags variable to the parent component Top.js)
const [tags, setTags] = React.useState([]);
//2, Put formText in the function received from the parent component and return it.
props.setTagsinput(tags);
console.log(props)
let tag_list = []
tag_list.push(tags);
const addTags = event => {
if (event.key === "Enter" && event.target.value !== "") {
setTags([...tags, event.target.value]);
event.target.value = "";
}
};
const removeTags = index => {
setTags([...tags.filter(tag => tags.indexOf(tag) !== index)]);
};
return (
<div className="tags-input">
<div className="tags_section">
{tags.map((tag, index) => (
<div className="tag tag-flex" key={index}>
<p className="tag-p">{tag}</p>
</div>
))}
</div>
<input
type="text"
onKeyUp={event => addTags(event)}
placeholder="Press enter to add tags"
/>
</div>
);
};
export default TagsInput;
Top.js
import React, {useState, useEffect} from 'react';
import axios from 'axios';
import Student from './Student';
import TagsInput from "./TagsInput";
const Top = () => {
const [ posts, setPosts] = useState([]);
const [ allPosts, setAllPosts] = useState([]);
let tag_list = []
const [searchKeyword, setSearchKeyword] = React.useState("");
const [searchTagKeyword, setTagSearchKeyword] = React.useState("");
console.log(searchKeyword)
const[tags_from_tagsinput, setTagsinput]= useState("");
console.log(tags_from_tagsinput);
useEffect(() => {
axios.get('xxx.com')
.then(result => {
setPosts(result.data.students);
setAllPosts(result.data.students);
if (searchKeyword) {
getSearchResult()
}
})},
[searchKeyword]);
const getSearchResult = () => {
console.log(searchKeyword)
const result = allPosts.filter((output, index) => {
return output.firstName.toLowerCase().includes(searchKeyword.toLowerCase())||output.lastName.toLowerCase().includes(searchKeyword.toLowerCase());
});
console.log(result)
setPosts(result);
};
const getTagSearchResult = () => {
console.log(searchTagKeyword)
const result = allPosts.filter((output, index) => {
return output.lastName.toLowerCase().includes(searchTagKeyword.toLowerCase());
});
console.log(result)
setPosts(result);
};
return (
<div>
<TagsInput setTagsinput={setTagsinput}/>
<div>
<input className="search-box" placeholder="" value={searchKeyword} onChange={(e) => setSearchKeyword(e.target.value)}/>
</div>
<div>
<input className="search-box" placeholder="" value={searchTagKeyword} onChange={(e) => setSearchKeyword(e.target.value)}/>
</div>
<div>
{searchKeyword &&
<p>{searchKeyword} Search</p>
}
{posts ?
<>
{posts.map((data, i) =>
<Student data={data} />
)}
</>
:
<div>
<p>Not Found!</p>
</div>
}
</div>
</div>
);
}
export default Top;
Student.js
import React, {useState} from 'react';
import TagsInput from './TagsInput';
const Student = (props) => {
const [show, setShow] = useState(false)
const gradesAverage = (grades) => {
let sum = 0;
grades.forEach(function(score) {
sum += Number(score);
});
let ave = sum / grades.length
return ave;
};
return (
<div className="flex">
<div className="image">
<img src={props.data.pic} className="profile" />
</div>
<div>
<p className="name">{props.data.firstName} {props.data.lastName}</p>
<button className="button" onClick={() => setShow(!show)}>
{show? <div className="button_p">-</div>:<div className="button_p">+</div>}
</button>
<div className="info">
<p>Email: {props.data.email}</p>
<p>Company: {props.data.company}</p>
<p>Skill: {props.data.skill}</p>
<p>Average Grade: {gradesAverage(props.data.grades)}%</p>
{show &&
<>
<p>Test 1: {props.data.grades[0]}%</p>
<p>Test 2: {props.data.grades[1]}%</p>
<p>Test 3: {props.data.grades[2]}%</p>
<p>Test 4: {props.data.grades[3]}%</p>
<p>Test 5: {props.data.grades[4]}%</p>
<p>Test 6: {props.data.grades[5]}%</p>
<p>Test 7: {props.data.grades[6]}%</p>
<p>Test 8: {props.data.grades[7]}%</p>
</>
}
<TagsInput />
</div>
</div>
</div>
);
}
export default Student;
You can not directly use one component hook declaration in another component, you need to have a callback function to update that state. I modified your code to use the top page setTagsinput in student tag input
Top.js
import React, { useState, useEffect } from "react";
import axios from "axios";
import Student from "./Student";
import TagsInput from "./TagsInput";
const Top = () => {
const [posts, setPosts] = useState([]);
const [allPosts, setAllPosts] = useState([]);
let tag_list = [];
const [searchKeyword, setSearchKeyword] = React.useState("");
const [searchTagKeyword, setTagSearchKeyword] = React.useState("");
console.log(searchKeyword);
const [tags_from_tagsinput, setTagsinput] = useState("");
console.log(tags_from_tagsinput);
useEffect(() => {
axios.get("xxx.com").then((result) => {
setPosts(result.data.students);
setAllPosts(result.data.students);
if (searchKeyword) {
getSearchResult();
}
});
}, [searchKeyword]);
const getSearchResult = () => {
console.log(searchKeyword);
const result = allPosts.filter((output, index) => {
return (
output.firstName.toLowerCase().includes(searchKeyword.toLowerCase()) ||
output.lastName.toLowerCase().includes(searchKeyword.toLowerCase())
);
});
console.log(result);
setPosts(result);
};
const getTagSearchResult = () => {
console.log(searchTagKeyword);
const result = allPosts.filter((output, index) => {
return output.lastName
.toLowerCase()
.includes(searchTagKeyword.toLowerCase());
});
console.log(result);
setPosts(result);
};
const setTagsFromStudent = (tags) => {
setTagsinput(tags);
};
return (
<div>
<div>
<input
className="search-box"
placeholder=""
value={searchKeyword}
onChange={(e) => setSearchKeyword(e.target.value)}
/>
</div>
<div>
<input
className="search-box"
placeholder=""
value={searchTagKeyword}
onChange={(e) => setSearchKeyword(e.target.value)}
/>
</div>
<div>
{searchKeyword && <p>{searchKeyword} Search</p>}
{posts ? (
<>
{posts.map((data, i) => (
<Student data={data} setStudentTags={setTagsFromStudent} />
))}
</>
) : (
<div>
<p>Not Found!</p>
</div>
)}
</div>
</div>
);
};
export default Top;
Student.js
import React, { useState } from "react";
import TagsInput from "./TagsInput";
const Student = (props) => {
const [show, setShow] = useState(false);
const gradesAverage = (grades) => {
let sum = 0;
grades.forEach(function (score) {
sum += Number(score);
});
let ave = sum / grades.length;
return ave;
};
return (
<div className="flex">
<div className="image">
<img src={props.data.pic} className="profile" />
</div>
<div>
<p className="name">
{props.data.firstName} {props.data.lastName}
</p>
<button className="button" onClick={() => setShow(!show)}>
{show ? (
<div className="button_p">-</div>
) : (
<div className="button_p">+</div>
)}
</button>
<div className="info">
<p>Email: {props.data.email}</p>
<p>Company: {props.data.company}</p>
<p>Skill: {props.data.skill}</p>
<p>Average Grade: {gradesAverage(props.data.grades)}%</p>
{show && (
<>
<p>Test 1: {props.data.grades[0]}%</p>
<p>Test 2: {props.data.grades[1]}%</p>
<p>Test 3: {props.data.grades[2]}%</p>
<p>Test 4: {props.data.grades[3]}%</p>
<p>Test 5: {props.data.grades[4]}%</p>
<p>Test 6: {props.data.grades[5]}%</p>
<p>Test 7: {props.data.grades[6]}%</p>
<p>Test 8: {props.data.grades[7]}%</p>
</>
)}
{/*pass settag from topTag component*/}
<TagsInput setStudentTags={props.setStudentTags} />
</div>
</div>
</div>
);
};
export default Student;
TagsInput.js
import React from "react";
const TagsInput = (props) => {
const [tags, setTags] = React.useState([]);
let tag_list = [];
tag_list.push(tags);
const addTags = (event) => {
if (event.key === "Enter" && event.target.value !== "") {
setTags([...tags, event.target.value]);
// call function pass down from toptag
props.setStudentTags(tags);
event.target.value = "";
}
};
const removeTags = (index) => {
setTags([...tags.filter((tag) => tags.indexOf(tag) !== index)]);
};
return (
<div className="tags-input">
<div className="tags_section">
{tags.map((tag, index) => (
<div className="tag tag-flex" key={index}>
<p className="tag-p">{tag}</p>
</div>
))}
</div>
<input
type="text"
onKeyUp={(event) => addTags(event)}
placeholder="Press enter to add tags"
/>
</div>
);
};
export default TagsInput;
You should consider exploring React context -https://reactjs.org/docs/context.html, its built exactly for something like this.
You are getting this error because, like you mentioned, TagsInput component is used in Student component but it doesn’t pass the state setter setTagsInput function to the TagsInput component.
Now, assuming you need tags created inside Student and displayed in Top, also assuming that both are rendered in the same parent component, you can create a state for tags in the parent component. This component will pass a state setter function to Student which passes the setter to TagsInput and the state itself to Top to use the list of tags.
Something like:
const App = () => {
const [tags,setTags] = useState([]);
return (<div>
<Top tags={tags} />
<Student setTags={setTags} />
</div>);
}
Your Student component can then pass it to TagsInput like:
const Student = (props) => {
return (<div>
{/* everything else */}
<TagsInput setTagsinput={props.setTags} />
</div>)
}
In your Top component you can create a function that updates your tags_from_tagsinput hook then pass it as props to the child component
import TagsInput from "./TagsInput";
const Top = () => {
const[tags_from_tagsinput, setTagsinput]= useState("");
console.log(tags_from_tagsinput);
const getTag = (value) => {
setTagsinput(value);
};
return (
<div>
<TagsInput getTag={getTag} />
</div>
);
}
export default Top;
Now from your TagsInput component you can call this function to update tags_from_tagsinput of Top, let's suppose that you want to updated when the user click on a button
import React from "react";
const TagsInput = (props) => {
return (
<div className="tags-input">
...
<button onClick={()=>{props.getTag(tags)}}>updated parent component</button>
</div>
);
};
export default TagsInput;

How Do I give dynamic colors to the each list here

import React, { useState, useEffect } from "react";
import "./style.css";
const getLocalItem = () => {
let list = localStorage.getItem("lists");
console.log(list);
if (list) {
return JSON.parse(list);
} else {
return [];
}
};
function App() {
const [text, setText] = useState("");
const [task, setTask] = useState(getLocalItem());
const changeText = (e) => {
setText(e.target.value);
};
const submitHandler = (e) => {
console.log("submited");
e.preventDefault();
setTask([...task, text]);
setText("");
};
const removeTask = (a) => {
const finalData = task.filter((curEle, index) => {
return index !== a;
});
setTask(finalData);
};
useEffect(() => {
localStorage.setItem("lists", JSON.stringify(task));
}, [task]);
return (
<>
<form onSubmit={submitHandler} className='form'>
<div className="action" >
<div >
<input
className="input"
type="text"
value={text}
onChange={changeText}
placeholder='add task...'
/>
</div>
<button type="submit" className="button" >
Add todo
</button>
</div>
<div className="listsData">
{task.map((value, index) => {
return (
<>
<div key={index}>
{value}
</div>
</>
);
})}
</div>
</form>
</>
);
}
export default App;
On adding each item I want a different color for each list. Currently, I am fetching list data from localstorage while fetching also it should remain same. which is working but the dynamic colors is what I need for each list. Any ideas or dynamic logics??
Let me know if u need more details regarding my code if u doont understand something

Passing data between two components in React.js

Currently learning React and building a side project where i can render rss-feeds in my browser window. It works in a single component.
Original working component
function App (){
const [rssUrl, setRssUrl] = useState('');
const [items, setItems] = useState([]);
const getRss = async (e) => {
e.preventDefault();
const urlRegex =
/(http|ftp|https):\/\/[\w-]+(\.[\w-]+)+([\w.,#?^=%&:\/~+#-]*[\w#?^=%&\/~+#-])?/;
if (!urlRegex.test(rssUrl)) {
return;
}
const res = await fetch(`https://api.allorigins.win/get?url=${rssUrl}`);
const { contents } = await res.json();
const feed = new window.DOMParser().parseFromString(contents, 'text/xml');
const items = feed.querySelectorAll('item');
const feedItems = [...items].map((el) => ({
link: el.querySelector('link').innerHTML,
title: el.querySelector('title').innerHTML,
author: el.querySelector('author').innerHTML,
}));
setItems(feedItems);
};
}
return (
<div className="App">
<form onSubmit={getRss}>
<div>
<h1>Next Pod For Chrome</h1>
<label> rss url</label>
<br />
<input onChange={(e) => setRssUrl(e.target.value)} value={rssUrl} />
</div>
<input type="submit" />
</form>
{items.map((item) => {
return (
<div>
<h1>{item.title}</h1>
<p>{item.author}</p>
<a href={item.link}>{item.link}</a>
</div>
);
})}
</div>
);
}
export default App;
At the moment I try to separate the functionality into two components. How can I pass a link from one component to another one where I want to trigger a function handled by the first component?
Any tips are much appreciated. Thanks.
Current state of component to search for rss-feed
function Search() {
const [rssUrl, setRssUrl] = useState('');
const formatRss = async (e) => {
e.preventDefault();
const urlRegex =
/(http|ftp|https):\/\/[\w-]+(\.[\w-]+)+([\w.,#?^=%&:\/~+#-]*[\w#?^=%&\/~+#-])?/;
if (!urlRegex.test(rssUrl)) {
return;
}
console.log(rssUrl);
};
return (
<div className="App">
<form onSubmit={formatRss}>
<div>
<h1>Next Pod For Chrome</h1>
<label>rss url</label>
<br />
<input onChange={(e) => setRssUrl(e.target.value)} value={rssUrl} />
</div>
<input type="Submit" />
</form>
</div>
);
}
export default Search;
Current stage of component to parse and render
function List(props) {
const [items, setItems] = useState([]);
const formatRss = async (e) => {
e.preventDefault();
console.log(rssUrl);
const res = await fetch(`https://api.allorigins.win/get?url=${rssUrl}`);
const { contents } = await res.json();
const feed = new window.DOMParser().parseFromString(contents, 'text/xml');
const items = feed.querySelectorAll('item');
const feedItems = [...items].map((el) => ({
link: el.querySelector('link').innerHTML,
title: el.querySelector('title').innerHTML,
author: el.querySelector('author').innerHTML,
}));
setItems(feedItems);
};
return (
<div className="App">
{items.map((item) => {
return (
<div>
<h1>{item.title}</h1>
<p>{item.author}</p>
<a href={item.link}>{item.link}</a>
</div>
);
})}
</div>
);
}
export default List;
You can declare the state on both's parent, for example: App.js
And use prop to pass the variable to the component
like this:
export default function App() {
const [rssUrl, setRssUrl] = useState("");
return (
<div className="App">
<Search rssUrl={rssUrl} setRssUrl={setRssUrl} />
<List rssUrl={rssUrl} />
</div>
);
}
Below is the live example for you:
https://codesandbox.io/s/cocky-tharp-7d5uu8?file=/src/App.js
There are many platforms where you can put the demo project which make it easier for people to answer your question.

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

map function to work with event.target.name, have multiple values tagged to index of map

The below functionality is only capable of running only one component (i.e. "ComponentTwo"), I want to modify it to have more component, but issue is as i am using map function to loop through component to map "value", same value will be passed to all the component.
In the code there is two function for handling the change currently i am using the "handleInputChange" which take value as input but i want it to work with name so that i can have name to distinguish between components, below is one commented function which i am trying to implement, but is not working.
p.s. if you need any clarifications let me know in comment section.
link to code:https://codesandbox.io/s/happy-hugle-mfstd?file=/src/App.js
import React, { Component, useState } from "react";
export default function App() {
const [inputValues, setInputValues] = useState(["Test"]);
const addNewInputvalue = () => {
setInputValues((prev) => {
return [...prev, ""];
});
};
const removeInput = (index) => {
setInputValues((prev) => {
const copy = [...prev];
copy.splice(index, 1);
return copy;
});
};
// const handleChange = (event) => {
// event.persist()
// setData(prev => ({ ...prev, [event.target.name]: event.target.value }))
// }
const handleInputChange = (index, value) => {
setInputValues((prev) => {
const copy = [...prev];
copy[index] = value;
return copy;
});
};
const consoleAllValues = () => {
console.log(inputValues);
};
return (
<div className="App">
<button onClick={addNewInputvalue}>New Input</button>
{inputValues.map((val, i) => {
return (<div>
<ComponentTwo
key={i}
index={i}
value={val}
onChange={handleInputChange}
removeInput={() => removeInput(i)}
/>
<ComponentThree />
<ComponenFour />
</div>
>
);
})}
<button onClick={consoleAllValues}>console log all values</button>
</div>
);
}
const ComponentTwo = (props) => {
return (
<div>
<p>Input: {props.index}</p>
<input
name={"right_value"}
onChange={(e) => props.onChange(props.index, e.target.value)}
type="text"
value={props.value}
/>
<button onClick={props.removeInput}>Remove Input</button>
</div>
);
};
Instead of using an array to store your values, you should create a key value object. See the changes I've made regarding your state, the way you iterate through the object inside your return statement and all the functions that manipulate your state.
import React, { Component, useState } from "react";
export default function App() {
const [inputValues, setInputValues] = useState({
'componentTwo': 'val1',
'componentThree': 'val2',
'componentFour': 'val3',
});
const addNewInputvalue = (name, value) => {
setInputValues((prev) => {
return {
...prev,
[name]: value,
}
});
};
const removeInput = (name) => {
setInputValues((prev) => {
const copy = {...prev};
delete copy[name];
return copy;
});
};
const handleInputChange = (name, value) => {
setInputValues((prev) => {
return {
...prev,
[name]: value,
};
});
};
const consoleAllValues = () => {
console.log(inputValues);
};
return (
<div className="App">
<button onClick={addNewInputvalue}>New Input</button>
{Object.keys(inputValues).map((name, i) => {
return (<div>
<ComponentTwo
key={name}
index={i}
value={inputValues[name]}
onChange={(value) => handleInputChange(name, value)}
removeInput={() => removeInput(name)}
/>
<ComponentThree />
<ComponenFour />
</div>
>
);
})}
<button onClick={consoleAllValues}>console log all values</button>
</div>
);
}
const ComponentTwo = (props) => {
return (
<div>
<p>Input: {props.index}</p>
<input
name={"right_value"}
onChange={(e) => props.onChange(e.target.value)}
type="text"
value={props.value}
/>
<button onClick={props.removeInput}>Remove Input</button>
</div>
);
};

Resources