React keep state of previous component and render new state - reactjs

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.

Related

React: prevent list from rerendering all elements on prop change

I'm trying to recreate the effect shown at https://hexed.it/
When you hover over either list the corresponding byte in the other list is also highlighted. I figured a panel with each list inside it that had a state with the current hovered byte would do but it seems that React wants to re-render the entire list or do something strange every time resulting in larger files being unbearably slow.
I see a lot of "use memo! use the useCallback hook!" when searching and I've tried... it's still slow and I'm not sure why. It seems like it's only rendering the updated HexByte but it's still unacceptably slow for large files.
Sandbox: https://codesandbox.io/s/flamboyant-ellis-btfk5s
Can someone help me quicken/smooth out the hovering?
I solved it using this answer: Prevent DOM element re-render in virtualized tree component using react-window
In short the things I've learned:
memo has no effect if a component has a useState in it
Large lists of data should be rendered using a library like react-window
The cell rendering function as mentioned in the answer above can't be part of a parent component
As an example for anyone coming here, the new HexPanel class looks like so
import Box from '#mui/material/Box';
import { memo } from 'react';
import { FixedSizeGrid as Grid, areEqual } from 'react-window';
const HexByte = memo(function HexByte(props) {
const onMouseEnter = () => {
props.onHover(props.index);
//setInside(true);
}
const onMouseLeave = () => {
//setInside(false);
}
const onClick = () => {
//setClicked(true);
}
return (
<span
style={{
display: 'inline-block',
padding: '5px',
backgroundColor: props.hoverIndex == props.index ? '#777' : 'transparent',
color: 'darkblue'
}}
onClick={onClick}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
>
{props.byte}
</span>
)
}, (prevProps, nextProps) => nextProps.hoverIndex != nextProps.index);
const Cell = memo(function({ data, columnIndex, rowIndex, style }) {
return (
<div style={style}>
<HexByte byte={data.hex[rowIndex][columnIndex]} onHover={data.onHover} hoverIndex={data.hoverIndex} index={`${rowIndex}${columnIndex}`} />
</div>
)
}, areEqual);
const HexPanel = (props) => {
return (
<Box
sx={{
fontFamily: 'Source Code Pro',
display: 'flex',
flexDirection: 'column',
}}
>
<Grid
columnCount={16}
columnWidth={30}
height={900}
itemData={props}
rowCount={props.hex.length}
rowHeight={35}
width={500}
>
{Cell}
</Grid>
</Box>
)
}
export default HexPanel;

React context magically change other state

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.

React Typescript page - window.print is printing unexpected

I have a react typescript page. It's basically composed of a title and some elements on top, an inner page, and then a side panel with buttons. I was tasked with adding a "print page" to the side panel which should print some third component and open up a print dialogue for it (the window.print for the inner page).
I have 2 problems so far:
when rendering this on the print button, the entire print doesn't work at all (nothing happens). I was able to solve this by setting the onprint to happen on page load. Then it opens the print dialogue on page load but still doesn't work on the print button.
the window.print is printing the entire context of all elements on the page, instead of just new component i'm trying for.
here is part of the code:
const printCoversheet = () => {
setCoversheetData({
documentId: document.id,
somedata: somedata?.data ?? '',,
});
console.log(coversheetData);
};
useEffect(() => {
!isEmpty(coversheetData) && window.print();
window.onafterprint = () => setCoversheetData({} as VerificationErrorCoversheetProps);
}, [coversheetData, setCoversheetData]);
<div css={{ display: 'flex', justifyContent: 'flex-start', marginTop: 16 }}>
<Button
variant="outlined"
size="large"
color="secondary"
onClick={() => printCoversheet()}
>
Print Coversheet
</Button>
</div>
{!isEmpty(coversheetData) && (
<div css={{ display: 'none', '#media print': { display: 'block' } }}>
{!coversheetData.verificationFailures.length ? (
<CoversheetPrint {...coversheetData} />
) : (
<VerificationErrorCoversheet {...coversheetData} />
)}
</div>
)}
I had to do the opposite of this in all other divs
In other divs I did:
window.print() all together didn't seem to work on localhost

Unexpected behaviour on removing tag's texts

