how to make a child to re-render by other child action? - javascript

i have an application like
export default function App(props) {
return <>
<FooList />
<hr/>
<CreateFoo />
</>;
}
my FooList is a class like
export default class FooList extends React.Component {
constructor(props) {
super(props);
this.state = {
foos = []
};
}
componentDidMount() {
Axios.get('/getFooListPath').then(res=>{
this.setState({foos=> res.data});
}).catch(err=>{
console.log(err);
});
}
render() {
const mapedFoos = this.state.foos.map((foo, i)=><li key={i}>foo</li>);
return <ul>mapedFoos</ul>;
}
}
and CreateFoo is like
export default function CreateFoo(props) {
const [txt, setTxt] = useState("");
const handleChange = (event) => setTxt(event.target.value);
const handleChange = () => {
Axios.post("/createFooPath", txt).then(res=>{
// TODO : maybe calling some method here
}).catch(err=>{
console.log(err);
});
};
return <>
<input type="text" onChnage={handleChange} />
<button onClick={handleClick} >create</button>
</>;
}
problem is that i want to re-render FooList after i successfully created foo.
i already thought about moving foos to parent App, and send it to FooList as props. but i want to make FooList independent from parent, so wherever i want to use it, it show me current foos on database

Related

Class Component ReactJS - setTimeout get wrong value in class component

App Component:
class App extends React.Component {
constructor() {
super();
this.state = {
user: 'Dan',
};
}
render() {
return (
<React.Fragment>
<label>
<b>Choose profile to view: </b>
<select
value={this.state.user}
onChange={e => this.setState({ user: e.target.value })}
>
<option value="Dan">Dan</option>
<option value="Sophie">Sophie</option>
<option value="Sunil">Sunil</option>
</select>
</label>
<h1>Welcome to {this.state.user}’s profile!</h1>
<p>
<ProfilePageClass user={this.state.user} />
<b> (class)</b>
</p>
</React.Fragment>
)
}
}
ProfilePageClass (the problem is here):
class ProfilePageClass extends React.Component {
showMessage = () => {
alert('Followed ' + this.props.user); // This get wrong value (new props)
};
handleClick = () => {
setTimeout(this.showMessage, 6000); // This get wrong value (new props)
};
render() {
return <button onClick={this.handleClick}>Follow</button>;
}
}
setTimeout does not display the message corresponding to the user that was originally followed
I think it's a problem with the props or this, but I'm not sure.
Can anyone tell me what is going on?
Nothing to do with the this keyword. When the app state changes, your component instance receives new props values, and the setTimeout callback that runs after they have changed will access the new values. This is sometimes desirable, sometimes not.
This is one of the differences between function components and class components. To get the user profile that was rendered when you clicked the button, you need to explicitly remember it when the button is clicked (or rendered):
class ProfilePageClass extends React.Component {
handleClick = () => {
const user = this.props.user;
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
setTimeout(() => {
alert('Followed ' + this.props.user); // current value (wrong)
alert('Followed ' + user); // old value (expected)
}, 6000);
};
render() {
return <button onClick={this.handleClick}>Follow</button>;
}
}
With a function component, you can't get it wrong (but accessing the current value is next to impossible without useRef):
function ProfilePageClass({user}) {
const handleClick = () => {
setTimeout(() => {
alert('Followed ' + user); // old value (expected)
}, 6000);
};
return <button onClick={this.handleClick}>Follow</button>;
}
It's likely there's a problem regarding the setTimeout your using, in which you're losing reference to the actual prop you wish to display. You can add something like this to your parent component:
this.myRef = React.createRef();
This will generate a ref that you can later pass in to the child component. You can set the refs current item in this fashion:
this.myRef.current = this.state.user
in order to populate the ref.
Try this modification, as there might be handleClick is not auto bound.
import React from "react";
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
user: 'Dan',
};
}
render() {
return (
<React.Fragment>
<label>
<b>Choose profile to view: </b>
<select
value={this.state.user}
onChange={e => this.setState({ user: e.target.value })}
>
<option value="Dan">Dan</option>
<option value="Sophie">Sophie</option>
<option value="Sunil">Sunil</option>
</select>
</label>
<h1>Welcome to {this.state.user}’s profile!</h1>
<p>
<ProfilePageClass user={this.state.user} />
<b> (class)</b>
</p>
</React.Fragment>
)
}
}
Profile alert
class ProfilePageClass extends React.Component {
//Define a constructor here
constructor(props){
super(props)
// Bind handleClick , so that `this` will point to parent component when you pass to child
this.handleClick= this.handleClick.bind();
}
showMessage = () => {
alert('Followed ' + this.props.user); // This get value (new props)
};
handleClick = () => {
setTimeout(this.showMessage, 100); // This get value (new props)
};
render() {
return <button onClick={this.handleClick}>Follow</button>;
}
}

