React component auto-remounted on state update, why? - javascript

I have a parent React.js component, passing a state and a setter to a child component.
Every time the child component use the setter, the child component is unmounted & remounted.
I'm new to react and I don't understand what is happening.
const useState = React.useState;
class MyComponnent extends React.Component {
intervalID = null;
componentDidMount() {
console.log("MOUNTING");
this.intervalID = setInterval(() => {
this.props.setA({ a: this.props.a.a + 1 });
}, 1000);
}
componentWillUnmount() {
clearInterval(this.intervalID);
}
render = () => {
return (
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
flexDirection: "column"
}}
>
<div
style={{
flexDirection: "row",
display: "flex",
justifyContent: "center",
alignItems: "center",
marginTop: "30px"
}}
>
{this.props.a.a}
</div>
</div>
);
};
}
function App() {
const [activeStep] = useState(0);
const [a, setA] = useState({ a: 0 });
// eslint-disable-next-line react/no-multi-comp
function StepPage() {
if (0 === 0) {
return <MyComponnent a={a} setA={setA} />;
} else {
return <MyComponnent />;
}
}
return (
<div>
<StepPage />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
.App {
font-family: sans-serif;
text-align: center;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>
<div id="root"></div>

The problem is that your are creating a new instance of your component by defining StepPage inside a render function.
You can refactor your StepPage component so it will be defined outside of render method,
function StepPage({ setA, a}) {
if (0 === 0) {
return <MyComponnent a={a} setA={setA} />;
} else {
return <MyComponnent />;
}
}
export default function App() {
const [activeStep] = useState(0);
const [a, setA] = useState({ a: 0 });
return (
<div>
<StepPage a={a} setA={setA} />
</div>
);
}

StepPage in your example is re-defined every time App renders.
Calling the function normally instead of using it as a React component alleviates the issue:
return (
<div>
{StepPage()}
</div>
);
const useState = React.useState;
class MyComponnent extends React.Component {
intervalID = null;
componentDidMount() {
console.log("MOUNTING");
this.intervalID = setInterval(() => {
this.props.setA({ a: this.props.a.a + 1 });
}, 1000);
}
componentWillUnmount() {
clearInterval(this.intervalID);
}
render = () => {
return (
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
flexDirection: "column"
}}
>
<div
style={{
flexDirection: "row",
display: "flex",
justifyContent: "center",
alignItems: "center",
marginTop: "30px"
}}
>
{this.props.a.a}
</div>
</div>
);
};
}
function App() {
const [activeStep] = useState(0);
const [a, setA] = useState({ a: 0 });
// eslint-disable-next-line react/no-multi-comp
function StepPage() {
if (0 === 0) {
return <MyComponnent a={a} setA={setA} />;
} else {
return <MyComponnent />;
}
}
return (
<div>
{StepPage()}
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
.App {
font-family: sans-serif;
text-align: center;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Related

React State not updating on key event

I'm running into an issue that I can't quite figure out. I'm building a Wordle clone, the state seems to be updating on some events and not on others, and I can't quite track down why.
I have a Keyboard component, which takes handleKeyClick as a prop from the parent component, and that is attached to two event handlers.
Parent Component
import { Box, Divider, Grid, Typography } from "#mui/material";
import { useState, useEffect, useCallback } from 'react';
import Keyboard from "../Keyboard";
import { v4 as uuid } from 'uuid'
import WordleNotifbar from "../WordleNotifBar";
import Loading from "../Utils/Loading";
import { IGuessState } from "../../types";
interface IGuessGridProps {
addGuess: Function,
guesses: any,
answer: any
}
const GuessGrid = (props: IGuessGridProps) => {
const { addGuess, guesses, answer } = props;
let [notif, setNotif] = useState<boolean>(false);
const [guess, setGuess] = useState<string[]>([]);
const styles = {
input: {
border: ".5px solid white",
height: "50px",
display: "flex",
borderRadius: "5px",
justifyContent: "center",
alignItems: "center",
backgroundColor: "",
color: "white",
},
container: {
minWidth: "300px",
width: "30%",
maxWidth: "450px",
margin: "0 auto",
marginTop: "15px",
},
}
// In the parent component, I have defined the function I'm passing in as a prop as such:
const handleAddCharacter = (char: string) => {
setGuess([...guess, char])
}
// Not fully implemented yet
const handleBackspace = (e: MouseEvent): void => {
e.preventDefault();
setGuess([...guess])
}
const handleSubmit = (): void => {
let word = guess.join('')
if (word.length === answer.length) {
addGuess(word.toLowerCase())
setGuess([]);
}
else {
setNotif(true);
setTimeout(() => {
setNotif(false);
}, 1000)
}
}
if (answer) {
return <>
<Divider />
<Grid container sx={styles.container} >
{answer.split('').map((_: string, index: number) => {
return (<Grid item xs={12 / answer.length} sx={styles.input} key={uuid()}>
<Box>
<Typography>
{guess[index]}
</Typography>
</Box>
</Grid>)
})}
</Grid>
<Keyboard guesses={guesses} answer={answer} handleKeyClick={handleAddCharacter} handleBackspace={handleBackspace} submitFunc={handleSubmit} />
{notif ? <WordleNotifbar message="Not Enough Characters" duration={1000} /> : ""}
</>;
} else {
return <Loading />
}
};
export default GuessGrid;
Keyboard Component
import { Box, Grid, SxProps, Theme, Typography } from "#mui/material";
import { useCallback, useEffect, useState } from 'react';
import { v4 as uuid } from 'uuid';
import BackspaceIcon from '#mui/icons-material/Backspace';
import React from "react";
interface IKeyboardProps {
guesses: string[],
answer: string,
handleKeyClick: any,
submitFunc: any,
handleBackspace: any
}
const Keyboard = (props: IKeyboardProps) => {
const { guesses, answer, handleKeyClick, submitFunc, handleBackspace } = props
const [guessedLetters, setGuessedLetters] = useState<string[]>();
const topRow = 'qwertyuiop'.toUpperCase().split('');
const middleRow = 'asdfghjkl'.toUpperCase().split('');
const bottomRow = 'zxcvbnm'.toUpperCase().split('');
const allKeys = topRow.concat(middleRow.concat(bottomRow));
// When the component is initialized, I am establishing an event listener in the window for the key press events.
useEffect(() => {
window.addEventListener('keypress', handlePhysicalKeyPress)
}, [])
useEffect(() => {
const allGuessedCharacters = guesses.join('').split('');
const uniqueGuessedCharacters = allGuessedCharacters.filter((val: string, index: number, self) => self.indexOf(val) === index)
setGuessedLetters(uniqueGuessedCharacters);
}, [guesses])
const handleVirtualKeyPress = (e: any) => {
handleKeyClick(e.target.textContent)
}
const handlePhysicalKeyPress = (e: KeyboardEvent) => {
e.preventDefault()
if (allKeys.includes(e.key.toUpperCase())) {
handleKeyClick(e.key.toUpperCase());
}
}
const genKeyStyles = (character: string, _: number): SxProps<Theme> => {
character = character.toLowerCase()
const styles = {
width: character === "bs" || character === "enter" ? "63px" : "33px",
marginX: "1px",
marginY: "1px",
borderRadius: "5px",
height: "50px",
color: "black",
textAlign: "center",
backgroundColor: "#DDD",
display: "flex",
justifyContent: "center",
alignItems: "center",
};
if (guessedLetters) {
if (answer.indexOf(character) >= 0 && guessedLetters.indexOf(character) >= 0) {
styles.backgroundColor = "green"
} else if (answer.indexOf(character) < 0 && guessedLetters.indexOf(character) >= 0) {
styles.backgroundColor = "#777"
}
}
return styles
}
return <Box sx={{ display: "flex", flexDirection: "column", justifyContent: "center", marginTop: "10px", }}>
<Box sx={{ display: "flex", justifyContent: "center" }}>
{topRow.map((letter: string, index: any) => {
return (
<Box sx={genKeyStyles(letter, index)} key={uuid()} onClick={handleVirtualKeyPress}>
<Typography key={uuid()}>{letter}</Typography>
</Box>
)
})}
</Box>
<Box sx={{ display: "flex", justifyContent: "center" }}>
{middleRow.map((letter: string, index: any) => {
return (
<Box sx={genKeyStyles(letter, index)} key={uuid()} onClick={handleVirtualKeyPress}>
<Typography key={uuid()}>{letter}</Typography>
</Box>
)
})}
</Box>
<Box sx={{ display: "flex", justifyContent: "center" }}>
<Box sx={genKeyStyles("enter", 1)} key={uuid()} onClick={submitFunc}>
<Typography key={uuid()}>enter</Typography>
</Box>
{bottomRow.map((letter: string, index: any) => {
return (
<Box sx={genKeyStyles(letter, index)} key={uuid()} onClick={handleVirtualKeyPress}>
<Typography key={uuid()}>{letter}</Typography>
</Box>
)
})}
<Box sx={genKeyStyles("bs", 1)} key={uuid()} onClick={handleBackspace}>
<Typography key={uuid()}><BackspaceIcon /></Typography>
</Box>
</Box>
</Box>
};
export default Keyboard;
What happens is that the virtual key press seems to update the state properly, but the physical keypress seems to reset the state back to an empty array. I can't really figure out a good reason why this is happening. Any thoughts? I appreciate your help in advance!
Link to Live Application
When you do:
useEffect(() => {
window.addEventListener('keypress', handlePhysicalKeyPress)
}, [])
...you are attaching a specific handlePhysicalKeyPress function as event listener. But that function is re-created at each component re-render, so you no longer reference the "current" function "version" (should you try to remove it, you would not be able to because it is no longer the same reference).
As such, the actual listener is the very first "version" of your function, which calls the very first "version" of your handleKeyClick prop, which is the very first "version" of your handleAddCharacter function, which knows only the very first version of your guess state... which is an empty array.
That is why when handlePhysicalKeyPress is executed by a key press, it builds a new guess array from an empty array.
While you should avoid this discrepancy between what you attach to your event listener and your actual "render-time" function, there should be a very simple solution to your specific case: should you use the functional form of your state setter, even if it is the "very first version", it should use the "current" state version:
setGuess((currentGuess) => [...currentGuess, char])

How do I associate seperate state to each button?

Hello
I am trying to associate a like button with each PaperCard component as shown in the code below. I have included the relevant code. Currently, The like button shows up and every time you click it the counter increases BUT all the buttons share the same state. So I am trying to fix that. I am new to JS and React.
Any help will be appreciated. Thanks!
function Home() {
const [likes, setLikes] = useState(0);
const incrementLikes = () => {
const addToLikes = likes + 1;
setLikes(addToLikes)
}
const loadMorePapers = () => {
setVisible((prevValue) => prevValue + 3);}
return (
<div>
<div style={{display:'flex', justifyContent:'center'}}>
<h1>Latest Papers</h1>
</div>
{apiData.slice(0, visible).map((paper) => (
<Grid key={paper.title}>
<button onClick={incrementLikes}>Likes: {likes}</button>
<PaperCard title={paper.title} abstract={paper.abstract}/>
</Grid>
))}
<div style={{display:'flex', justifyContent: 'center'}}>
<Button variant="contained" onClick={loadMorePapers}>Load More</Button>
</div>
</div>
)
}
The element from the map callback is extracted as a component, and now every button has its own state.
function Home() {
return (
<div>
<div style={{ display: "flex", justifyContent: "center" }}>
<h1>Latest Papers</h1>
</div>
{apiData.slice(0, visible).map((paper) => (
<LikeButton paper={paper} key={paper.title} />
))}
<div style={{ display: "flex", justifyContent: "center" }}>
<button variant="contained" onClick={loadMorePapers}>Load More</button>
</div>
</div>
);
}
function LikeButton(paper) {
const [likes, setLikes] = useState(0);
const incrementLikes = () => {
const addToLikes = likes + 1;
setLikes(addToLikes);
};
return (
<div key={paper.title}>
<button onClick={incrementLikes}>Likes: {likes}</button>
<PaperCard title={paper.title} abstract={paper.abstract}/>
</div>
);
}
Create a new functional component called LikeButton (or something relevant) to house the state for each button independently.
In that component, add the state values you want to track per each button. In your case it seems to just be the likes.
So could be something like:
const LikeButton = () => {
const [likes, setLikes] = useState(0); //likes controlled by state of component
const incrementLikes = () => {
setLikes((prevState) => prevState + 1);
};
return <button onClick={incrementLikes}>Likes: {likes}</button>;
};
Then add that component in place of your existing button and remove the state for likes in the Home component. E.g.:
function Home() {
const loadMorePapers = () => {
setVisible((prevValue) => prevValue + 3);
};
return (
<div>
<div style={{ display: "flex", justifyContent: "center" }}>
<h1>Latest Papers</h1>
</div>
{apiData.slice(0, visible).map((paper) => (
<Grid key={paper.title}>
<LikeButton/>
<PaperCard title={paper.title} abstract={paper.abstract} />
</Grid>
))}
<div style={{ display: "flex", justifyContent: "center" }}>
<Button variant="contained" onClick={loadMorePapers}>
Load More
</Button>
</div>
</div>
);
}
Should you want to control state from the Home component, you can pass the likes as props, but it doesn't seem necessary for what you want.
In this situation you should consider using a reusable button component in order to control state within the component itself. Then you do not have to worry about the buttons sharing the same state. Here would be a simple example of a button component that will track it's count independent of the other buttons that are rendered:
import React, { useState } from 'react';
export default function CounterButton() {
const [count, setCount] = useState(0);
function incrementLikes() {
setCount(count + 1);
}
return (
<button onClick={incrementLikes}>
{count} likes
</button>
);
}
You could simply render these buttons like in the pseudo code below:
{[0, 1, 2, 3].map((num: number, index: number) => (
<div key={index}>
<CounterButton />
</div>
))}
I think you're doing too much in one component. The "likes" in your example are for an individual paper, not for the whole site, right?
Maybe something like this...
function Home() {
const loadMorePapers = () => {
setVisible((prevValue) => prevValue + 3);
}
return (
<div>
<div style={{display:'flex', justifyContent:'center'}}>
<h1>Latest Papers</h1>
</div>
{apiData.slice(0, visible).map((paper) => (
<Paper {...paper} key={paper.title} />
))}
<div style={{display:'flex', justifyContent: 'center'}}>
<Button variant="contained" onClick={loadMorePapers}>Load More</Button>
</div>
</div>
);
}
function Paper(props){
const [likes, setLikes] = useState(0);
const incrementLikes = () => setLikes(likes + 1)
return (
<Grid>
<button onClick={incrementLikes}>Likes: {likes}</button>
<PaperCard title={paper.title} abstract={paper.abstract}/>
</Grid>
)
}
If the data from the api has a key/id you can pass that to your incrementLikes function and use it to increment the likes for the right item.
const [apiData, setApidData] = useState(...)
const incrementLikes = (id) => {
const updated = apiData.map((paper) => {
if (paper.id === id) {
return {
...paper,
likes: paper.likes + 1
};
}
return paper;
});
setApidData(updated);
};
Then pass the id in the button
<button onClick={() => incrementLikes(paper.id)}>Likes: {paper.likes}</button>
// Get a hook function
const { useState } = React;
const PaperCard = ({ title, abstract }) => {
return (
<div>
<p>{title}</p>
<p>{abstract}</p>
</div>
);
};
const Header = () => {
const [apiData, setApidData] = useState([
{
title: 'Testing likes',
id: 1,
likes: 0,
abstract: 'abs',
},
{
title: 'More likes',
id: 3,
likes: 5,
abstract: 'abstract',
}
]);
const incrementLikes = (id) => {
const updated = apiData.map((paper) => {
if (paper.id === id) {
return {
...paper,
likes: paper.likes + 1
};
}
return paper;
});
setApidData(updated);
};
const loadMorePapers = (e) => {};
return (
<div>
<div style={{ display: 'flex', justifyContent: 'center' }}>
<h1>Latest Papers</h1>
</div>
{apiData.map((paper) => (
<div key={paper.title}>
<button onClick={() => incrementLikes(paper.id)}>Likes: {paper.likes}</button>
<PaperCard title={paper.title} abstract={paper.abstract} />
</div>
))}
<div style={{ display: 'flex', justifyContent: 'center' }}>
<button variant='contained' onClick={loadMorePapers}>
Load More
</button>
</div>
</div>
);
};
// Render it
ReactDOM.render(<Header />, document.getElementById('react'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>

How to change css code by pressing a button

Goal:
When you press on the button 'ok', the id element named test2 should be display non and id element named test1 should be display block with support of css code.
And also please take account to the color of the text that is located in the css code.
Problem:
I don't know how to solve it.
What is needed to be changed in the source code in order to achieve the goal?
Stackblitz:
https://stackblitz.com/edit/react-modal-gdh4hp?
Info:
*I'm newbie in Reactjs
Thank you!
index.js
import React, { Component } from 'react';
import { render } from 'react-dom';
import { Modal } from './modal';
import './style.css';
class App extends Component {
constructor() {
super();
this.state = {
modal: true
};
}
handleCloseModal = () => {
alert('ddd');
};
render() {
const { modal } = this.state;
const non = {
display: 'none',
color: 'yellow'
};
const block = {
display: 'block',
color: 'yellow'
};
return (
<div>
{modal ? (
<Modal
onClose={() => {
this.setState({ modal: false });
}}
>
<div id="test1" style={non}>Awesome1</div>
<div id="test2">Awesome2</div>
<button onClick={() => this.handleCloseModal()}>ok</button>
</Modal>
) : (
<button
onClick={() => {
this.setState({ modal: true });
}}
>
Show modal
</button>
)}
</div>
);
}
}
render(<App />, document.getElementById('root'));
modal.js
import React from 'react';
export class Modal extends React.Component {
render() {
const { children, onClose } = this.props;
return (
<div style={{position: "absolute", top: 0, left: 0, width: "100%", height: "100%", background: "gray"}} onClick={ev => onClose()}>
<div
style={{margin: "auto", background: "white", border: "red", width: "500px", height: "300px"}}
onClick={ev => ev.stopPropagation()}>
{ children }
</div>
</div>
);
}
}
You can simply do that :
ok() => {
document.getElementById('test1').style.display = 'block'
document.getElementById('test2').style.display = 'none'
}
You can use state
class App extends Component {
constructor() {
super();
this.state = {
modal: true,
showBlock: "",
showNon: "",
color: ""
};
}
handleCloseModal = () => {
this.setState({showBlock: "block"});
this.setState({showNon: "none"});
this.setState({color: "yellow"});
alert('ddd');
};
render() {
const { modal } = this.state;
return (
<div>
{modal ? (
<Modal
onClose={() => {
this.setState({ modal: false });
}}
>
<div id="test1" style={{display: this.state.showBlock, color: this.state.color}}>Awesome1</div>
<div id="test2" style={{display: this.state.showNon, color: this.state.color}}>Awesome2</div>
<button onClick={() => this.handleCloseModal()}>ok</button>
</Modal>
) : (
<button
onClick={() => {
this.setState({ modal: true });
}}
>
Show modal
</button>
)}
</div>
);
}
}
render(<App />, document.getElementById('root'));
React way to achieve that is to use useRef hook (or createRef for class approach):
Class approach:
constructor(props) {
this.testRef = React.createRef()
}
const toggleBlock = () => {
testRef.current.style.display = 'block'
testRef.current.style.color = 'yellow'
}
render() {
return (
<>
<div id="test1" ref={testRef}>Awesome1</div>
<button onclick={this.toggleBlock}>Ok</button>
</>
)
}
Hooks approach:
const testRef = useRef(null)
const toggleBlock = () => {
testRef.current.style.display = 'block'
testRef.current.style.color = 'yellow'
}
return (
<>
<div id="test1" ref={testRef}>Awesome1</div>
<button onclick={this.toggleBlock}>Ok</button>
</>
)

Re-render component with useEffect when something changes in a child component

I have an application that lets a user to add tasks in a list. The tasks are fetched from the API and are displayed with the "List" component. When a user adds a new task from the "AddButton" component the task is stored in the database.
I want the to re-render the "List" component when the handleSubmit function happens on the "AddButton" component and adds the task to the database. The "addTask" and "getTasks" are fetching data from the API.
Thanks for your help in advance.
List component
import React, { useState, useEffect } from 'react';
import { makeStyles } from '#material-ui/styles';
import List from '#material-ui/core/List';
import ListItem from '#material-ui/core/ListItem';
import ListItemText from '#material-ui/core/ListItemText';
import Moment from 'react-moment';
import { getTasks } from './services/getTasks';
import AddButton from './AddButton';
import './App.css';
const useStyles = makeStyles(theme => ({
root: {
display: 'flex',
flexDirection: 'column',
width: '100%',
justifyContent: 'space-between',
height: '100%',
fontSize: '16px',
},
listItemLinkRoot: {
paddingLeft: theme.spacing(3),
width: '100%',
'&:hover': {
backgroundColor: '#212121',
color: 'white',
},
display: 'flex',
flexDirection: 'column',
alignItems: 'flex-start',
},
buttonContainer: {
display: 'flex',
width: '100%',
flexDirection: 'column',
justifyContent: 'flex-end',
},
list: {
flexGrow: 1,
overflow: 'auto',
},
listItemText: {
marginBottom: 8,
// fontSize: 20,
},
}));
function ListItemLink(props) {
return <ListItem button component="a" {...props} />;
}
export default function TaskList() {
const classes = useStyles();
const [tasks, setTasks] = useState([]);
useEffect(() => {
const fetchData = async () => {
const result = await getTasks();
setTasks(result);
};
fetchData();
}, []);
return (
<div className={classes.root}>
<List classes={{ root: classes.list }}>
{tasks.map(task => (
<ListItemLink
divider
key={task.id}
classes={{ root: classes.listItemLinkRoot }}
href="simple-list"
>
<ListItemText
classes={{ root: classes.listItemText }}
primary={task.description}
/>
<Moment
classes={{ root: classes.listItemDate }}
format="DD/MM/YYYY"
>
{task.createdAt}
</Moment>
</ListItemLink>
))}
</List>
<div className={classes.buttonContainer}>
<AddButton classes={{ root: classes.add }} />
</div>
</div>
);
}
AddButton component
import React, { useState } from 'react';
import { makeStyles } from '#material-ui/core/styles';
import Fab from '#material-ui/core/Fab';
import AddIcon from '#material-ui/icons/Add';
import TextField from '#material-ui/core/TextField';
import { addTask } from './services/postTask';
const useStyles = makeStyles(theme => ({
cont: {
display: 'flex',
flexDirection: 'row',
paddingBottom: '24px',
justifyContent: 'space-between',
backgroundColor: '#e0e0e0',
width: '100%',
alignItems: 'center',
felxGrow: 1,
},
fab: {
marginTop: theme.spacing(2),
marginRight: theme.spacing(2),
width: '100%',
},
textField: {
marginLeft: theme.spacing(3),
marginTop: 0,
marginBottom: 0,
flexGrow: 1,
},
}));
export default function AddButton() {
const classes = useStyles();
const [task, setTask] = useState({
description: '',
completed: false,
});
const handleChange = ev => {
setTask({ ...task, [ev.target.id]: ev.target.value });
};
const handleSubmit = () => {
addTask(task);
};
return (
<div className={classes.cont}>
<TextField
onChange={handleChange}
id="description"
label="Add a task"
rowsMax="4"
className={classes.textField}
margin="normal"
/>
<Fab
onClick={handleSubmit}
variant="extended"
size="small"
color="primary"
aria-label="add"
className={classes.fab}
>
<AddIcon />
Add
</Fab>
</div>
);
}
In your list component you can have your handleSubmit function and pass it down to your child AddButton component:
<AddButton classes={{ root: classes.add }} handleSubmit={handleSubmit} />
One solution I can think of is to move the fetchData function outside of your useEffect hook and pass it to the Button as a prop:
const fetchData = async () => {
const result = await getTasks();
setTasks(result);
};
useEffect(() => {
fetchData();
}, []);
...
<AddButton classes={{ root: classes.add }} refetch={fetchData}/>
Then in AddButton (assuming addTask() is async).
const handleSubmit = () => {
addTask(task)
.then(res => props.refetch())
};
Though it may make more sense to handle all of the state functionality in the parent component.

Rerender Component on Function Return

I have a class function that filters my props then uses it to render a deckswiper. The problem is that the function doesn't complete by the time the deckswiper renders so it renders a blank deckswiper. Is there a way that I can either make it rerender when the deck is complete or make the function asynchronous? Or should I be filtering this data elsewhere? When I first refresh the page the deckswiper is blank, then if I click my button to add more data it seems to work.
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Container, View, DeckSwiper, Text, Spinner, Button, Icon } from 'native-base';
import { findDogs, addDog, blacklistDog } from '../actions';
import SwipeDogItem from './SwipeDogItem';
class SwipeDogSelect extends Component {
componentWillMount() {
this.props.findDogs();
}
shouldComponentUpdate(nextProps) {
if(nextProps.blacklist !== this.props.blacklist) {
return false;
}
return true;
}
dogBreedString(breed) {
if (Array.isArray(breed)) {
let breedString = '';
for (let i = 0; i < breed.length; i++) {
breedString += `${breed[i].$t}, `;
}
return breedString.slice(0, -2);
}
return breed.$t;
}
filterDogs() {
const { dogs, gender, selectedBreeds, blacklist, size } = this.props;
return dogs.filter((pet) => {
return blacklist.indexOf(pet.id.$t) === -1 &&
(selectedBreeds > 248 || Object.values(pet.breeds.breed).filter(val => !selectedBreeds.includes(val)).length < 1) &&
(gender === 'either' || pet.gender.$t === gender) &&
(size === 'any' || pet.size.$t === size);
});
}
renderDeckSwiper() {
console.log(this.props.dogs);
if (this.props.findingDogs || typeof this.props.dogs === 'string') {
return (<Spinner color='black' />);
} else if (this.props.dogs === undefined) {
return (
<Text>No dogs found.</Text>
);
}
return (
<DeckSwiper
ref={mr => (this._deckSwiper = mr)}
dataSource={this.filterDogs()}
renderItem={dog => {
return (
<SwipeDogItem
dog={dog}
breed={this.dogBreedString(dog.breeds.breed)}
/>
);
}}
renderEmpty={() => {
return (<Text>No dogs found. Try less filters or refreshing.</Text>);
}}
onSwipeRight={(dog) => { this.props.addDog(dog); }}
onSwipeLeft={(dog) => { this.props.blacklistDog(dog.id.$t); }}
loop={false}
/>
);
}
render() {
return (
<Container>
<View>
{this.renderDeckSwiper()}
</View>
<View
style={styles.buttonViewStyles}
>
<Button
style={styles.buttonsStyles}
rounded
large
onPress={() => {
this.props.blacklistDog(this._deckSwiper._root.state.selectedItem.id.$t);
this._deckSwiper._root.swipeLeft();
}}
>
<Icon style={styles.buttonIconStyles} name="close" fontSize='40' color='red' />
</Button>
<Button
warning
rounded
style={styles.buttonsStyles}
large
onPress={() => this.props.findDogs()}
>
<Icon style={styles.buttonIconStyles} name='refresh' />
</Button>
<Button
rounded
style={styles.buttonsStyles}
large
danger
color='red'
onPress={() => {
this.props.addDog(this._deckSwiper._root.state.selectedItem);
this._deckSwiper._root.swipeLeft();
console.log(this._deckSwiper._root);
}
}
>
<Icon style={styles.buttonIconStyles} color='red' name="heart" active />
</Button>
</View>
</Container>
);
}
}
const styles = {
buttonsStyles: {
borderWidth: 1,
borderColor: 'rgba(0,0,0,0.2)',
alignItems: 'center',
justifyContent: 'center',
width: 75,
height: 75,
borderRadius: 100,
marginTop: 100,
},
buttonViewStyles: {
flexDirection: "row",
flex: 1,
position: "absolute",
bottom: 15,
left: 15,
right: 15,
justifyContent: "space-between",
padding: 15
},
buttonIconStyles: {
fontSize: 45,
}
};
const mapStateToProps = state => {
const { selectedBreeds, gender, size } = state.settings;
const { dogs, findingDogs } = state.findDogsReducer;
const { blacklist } = state.dogs;
return {
dogs,
findingDogs,
blacklist,
selectedBreeds,
gender,
size
};
};
export default connect(mapStateToProps, { findDogs, addDog, blacklistDog })(SwipeDogSelect);
I ended up switching to another swiper because native base's swiper was causing the issue.

Categories