React: Render and link toggle button outside the class - javascript

I have the following example where the toggleComponent.js is working perfectly.
The problem here is that I don't want to render the <ContentComponent/> inside the toggle, rather I want the opposite, I want to toggle the <ContentComponent/> that will be called in another component depending on the state of the toggle.
So the <ContentComponent/> is outside the toggleComponent.js, but they are linked together. So I can display it externally using the toggle.
An image to give you an idea:
Link to funtional code:
https://stackblitz.com/edit/react-fwn3rn?file=src/App.js
import React, { Component } from "react";
import ToggleComponent from "./toggleComponent";
import ContentComponent from "./content";
export default class App extends React.Component {
render() {
return (
<div>
<ToggleComponent
render={({ isShowBody, checkbox }) => (
<div>
{isShowBody && <h1>test</h1>}
<button onClick={checkbox}>Show</button>
</div>
)}
/>
<ToggleComponent
render={({ isShowBody, checkbox }) => (
<div>
{isShowBody && (
<h1>
<ContentComponent />
</h1>
)}
<button onClick={checkbox}>Show</button>
</div>
)}
/>
</div>
);
}
}

Bit tweaked your source.
Modified ToggleComponent
import React from "react";
export default class ToggleComponent extends React.Component {
constructor() {
super();
this.state = {
checked: false
};
this.handleClick = this.handleClick.bind(this);
}
handleClick = () => {
this.setState({ checked: !this.state.checked });
this.props.toggled(!this.state.checked);
};
checkbox = () => {
return (
<div>
<label>Toggle</label>
<span className="switch switch-sm">
<label>
<input type="checkbox" name="select" onClick={this.handleClick} />
<span />
</label>
</span>
</div>
);
};
render() {
return this.checkbox();
}
}
Added OtherComponent with ContentComponent inside.
import React, { Component } from "react";
import ContentComponent from "./content";
export default class OtherComponent extends React.Component {
render() {
return <div>{this.props.show ? <ContentComponent /> : null}</div>;
}
}
Separated as per your requirement.
Modified App
import React, { Component, PropTypes } from "react";
import ToggleComponent from "./toggleComponent";
import OtherComponent from "./otherComponent";
export default class App extends React.Component {
constructor() {
super();
this.toggled = this.toggled.bind(this);
this.state = { show: false };
}
toggled(value) {
this.setState({ show: value });
}
render() {
return (
<div>
<ToggleComponent toggled={this.toggled} />
<OtherComponent show={this.state.show} />
</div>
);
}
}
Working demo at StackBlitz.

If you want to share states across components a good way to do that is to use callbacks and states. I will use below some functional components but the same principle can be applied with class based components and their setState function.
You can see this example running here, I've tried to reproduce a bit what you showed in your question.
import React, { useState, useEffect, useCallback } from "react";
import "./style.css";
const ToggleComponent = props => {
const { label: labelText, checked, onClick } = props;
return (
<label>
<input type="checkbox" checked={checked} onClick={onClick} />
{labelText}
</label>
);
};
const ContentComponent = props => {
const { label, children, render: renderFromProps, onChange } = props;
const [checked, setChecked] = useState(false);
const defaultRender = () => null;
const render = renderFromProps || children || defaultRender;
return (
<div>
<ToggleComponent
label={label}
checked={checked}
onClick={() => {
setChecked(previousChecked => !previousChecked);
}}
/>
{render(checked)}
</div>
);
};
const Holder = () => {
return (
<div>
<ContentComponent label="First">
{checked => (
<h1>First content ({checked ? "checked" : "unchecked"})</h1>
)}
</ContentComponent>
<ContentComponent
label="Second"
render={checked => (checked ? <h1>Second content</h1> : null)}
/>
</div>
);
};
PS: A good rule of thumb concerning state management is to try to avoid bi-directional state handling. For instance here in my example I don't use an internal state in ToggleComponent because it would require to update it if given checked property has changed. If you want to have this kind of shared state changes then you need to use useEffect on functional component.
const ContentComponent = props => {
const { checked: checkedFromProps, label, children, render: renderFromProps, onChange } = props;
const [checked, setChecked] = useState(checkedFromProps || false);
const defaultRender = () => null;
const render = renderFromProps || children || defaultRender;
// onChange callback
useEffect(() => {
if (onChange) {
onChange(checked);
}
}, [ checked, onChange ]);
// update from props
useEffect(() => {
setChecked(checkedFromProps);
}, [ checkedFromProps, setChecked ]);
return (
<div>
<ToggleComponent
label={label}
checked={checked}
onClick={() => {
setChecked(previousChecked => !previousChecked);
}}
/>
{render(checked)}
</div>
);
};
const Other = () => {
const [ checked, setChecked ] = useState(true);
return (
<div>
{ checked ? "Checked" : "Unchecked" }
<ContentComponent checked={checked} onChange={setChecked} />
</div>
);
};

