What I would like to be able to do is to initialize my context with a state and a function that updates that state.
For example, say I have the following:
export default function MyComponent () {
const MyContext = React.createContext()
const [myState, setMyState] = useState('1')
const contextValue = {
currentValue: myState,
setCurrentValue: (newValue) => setMyState(newValue)
}
return (
<MyContext.Provider value={contextValue}>
<MyContext.Consumer>
{e => <div onClick={() => e.setCurrentValue('2')}> Click me to change the value </div>}
{e.currentValue}
</MyContext.Consumer>
</MyContext.Provider>
)
}
The {e.currentValue} correctly outputs '1' at first, but when I click the button, nothing changes.
What I would expect is that e.setCurrentValue('2') would call setMyState('2'), which would update the state hook. This would then change the value of myState, changing the value of currentValue, and making '2' display.
What am I doing wrong?
You would want to return a fragment from the context as one JSX root.
Check here - https://playcode.io/931263/
import React, { createContext, useState } from "react";
export function App(props) {
const MyContext = React.createContext();
const [myState, setMyState] = useState("1");
const contextValue = {
currentValue: myState,
setCurrentValue: newValue => setMyState(newValue)
};
return (
<MyContext.Provider value={contextValue}>
<MyContext.Consumer>
{e => (
<>
<div onClick={() => e.setCurrentValue("2")}>
Click me to change the value
</div>
{e.currentValue}
</>
)}
</MyContext.Consumer>
</MyContext.Provider>
);
}
You're using e.currentValue outside of MyContext.Consumer context which does not have e, so it's throwing an error that e is not defined from e.currentValue.
You can wrap them up together under <MyContext.Consumer>{e => {}}</MyContext.Consumer>
function MyComponent() {
const MyContext = React.createContext();
const [myState, setMyState] = React.useState("1");
const contextValue = {
currentValue: myState,
setCurrentValue: (newValue) => setMyState(newValue),
};
return (
<MyContext.Provider value={contextValue}>
<MyContext.Consumer>
{(e) => (
<div>
<div onClick={() => e.setCurrentValue("2")}>
Click me to change the value
</div>
<div>{e.currentValue}</div>
</div>
)}
</MyContext.Consumer>
</MyContext.Provider>
);
}
ReactDOM.render(<MyComponent />, document.getElementById("root"));
<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="root"></div>
Related
I have a simple React component named Child which increments its count when clicked. Also, an outside state is saved which get updated when the Child component is incremented.
const Child = ({ onChange }) => {
const [count, setCount] = useState(0);
return (
<div>
<h1
onClick={() => {
setCount(prev => prev + 1);
onChange(1);
}}
>
Child count = {count}
</h1>
</div>
);
};
const App = () => {
const [count, setCount] = useState(0);
return (
<div className="App">
<h1> Parent Count = {count} </h1>
<Child onChange={(i) => setCount(count + i)} />
</div>
);
}
Above code works as expected. However, when I use the Context API to manage the outside state, the Child component's internal count remains at 0.
const App = () => {
const [count, setCount] = useState(0);
const AppContext = React.createContext();
return (
<div className="App">
<AppContext.Provider value={{ state: count, setState: setCount }}>
<>
<AppContext.Consumer>
{({ state }) => <h1> Parent Count = {state} </h1>}
</AppContext.Consumer>
<AppContext.Consumer>
{({ state, setState }) => (
<Child onChange={(i) => setState(state + i)} />
)}
</AppContext.Consumer>
</>
</AppContext.Provider>
</div>
);
};
Sample CodeSandbox.
Why the internal state of the element inside AppContext.Provider always reset to its initial state?
First, the creation of a context should be outside of a Component, which means this line:
const AppContext = React.createContext()
should be outside of App. Second, a context setup can be simpler, as below (I separated each component in its own file, so it resembles to common use cases), and working forked CodeSandbox here.
App:
import { useState, createContext } from "react";
import Child from "./Child";
export const AppContext = createContext();
const App = () => {
const [count, setCount] = useState(0);
return (
<div className="App">
<h1> Parent Count = {count} </h1>
<AppContext.Provider value={{ state: count, setState: setCount }}>
<Child />
</AppContext.Provider>
</div>
);
};
export default App;
Child:
import { useContext, useState } from "react";
import { AppContext } from "./App";
const Child = () => {
const { state, setState } = useContext(AppContext);
const [count, setCount] = useState(0);
return (
<div>
<h1
onClick={() => {
setCount((prev) => prev + 1);
setState(state + 1);
}}
>
Child count = {count}
</h1>
</div>
);
};
export default Child;
Can anyone give an answer?
Unable to update the state when I click getbtn -> placeRange pass jsx to setbtn ->then unable to update the State when Silde the Range.
import React, { useState } from "react";
export default function Stack() {
const [value, setvalue] = useState(0);
const [btn, setbtn] = useState(<></>);
function placeRange() {
const jsx = (
<>
<input
type="range"
onChange={(e) => {
setvalue(e.target.value);
}}
/>
<h1>{value}</h1>
</>
);
setbtn(jsx);
}
return (
<>
<button onClick={placeRange}>getrange</button>
{btn}
</>
);
}
This seems to be working for me.
Here's a working example at codesandbox
import { useState } from "react";
export default function App() {
const [value, setvalue] = useState(0);
const [btn, setbtn] = useState(<></>);
function placeBtn() {
const jsx = (
<>
<button
onClick={() => {
setvalue(1);
}}
>
Convert to 1
</button>
</>
);
setbtn(jsx);
}
return (
<>
<h1>{value}</h1>
<button onClick={placeBtn}>getbtn</button>
{btn}
</>
);
}
Not sure I fully understand what you are trying to achieve, this is not clear enough for me.
From your code it seems you are trying to set an input range in place when clicking the button, then, you want this range to update the number below it.
If this is the case I suggest the following solution:
import React, { useState } from "react";
export default function Stack() {
const [value, setvalue] = useState(0);
const [showRange, setShowRange] = useState(false);
function placeRange() {
setShowRange(!showRange);
}
return (
<>
<button onClick={placeRange}>getrange</button>
{showRange && (
<>
<input
type="range"
onChange={(e) => {
setvalue(e.target.value);
}}
/>
<h1>{value}</h1>
</>
)}
</>
);
}
How would I click a input (open file dialog) in a child component from the parent component? Here's what I have but I'm not quite sure how to bring it home in the click handler.
import React, { createRef } from 'react';
const inputRef = createRef();
// Parent ==============
const Parent = () => {
const handleClick = () => {
// open file dialog in child
};
return (
<>
<Child />
<button onClick={handleClick}></button>
</>
);
};
// Child ==============
const Child = () => {
return (
<>
<input type="file" ref={inputRef} />
</>
);
};
I used forwardRef to pass the ref from parent to child.
import React, { createRef } from 'react';
const inputRef = createRef();
// Parent ==============
const Parent = () => {
const handleClick = () => {
inputRef.current.click();
};
return (
<>
<Child ref={inputRef}/>
<button onClick={handleClick}></button>
</>
);
};
// Child ==============
const Child = React.forwardRef((props, ref) => {
return (
<>
<input type="file" ref={ref} />
</>
);
});
Just use inputRef.current.click(); inside your handleClick method.
import React, { createRef } from 'react';
const inputRef = createRef();
// Parent ==============
const Parent = () => {
const handleClick = () => {
inputRef.current.click();
};
return (
<>
<Child />
<button onClick={handleClick}></button>
</>
);
};
// Child ==============
const Child = () => {
return (
<>
<input type="file" ref={inputRef} />
</>
);
};
On my follow up question from here : How to pass data from child to parent component using react hooks
I have another issue.
Below is the component structure
export const Parent: React.FC<Props> = (props) => {
const [disabled, setDisabled] = React.useState(false);
const createContent = (): JSX.Element => {
return (
<Authorization>
{<ErrorPanel message={errorMessage} setDisabled={setDisabled}/>}
<MyChildComponent/>
</<Authorization>
);
}
return (
<Button onClick={onSubmit} disabled={disabled}>My Button</Button>
{createContent()}
);
};
const Authorization: React.FC<Props> = (props) => {
const [disabled, setDisabled] = React.useState(false);
const render = (errorMessage : JSX.Element): JSX.Element => {
return (
<>
{<ErrorPanel message={errorMessage} setDisabled={setDisabled}/>}
</>
);
};
return (
<>
<PageLoader
queryResult={apiQuery}
renderPage={render}
/>
{props.children}
</>
);
};
How do I pass the disabled state value from Authorization component to my child which is invoked by
{props.children}
I tried React.cloneElement & React.createContext but I'm not able to get the value disabled to the MyChildComponent. I could see the value for disabled as true once the errorMessage is set through the ErrorPanel in the Authorization component.
Do I need to have React.useEffect in the Authorization Component?
What am I missing here?
You need to use React.Children API with React.cloneElement:
const Authorization = ({ children }) => {
const [disabled, setDisabled] = React.useState(false);
const render = (errorMessage) => {
return (
<>{<ErrorPanel message={errorMessage} setDisabled={setDisabled} />}</>
);
};
return (
<>
<PageLoader queryResult={apiQuery} renderPage={render} />
{React.Children.map(children, (child) =>
React.cloneElement(child, { disabled })
)}
</>
);
};
// |
// v
// It will inject `disabled` prop to every component's child:
<>
<ErrorPanel
disabled={disabled}
message={errorMessage}
setDisabled={setDisabled}
/>
<MyChildComponent disabled={disabled} />
</>
You can make use of React.cloneElement to React.Children.map to pass on the disabled prop to the immediate children components
const Authorization: React.FC<Props> = (props) => {
const [disabled, setDisabled] = React.useState(false);
const render = (errorMessage : JSX.Element): JSX.Element => {
return (
<>
{<ErrorPanel message={errorMessage} setDisabled={setDisabled}/>}
</>
);
};
return (
<>
<PageLoader
queryResult={apiQuery}
renderPage={render}
/>
{React.Children.map(props.children, child => {
return React.cloneElement(child, { disabled })
})}
</>
);
};
UPDATE:
Since you wish to update the parent state to, you should store the state and parent and update it there itself, instead of storing the state in child component too.
export const Parent: React.FC<Props> = (props) => {
const [disabled, setDisabled] = React.useState(false);
const createContent = (): JSX.Element => {
return (
<Authorization setDisabled={setDisabled}>
{<ErrorPanel message={errorMessage} disabled={disabled} setDisabled={setDisabled}/>}
<MyChildComponent disabled={disabled}/>
</<Authorization>
);
}
return (
<Button onClick={onSubmit} disabled={disabled}>My Button</Button>
{createContent()}
);
};
const Authorization: React.FC<Props> = (props) => {
const render = (errorMessage : JSX.Element): JSX.Element => {
return (
<>
{<ErrorPanel message={errorMessage} disabled={props.disabled} setDisabled={props.setDisabled}/>}
</>
);
};
return (
<>
<PageLoader
queryResult={apiQuery}
renderPage={render}
/>
{props.children}
</>
);
};
I have trouble debugging this code.
I have an App component:
function App() {
const [state, setState] = useState(0);
const onSelectItem = () => {
console.log("🐞: onSelectItem -> currentState", state);
};
// items is an array of ReactNode: button, when click on it. It will log the currentState.
const items = ["FirstItem", "SecondItem"].map(item => (
<button key={item} onClick={() => onSelectItem()}>
{item}
</button>
);
);
return (
<div className="App">
<Menu items={items} />
<hr />
<button onClick={() => setState(prevState => prevState + 1)}>Change State</button>
</div>
);
}
My Menu components will receive items prop, and render it. It also has ability to set the active item. For simplicity's sake, I render a button to set activeItem to the first one. The active item will also be rendered.
function Menu({ items }) {
const [activeItem, setActiveItem] = useState(items[0]);
return (
<div>
{items}
<hr />
{activeItem}
</div>
);
}
Now, come to the main part:
I press the button (before hr) => it shows currentState (OK)
I press the active button (after hr) => it shows currentState (OK)
I press change state button => the state now changes to 1 (OK)
Now, if I press the button (before hr ) => It shows currentState is 1 (OK)
But, if I press the active button (after hr ) => It still shows 0 (which is the last state) (???)
My guess is React keeps remembering everything when using useState. But I'm not sure. Could anyone explain this for me!
I also include the snippets for you to easily understand my problem.
const {useState} = React;
function Menu({ items }) {
const [activeItem, setActiveItem] = useState(items[0]);
return (
<div>
{items}
<hr />
<span>Active Item:</span>
{activeItem}
</div>
);
}
function App() {
const [state, setState] = useState(0);
console.log(state);
const onSelectItem = () => {
console.log("🐞: onSelectItem -> currentState", state);
};
const items = ["FirstItem", "SecondItem"].map(item => {
return (
<button key={item} onClick={() => onSelectItem()}>
{item}
</button>
);
});
return (
<div className="App">
<Menu items={items} />
<hr />
<button onClick={() => setState(prevState => prevState + 1)}>Change State</button>
</div>
);
}
ReactDOM.render(<App />, document.getElementById('app'));
<div id="app"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="react"></div>
You are trying to access the state from App component in your Menu component.
State is local to that component and can't be accessed outside, if you would like to access the state outside the component you can refer to the useContext hook implementation.
https://reactjs.org/docs/hooks-reference.html#usecontext
Reason you are seeing 0 in the Active state is that is the default value of useState.
You need to pass key to your menu component.
Whenever there is change in props, the component has to re-render with new props.
Refer this artcile from their official docs - https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#recommendation-fully-uncontrolled-component-with-a-key
Change I made is passing state as key to Menu component
const {useState} = React;
function Menu({ items }) {
const [activeItem, setActiveItem] = useState(items[0]);
return (
<div>
{items}
<hr />
<span>Active Item:</span>
{activeItem}
</div>
);
}
function App() {
const [state, setState] = useState(0);
console.log(state);
const onSelectItem = () => {
console.log("🐞: onSelectItem -> currentState", state);
};
const items = ["FirstItem", "SecondItem"].map(item => {
return (
<button key={item} onClick={() => onSelectItem()}>
{item}
</button>
);
});
return (
<div className="App">
<Menu items={items} key={state}/>
<hr />
<button onClick={() => setState(prevState => prevState + 1)}>Change State</button>
</div>
);
}
ReactDOM.render(<App />, document.getElementById('app'));
<div id="app"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="react"></div>