I have created an array that gives a certain ID to each component every time it is added to the array. But now that the component is created, I want to be able to pull the data. Like the boxid or title when needed.
I currently am trying to make the value of <textarea /> in Todobox.jsx the unique boxid. But it does not seem to print a value.
Here is my code:
ElementContext.js:
import React, { createContext, useState } from 'react';
import Todobox from './components/Todobox';
export const ElementContext = createContext();
export const ElementContextProvider = ({children}) => {
const [elements, setElements] = useState([]);
const [elementId, setElementId] = useState(0);
const [refDict, setRefDict] = useState({});
const newElementId = (elements) =>{
setElementId(elementId + 1);
console.log(elementId)
}
const newElement = () => {
newElementId();
if (!refDict[elementId]) { //so if nothing in "refDict" that means the elementId is unique
setElements(prev => [...prev, { title: 'Placeholder', boxid: elementId }]);
setRefDict((prev) => ({...prev, [elementId]: true}));
}
console.log(elements);
};
const value = {
elements,
setElements,
newElement,
elementId
};
return(
<ElementContext.Provider value={value}>
{children}
</ElementContext.Provider>
)
};
HomePage.jsx:
import react from 'react';
import { useEffect, useContext } from 'react';
import '../App.css';
import Todobox from './Todobox';
import { ElementContext } from '../ElementContext';
export default function HomePage(){
const { elements, setElements, newElement, elementId } = useContext(ElementContext);
return(
<div className='page-container'>
<div className='header'>
<a className='header-title'>Trello Clone!</a>
<a className='header-button' onClick={newElement}>Create a list</a>
</div>
<div className='element-field'>
{elements.map((elements, elementId) => <Todobox key={elementId} />)}
</div>
</div>
)
}
Todobox.jsx:
import React from 'react';
import Item from './Item';
import { useContext } from 'react';
import '../App.css';
import { ElementContext } from '../ElementContext';
export default function Todobox(){
const { elements, setElements, newElement, elementId } = useContext(ElementContext);
return(
<div className='element-box'>
<a className='element-title'>PlaceHolder</a>
<Item />
<textarea
className='element-input'
type='text'
placeholder={elements.boxid}
/>
</div>
)
}
I am pretty new to react and such so any help is appreciated!
The elements array in state (and context) is an array of objects, and those objects have a boxid property. To use it, you need to reference or pass down that property when iterating over the elements. So your starting point would be to change this line:
elements.map((elements, elementId) => <Todobox key={elementId} />)
I'd change Todobox so that it doesn't use context at all, and is instead passed down all values it needs to render from the parent, including the boxid.
Other issues:
The .map callback parameter is not an array - it's a single object, so it should probably be called element, not elements, to avoid confusion
Keys should usually not be the index of the element in the array. Use a more unique identifier, such as the boxid, which will never change for a particular object. (In your current implementation, the elementId in the .map callback may not reflect the actual boxid of the object being iterated over, if you ever remove an item from the middle of the state array.)
elements.map((element) => <Todobox key={element.boxid} boxid={element.boxid} />)
export default function Todobox({ boxid }){
return(
<div className='element-box'>
<a className='element-title'>PlaceHolder</a>
<Item />
<textarea
className='element-input'
type='text'
placeholder={boxid}
/>
</div>
)
}
Related
I'm trying to give one component the array value at a certain index and assign a value that i want from the child component.
I want it like this because I'm trying to do a survey app and the number of question can be different. this is just a little test that concludes what I want.
the base Component
import React, { useState } from 'react';
import './style.css';
import Comp from './Component.js';
export default function App() {
const [results, setResults] = useState([]);
results.length = 20;
results[3] = 'kiss';
results[2] = [12, 454, 45];
console.log(results);
return (
<div>
<h1>Hello StackBlitz!</h1>
<p>Start editing to see some magic happen :)</p>
<Comp result={results[5]}/>
<button onClick={() => console.log(results)}> SHOW </button>
</div>
);
}
the component
import React, { useState } from 'react';
const Comp = ({result}) => {
result = 1
console.log(result)
return (
<div>
hhhhh
</div>
);
}
export default Comp
here is a place I set it up => https://stackblitz.com/edit/react-mfpk5f?file=src%2FApp.js,src%2FComponent.js
every suggestion is highly appreciated!
parent componentHere i have tried a way to find solution ,
just keep a callback function in child component and call a function of parent component inside child so that u can pass data to it .
child component
If you want to add more, you use the setResults() e.g. setResults(['kiss']); so now your results='kiss', if you want more is setResults(...results,[12, 454, 45]); and now your results= kiss,12,454,45 . But:
import React, { useState } from 'react';
import './style.css';
import Comp from './Component.js';
export default function App() {
const [results, setResults] = useState(['hiii']);
function handleClick() {
const array1 = 'kiss';
const array2 = [12, 454, 45];
setResults([...results, array1, array2]);
console.log(results);
}
return (
<div>
<h1>Hello StackBlitz!</h1>
<p>Start editing to see some magic happen :)</p>
<Comp result={results[5]} />
<button onClick={() => handleClick()}> SHOW </button>
</div>
);
}
First you need to add the values when something happened, e.g. onClick={...}.
<Comp result={results[5]}/> this is correct, but you call when the result=[] show you need to call after updating, e.g.
import React, { useState } from 'react';
import './style.css';
import Comp from './Component.js';
export default function App() {
const [results, setResults] = useState(['Hi']);
function handleClick() {
const array1 = 'kiss';
const array2 = [12, 454, 45];
setResults([...results, array1]);
console.log(results);
}
return (
<div>
<h1>Hello StackBlitz!</h1>
<p>Start editing to see some magic happen :)</p>
<Comp result={results} />
<button onClick={() => handleClick()}> {results} </button>
<div>
{results.map(() => {
return <Comp result={results[5]} />;
})}
</div>
</div>
);
}
Of course this is NOT the best solution, but I hope you understand what happened, and like you will see you need to press the button 5 times to get something for the results[5] <Comp result={results[5]} />
and for the last you need to change the Comp:
import React, { useState } from 'react';
const Comp = ({result}) => {
const [compResults, setcompResults] = useState(result);
console.log(compResults)
return (
<div>
{compResults}
</div>
);
}
export default Comp
I'm just learning React and ran into a problem. I'm making a bunch of counter components that look like this :
The problem is that I have defined the state in each of these counters which is 3 of them and I'd like to pass the value (number) into the parent so I can add it up and display the total count.
Here is my child counter component:
import React, { useState } from "react";
const Counter = () => {
const [count, setcount] = useState(0)
const handleIncrement=()=>{
setcount(count+1);
}
const handleDecrement=()=>{
setcount(count+-1);
}
return (
<div>
<button onClick={()=>{handleIncrement()}}>+</button>
<span>{count}</span>
<button onClick={()=>{handleDecrement()}}>-</button>
</div>
);
};
export default Counter;
And here is the parent which I want to pass my values to so I can add them up and show the total :
import React from 'react'
import Counter from './Counter'
const Counters = () => {
return (
<>
<h3>totalcount:</h3>
<Counter/>
<Counter/>
<Counter/>
</>
)
}
export default Counters
What I tried was to make multiple states but I can't get a good way to make this. I know there's an easy answer for this and I'm making it too complicated. If you guys have other optimization for my code please share.
In React state goes top to bottom. A nested component can update the state of a parent if a function defined in the parent has been passed to it as props. Which means, what you wanna do is not possible as your code is set up. A way to achieve what you are looking for is:
Pass setCounter down to each Counter instance as props, like so:
import React, { useState } from 'react'
import Counter from './Counter'
const Counters = () => {
const [countOne, setCountOne] = useState(0)
const [countTwo, setCountTwo] = useState(0)
const [countThree, setCountThree] = useState(0)
return (
<>
<h3>totalcount: {countOne + countTwo countThree} </h3>
<Counter count = {countOne} setCount = {setCountOne} />
<Counter count = {countTwo} setCount = {setCount} />
<Counter count = {countThree} setCount = {setCountThree} />
</>
)
}
export default Counters
Get setCounter from the props inside Counter and use it where you want:
import React, { useState } from "react";
const Counter = ({count, setCount}) => {
const handleIncrement=()=>{
setCount(count+1);
}
const handleDecrement=()=>{
setCount(count+-1);
}
return (
<div>
<button onClick={()=>{handleIncrement()}}>+</button>
<span>{count}</span>
<button onClick={()=>{handleDecrement()}}>-</button>
</div>
);
};
export default Counter;
What I want:
I'm trying to add a dynamic theme option to a react-styleguidist project I'm working on. Following the idea laid out in this unfinished and closed pr, I added a custom ThemeSwitcher component, which is a select menu that is rendered in the table of contents sidebar. Selecting an option should update the brand context, which renders the corresponding theme using styled-components' BrandProvider. It should function like the demo included with the closed pr: https://fancy-sg.surge.sh/.
What's not working:
I can't access the same context in my ThemedWrapper as is provided and updated in the StyleguideWrapper and ThemeSwitcher. Examining the tree in the React Components console, it looks like react-styleguidist may render ReactExample outside of the StyleguideRenderer, which means it loses the context from the provider in that component.
Assuming I'm correct about the context not updating in ThemedWrapper due to it being located outside of StyleGuideRenderer, two high level ideas I have (but haven't been able to figure out how to do) are:
Find the correct component that is an ancestor of both StyleGuideRenderer and ReactExample in the react-styleguidist library and add the BrandProvider there so that ThemedWrapper now has context access
Some other context configuration that I haven't found yet that will allow two components to consume the same context without having a provider as an ancestor (is this possible??)
What I have:
Here are the condensed versions of the relevant code I'm using.
brand-context.js (exports context and provider, inspired by Kent C Dodds
import React, { createContext, useState, useContext } from 'react';
const BrandStateContext = createContext();
const BrandSetContext = createContext();
function BrandProvider({ children, theme }) {
const [brand, setBrand] = useState(theme);
return (
<BrandStateContext.Provider value={brand}>
<BrandSetContext.Provider value={(val) => setBrand(val)}>
{children}
</BrandSetContext.Provider>
</BrandStateContext.Provider>
);
}
function useBrandState() {
return useContext(BrandStateContext);
}
function useBrandSet() {
return useContext(BrandSetContext);
}
export { BrandProvider, useBrandState, useBrandSet };
StyleGuideWrapper.jsx (Copy of rsg-components/StyleguideRenderer, with addition of ThemeSwitcher component to toggle theme from ui; passed in styleguide config as StyleGuideRenderer)
import React from 'react';
import cx from 'clsx';
import Styled from 'rsg-components/Styled';
import ThemeSwitcher from './ThemeSwitcher';
import { BrandProvider } from './brand-context';
export function StyleGuideRenderer({ children, classes, hasSidebar, toc }) {
return (
<BrandProvider>
<div className={cx(classes.root, hasSidebar && classes.hasSidebar)}>
<main className={classes.content}>
{children}
</main>
{hasSidebar && (
<div className={classes.sidebar} data-testid="sidebar">
<section className={classes.sidebarSection}>
<ThemeSwitcher classes={classes} />
</section>
{toc}
</div>
)}
</div>
</BrandProvider>
);
}
StyleGuideRenderer.propTypes = propTypes;
export default Styled(styles)(StyleGuideRenderer);
ThemeSwitcher.jsx
import React from 'react';
import Styled from 'rsg-components/Styled';
import { useBrandSet, useBrandState } from './brand-context';
const ThemeSwitcher = ({ classes }) => {
const brand = useBrandState();
const setBrand = useBrandSet();
const onBrandChange = (e) => setBrand(e.target.value);
const brands = ['foo', 'bar'];
return (
<label className={classes.root}>
Brand
<select value={brand} onChange={onBrandChange}>
{brands.map((brand) => (
<option key={brand} value={brand}>{brand}</option>
))}
</select>
</label>
);
};
export default Styled(styles)(ThemeSwitcher);
ThemedWrapper.jsx (passed in styleguide config as Wrapper, and wraps each example component to provide them to styled-components)
import React from 'react';
import { ThemeProvider } from 'styled-components';
import { BrandStateContext } from './brand-context';
const LibraryProvider = ({ brand, children }) => {
return (
<ThemeProvider theme={brand}>{children}</ThemeProvider>
);
};
function ThemedWrapper({ children }) {
return (
<BrandStateContext.Consumer>
{brand => (
<LibraryProvider brand={brand}>{children}</LibraryProvider>
)}
</BrandStateContext.Consumer>
);
}
export default ThemedWrapper;
I'm trying to make a component that takes a list of other components as a prop and then renders one of those components as a child according to an index held in the parent's state.
Ultimately I want to be able to call the 'getValidation' function of a child element in the array using imperative handler and forwardRef methodology. I've done this for a single child component but can't figure out how to do it with an array of children. I thought about creating an array of refs in the parent component but couldn't get that right. Would appreciate any help and alternative ways of going about this are more than welcome.
E.g.
Parent:
import React, {createRef, useRef, useEffect, useState} from 'react';
const Parent = props => {
const [currentChildIndex, setCurrentChildIndex] = useState(0);
return (
<div className='parent'>
{
props.children[currentChildIndex]
}
</div>
)
};
export default Parent;
Child:
import React, {forwardRef, useEffect, useImperativeHandle, useRef} from 'react';
const Child = forwardRef((props, ref) => {
isValidated() {
//stuff that validates the form
return true;
}
useImperativeHandle(ref, () => ({
getValidation() {
return isValidated();
}
}));
return (
<div className='child'>
{form with inputs and things}
</div>
)
});
export default Child;
So I'm not sure if this is the best way to do it but I was having trouble asigning the refs to my array from inside the render method of the Parent component so I made a function to render the child component and called that from the render method and it seems to be working:
import React, {createRef, useRef, useEffect, useState} from 'react';
const Parent = props => {
const [currentChildIndex, setCurrentChildIndex] = useState(0);
function renderChildComponent(CurrentComponent) {
return (
<CurrentComponent ref={childRefs[currentChildIndex] = createRef()} />
)
}
return (
<div className='parent'>
{
renderChildComponent(props.children[currentChildIndex])
}
</div>
)
};
export default Parent;
I'm having an issue where react-loadable is causing one of my input components to re-render and lose focus after a state update. I've done some digging and I can't find anyone else having this issue, so I think that I'm missing something here.
I am attempting to use react-loadable to dynamically include components into my app based on a theme that the user has selected. This is working fine.
./components/App
import React from 'react';
import Loadable from 'react-loadable';
/**
* Import Containers
*/
import AdminBar from '../../containers/AdminBar';
import AdminPanel from '../../components/AdminPanel';
import 'bootstrap/dist/css/bootstrap.css';
import './styles.css';
const App = ({ isAdmin, inEditMode, theme }) => {
const MainContent = Loadable({
loader: () => import('../../themes/' + theme.name + '/components/MainContent'),
loading: () => (<div>Loading...</div>)
});
const Header = Loadable({
loader: () => import('../../themes/' + theme.name + '/components/Header'),
loading: () => (<div>Loading...</div>)
});
return (
<div>
{
(isAdmin) ? <AdminBar
className='admin-bar'
inEditMode={inEditMode} /> : ''
}
<Header
themeSettings={theme.settings.Header} />
<div className='container-fluid'>
<div className='row'>
{
(isAdmin && inEditMode) ? <AdminPanel
className='admin-panel'
theme={theme} /> : ''
}
<MainContent
inEditMode={inEditMode} />
</div>
</div>
</div>
);
};
export default App;
./components/AdminPanel
import React from 'react';
import Loadable from 'react-loadable';
import './styles.css';
const AdminPanel = ({ theme }) => {
const ThemedSideBar = Loadable({
loader: () => import('../../themes/' + theme.name + '/components/SideBar'),
loading: () => null
});
return (
<div className='col-sm-3 col-md-2 sidebar'>
<ThemedSideBar
settings={theme.settings} />
</div>
);
};
export default AdminPanel;
This is what my <ThemedSideBar /> components looks like:
./themes/Default/components/SideBar
import React from 'react';
import ThemeSettingPanel from '../../../../components/ThemeSettingPanel';
import ThemeSetting from '../../../../containers/ThemeSetting';
import './styles.css';
const SideBar = ({ settings }) => {
return (
<ThemeSettingPanel
name='Header'>
<ThemeSetting
name='Background Color'
setting={settings.Header}
type='text'
parent='Header' />
<ThemeSetting
name='Height'
setting={settings.Header}
type='text'
parent='Header' />
</ThemeSettingPanel>
);
};
export default SideBar;
./components/ThemeSettingPanel
import React from 'react';
import { PanelGroup, Panel } from 'react-bootstrap';
const ThemeSettingPanel = ({ name, children }) => {
return (
<PanelGroup accordion id='sidebar-accordion-panelGroup'>
<Panel>
<Panel.Heading>
<Panel.Title toggle>{name}</Panel.Title>
</Panel.Heading>
<Panel.Body collapsible>
{children}
</Panel.Body>
</Panel>
</PanelGroup>
);
};
export default ThemeSettingPanel;
./containers/ThemeSetting
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { themeSettingChange } from '../App/actions';
import ThemeSetting from '../../components/ThemeSetting';
class ThemeSettingContainer extends Component {
constructor(props) {
super(props);
this.handleOnChange = this.handleOnChange.bind(this);
}
handleOnChange(name, parent, value) {
const payload = {
name: name,
parent,
value: value
};
this.props.themeSettingChange(payload);
}
render() {
return (
<ThemeSetting
name={this.props.name}
setting={this.props.setting}
parent={this.props.parent}
type={this.props.type}
handleOnChange={this.handleOnChange} />
);
}
}
//----Redux Mappings----//
const mapStateToProps = (state) => ({
});
const mapDispatchToProps = {
themeSettingChange: (value) => themeSettingChange(value)
};
export default connect(mapStateToProps, mapDispatchToProps)(ThemeSettingContainer);
./component/ThemeSetting
import React from 'react';
import TextField from '../common/TextField';
import './styles.css';
const ThemeSetting = ({ name, setting, type, parent, handleOnChange }) => {
return (
<div className='row theme-setting'>
<div className='col-xs-7'>
{name}
</div>
<div className='col-xs-5'>
{
generateField(type, setting, name, parent, handleOnChange)
}
</div>
</div>
);
};
function generateField(type, setting, name, parent, handleOnChange) {
const value = setting ? setting[name] : '';
switch (type) {
case 'text':
return <TextField
value={value}
name={name}
parent={parent}
handleOnChange={handleOnChange} />;
default:
break;
}
}
export default ThemeSetting;
./components/common/TextField
import React from 'react';
import { FormControl } from 'react-bootstrap';
const TextField = ({ value, name, parent, handleOnChange }) => {
return (
<FormControl
type='text'
value={value}
onChange={(e) => {
handleOnChange(name, parent, e.target.value);
}} />
);
};
export default TextField;
When a field inside of my Admin Panel is updated, a state change is triggered. It seems like this triggers react-loadable to re-render my <ThemedSideBar /> components which destroys my input and creates a new one with the updated value. Has anyone else had this issue? Is there a way to stop react-loadable from re-rendering?
EDIT: Here is the requested link to the repo.
EDIT: As per conversation in the comments, my apologies, I misread the question. Answer here is updated (original answer below updated answer)
Updated answer
From looking at the react-loadable docs, it appears that the Loadable HOC is intended to be called outside of a render method. In your case, you are loading ThemedSideBar in the render method of AdminPanel. I suspect that the change in your TextEdit's input, passed to update your Redux state, and then passed back through the chain of components was causing React to consider re-rendering AdminPanel. Because your call to Loadable was inside the render method (i.e. AdminPanel is a presentational component), react-loadable was presenting a brand new loaded component every time React hit that code path. Thus, React thinks it needs to destroy the prior component to appropriately bring the components up to date with the new props.
This works:
import React from 'react';
import Loadable from 'react-loadable';
import './styles.css';
const ThemedSideBar = Loadable({
loader: () => import('../../themes/Default/components/SideBar'),
loading: () => null
});
const AdminPanel = ({ theme }) => {
return (
<div className='col-sm-3 col-md-2 sidebar'>
<ThemedSideBar
settings={theme.settings} />
</div>
);
};
export default AdminPanel;
Original answer
It seems that your problem is likely related to the way you've built TextField and not react-loadable.
The FormControl is taking value={value} and the onChange handler as props. This means you've indicated it is a controlled (as opposed to uncontrolled) component.
If you want the field to take on an updated value when the user types input, you need to propagate the change caught by your onChange handler and make sure it gets fed back to the value in the value={value} prop.
Right now, it looks like value will always be equal to theme.settings.Height or the like (which is presumably null/empty).
An alternative would be to make that FormControl an uncontrolled component, but I'm guessing you don't want to do that.