Related

change object into array present another component in react

i am sorry for the very basic question as i m new to react js. i have created an array in a react component and render it through map function in one component and i want to change (add/Subject) in the array from an other component on basis of _id. the following is a sample that helps you better to understand what i actually want. Thanks in advance Sir
{*Array Component*}
const ArrayData =[
{
_id:1,
title:"All Searches"
},
{
_id:5,
title:1
},
{
_id:6,
title:"4"
}
]
export default ArrayData;
{*2nd Component*}
import react from "react"
import ArrayData from ArrayComponent
class Parent extends React.Component {
constructor() {
super();
this.state = {
ArrayData:ArrayData,
collapsed: false,
}
}
render() {
const { ArrayData } = this.state;
return (
<>
<FirstChild Data={ArrayData} />
<SecondChild />
</>
);
}
}
export default Parent;
!------------------------------------------!
{*FirstChild*}
class FirstChild extends React.Component {
constructor(props){
super();
this.state={
ArrayData:props.ArrayData
}
}
render() {
const { ArrayData} = this.state;
const renderArray = ArrayData.slice(0, 5).map(Object => {
return <h1>{object._id} </h1>
})
return (
<>
{renderArray}
</>
);
}
}
export default FirstChild;
!-----------------------------------------!
{*SecondChild*}
import { React } from "react";
const SecondChild = () => {
const handleUpdate=(_id, Title) =>{
{*function that can add the inputs as a object into that arrayComponent*}
}
const handleDelete=(_id) =>{
{*function that can delete a object from that arrayComponent having the id given by User in the feild*}
}
return (
<>
<input type='text' name='_id' placeHolder="Which object you want to delete" />
<button type=Submit onClick={handleDelete} >Delete</button>
<br></br>
<input type='text' name='_id' />
<input type='text' name='title' />
<button type=Submit onClick={handleUpdate} >Update</button>
</>
);
}
export default SecondChild;
CODESANDBOX
All you need to do is declare handleDelete and handleUpdate on the Parent component and pass it as a props in SecondChild component. If we place state and its methods in Parent component, then it is easy to track, debug, and easy to maintain. It would be easy to pass the methods if we define another component, let say ThirdComponent and it also contains functionality to perform CRUD operation on the ArrayData array.
In the FirstChild component, you were destructuring the ArrayData const { ArrayData} = this.state; and using it in the render method. It won't update we receive new props because you are rendering the array state that gets created once(as a constructor will get called once) and we want the latest value of ArrayData from the parent component. We can use props directly in the render method. You need to see the react lifecycle methods.
Parent.js
import React from "react";
import ArrayData from "./ArrayComponent";
import FirstChild from "./FirstChild";
import SecondChild from "./SecondChild";
class Parent extends React.Component {
constructor() {
super();
this.state = {
ArrayData: ArrayData,
collapsed: false
};
this.handleDelete = this.handleDelete.bind(this);
this.handleUpdate = this.handleUpdate.bind(this);
}
handleDelete(id) {
const idToDelete = parseInt(id, 10);
this.setState((state) => {
const filteredArrayData = state.ArrayData.filter(
(el) => el._id !== idToDelete
);
return {
ArrayData: filteredArrayData
};
});
}
handleUpdate(newObj) {
console.log(newObj);
this.setState((state) => ({
ArrayData: [...state.ArrayData, newObj]
}));
}
render() {
return (
<>
<FirstChild Data={this.state.ArrayData} />
<SecondChild
handleUpdate={this.handleUpdate}
handleDelete={this.handleDelete}
/>
</>
);
}
}
export default Parent;
FirstChild.js
import React from "react";
class FirstChild extends React.Component {
render() {
return (
<>
{this.props.Data.slice(0, 5).map((el) => {
return <h1 key={el._id}>{el._id}</h1>;
})}
</>
);
}
}
export default FirstChild;
SecondChild.js
import React, { useState } from "react";
const SecondChild = ({ handleUpdate, handleDelete }) => {
const [idToDelete, setIdToDelete] = useState(null);
const [newID, setNewID] = useState(null);
const [newTitle, setNewTitle] = useState("");
return (
<>
<input
name="_id"
type="number"
onChange={(e) => setIdToDelete(e.target.value)}
placeholder="Which object you want to delete"
/>
<button type="submit" onClick={() => handleDelete(idToDelete)}>
Delete
</button>
<br></br>
<br></br>
<br></br>
<input
type="text"
name="_id"
placeholder="id"
onChange={(e) => setNewID(e.target.value)}
/>
<input
type="text"
name="title"
placeholder="title"
onChange={(e) => setNewTitle(e.target.value)}
/>
<button
type="submit"
onClick={() => handleUpdate({ _id: newID, title: newTitle })}
>
Update
</button>
</>
);
};
export default SecondChild;