How to pass props through Link method in React?

I am trying to pass a prop from one component in which I search for and select a game to another component where I will render the details of the selected game. I am keeping my components as two separate pages, but I am struggling to get anything passing down to the child component. Here are my two files, and I have no idea where I am going wrong.
import React, { Component } from "react";
import Selected from "./Selected";
import { Link } from 'react-router-dom';
class Search extends Component {
constructor(props) {
super(props);
this.state = {
/*
API request format:
GET https://api.rawg.io/api/platforms?key=YOUR_API_KEY
GET https://api.rawg.io/api/games?key=YOUR_API_KEY&dates=2019-09-01,2019-09-30&platforms=18,1,7
Docs: https://api.rawg.io/docs
*/
baseURL: "https://api.rawg.io/api/games?",
apiKey: `key=${process.env.REACT_APP_RAWG_API_KEY}&`,
gamesQuery: "search=",
searchInput: "",
// later on we can determine whether to add additional parameters like page size, genres, etc.
searchURL: "",
gallery : [],
selectedGame: [],
};
}
handleChange = (event) => {
this.setState({
// we're grabbing the element or elements and dynamically setting the input value to the key corresponding to the input id of the same name in this.state
[event.target.id]: event.target.value,
});
};
handleSubmit = (event) => {
// keep the page from refreshing on submit
event.preventDefault();
this.setState(
{
// builds out our search url from the pieces we've assembled
searchURL:
this.state.baseURL +
this.state.apiKey +
this.state.gamesQuery +
this.state.searchInput,
},
() => {
// we fetch the url from the api
fetch(this.state.searchURL)
// .then waits till the fetch is complete
.then((response) => {
return response.json();
})
.then(
(json) => this.setState({
gallery : json.results
}),
(err) => console.log(err)
);
}
);
};
handleInspect = (event) => {
for (let i in this.state.gallery) {
if (i.id === event.id) {
this.setState ({
selectedGame : i
})
}
}
}
render() {
let game;
if (this.state.selectedGame) {
game = this.state.selectedGame
}
return (
<div>
<form onSubmit={this.handleSubmit}>
<label>Search</label>
<input
id="searchInput"
type="text"
placeholder="What's the Name of the Game"
value={this.state.searchInput}
onChange={this.handleChange}
/>
<input type="submit" value="Find Games" />
</form>
<div id='gallery'>
{this.state.gallery.map(function(d, idx){
return (
<li key={idx}>
<a href={"/selected/"+d.id}
onClick={()=>this.handleInspect(d.id)}
>{d.name}</a>,
{d.id},
<Link to={{pathname: `/selected/${d.id}`,
gameResults : game}} />,
</li>)})}
</div>
</div>
);
}
}
export default Search;
And the component I try to pass to and fails.
import React from 'react';
import { Link } from 'react-router-dom';
class Selected extends React.Component {
render() {
{console.log(this.props)}
return (
<h1>woo</h1>
);
}};
export default Selected;
The result is below, with no props having been passed at all

React-Redux: State always one input behind