In my project I have used react-native-tags to get the different variants of colour from the user.
Here is the code :
<View style={{ marginVertical: 10, marginLeft: 20, justifyContent: 'center' }}>
<MText size={18}>Colors</MText>
</View>
<Tags
initialText=""
textInputProps={{
placeholder: "Enter Color",
blurOnSubmit:false
}}
initialTags={this.state.colors}
createTagOnString={[","]}
createTagOnReturn={true}
onChangeTags={(tags) => {
this.setState((prevState) => ({
colors: tags
}), () => this.handleVariantChanges());
}}
onTagPress={(index, tagLabel, event, deleted) => console.log(index, tagLabel, event, deleted ? "deleted" : "not deleted")
}
containerStyle={styles.tagContainer}
inputStyle={{ backgroundColor: "white" }}
renderTag={({ tag, index, onPress, deleteTagOnPress, readonly }) => (
<TouchableOpacity style={styles.tag} onPress={onPress} key={index.toString()}>
<Icon name="times" color="#aaa" />
<MText>{' '}{tag}</MText>
</TouchableOpacity>
)}
/>
Now, after running project I added some tags. Tags is added successfully. Then I continue to add another tags but spelling is not right so I removed till first character, but previous tags also removed automatically.
Another problem is that If I want to remove tag after adding some tags I am not able to remove that tag.
Here is problem gif :
Please help me what's going wrong here !!!
Note : This issue only happens if I use state value as an initialTags. If I keep initialTags as a blank array then everything working properly.
The issue is how react and react native tag works. Lets say you have two tags and you are entering text. When you hit backspaces delete the first character of the text that is not a tag yet, it automatically pops the second tag from the array. Since you are setting the state, the component is re-rendered and that removes the tag entirely.
You can fix this by using shouldComponentUpdate and returning false
shouldComponentUpdate(nextProps, nextState) {
return false;
}
What I would recommend is having a separate component for the tags and use that in your form and have a callback that updates the form when onChangeTags is triggered.

react-dropzone child icon not changing on state change

I have a react project and I am using the react-dropzone component:
import Dropzone from 'react-dropzone';
I want to make it stateful and show different images and text based on the state. I defined my states as:
const status = {
ready: 'ready',
preview: 'preview',
error: 'error',
requested: 'requested',
success: 'success',
failed: 'failed',
};
The state can change based on user actions (so when they drag a file onto the dropzone I update status in state as follows:
onDrop(acceptedFiles, rejectedFiles) {
// do some stuff here...
this.setState({ status: status.preview });
}
My render method is a three step process:
1. the actual render methos
render() {
const config = {
iconFiletypes: ['.xlsx'],
showFiletypeIcon: true,
};
return (
<div style={{ marginBottom: '30px' }}>
<Dropzone
config={config}
onDrop={files => this.onDrop(files)}
//className="dropzone"
multiple={false}
>
{this.renderDropZoneContent()}
</Dropzone>
</div>
);
}
choose what to render based on state:
renderDropZoneContent() {
switch (this.state.status) {
case status.ready:
return this.renderReadyState();
case status.preview:
return this.renderPreviewState();
// and on down for each state / status + default case...
}
}
and finally the code to render each case as functions:
renderPreviewState() {
return (
<div style={{ marginTop: '35px', textAlign: 'center' }}>
<i className="far fa-file-excel" style={{ verticalAlign: 'middle', fontSize: '50px' }} />
{/* There is more jsx here but I removed it for clarity */}
</div>
);
}
renderReadyState() {
return (
<div style={{ marginTop:'35px', textAlign:'center'}>
<i className="fas fa-cloud-upload-alt" style={{ verticalAlign: 'middle', fontSize: '50px' }} />
</div>
);
}
Nothing too crazy. My problem is that as the state changes, the text updates but the icon does not. This is an interesting problem because the logic of the application works, but its the specific element that does not get updated. Even more interesting is that I tried wrapping the entire return in another div and got the error: Uncaught DOMException: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node. I'm banging my head against the wall. If anyone has come across this before and have any tips it is greatly appreciate!!
Probably a conflict with how Font Awesome and React handle rendering.
If you are using React we recommend the react-fontawesome package or Web Fonts with CSS.
https://fontawesome.com/how-to-use/on-the-web/using-with/react

Resources