How can i pass props through methods inside components?

i have a react component named "List" that renders smaller components "Post" using a button through method "Addpost()" that takes 2 props from the input form. I have saved the input in 2 varables but i don't know how to pass these props to the Addpost() method inside the return of List's render().
//=========== List component ==============
class List extends React.Component{
renderPost(title,content){
return(
<Post titolo={title} contenuto={content}/>
);
}
renderPost just render the Post component in a in the HTML
addPost(title,content){
title = document.getElementById("inputTitle").value;
content = document.getElementById("inputContent").value;
console.log(title, content)
this.renderPost(title,content);
}
addPost should take the input value and use renderPost to render the Post component with that title and content
render(){
return(
<div>
{this.renderPost("testTitle","testContent")}
<form>
Title:<br></br>
<input type="text" id="inputTitle"/><br></br>
Content:<br></br>
<input type="text" id="inputContent"/>
</form><br></br>
<button className="square"
how can i make this work? title and content are not defined
onClick={() =>
this.addPost(title,content)
Add Post!
</button>
</div>
);
}
}
//=========== Post component ==============
class Post extends React.Component {
render() {
return (
<li className="w3-padding-16">
<img src="/w3images/workshop.jpg" alt="Imagedf" className="w3-left w3-margin-right" />`enter code here`
<span className="w3-large">
{this.props.titolo}
</span><br></br>
<span>{this.props.contenuto}</span>
</li>
);
}
}
Basically, whenever you're dealing with forms and inputs, you would use refs.
App.js
import React from 'react';
import logo from './logo.svg';
import './App.css';
import PostList from './components/PostList'
import AddPostForm from './components/AddPostForm'
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
posts: [] //state is handled here
}
this.addPost = this.addPost.bind(this)
}
addPost(title, content) {
let newPost = { title, content }
this.setState(({ posts }) => { return { posts: [...posts, newPost] } } )
}
render() {
const { posts } = this.state
return (
<div>
<AddPostForm onNewPost={this.addPost} /> //we pass addPost to the component
<br />
<PostList posts={posts} />
</div>
);
}
}
export default App;
Post.js
import React from 'react';
function Post({titolo, contenuto}) {
return (
<li className="w3-padding-16">
<img src="/w3images/workshop.jpg" alt="Imagedf" className="w3-left w3-margin-right" />`enter code here`
<span className="w3-large">
{titolo}
</span><br></br>
<span>{contenuto}</span>
</li>
);
}
export default Post
AddPostForm.js
import React from 'react';
const addPostForm = ({onNewPost = f => f}) => { //onNewPost method is passed by props from the parent
let _titleInput, _contentInput //these are our refs, see the docs for more information
const submit = (e) => {
e.preventDefault()
onNewPost(_titleInput.value, _contentInput.value) //here we call the addPost function that was passed to the component
_titleInput.value = '' //empty the inputs
_contentInput.value = ''
_titleInput.focus() //set focus
}
return (
<form onSubmit={submit}>
Title:<br></br>
<input type="text" ref={title => _titleInput = title} /><br></br>{/* Note the ref attribute */}
Content:<br></br>
<input type="text" ref={content => _contentInput = content} />
<button className="square">Add a new post</button>
</form>
)
}
export default addPostForm
PostList.js
import React from 'react';
import Post from './Post';
const PostList = ({ posts=[] }) => {
return (
<div className="post-list">
{
posts.map((post, index) =>
<Post key={index} titolo={post.title} contenuto={post.content} />
)
}
</div>
)
}
export default PostList
And the result:
edit
renderPost just render the Post component in a in the HTML
state = { inputTitle: '', inputContent: '' }
addPost(title,content){
title = document.getElementById("inputTitle").value;
content = document.getElementById("inputContent").value;
console.log(title, content)
this.renderPost(title,content);
}
addPost should take the input value and use renderPost to render the Post
component with that title and content
render(){
return(
<div>
{this.renderPost("testTitle","testContent")}
<form>
Title:<br></br>
<input type="text" value={this.inputTitle} onChnage={event => setState({ inputTitle: event.target.value }) }><br></br>
Content:<br></br>
<input type="text" value={this.inputContent} onChnage={event => setState({ inputContent: event.target.value }) } />
</form><br></br>
<button className="square"
on click function
onClick={() =>
this.addPost(this.inputTitle,this.inputContent)
Add Post!
</button>
</div>
);
}
}

