React context magically change other state - reactjs

Can anyone explain this thing? Why does other state magically change value when I modify a context?
title here is modified when I change someState even though these 2 value never interact with each other at all
const LapContext = React.createContext();
function Btn(arg){
return <div onClick={arg.click} className="btn" style={{display: "flex", alignItems: "center", justifyContent: "center", borderRadius: "15px", width: "275px", height: "6em", outline: "1px solid orange"}}>
<p style={{color:"rgb(255,165,0)", fontSize: "2em"}}>{arg.val}</p>
</div>
}
function Control(arg){
let [someState, setSomeState] = React.useContext(LapContext);
function setTitle(){
arg.setter("asdasd");
}
function changeSomeState(){
setSomeState(["a"]);
}
return <div>
<Btn val={"Button1"} click={setTitle}/>
<Btn val={"Button2"} click={changeSomeState}/>
</div>
}
function Main(arg){
{/*this state is modified for no reason*/}
let [title, setTitle] = React.useState("default");
return <div>
<p style={{fontSize: "3em", color: "yellow"}}>{title}</p>
<Control setter={setTitle}/>
</div>
}
function Page(){
{/*this state is shared with context*/}
let [someState, setSomeState] = React.useState(["some value"]);
return <LapContext.Provider value={[someState, setSomeState]}>
<Main key={uuid.v4()}/>
</LapContext.Provider>
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Page/>);

You have quite a few problems in your code, for example, you shouldn't pass imperative API like that, should rely on props/onChange callbacks API instead, it's more React way. Take a look at React docs on how components should interact with each other:
https://reactjs.org/docs/components-and-props.html
But if you just want an answer to the question, then the problem is here:
<Main key={uuid.v4()}/>
You generate a unique key on every re-render. This will cause React to identify the entire component as a "new" component among its siblings, un-mount it, and mount it back from scratch. Which results in your state "resetting" to the default one.
"key" is usually used in the List-like components, you don't need it here. Just remove it and the state will go back to "normal" behaviour.

Related

How can I create a React functionnal component and get HTML attributes and ref in props with typescript?

I'm trying to make a Trello like, I'm using react-beautiful-dnd and started from the classical example.
This is where I render the card (I just replaced the div with Task, my component)
<Task
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={{
userSelect: "none",
padding: 16,
margin: "0 0 8px 0",
minHeight: "50px",
...provided.draggableProps.style,
}}
>
{item.content}
</Task>
In my component, I want to get all div attributes passed in the code above.
So I did this :
import React from "react";
const Task: React.FC<JSX.IntrinsicElements["div"]> = (props) => {
const { children } = props;
return (
<div className="card card-green" {...props}>
<div className="card-content">
<div className="content">{children}</div>
</div>
</div>
);
};
export default Task;
It looks good, excepted for the "ref", I got a message in my console when I try to drag my card
Invariant failed: Cannot get dimension when no ref is set
And indeed my ref is always undefined.
Do you have an idea ?
I tried several examples found but no one works. I think I am missing a type somewhere

How can I access the "arrow" button click event on Material UI data grid?

