Is there any api that allow us to write code something like this:
const MyComponents = () => {
const [number, setNumber] = useState(0);
return {
Btn: <Button onPress={() => setNumber(number + 1)}>
{number}
</Button>,
Log: <p>{number}</p>
}
}
const Perent = () => <>
<div ...>
<MyComponents.Btn/>
...
...
</div>
<MyComponents.Log/>
</>
Some kind of ability to group some Component.And render them in different places...
Seems like this would be better achieved by using a Context.
E.g.
const { createContext, useState, useContext } = React;
const CountContext = createContext();
const CountContainer = ({ children }) => {
const [number, setNumber] = useState(0);
return <CountContext.Provider value={{ number, setNumber }}>
{children}
</CountContext.Provider>
};
const CountButton = () => {
const { number, setNumber } = useContext(CountContext);
return <button onClick={() => setNumber((c) => c + 1)}>
{number}
</button>;
};
const CountLog = () => {
const { number } = useContext(CountContext);
return <p>{number}</p>;
};
const SomeCountButtons = () => <div><CountButton /><CountButton /></div>;
const App = () => (<div>
<CountContainer>
<CountButton />
<CountLog />
</CountContainer>
<CountContainer>
<SomeCountButtons />
<CountLog />
</CountContainer>
</div>);
ReactDOM.render(<App />, document.getElementById('app'));
<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="app"></div>
Then any <CountButton>s or <CountLog>s that occur anywhere within the same <CountContainer> will be able to share their state.
Related
I have a useRef hook and two components. In one component, I increase the value on click by 1 unit, and in the second component, I draw the value. I pass the value itself through useContext.
Now the problem is that the value is not being redrawn. How can this be fixed?
export const ContactContext = React.createContext();
function App() {
const countItem = useRef(1);
const value = { countItem };
return (
<ContactContext.Provider value={value}>
<div>
<AddValue />
</div>
<div>
<Logo />
</div>
</ContactContext.Provider>
);
}
const AddValue = () => {
const { countItem } = useContext(ContactContext);
const addItemHandler = () => {
countItem.current = countItem.current + 1;
};
return (
<>
<div>
<button
onClick={addItemHandler}
>
<img src="plus.svg" alt="plus logo" />
</button>
</div>
</>
);
};
function Logo() {
const { countItem } = useContext(ContactContext);
return (
<p data-testid="statistics">
{`Count of channels: ${countItem.current}`} <br />
</p>
);
}
useRef wont cause components in React to rerender
function App() {
const [countItem, setCountItem] = useState(1)
const value = { countItem, setCountItem };
In AddValue
const AddValue = () => {
const { countItem, setCountItem } = useContext(ContactContext);
const addItemHandler = () => {
setCountItem(c => c +1)
};
Reading the new React docs for state management will help
Hope it helps
Replace useRef with useState.
useRef update the value but does not rerender.
I'm dynamically generating children components of HOC parent (see below). I pass the props directly to one of children and set the prop in it. I expect to see child re-rendering on props change but it doesn't.
Is the code incorrect somewhere?
ParentComponent
...
const ParentComponent = ({children}) => {
const [state1, setState1] = useState(true);
...
const changeOpacity = event => setState1(!state1);
const renderChildren = React.useCallback(() => React.Children.toArray(children).map((child, index) => (
<div key={index} style={{opacity: `${state1 ? 0 : 1}`}}>
{child}
</div>
)), [state1]);
return (
<div>
<Button onClick={changeOpacity}>Toggle Opacity</Button>
{renderChildren()}
</div>
);
};
App.js
...
const App = () => {
const [prop1, setProp1] = useState(123);
return (
<ParentComponent>
<Child1 prop1={prop1} setProp1={setProp1} />
<Child2 />
</ParentComponent>
);
};
In your ParentComponent, the children are cloned and then used to render as a part of the return value from the renderChildren function. Since the logic to compute children is not run on change of props to children, your child component is not affected by a change in its prop.
You can add children dependency to useCallback and it will work fine.
const { useState, useCallback } = React;
const ParentComponent = ({children}) => {
const [state1, setState1] = useState(true);
const changeOpacity = event => setState1(!state1);
const renderChildren = useCallback(() => React.Children.map(children, (child, index) => (
<div key={index} style={{opacity: `${state1 ? 0 : 1}`}}>
{child}
</div>
)), [children, state1]);
return (
<div>
<button onClick={changeOpacity}>Toggle Opacity</button>
{renderChildren()}
</div>
);
};
const Child1 = ({prop1, setProp1}) => <div>{prop1} <button onClick={() => setProp1(234)}>Click</button></div>;
const Child2 = () => <div>Hello</div>
const App = () => {
const [prop1, setProp1] = useState(123);
return (
<ParentComponent>
<Child1 prop1={prop1} setProp1={setProp1} />
<Child2 />
</ParentComponent>
);
};
ReactDOM.render(<App />, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="app" />
Is there anything prevent you from the approach below;
const ParentComponent = ({children}) => {
const [state1, setState1] = useState(true);
...
const changeOpacity = event => setState1(!state1);
const renderChildren = useCallback(() => React.Children.toArray(children).map((child, index) => (
<div key={index}>
{child}
</div>
)), [children]);
return (
<div>
<Button onClick={changeOpacity}>Toggle Opacity</Button>
{state1 && renderChildren()}
</div>
);
};
I have a component where I am storing API response in responseArray and I am using this responseArray to initialize matchFundResults state using hooks. Next, I am trying to run a useEffect using matchFundResults as a dependency but matchFundResults is always coming as blank whereas I have value in responseArray. How should this be fixed?
const MatchFundModal = ({ row, val }) => {
let rightBody;
const dispatch = useDispatch();
const selectedRows = useSelector((state) => state.pcPerformance.processor.checkedRows.selectedRows || []);
const responseArray = useSelector((state) => state.pcPerformance.processor.fundAliases);
const [showMatchFundModal, setshowMatchFundModal] = useState(val);
const [matchFundResults, setMatchFundResults] = useState(responseArray);
const [activeRowData, setActiveRowData] = useState({ Id: null, FundName: null, SourceId: null });
const [selectedMatchFund, setSelectedMatchFund] = useState();
const [searchFieldVal, setSearchFieldVal] = useState();
if (!activeRowData.Id) {
const firstRow = selectedRows.length > 0 && selectedRows[0];
setActiveRowData({ Id: firstRow.Id, FundName: firstRow.FundName, SourceId: firstRow.SourceId });
//dispatch(getFundNameAliasMatch(firstRow.FundName, firstRow.SourceId));
}
useEffect(() => {
dispatch(getFundNameAliasMatch(activeRowData.FundName, activeRowData.SourceId));
}, [activeRowData.Id]);
console.log('Helloworld responseArray', responseArray);
console.log('Helloworld matchFundResults', matchFundResults);
useEffect(() => {
rightBody = matchFundResults**.map((item) => {
return (
<li key={item.FundId}>
<input
type="radio"
value={item.FundId}
name="action-radio"
id={`action-radio-${item.FundId}-${item.SourceId}`}
onClick={(e) => handleRadioButtonClick(e)}
/>
<span>{item.FundName}</span>
<br />
<span className="searchFundID">#{item.FundId}</span>
</li>
);
});
}, [matchFundResults, activeRowData.Id]);
const matchFundBody = (
<div className="matchFundModal grid">
<p className="matchFundModal__header 12">Match or add</p>
<div className="matchFundModal__body 12">
<div className="matchFundModal__body__right 6">
<p id="possibleMatchText">Possible matches</p>
<ul>{rightBody}</ul>
</div>
</div>
<div className="matchFundModal__footer 12">
<button className="matchFundModal__footer__button">Match Selected</button>
</div>
</div>
);
return (
<Fragment>
<Modal
isOpen={showMatchFundModal}
bodyContent={matchFundBody}
showHeader={false}
handleOnModalToggleFunction={hideModal}
handleOnModalPrimaryButtonClick={onPrimaryButtonClick}
handleOnModalSecondaryButtonClick={hideModal}
primaryButtonText={'Match Fund'}
centered={true}
size="sm"
hideFooterButtons={true}
modalClasses="matchFundModal"
showFooter={false}
/>
</Fragment>
);
};
export default MatchFundModal;```
[![enter image description here][1]][1]
[1]: https://i.stack.imgur.com/HxIv4.png
I don't know why you would want to copy responseArray to matchFundResults instead of just using responseArray directly but you never use setMatchFundResults when responseArray changes so you only set it initially and at that time responseArray is probably an empty array. You could do the following:
const responseArray = useSelector((state) =>
state.pcPerformance.processor.fundAliases);
const [matchFundResults, setMatchFundResults] = useState(responseArray);
//every time responseArray changes you need to set matchFundResults
useEffect(()=>setMatchFundResults(responseArray),[responseArray])
But it probably would be better to not copy redux state to local state and instead just use redux state directly.
Your comment suggest you have all data in redux state and would like to filter the data (the reason why you copy redux state to local state). You could do that with selectors in the following way:
const { Provider, useSelector } = ReactRedux;
const { createStore } = Redux;
const { createSelector } = Reselect;
const { useState, useMemo } = React;
const initialState = {
data: [
'hello world',
'hello redux',
'hello react',
'goodbye jquery',
],
};
const reducer = (state) => state;
//selectors
const selectData = (state) => state.data;
const createSelectFilteredData = (filter) =>
createSelector([selectData], (data) =>
data.filter((item) =>
item.toLowerCase().includes(filter.toLowerCase())
)
);
//creating store with redux dev tools
const store = createStore(reducer, initialState);
const App = () => {
const [filter, setFilter] = useState('');
const selectFilteredData = useMemo(
() => createSelectFilteredData(filter),
[filter]
);
const filteredData = useSelector(selectFilteredData);
return (
<div>
<label>
filter:
<input
type="text"
value={filter}
onChange={(e) => setFilter(e.target.value)}
/>
</label>
<div>
filtered data:
<pre>
{JSON.stringify(filteredData, undefined, 2)}
</pre>
</div>
</div>
);
};
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
<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>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/reselect/4.0.0/reselect.min.js"></script>
<div id="root"></div>
I have two components. MyButton and MyLabel component. I created 3 MyButton Compoents and 3 MyLabel Components. Each button has a different increment value. When you click on a button , the respective label should be updated not all the labels. At present all the labels are updating.
function MyButton(props) {
const onclick = () => {
props.onNumberIncrement(props.toBeIncremented);
};
return <button onClick={onclick}>+{props.toBeIncremented}</button>;
}
const MyLabel = function(props) {
return <label> {props.counter}</label>;
};
function App(props) {
const [counter, mySetCounter] = React.useState(0);
const handleClick = (incrementValue) => {
mySetCounter(counter + incrementValue);
};
return (
<div>
<MyButton
counter={counter}
onNumberIncrement={handleClick}
toBeIncremented={5}
/>
<MyButton
counter={counter}
onNumberIncrement={handleClick}
toBeIncremented={10}
/>
<MyButton
counter={counter}
onNumberIncrement={handleClick}
toBeIncremented={15}
/>
<br />
<MyLabel counter={counter} />
<MyLabel counter={counter} />
<MyLabel counter={counter} />
</div>
);
}
ReactDOM.render(<App />, document.getElementById('root'));
jsfiddle:
click here
Create a generator of button/label pairs with their local state and step. Generate the buttons and labels, and render them:
const useGenerateButtonAndLabel = step => {
const [counter, mySetCounter] = React.useState(0);
const onclick = React.useCallback(
() => mySetCounter(counter + step),
[step, counter]
);
return [
<button onClick={onclick}>+{step}</button>,
<label> {counter}</label>
];
};
function App(props) {
const [button1, label1] = useGenerateButtonAndLabel(5);
const [button2, label2] = useGenerateButtonAndLabel(10);
const [button3, label3] = useGenerateButtonAndLabel(15);
return (
<div>
{button1}
{button2}
{button3}
<br />
{label1}
{label2}
{label3}
</div>
);
}
ReactDOM.render(<App />, document.getElementById('demo'));
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="demo"></div>
If you also need a total, each generated pair can also return it's current counter, and you can sum them in the parent. In this example, I also automate the items creation/rendering with Array.from(), map, and reduce.
const useGenerateButtonAndLabel = step => {
const [counter, mySetCounter] = React.useState(0);
const onclick = React.useCallback(
() => mySetCounter(counter + step),
[step, counter]
);
// step is used here is a key, but if step is not unique, it will fail. You might want to generate a UUID here
return [
<button key={step} onClick={onclick}>+{step}</button>,
<label key={step}> {counter}</label>,
counter
];
};
const sum = items => items.reduce((r, [,, counter]) => r + counter, 0);
function App(props) {
const items = Array.from({ length: 5 },
(_, i) => useGenerateButtonAndLabel(5 * (i + 1))
);
return (
<div>
{items.map(([button]) => button)}
<br />
{items.map(([, label]) => label)}
<div>Total: {sum(items)}</div>
</div>
);
}
ReactDOM.render(<App />, document.getElementById('demo'));
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="demo"></div>
Here is another solution using a single state variable :
function App(props) {
const [appState, setAppState] = React.useState([
{value: 0, incrementValue: 5},
{value: 0, incrementValue: 10},
{value: 0, incrementValue: 15}
]);
const handleClick = clickedIndex => () => {
setAppState(
appState.map((item, index) => clickedIndex !== index ?
item : ({...item, value: item.value + item.incrementValue})
)
);
};
return (
<div>
{
appState.map(({value, incrementValue}, index) => (
<MyButton
key={index}
counter={value}
onNumberIncrement={handleClick(index)}
toBeIncremented={incrementValue}
/>
));
}
<br />
{
appState.map(
({value}, index) => <MyLabel key={index} counter={value} />
);
}
</div>
);
}
I have an app I'm building in React where the url never updates despite each screen taking up the entire page.
What is the best way to render each screen when it's the screen to be viewed?
export const App = () => {
const [activeScreen, setActiveScreen] = useState("PAGE_1");
const goToScreen2 = () => setActiveScreen("PAGE_2");
const goToScreen3 = () => setActiveScreen("PAGE_3");
return (
<Wrapper>
{activeScreen === "PAGE_1" &&
<Page1 nextScreen={goToScreen2} />
}
{activeScreen === "PAGE_2" &&
<Page2 nextScreen={goToScreen3} />
}
{activeScreen === "PAGE_3" &&
<Page3 />
}
</Wrapper>
);
};
I would do something like this:
function Page1() {
return 'Page 1'
}
function Page2() {
return 'Page 2'
}
function Page3() {
return 'Page 3'
}
function Pager({
next,
prev,
children
}) {
return (
<div>
<button onClick={prev}>Prev</button>
{children}
<button onClick={next}>Next</button>
</div>
)
}
const pages = [Page1, Page2, Page3]
const App = () => {
const [activeScreen, setActiveScreen] = React.useState(0);
const goNext = () => setActiveScreen((activeScreen + 1) % pages.length);
const goBack = () => setActiveScreen((activeScreen - 1 + pages.length) % pages.length);
const ActivePage = pages[activeScreen]
return (
<Pager next={goNext} prev={goBack}>
<ActivePage />
</Pager>
);
};
ReactDOM.render( <App /> , document.querySelector('#root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.1/umd/react-dom.production.min.js"></script>
<div id="root" />