React To-Do app: Removing items, but keeping individual state

I have run into a problem with my React application. So far there is enough functionality in place to add To-Do items to the list, remove them by index and mark them done (text-decoration: line-through).
When I remove an item that is already crossed out, I would expect the other items to keep their own state, however they don't. Here's what I mean.
Let's remove the crossed out item
Why is the bottom one crossed out now?
Here's my code
ToDoApp.js
import React from 'react';
import Header from './Header';
import AddToDo from './AddToDo';
import FilterToDo from './FilterToDo';
import ToDoList from './ToDoList';
import ListButtons from './ListButtons';
export default class ToDoApp extends React.Component {
state = {
toDos: []
};
handleAddToDo = (toDo) => {
if (!toDo) {
return "Nothing was added!";
}
this.setState((prevState) => ({
toDos: prevState.toDos.concat([toDo])
}));
};
handleRemoveToDo = (removeIndex) => {
this.setState((prevState) => ({
toDos: prevState.toDos.filter((toDo, index) => index !== removeIndex)
}));
};
render() {
return (
<div>
<Header />
<AddToDo
handleAddToDo={this.handleAddToDo}
/>
<FilterToDo />
<ToDoList
toDos={this.state.toDos}
handleRemoveToDo={this.handleRemoveToDo}
/>
<ListButtons />
</div>
);
};
};
ToDoList.js
import React from 'react';
import ToDoListItem from './ToDoListItem';
const ToDoList = (props) => (
<div>
<h3>To Do List</h3>
<div>
{props.toDos.map((toDo , index) => (
<ToDoListItem
key={index}
index={index}
toDoTitle={toDo}
handleRemoveToDo={props.handleRemoveToDo}
/>))}
</div>
</div>
);
export default ToDoList;
ToDoListItem.js
import React from 'react';
export default class ToDoListItem extends React.Component {
state = {
done: false
};
handleDoneTrigger = () => {
this.setState((prevState) => ({ done: !prevState.done }));
};
render() {
return (
<div>
<p
className={this.state.done ? "done" : ""}
>{this.props.toDoTitle}</p>
<button onClick={(e) => {
this.props.handleRemoveToDo(this.props.index)
}}>Remove</button>
<button onClick={this.handleDoneTrigger}>Done</button>
</div>
);
}
};
The problem is with this piece of code:
<ToDoListItem
key={index}
index={index}
toDoTitle={toDo}
handleRemoveToDo={props.handleRemoveToDo}
/>))}
as you set the index as key of ToDoListItem. Instead of index assign some unique key to each element because when you delete an item its index assigned to following item in the list.
This will be helpful to dig more into deep: https://medium.com/#robinpokorny/index-as-a-key-is-an-anti-pattern-e0349aece318