In the documentation of Material UI Data Grid it has this code snippet (I've made a couple slight changes like importing DataGrid instead of DataGridPro, etc.):
import { DataGrid, useGridApiRef } from '#material-ui/data-grid';
import Alert from '#material-ui/lab/Alert';
export default function PageChange() {
const apiRef = useGridApiRef();
const [message, setMessage] = React.useState('');
const data = {'dummy-data-for-now': ''};
React.useEffect(() => {
/* ---- Theoretically I would replace columnResize with 'pageChange' ---- */
return apiRef.current.subscribeEvent('columnResize', (params) => {
setMessage(
`Column ${params.colDef.headerName} resized to ${params.computedWidth}px.`,
);
});
}, [apiRef]);
return (
<div style={{ width: '100%' }}>
<div style={{ height: 180, width: '100%' }}>
<DataGrid apiRef={apiRef} {...data} />
</div>
{message && (
<Alert severity="info" style={{ marginTop: 8 }}>
{message}
</Alert>
)}
</div>
);
}
I am implementing API pagination of 10 results at a time. I would like to be able to make a new API call for the next (or previous) 10 results when I click the arrow buttons.
The documentation refers to pageChange as an event option, but when I use the code snippet, I don't know how to utilize it. I would really appreciate any insight anyone might have on how to handle this situation.
This is a picture of the data grid button, for reference
#MaxAlex, thank you for the tip! This sent me on the right path and I finally got it working with server-side pagination. Resource
The key for me was to realize that on the onPageChange property within the DataGrid: onPageChange={(newPage) => handlePageChange(newPage)}, "newPage" was an object with the page number. I didn't need to specifically access the forward and backward arrows, but instead, just manage "newPage" in my handlePageChange function.

Redux-Persist Changes State after Application is ReHydrated

Working on something similar to Jotform.
This is my state:
const InitialState = {
BaseContainerEl: []
}
This is my approach, let's say I want to create Full Name fields (i.e three inputs with first, middle and last names). So, I create a component that renders a p-tag and onClick it runs the updateBase function where I stored the exact result (i.e the 3 inputs) in a variable 'el' and it update's the state using the virtual DOM created and stored in 'el'.
And it works fine, because after updating the state I render the element.
const FullName = (props: any) => {
const updateBase = () => {
const key = uuidv4();
const el = (
<BaseContainer key={key} id={key}>
<InputCustom
placeholder='First Name'
type='text'
label='First Name'
formControl='form-control form-control-sm'
style={{ width: '90%' }}
/>
<InputCustom
placeholder='Middle Name'
type='text'
label='Middle Name'
formControl='form-control form-control-sm'
style={{ width: '90%' }}
/>
<InputCustom
placeholder='Last Name'
type='text'
label='Last Name'
formControl='form-control form-control-sm'
style={{ width: '90%' }}
/>
</BaseContainer>
);
props.updateBaseContainerEl(el)
}
return (
<p draggable onClick={updateBase} className="text-muted" style={{
borderRadius: '2px',
boxShadow: ' 20px 20px 60px #bebebe-20px -20px 60px #ffffff',
}}>
<i className="fe fe-user" style={{
}}></i>
Full Name
</p>
);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
This the problem:
I am persisting the state using redux-persit, now when I refresh the application it rehydrates and after rehydration the virtual DOM stored in the state (i.e the react component) changes with some properties and values missing. According to the redux-persist docs, there are some values in javascript that cannot be represented in json.
So, it errors out: Uncaught Error: Objects are not valid as a React child (found: object with keys {type, key, ref, props, _owner, _store}). If you meant to render a collection of children, use an array instead.
Below are images of what it looks like before rehydration and after.
Is there any way around this?

React-testing-library not rendering computed styles from stylesheet

Basic scenario is such: I have a component which has width: 100% as defined in a stylesheet. Therefore it should retain the width of its parent component. I want to calculate the width of my component and apply it to my child component because I am rendering it via createPortal and I would like them to be the same width. This works in the browser. However, in my test, I am finding that window.getComputedStyle(component) is not returning any of the styles applied from the stylesheet.
As suggested, I could mock the javascript window, but that's actually counter to what I'm hoping to do, I think. I want to verify the behavior that is present in the browser, that window.getComputedStyle() returns all styles applied, not just the inline styles.
I have put a simple example into a codesandbox: https://codesandbox.io/s/goofy-wilson-6v4dp
Also here:
function App() {
return (
<div className="App">
<WidthComponent />
</div>
)
}
function WidthComponent() {
const myInput = useRef();
const [inputWidth, setInputWidth] = useState(0);
useEffect(() => {
console.log("in handleLoad");
const width = myInput.current ? myInput.current.offsetWidth : 0;
setInputWidth(width);
}, [myInput]);
return (
<div className="inherited-width" ref={myInput}>
<div style={{ width: inputWidth }} className="child-element">
Hello
</div>
</div>
);
}
// test
test("width is inherited", () => {
const { rerender } = render(
<div style={{ width: "452px" }}>
<WidthComponent />
</div>
);
const element = document.getElementsByClassName("child-element").item(0);
rerender(
<div style={{ width: "452px" }}>
<WidthComponent />
</div>
);
expect(window.getComputedStyle(element).width).toBe("452px");
});
.App {
font-family: sans-serif;
text-align: center;
width: 500px;
}
.inherited-width {
width: inherit;
}
Any help is appreciated.
However, in my test, I am finding that window.getComputedStyle(component) is not returning any of the styles applied from the stylesheet.
Note that if you're running your tests in JSDOM (i.e. every Jest test) then CSS isn't fully implemented. Specifically, the cascade part of CSS is not implemented (https://github.com/jsdom/jsdom/pull/2690). Inheritance is only partially implemented (display and visibility) (https://github.com/jsdom/jsdom/issues/2160).
I would suggest running tests that assert on computed styles only in browsers, not JSDOM. A codesandbox test is not running in an actual browser environment.

React keep state of previous component and render new state

I am working on a news app where I call an API and display cards of trending news.(picture 1)
<div style={{display: 'flex', flexWrap: 'wrap', padding: 20, alignItems: 'center', justifyContent: 'center' }}>
{this.state.news.map(news => (
<NewsCard
key={news.url}
_id={news.url}
description={news.description}
image={news.urlToImage}
source={news.source.name}
title={news.title}
summary={this.state.summary}
onExpand={this.handleDetailClick.bind(this, news.url)}
onSave={this.handleDetailClick.bind(this, news.url)}
/>
))}
</div>
Upon expanding a card it the shows a gist of the article after calling a second API.(picture 2)
handleDetailClick = link => {
// console.log('hadle detail', link);
API.summarize(link)
.then((res) => {
// console.log(res.body);
this.setState({
summary: res.body
})
// console.log(this.state.summary);
})
}
However I am stuck on this issue whereupon calling the gist API on a second card the first card gets the gist of the second card. How do I prevent this from happening?(picture 3)
So far I've tried using the componentDidUpdate lifecycle method to compare states and keep the state persistent for the first card but I am not able to figure this out.
Any pointers?
You'll need to add in some way of associating the summary data with the correct news item. Currently your program just gives the current summary to all NewsCard components.
The simplest way to do this with your current code would be to also record the url that the summary came from in your main component's (the parent of the NewsCard components) state as well. Then only pass the summary to the correct news item.
For example (adding summaryUrl to state) (note the summary= line):
<div style={{display: 'flex', flexWrap: 'wrap', padding: 20, alignItems: 'center', justifyContent: 'center' }}>
{this.state.news.map(news => (
<NewsCard
key={news.url}
_id={news.url}
description={news.description}
image={news.urlToImage}
source={news.source.name}
title={news.title}
summary={news.url === this.state.summaryUrl ? this.state.summary : ""}
onExpand={this.handleDetailClick.bind(this, news.url)}
onSave={this.handleDetailClick.bind(this, news.url)}
/>
))}
</div>
So we only pass this.state.summary to the NewsCard if the url for the current news item matches the url for the summary.
Then the handleDetailClick method should be updated to:
handleDetailClick = link => {
// console.log('hadle detail', link);
API.summarize(link)
.then((res) => {
// console.log(res.body);
this.setState({
summary: res.body,
summaryUrl: link,
})
// console.log(this.state.summary);
})
}
Another way you might consider doing this is by passing the second API call off to the NewsCard component. So each NewsCard would have its summary as part of its own state and would fetch the summary when clicked on. This way there would be no confusion about which news item the summary belongs to. It could also help clean up your parent component by delegating tasks/data to children as the component grows.

Resources