I've created a class based component that renders an input field. I need the global state to update while the user types into the input. The issue is that the global state is always one step (render?) behind from what's actually in the input field. For example, when I write “winners” in the input, the state is “winner” instead. How can I fix this?
Component
class TeamCardT1 extends Component {
constructor(props) {
super(props);
// local state
this.state = {
team_one_name: "Team One",
};
// bind events
this.handleName = this.handleName.bind(this);
};
handleName = e => {
this.setState({
...this.state,
team_one_name: e.target.value
});
this.props.handleSubmit(this.state);
};
render() {
const { team_one_name } = this.state;
return (
<>
<div className="teamCard_container">
<input
type="text"
id="team_one_name"
name="team_one_name"
value={team_one_name}
onChange={this.handleName}
maxLength="35"
minLength="2"
className="teamCard_teamName" />
<PlayersCardT1 />
<ScoreCard />
</div>
</>
)
};
}
index.js for the component
const mapStateToProps = ({ team_one_name }) => {
return {
team_one_name,
};
};
// Dispatch
const mapDispatchToProps = dispatch => {
return {
handleSubmit: (data) => { dispatch(updateTeamOneName(data)) }
};
};
export default connect(mapStateToProps, mapDispatchToProps)(TeamCardT1);
You handleSubmit with the previous state, change to the current value.
handleName = e => {
this.setState({ team_one_name: e.target.value });
this.props.handleSubmit(e.target.value);
};
Notice that you already have a shallow merge with setState so you don't need to destruct this.state.
state is one step behind because you should call the prop function as a setState callback by this way the prop function will call just after the state set.
handleName = e => {
this.setState({
...this.state,
team_one_name: e.target.value
}, () => {
this.props.handleSubmit({value: e.target.value});
});
};

todo list app - onChange on ReactJS doesn't work?

I am new to ReactJS and I'm doing a simple to-list with it. I am working on the part of check box. What I want to do is to uncheck the box whenever i click it or vice versa. But nothing has been changed, so I am wondering if anything wrong....
Below is the App.js and index.js file
import React from 'react';
import Todolist from './Todolist';
import todoData from './todoData';
class App extends React.Component {
constructor(){
super()
this.state = {
todos: todoData //grab the raw data
}
this.handleChange = this.handleChange.bind(this)
}
handleChange (id){
console.log(id)
this.setState (prevState => {
const updatedtodos = prevState.todos.map(todo => {
if(todo.id == id) {
todo.completed = !todo.completed
}
return todo
})
return {
todos: updatedtodos
}
})
}
render() {
const TodoItem = this.state.todos.map(item => <Todolist key={item.id} item={item} handleChange={this.handleChange}/>)
return(
<div>
{TodoItem}
</div>
);
}
}
export default App;
import React from 'react';
function Todolist (props){
return(
<div className="wholelist">
<input
type="checkbox"
checked={props.item.completed}
onChange={()=> props.handleChange(props.item.id)}
/>
<label className="items">{props.item.name}</label>
</div>
);
}
export default Todolist;
Change handleChange:
handleChange(id) {
console.log(id);
const updatedtodos = this.state.todos.map(todo => {
if (todo.id === id) {
todo.completed = !todo.completed;
}
return todo;
});
this.setState({
todos: updatedtodos
});
}
you need to change two things.
firstly:
handleChange = (id) => { // I've changed this to an arrow function so it can access parent scope and you can use "this" keyword
console.log(id)
this.setState (prevState => {
const todos = this.state.todos.map(todo => {
if(todo.id === id) {
todo.completed = !todo.completed
}
return todo
})
this.setState({ todos }) // you can use object shorthand here to change todos in state to use the todos set above
})
}
secondly:
change this: handleChange={this.handleChange}
to: handleChange={() => this.handleChange(item.id)}
you said that handleChange(id) need to take an id, so we need to pass one in when we call it
let me know if that works!

Unmount component on click in child component button // React