How to Update This Reactjs Select

I have this wrapper class that is used because I am using Formik and the FieldArray
import React, { Component } from "react";
import { ReactDOM } from "react-dom";
import Select from "react-select";
import { observer } from "mobx-react";
import { axiosInstance } from "../stores/AxiosInstance";
#observer
export default class CountryStateSelectComponent extends React.Component {
constructor(props) {
super(props);
this.state = { stateOptions: [] };
}
handleCountryChange = value => {
const that = this;
axiosInstance
.get(`/States?countryId=${value.value}`)
.then(function(response) {
that.props.onChange(that.props.countryName, value);
that.props.onChange(that.props.stateName, null);
const states = response.data.map(state => {
return { label: state.name, value: state.id };
});
// if I move out state select code then won't need to update state here but don't know how to call something like updateState(record)
that.setState({
stateOptions: states
});
});
};
handleStateChange = value => {
console.log(this.props.stateName, value)
this.props.onChange(this.props.stateName, value);
};
handleCountryBlur = () => {
this.props.onBlur(this.props.countryName, true);
};
handleStateBlur = () => {
this.props.onChange(this.props.stateName, true);
};
render() {
const props = this.props;
return (
<React.Fragment>
<div className="field">
<label className="label">Country</label>
<div className="control">
<Select
options={props.options}
isMulti={props.isMulti}
onChange={this.handleCountryChange}
onBlur={this.handleCountryBlur}
closeMenuOnSelect={props.closeMenuOnSelect}
/>
{this.props.CountryError}
</div>
</div>
<div className="field">
<label className="label">State/Province</label>
<div className="control">
<Select
options={this.state.stateOptions}
onChange={this.handleStateChange}
onBlur={this.handleStateBlur}
/>
{this.props.StateError}
</div>
</div>
</React.Fragment>
);
}
}
However what I found is that when the State gets selected the value does not get stored in Formik(it gets stored as undefined and sometimes true).
So now I am thinking maybe moving out the State Zip out and making it's own wrapper or something but I don't know how to get the "states" that came back and populate the correct state box as they can generate many.
#inject("AccountSetupStore")
#observer
export default class MyComponent extends Component {
constructor(props) {
super(props);
this.state = { records: [this.generateRecord(1, true, true)] };
}
componentDidMount() {
const accountSetupStore = this.props.AccountSetupStore;
accountSetupStore.getCountries();
}
updateState(record) {
// would like to call this method that can somehow update the record
// propblem is I don't seem to have access to props when this function is being called from the CountryStateSelectComponent
}
render() {
const props = this.props;
const accountSetupStore = props.AccountSetupStore;
const countries = [];
for (const country of accountSetupStore.countries) {
countries.push({ value: country.id, label: country.name });
}
return (
<section className="accordions">
<Formik
initialValues={{
records: this.state.records
}}
onSubmit={(
values,
{ setSubmitting, setErrors}
) => {
console.log(values,"values");
}}
validationSchema={Yup.object().shape({
branches: Yup.array()
.of(
Yup.object().shape({
})
)
})}
render={({
values,
setFieldValue,
setFieldTouched,
}) => (
<FieldArray
name="records"
render={arrayHelpers => (
<Form>
{values.records.map((record, index) => {
return (<article}>
<CountryStateSelectComponent options={countries}
onChange={setFieldValue}
countryName={`records[${index}].selectedCountry`}
stateName={`records[0].selectedState`}
onBlur={setFieldTouched}
isMulti={false}
index = {index}
closeMenuOnSelect={true}
CountryError = {<ErrorMessage name={`records[${index}].selectedCountry`}/>}
StateError= {<ErrorMessage name={`records[${index}].selectedState`}/>}
/>
</article>)
})}
</Form>
)}
/>
)}
/>
</section>
);
}
}
React Select onChange sends the value to the method supplied
const onStateChange = (selectedOption, {action}) => {
//do something with the selectedOption according to the action
}
<Select onChange={onStateChange} />
See the documentation for the onChange in the Props documentation.

