Let's start with some code:
export default class BookingDriverContainer extends React.PureComponent {
static propTypes = {
bookingId: PropTypes.number.isRequired,
};
constructor(props) {
super(props);
this.state = {
loading: true,
saving: false,
};
}
componentDidMount() {
ajax(Router.route('api.bookingDrivers', {
id: this.props.bookingId,
})).then(res => {
if(res.ok) {
this.setState({
loading: false,
segments: res.data.segments,
drivers: res.data.drivers,
});
}
});
}
driverChanged = segmentId => ev => {
console.log(`Driver for segment ${segmentId} changed to ${ev.target.value}`);
};
render() {
if(this.state.loading) {
return <img src={loadingGif} width="220" height="20" alt="Loading..."/>;
}
return (
<table className="index-table">
<thead>
<tr>
<th style={{width: '50px'}}>Seg.</th>
<th style={{width: '70px'}}>Unit</th>
<th style={{width: '140px'}}>Driver</th>
<th style={{width: '100px'}}>Driver #</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{this.state.segments.map(seg => (
<tr key={seg.id}>
<td>*snip*</td>
<td>*snip*</td>
<td>
<SelectBox onChange={this.driverChanged(seg.id)}>
<option value="" className="not-set">TBD</option>
{this.state.drivers.map(([id, name]) => (
<option key={id} value={id}>{name}</option>
))}
</SelectBox>
</td>
<td>*snip*</td>
<td>*snip*</td>
</tr>
))}
</tbody>
</table>
)
}
}
In this example, my component is not pure because of this: onChange={this.driverChanged(seg.id)}. i.e., every time my component renders, that creates a new function, which will cause <SelectBox> to be re-rendered even though nothing has changed.
How can this be fixed without introducing a large framework like Redux?
Best way I've found so far is just to memoize the function.
driverChanged = memoize(segmentId => ev => {
// ...
});
This way, given the same segmentId it will return the same function instance, which will allow the PureRenderMixin et al. to work their magic.
Here's my memoize function:
export default function memoize(fn, options={
serialize: fn.length === 1 ? x => x : (...args) => JSON.stringify(args),
}) {
let cache = new Map();
return (...args) => {
let key = options.serialize(...args);
if(cache.has(key)) {
return cache.get(key);
}
let value = fn(...args);
cache.set(key, value);
return value;
}
}
It only supports JSON-serializable variables, but you can find a more advanced memoizer on npm if you wish.
If all you want is to prevent creating new functions every time the component renders, you could simply create another component to contain SelectBox component:
class MySelectBox extends React.Component {
static propTypes = {
drivers: PropTypes.arrayOf(PropTypes.object).isRequired,
segId: PropTypes.string.isRequired,
driverChanged: PropTypes.function.isRequired,
}
render() {
const {
drivers,
segId,
driverChanged,
} = this.props;
return (
<SelectBox onChange={ev => driverChanged(ev, segId)}>
<option value="" className="not-set">TBD</option>
{drivers.map(([id, name]) => (
<option key={id} value={id}>{name}</option>
))}
</SelectBox>
);
}
}
Then use this new component inside BookingDriverContainer with the proper properties:
export default class BookingDriverContainer extends React.Component {
// ...
driverChanged = (ev, segId) => {
console.log(`Driver for segment ${segId} changed to ${ev.target.value}`);
}
render() {
// ...
return (
<table className="index-table">
<thead>
{/* ... */}
</thead>
<tbody>
{this.state.segments.map(seg => (
<tr key={seg.id}>
<td>*snip*</td>
<td>*snip*</td>
<td>
<MySelectBox
driverChanged={this.driverChanged}
segId={seg.id}
drivers={this.state.drivers}
/>
</td>
<td>*snip*</td>
<td>*snip*</td>
</tr>
))}
</tbody>
</table>
)
}
}
This way you do not create new functions at every render times.
Related
please advise how to pass the input value to the dispatch method.I want to pass the policy number to the action method in index.js which is in action folder.The retrievepolicy method need to get the policy number as an argument
On submit event,the retrievepolicy method will be dispatched
import React from 'react'
import { connect } from 'react-redux'
import { sayHello } from '../actions'
import { retrievePolicy } from '../actions'
class Button extends React.Component {
constructor(props) {
super(props);
this.state = {
policyNumber: ""
}
this.handleChange = this.handleChange.bind(this);
}
render() {
const { saySomething, retrievePolicy, whatsUp } = this.props;
return (
<div class="container">
<div class="text-white bg-dark" >
<button class="btn btn-info" onClick={saySomething}>PRESS TO DISPATCH
FIRST ACTION</button>
<h2>{whatsUp}</h2>
<table>
<tr>
<td><label for="exampleInputEmail" class="label">Policy Number</label>
</td>
<td><input type="textbox" class="textarea" id="policyNumberid"
name="policyNumberid"
value={this.state.policyNumber} onChange={this.handleChange}
/></td>
</tr>
<tr>
<td>Data Location</td>
<select class="form-control" id="timezone" name="timezone"
selected="selected" >
<option value="" selected disabled hidden>Choose here</option>
<option value="Test">\\w</option>
<option value="Stage">\\ws</option>
</select>
</tr>
<tr><button class="btn btn-info" onClick={retrievePolicy} >Retrieve
Policy Information</button></tr>
</table>
</div>
</div>
)
}
handleChange(typedText) {
alert('hi')
this.setState({ policyNumber: typedText.target.value }, () => {
console.log(this.state.policyNumber);
});
}
};
const mapStateToProps = (state) => ({
whatsUp: state.policy,
stateObject: state
})
const mapDispatchToProps = (dispatch) => (
{
saySomething: () => { dispatch(sayHello()) },
retrievePolicy: (policyNumber) => {
dispatch(retrievePolicy(policyNumber)) }
}
)
Button = connect(mapStateToProps, mapDispatchToProps)(Button)
export default Button
I'm using Laravel 5.6 and Vuejs 2.
If I click on my checkbox and make the value true it's supposed to save a 1 in the database and if I click my checkbox and make the value false it saves a 0.
The problem I'm having is that if I click my checkbox and make it true, it doesn't save the correct value, no changes is made to the database and I don't get any errors. If I click on my checkbox and make it false, it saves the 0 correctly.
I did notice that even when my value is supposed to be true, I do get a false when I dd($category->has('active')
I'm not sure where I'm going wrong or how to fix it.
My vue file
<template>
<div class="card-body">
<table class="table">
<thead class="thead-dark">
<tr>
<th scope="col">Active</th>
<th scope="col">Title</th>
<th scope="col">Edit</th>
<th scope="col">Delete</th>
</tr>
</thead>
<tbody>
<tr v-for="(category, index) in categoriesNew" >
<td>
<label>checkbox 1</label>
<input name="active" type="checkbox" v-model="category.active" #click="checkboxToggle(category.id)">
</td>
<td>
{{ category.title }}
</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
export default {
props: ['categories'],
data(){
return {
categoriesNew: this.categories
}
},
methods: {
checkboxToggle(id){
console.log(id);
axios.put('/admin/category/active/'+id, {
categories: this.categoriesNew
}).then((response) => {
//Create success flash message
})
},
},
mounted() {
console.log('Component mounted.')
}
}
</script>
my routes
Route::put('admin/products/updateAll', 'Admin\ProductsController#updateAll')->name('admin.products.updateAll');
Route::put('admin/category/active/{id}', 'Admin\CategoryController#makeActive')->name('admin.category.active');
Route::resource('admin/category', 'Admin\CategoryController');
Route::resource('admin/products', 'Admin\ProductsController');
my CategoryController#makeActive
public function makeActive(Request $request, $id)
{
$category = Category::findOrFail($id);
if($request->has('active'))
{
$category->active = 1;
}else{
$category->active = 0;
}
$category->save();
}
I hope I made sense. If there is anything that isn't clear or if you need me to provide more info, please let me know
Try changing this line
categories: this.categoriesNew
to
categories: category.active
and add a data prop at the top called category.active: ''
I've managed to get it to work. This is what I did.
vue file
<template>
<div class="card-body">
<table class="table">
<thead class="thead-dark">
<tr>
<th scope="col">Active</th>
<th scope="col">Title</th>
<th scope="col">Edit</th>
<th scope="col">Delete</th>
</tr>
</thead>
<tbody>
<tr v-for="(category, index) in categories" >
<td>
<label>checkbox 1</label>
<input type="checkbox" v-model="category.active" #click="checkboxToggle(category)">
</td>
<td>
{{ category.title }}
</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
export default {
props: ['attributes'],
data(){
return {
categories: this.attributes,
}
},
methods: {
checkboxToggle (category) {
axios.put(`/admin/category/${category.id}/active`, {
active: !category.active
}).then((response) => {
console.log(response)
})
}
},
mounted() {
console.log('Component mounted.')
}
}
</script>
my routes
Route::put('admin/category/{category}/active', 'Admin\CategoryController#makeActive')->name('admin.category.active');
and my CategoryController#makeActive
public function makeActive(Request $request, $id)
{
$category = Category::findOrFail($id);
if(request('active') === true)
{
$category->active = 1;
}else{
$category->active = 0;
}
$category->save();
}
I try to show detail of my posts by slugs but data won't render. i just get my navbar and white page,
Code
controller
public function single($slug)
{
$post = Post::where('slug', $slug)->first();
return response()->json([
"post" => $post
], 200);
}
single.vue where i show my single post data
<template>
<div class="post-view" v-if="post">
<div class="user-img">
<img src="...." alt="">
</div>
<div class="post-info">
<table class="table">
<tr>
<th>ID</th>
<td>{{ post.id }}</td>
</tr>
<tr>
<th>Title</th>
<td>{{ post.title }}</td>
</tr>
<tr>
<th>Body</th>
<td>{{ post.body }}</td>
</tr>
</table>
<router-link to="/blog">Back to all post</router-link>
</div>
</div>
</template>
<script>
export default {
created() {
if (this.posts.length) {
this.project = this.posts.find((post) => post.slug == this.$route.params.slug);
} else {
axios.get(`/api/posts/${this.$route.params.slug}`)
.then((response) => {
this.post = response.data.post
});
}
},
data() {
return {
post: null
};
},
computed: {
currentUser() {
return this.$store.getters.currentUser;
},
posts() {
return this.$store.getters.posts;
}
}
}
</script>
vuex store.js
state: {
posts: []
},
getters: {
posts(state) {
return state.posts;
}
},
mutations: {
updatePosts(state, payload) {
state.posts = payload;
}
},
actions: {
getPosts(context) {
axios.get('/api/posts')
.then((response) => {
context.commit('updatePosts', response.data.posts);
})
}
}
Question
Why I can't get my post data? is there any mistake in my code?
................................................................................................................................................................................
You're calling /api/posts/${this.$route.params.slug}, which (by REST convention) returns ONE post object.
When setting your post (this.post = response.data.post) you should use response.data (without .post)
Have component that works with React, Redux and AJAX:
class IncomeProfile extends Component {
constructor(props) {
super(props)
this.state = {
items: this.props.items || []
}
}
componentDidMount() {
this.props.IncomeListProfile();
}
componentWillReceiveProps(nextProps) {
this.setState({ items: nextProps.items });
}
filterList(event) {
var updatedList = this.state.items;
updatedList = updatedList.filter(function(item) {
return item.toLowerCase().search(event.target.value.toLowerCase()) !== -1;
});
this.setState({items: updatedList}); // now this.state.items has a value
}
render() {
var elems = this.props.items;
if (typeof elems == 'undefined') {
elems = [];
}
//console.log(elems);
return (
<div>
<table className='table'>
<thead className='theadstyle'>
<th>date
<input></input>
</th>
<th>
<input onChange={this.filterList} type='text' placeholder='keyword search'></input>
</th>
<th>
<input type='text' placeholder='amount search'></input>
</th>
<th>amount</th>
<th>Show archived</th>
</thead>
<div className='tbodymar'></div>
<tbody >
{elems.map((item) => (
<tr key={item.course_id}>
<td>{item.created_at}</td>
<td>{item.name}</td>
<td>{item.remark}</td>
<td>{item.income_amount}</td>
<td>more options</td>
</tr>
))}
</tbody>
</table>
</div>
)
}
}
const mapDispatchToProps = function(dispatch) {
return {
IncomeListProfile: () => dispatch(IncomeProfileList())
}
}
const mapStateToProps = function(state) {
//var mystore = state.toJS()
var mystore = state.getIn(['incomeProfileList'])['course_list'];
//console.log(mystored.hh);
var copy = Object.assign({}, mystore);
return {items: copy.course_list};
}
export default connect(mapStateToProps, mapDispatchToProps)(IncomeProfile);
When I enter something in input, I get error Cannot read property 'state' of undefined, but console.log show's my state. What wrong with filter ? Where mistake? if I had right state?
this.props.items only get populate after componentDidMount?
If so, and you want to set the items in the state too. You can use componentWillReceiveProps method to set the new props to you state.
componentWillReceiveProps(nextProps) {
this.setState({ items: nextProps.items });
}
<script>
(function () {
jQuery.fn.szThreeStateColor = function (settings) {
var cfg = {
"overBgColor": "green",
"overFgColor": "white",
"clickBgColor": "blue",
"clickFgColor": "white"
};
if(settings) {
$.extend(cfg, settings);
}
var clickIdx = -1;
$thisObj = $(this);
var iniBgColor = $thisObj.find("tbody td").css("background-color");
var iniFgColor = $thisObj.find("tbody td").css("color");
var iniHeight = $thisObj.find("tr").css("height");
$thisObj.find("tbody tr").bind("mouseover", function (e) {
if($(this).index() != clickIdx) {
$(this).css({
"background-color": cfg.overBgColor,
"color": cfg.overFgColor
});
}
});
$thisObj.find("tbody tr").bind("mouseout", function (e) {
if($(this).index() != clickIdx) {
$(this).css({
"background-color": iniBgColor,
"color": iniFgColor
});
}
});
$thisObj.find("tbody tr").bind("click", function (e) {
//console.log($(this).index() + ":" + clickIdx);
if($(this).index() != clickIdx) {
if(clickIdx >= 0) {
$thisObj.find("tbody tr:eq(" + clickIdx + ")").css({
"background-color": iniBgColor,
"color": iniFgColor
});
}
$(this).css({
"background-color": cfg.clickBgColor,
"color": cfg.clickFgColor
});
clickIdx = $(this).index();
}
});
return this;
}
})($);
$(document).ready(function () {
$("#table1")
.szThreeStateColor({
"overBgColor": "#34ef2a",
"overFgColor": "#000000",
"clickBgColor": "#333333"
});
$("#table2").szThreeStateColor();
});
</script>
</HEAD>
<BODY>
<table id="table1" width='300' border='1'>
<thead>
<tr><td>name</td><td>city</td><td>age</td></tr>
</thead>
<tbody>
<tr>
<td>1</td><td>2</td><td>3</td>
</tr>
<tr>
<td>1</td><td>2</td><td>3</td>
</tr>
<tr>
<td>1</td><td>2</td><td>3</td>
</tr>
</tbody>
</table>
<table id="table2" width='300' border='1'>
<thead>
<tr><td>name</td><td>city</td><td>age</td></tr>
</thead>
<tbody>
<tr>
<td>1</td><td>2</td><td>3</td>
</tr>
<tr>
<td>1</td><td>2</td><td>3</td>
</tr>
<tr>
<td>1</td><td>2</td><td>3</td>
</tr>
<tr>
<td>1</td><td>2</td><td>3</td>
</tr>
</tbody>
</table>
This plugin sets different cell colors when the events mouseover, mouseout, click are fired. The plugin works fine with a single table but works abnormally when multiple tables are used. Maybe the variable clickIdx is shared by each table. How can I prevent sharing of that variable?
You can do so by wrapping your plugin in return this.each(function() {...}).
jQuery.fn.szThreeStateColor = function (settings) {
// ...
return this.each(function() {
// variables that need to be private go in here
});
}