I am struggling with successfully removing component on clicking in button. I found similar topics on the internet however, most of them describe how to do it if everything is rendered in the same component. In my case I fire the function to delete in the child component and pass this information to parent so the state can be changed. However I have no idea how to lift up the index of particular component and this is causing a problem - I believe.
There is a code
PARENT COMPONENT
export class BroadcastForm extends React.Component {
constructor (props) {
super(props)
this.state = {
numberOfComponents: [],
textMessage: ''
}
this.UnmountComponent = this.UnmountComponent.bind(this)
this.MountComponent = this.MountComponent.bind(this)
this.handleTextChange = this.handleTextChange.bind(this)
}
MountComponent () {
const numberOfComponents = this.state.numberOfComponents
this.setState({
numberOfComponents: numberOfComponents.concat(
<BroadcastTextMessageForm key={numberOfComponents.length} selectedFanpage={this.props.selectedFanpage}
components={this.state.numberOfComponents}
onTextChange={this.handleTextChange} dismissComponent={this.UnmountComponent} />)
})
}
UnmountComponent (index) {
this.setState({
numberOfComponents: this.state.numberOfComponents.filter(function (e, i) {
return i !== index
})
})
}
handleTextChange (textMessage) {
this.setState({textMessage})
}
render () {
console.log(this.state)
let components = this.state.numberOfComponents
for (let i = 0; i < components; i++) {
components.push(<BroadcastTextMessageForm key={i} />)
}
return (
<div>
<BroadcastPreferencesForm selectedFanpage={this.props.selectedFanpage}
addComponent={this.MountComponent}
textMessage={this.state.textMessage} />
{this.state.numberOfComponents.map(function (component) {
return component
})}
</div>
)
}
}
export default withRouter(createContainer(props => ({
...props
}), BroadcastForm))
CHILD COMPONENT
import React from 'react'
import { createContainer } from 'react-meteor-data'
import { withRouter } from 'react-router'
import { BroadcastFormSceleton } from './BroadcastForm'
import './BroadcastTextMessageForm.scss'
export class BroadcastTextMessageForm extends React.Component {
constructor (props) {
super(props)
this.handleChange = this.handleChange.bind(this)
this.unmountComponent = this.unmountComponent.bind(this)
}
handleChange (e) {
this.props.onTextChange(e.target.value)
}
unmountComponent (id) {
this.props.dismissComponent(id)
}
render () {
console.log(this.props, this.state)
const textMessage = this.props.textMessage
return (
<BroadcastFormSceleton>
<div className='textarea-container p-3'>
<textarea id='broadcast-message' className='form-control' value={textMessage}
onChange={this.handleChange} />
</div>
<div className='float-right'>
<button type='button'
onClick={this.unmountComponent}
className='btn btn-danger btn-outline-danger button-danger btn-small mr-3 mt-3'>
DELETE
</button>
</div>
</BroadcastFormSceleton>
)
}
}
export default withRouter(createContainer(props => ({
...props
}), BroadcastTextMessageForm))
I am having problem with access correct component and delete it by changing state. Any thoughts how to achieve it?
Please fix the following issues in your code.
Do not mutate the state of the component. Use setState to immutably change the state.
Do not use array index as the key for your component. Try to use an id field which is unique for the component. This will also help with identifying the component that you would need to unmount.
Try something like this. As mentioned before, you don't want to use array index as the key.
class ParentComponent extends React.Component {
constructor() {
this.state = {
// keep your data in state, as a plain object
textMessages: [
{
message: 'hello',
id: '2342334',
},
{
message: 'goodbye!',
id: '1254534',
},
]
};
this.handleDeleteMessage = this.handleDeleteMessage.bind(this);
}
handleDeleteMessage(messageId) {
// filter by Id, not index
this.setState({
textMessages: this.state.textMessages.filter(message => message.id !== messageId)
})
}
render() {
return (
<div>
{this.state.textMessages.map(message => (
// Use id for key. If your data doesn't come with unique ids, generate them.
<ChildComponent
key={message.id}
message={message}
handleDeleteMessage={this.handleDeleteMessage}
/>
))}
</div>
)
}
}
function ChildComponent({message, handleDeleteMessage}) {
function handleClick() {
handleDeleteMessage(message.id)
}
return (
<div>
{message.message}
<button
onClick={handleClick}
>
Delete
</button>
</div>
);
}

Categories