Changing specific index of an array in state instead of all of my state object

I'm doing a React refresher. I set state in App.js and created an event called handleUserNameChange() to change usernames in the state object. Each input from UserInput.js should change it's relative UserOutput component's username in state that's set in App.js. How can I make so that when I type text into one input it only changes one index in my users array in state? Do I need to change my handleUserNameChange event?
App.js
import React, { Component } from 'react';
//Components
import UserInput from './UserInput/UserInput';
import UserOutput from './UserOutput/UserOutput';
class App extends Component {
state = {
users: [
{user: 'Debbie'},
{user: 'Ronald'}
]
};
handleUserNameChange = (event) => {
this.setState({
users: [
{user: event.target.value},
{user: event.target.value}
]
});
};
render() {
return (
<div className="App">
<UserOutput
username = {this.state.users[0].user}
/>
<UserInput
nameChange={this.handleUserNameChange} />
<UserOutput
username={this.state.users[1].user}
/>
<UserInput
nameChange={this.handleUserNameChange}
/>
</div>
);
}
}
export default App;
UserOuput.js
import React from 'react';
const UserOutput =(props) => {
return (
<div>
<h3>{props.username}</h3>
</div>
);
}
export default UserOutput;
UserInput.js
import React from 'react';
const UserInput = (props) => {
return (
<div>
<input type="text"
onChange={props.nameChange}
/>
</div>
);
}
export default UserInput;
In App.js:
<UserInput
nameChange={this.handleUserNameChange(0)}//0 for first, 1 for second
/>
handleUserNameChange = (index) => (event) => {
this.setState({
users: this.state.users.map(
(user,i)=>
(i===index)
? event.target.value
: user
)
});
};
It would probably be better to not hardcode user 0 and user 1 but just map the state to react modules.
render() {
const userInput = index =>
<UserInput
nameChange={this.handleUserNameChange(index)} />;
const UserOutput = user =>
<UserOutput
username = {user}/>;
return (
<div className="App">
this.state.users.map(
(user,index)=>
<div>{userInput(index)}{UserOutput(user)}</div>
)
</div>
);
}
pass the index value in handleUserNameChange function from render function and use double arrow in handleUserNameChange to get the index value.
handleUserNameChange = index => event => {
this.setState(prevState => {
const users = [...prevState.users];
users[index].user = event.target.value;
return { users };
});
};
render() {
return (
<div className="App">
{this.state.users.map((user, index) => (
<React.Fragment>
<UserOutput username={user} />
<UserInput nameChange={this.handleUserNameChange(index)} />
</React.Fragment>
))}
</div>
);
}

Categories

Resources