I am newbie to React. In my CRUD aplication I have a Main component. Then in the List Component I make an API call to load item from server. The problem is that after a new item submitting in Create component I need to refresh my List in order to see a new item added. What is the possible non-flux solution?
Main.jsx
export default class Main extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
showModal: false,
};
this.openModal = this.openModal.bind(this);
this.closeModal = this.closeModal.bind(this);
}
openModal() {
this.setState({showModal: true});
}
closeModal() {
this.setState({showModal: false});
}
render() {
return (
<div>
<div class="navbar">
<div class="nav-item"onClick={this.openModal}>Create</div>
</div>
<div class="modal" show={this.state.showModal} onHide={this.closeModal}>
<div class="modal-body">
<Create onItemCreate={this.closeModal}/>
</div>
</div>
<List />
</div>
);
}
}
UPDATE:
List.jsx
export default class List extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
items: []
};
}
refreshList() {
$.ajax({
url : apiPrefix,
dataType : 'json',
type : 'GET',
success: data => {
this.setState({items: data.items});
},
error: (xhr, status, err) => {
console.error(apiPrefix, status, err.toString());
}
});
}
render() {
if( this.state.findings === undefined ) {
return <div>Loading...</div>
} else {
return(
<div>
<div>
{
this.state.findings.map((item) => {
return <Item key={item._id} item={item} />
})
}
</div>
</div>
);
}
}
componentWillMount() {
this.refreshList();
}
}
Create.jsx
export default class Create extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
};
}
// Perform a post request to save a formData
onSubmit({formData}) {
formData.type = this.state.selectValue;
axios.post(apiPrefix, formData)
.then(() => {
this.closeModal();
// THIS LIST NEED TO BE REFRESHED
});
}
closeModal() {
this.props.onFindingCreated();
}
render() {
return (
<div>
<div class="form"></div>
</div>
)
}
}
when your control reaches to Create page you can check it conditionally in render as:-
Create a variable that will store your state initially when your create page will be loaded first
render{
if(var==null) //will load your var when this class will run first
{
var=this.props.onItemCreate.showModal
return()..
}
else if(this.props.onItemCreate.showModal!=var and var!=null)
{
var=[] //empty variable and update it to new props
var=this.props.onItemCreate.showModal
}
}
Or
you can use
componentWillReceiveProps in your child class and check if props has been updated then simply set your state to that props and hence your list also
Related
It seems just recently React won't treat this.props.children as a function as it did in the past.
I just coded a Modal component where its closeModal function should be passed to the children,
render() {
return (
<Modal>
{
(closeModal) => {
return (<span onClick={closeModal}>Click to close modal</span>)
}
}
</Modal>
)
}
Modal looks like this
class Modal extends React.Component {
constructor(props) {
this.state = { show: true }
this.close = this.closeModal.bind(this)
}
closeModal() {
this.setState({ show: false })
}
render() {
if (!this.state.show)
return null
return (
<div>
{ this.props.children }
</div>
)
}
}
I tried to pass function as a prop via this.props.children({ closeModal: this.closeModal }), guess what, this.props.children is not a function according to latest React 16.9.
As a reference for the folks working with GraphQL, I see Apollo client's <Mutation> and <Query> working quite much the same way.
How can it be achieved?
Edit: Why not a duplicate?
Because other answers rely on this.props.children as function whereas recently React is now rendering error demanding a new approach to this issue:
TypeError: this.props.children is not a function
I've answered the updates needed to show what is wrong and how it can be changed in-line below.
class Modal extends React.Component {
constructor(props) {
// 1️⃣ Make sure to make `props` available in this component.
// This call is what makes `this.props` call to be available within `Modal`.
super(props);
this.state = { show: true };
// 2️⃣ Assign a newly bound method to the matching class method
// this.close = this.closeModal.bind(this);
this.closeModal = this.closeModal.bind(this);
}
closeModal() {
this.setState({ show: false });
}
render() {
if (!this.state.show) return null;
// 3️⃣ Pass the modal handler to children
// If you had kept `this.close`, then when you pass the handler
// "{ closeModal: this.close }" instead of "{ closeModal: this.closeModal }"
return <div>{this.props.children({ closeModal: this.closeModal })}</div>;
}
}
function App() {
return (
<div className="App">
<Modal>
{/* 4️⃣ destructure `closeModal` */}
{({ closeModal }) => {
return <button onClick={closeModal}>Click to close modal</button>;
}}
</Modal>
</div>
);
}
As Emile Bergeron has kindly pointed out, you can pass this.props.children(this.close) instead of an object but I found it easier to read/use.
You can fork and try or run the code snippet below~
Thanks Emile Bergeron for the suggestion in the comment~
class Modal extends React.Component {
constructor(props) {
super(props);
this.state = { show: true };
// this.close = this.closeModal.bind(this);
this.closeModal = this.closeModal.bind(this);
}
closeModal() {
this.setState({ show: false }, () => {
console.log(`Modal closed`);
});
}
render() {
if (!this.state.show) return null;
return <div>{this.props.children({ closeModal: this.closeModal })}</div>;
}
}
function App() {
return (
<div className="App">
<Modal>
{({ closeModal }) => {
return (
<button type="button" onClick={closeModal}>
Click to close modal
</button>
);
}}
</Modal>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<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>
try this one
class Modal extends React.Component {
constructor(props) {
this.state = { show: true }
this.close = this.closeModal.bind(this)
}
closeModal() {
this.setState({ show: false })
}
render() {
if (!this.state.show)
return null
return (
<div>
{ this.props.children(this.close) }
</div>
)
}
}
I have 3 components. App.js - Main. localLog.jsx stateless, LoadBoard.jsx statefull. I want to Take string of data from LoadBoard and display it in localLog.jsx. The problem is that I can't figure it out why LocalLog is not displaying on screen.
console.log(this.data.Array) in App.jsx localLog is ["configuration"]
(2) ["configuration", "It's good configuration"]
App.jsx
class App extends Component {
constructor(props) {
super(props);
this.dataArray = [];
this.state = {
headers: []
};
this.localLog = this.localLog.bind(this);
}
localLog(data) {
if (data) {
this.dataArray.push(data);
console.log(this.dataArray);
this.dataArray.map(data => {
return <LocalLog info={data} />;
});
}
}
render() {
return (
<>
<LoadBoard apiBase={this.state.apiBase} localLog={this.localLog} />
<pre id="log_box">{this.localLog()}</pre>
</>
);
}
}
localLog.jsx
let localLog = props => {
return (
<pre className={classes.background}>
<ul className={classes.ul}>
<li>{props.info}</li>
<li>hello world</li>
</ul>
</pre>
);
};
export default localLog;
LoadBoard.jsx
class LoadBoard extends Component {
constructor(props) {
super(props);
this.state = {
positionToId: []
};
}
componentDidMount() {
this.props.localLog("configuration");
this.props.localLog(`It's good configuration`);
}
render() {
return (
<div>
<h1>Nothing interesting</h1>
</div>
);
}
}
You are not returning anything from the localLog method, should be:
return this.dataArray.map(data => {
return <LocalLog info={data} />;
});
EDIT:
here is what your App component should look like.
class App extends Component {
constructor(props) {
super(props);
this.state = {
headers: [],
logs: []
};
this.addLog = this.addLog.bind(this);
}
// Add log to state
addLog(log) {
this.setState(state => ({
...state,
logs: [...state.logs, log]
}));
}
render() {
return (
<>
<LoadBoard apiBase={this.state.apiBase} localLog={this.addLog} />
<pre id="log_box">
{this.state.logs.map(log => {
return <LocalLog info={log} />;
})}
</pre>
</>
);
}
}
you should use setState method in order to re-render the component.
you can try this.
class App extends Component {
constructor(props) {
super(props);
this.state = {
headers: [],
dataArray: []
};
this.localLog = this.localLog.bind(this);
}
localLog(data) {
if (data) {
this.state.dataArray.push(data);
this.setState({dataArray: this.state.dataArray})
}
}
render() {
return (
<>
<LoadBoard apiBase={this.state.apiBase} localLog={this.localLog} />
<pre id="log_box">{this.state.dataArray.map(i => <LoaclLog info={i}/>)}</pre>
</>
);
}
}
The parent component Dashboard holds the state for every ListItem I add to my Watchlist. Unfortunately, every time I am adding an Item, it gets added to the DB, but only shows up when I refresh the browser.
class UserDashboard extends React.Component {
state = {
data: []
}
componentWillMount() {
authService.checkAuthentication(this.props);
}
isLoggedIn = () => {
return authService.authenticated()
}
getAllCoins = () => {
//fetches from backend API
}
addWishlist = () => {
this.getAllCoins()
.then(things => {
this.setState({
data: things
})
})
console.log("CHILD WAS CLICKED")
}
componentDidMount() {
this.getAllCoins()
.then(things => {
this.setState({
data: things
})
})
}
render() {
return (
<div className="dashboard">
<h1>HI, WELCOME TO USER DASHBOARD</h1>
<SearchBar
addWishlist={this.addWishlist}
/>
<UserWatchlist
data={this.state.data}
/>
</div>
);
}
}
The User Watchlist:
class UserWatchlist extends React.Component {
constructor(props) {
super(props)
}
// componentDidUpdate(prevProps) {
// if (this.props.data !== prevProps.data) {
// console.log("CURRENT", this.props.data)
// console.log("PREVs", prevProps.data)
// }
// }
render() {
return (
<div>
<h2>These are tssssyou are watching:</h2>
<ul className="coin-watchlist">
{
this.props.data.map((coin, idx) => {
return <ListItem key={idx}
coin={coin.ticker}
price={coin.price}
/>
})
}
</ul>
</div>
)
}
}
The search Bar that shows potential Items to watch over:
class SearchBar extends React.Component {
constructor(props) {
super(props)
this.state = {
coins: [],
searchValue: ""
}
}
searchHandler = e => {
e.preventDefault()
const value = e.target.value
this.setState({
searchValue: value
});
if (value === "") {
this.setState({
coins: []
})
} else {
this.getInfo()
}
}
getInfo = () => {
// Searches the API
}
addWishlist = () => {
this.props.addWishlist();
}
render() {
const {coins, searchValue} = this.state
return (
<div className="coin-search">
<form>
<input
type="text"
className="prompt"
placeholder="Search by ticker symbol"
value={searchValue}
onChange={this.searchHandler}
/>
</form>
<ul className="search-suggestions">
{
coins.filter(searchingFor(searchValue)).map( coin =>
<Currency
coin={coin}
addWishlist={this.addWishlist}
/>
)
}
</ul>
</div>
);
}
}
And the actual Currency that gets clicked to be added:
class Currency extends React.Component {
addToWatchlist = () => {
// POST to backend DB to save
};
fetch("/api/add-coin", settings)
.catch(err => {
return err
})
}
clickHandler = () => {
this.addToWatchlist()
this.props.addWishlist()
}
render() {
return(
<div className="search-results">
<li>
<h3> { this.props.coin.currency } </h3>
<button
className="add-to-list"
onClick={this.clickHandler}
>
+ to Watchlist
</button>
</li>
</div>
)
}
}
As you can see, I am sending props down all the way down to child. When I click the button to Add to Watchlist, I see the console.log message appear, saying "CHILD WAS CLICKED". I've even tried just calling the method to fetch from backend API again.
Also, in UserWatchlist, I've tried a componentDidUpdate, but both prevProps and this.props show the very same array of data. Somewhere in the chain, my data is getting lost.
This is also my first time posting a question here, so if it can be improved, I am happy to add extra details and contribute something to this community
You probably forgot to wait for addToWatchlist to complete:
addToWatchlist = () => {
// POST to backend DB to save
return fetch("/api/add-coin", settings)
.catch(err => {
return err
})
}
clickHandler = () => {
this.addToWatchlist().then(() => {
this.props.addWishlist()
})
}
I'm experimenting with React and I'm trying to create a Search to filter a list of items. I have two components, the main one displaying the list of items which calls the Search component.
I have an onChange function that sets the term in the state as the input value and then calls searchItems from the main component to filter the list of items. For some reason in searchItems, this.state is undefined. I thought adding bind to onInputChange in the Search component would sort it out but it did not make any difference. Maybe there's something I'm missing.
Main Component
import React, { Component } from 'react';
import _ from 'lodash';
import Search from './search';
class Items extends Component {
constructor(props) {
super(props);
this.state = {
error: null,
isLoaded: false,
items: []
};
}
componentDidMount() {
fetch("[url].json")
.then(res => res.json())
.then(
(result) => {
this.setState({
isLoaded: true,
items: result
});
}
),
(error) => {
this.setState({
isLoaded: true,
error
})
}
}
searchItems(term) {
const { items } = this.state;
const filtered = _.filter(items, function(item) {
return item.Name.indexOf(term) > -1;
});
this.setState({ items: filtered });
}
render() {
const { error, isLoaded, items } = this.state;
if (error) {
return <div>Error: {error.message}</div>;
}
else if (!isLoaded) {
return <div>Loading...</div>;
}
else {
return (
<div>
<Search onSearch={this.searchItems}/>
<ul>
{items.map(item => (
<li key={item.GameId}>
{item.Name}
</li>
))}
</ul>
</div>
)
}
}
}
export default Items;
Search Component
import React, { Component } from 'react';
class Search extends Component {
constructor(props) {
super(props);
this.state = {
term: ''
};
}
render() {
return (
<div>
<input type="text" placeholder="Search" value={this.state.term} onChange={event => this.onInputChange(event.target.value)} />
</div>
);
}
onInputChange(term) {
this.setState({ term });
this.props.onSearch(term);
}
}
export default Search;
You didn't bind searchItems() in the Items component.
Try changing it to an arrow function:
searchItems = () => {
// blah
}
or otherwise binding it in the constructor():
constructor() {
// blah
this.searchItems = this.searchItems.bind(this);
}
or when you call it.
You can read more about this here.
This is the code my react component:
class App extends React.Component {
constructor() {
super();
}
update(e) {
axios.get('https://myjsonurl.com', {
params: {
phrase: e.target.value
}
}).then(function (response) {
console.log(response.data);
}).catch(function (err) {
console.log(err);
});
}
render() {
return (
<div>
<input type="text" onChange={this.update} />
</div>
)
}
}
By searching I get correct results in console of my browser. Can someone give me some tipps how can I insert this data in a list?
Is there to create a new component and tell in response to use this component?
Create a state variable and save the response inside that. Call a function from render method that will create the list from that response.
Bind the update method inside constructor.
Like this:
class App extends React.Component {
constructor() {
super();
this.state = {
data: []
}
this.update = this.update.bind(this); //bind the method
}
update(e) {
axios.get('https://myjsonurl.com', {
params: {
phrase: e.target.value
}
}).then((response) => {
this.setState({data: response.data}); //set the response in state
}).catch((err) => {
console.log(err);
});
}
createList(){
if(!this.state.data.length) return null;
return <ul>
{this.state.data.map((el, index) => <li key={index}> {el.KeyName} </li>)}
</ul>
}
render() {
return (
<div>
<input type="text" onChange={this.update} />
{this.createList()}
</div>
)
}
}
Note: Replace el.KeyName with actual key that you want to show, and assign some unique value to key key={index} i used index because don't know the details about the response.